Hale's words, in familiar terms
Hale uses a small vocabulary of its own. Most of it names things you already know from other languages and tools, with one difference each that is worth knowing. This page gives the familiar idea first, then the difference. A dotted word anywhere on the site shows the same gloss when you hover it and links here.
The language
- locus
Familiar: a component that owns its own state and talks to others only through messages, like an actor, a small service, or an object nobody else can reach into. An app, a cache, a request handler and a library are all loci. The difference: there is no other kind of thing, so the compiler can see the whole graph of who owns what and who talks to whom.
- topic
Familiar: a named, typed message channel, like a pub/sub topic in Kafka or NATS. The difference: it is declared in source with its payload type, and publishing and subscribing are declarations too, so the compiler knows every sender and every receiver.
- bus
Familiar: an event bus. In a locus, the
busblock lists which topics it publishes and subscribes to; at runtime, the bus delivers them. The difference: a bus edge can be a direct call, an in-process queue, a socket or a broker, and the program does not change when that changes. - keyed_by
Familiar: a partition or sharding key. A keyed topic delivers each message only to the subscriber whose key matches. The difference: the routing is part of the topic's declaration, so the handler never filters and the compiler checks the key's type.
- the send,
<-Familiar: publishing a message.
Broadcast <- mputsmon the Broadcast topic. The difference: a send is only allowed from a locus that declared it may publish there. - main locus
Familiar:
main()plus the deployment manifest, in one place. The difference: the root component also says where each part runs and how each channel travels, and nothing else in the program mentions a thread or a transport. - placement
Familiar: thread affinity and scheduling: a shared pool, a dedicated thread, a pinned core, a NUMA node. The difference: it is a block in
main, and the components it places do not change. - bindings
Familiar: the wiring in a deployment config: which transport carries which channel. In-process, a Unix socket, a shared-memory ring, or an adapter to a broker like NATS. The difference: the transport is itself a child component, so it is supervised like one, and a binding that cannot open fails at startup, so no route drops messages silently.
- params
Familiar: a component's typed fields with defaults: its constructor arguments and its state. The difference: params are a contract, so constructing a component with a field it never declared is a compile error.
- birth, run, dissolve
Familiar: constructor, main body, destructor. The difference: dissolve frees the component's whole memory region at once, so there is no garbage collector and no borrow checker.
- supervision, on_failure
Familiar: Erlang-style supervisors: a parent handles a child's failure and can restart it, quarantine it, or pass it up. The difference: there is no other error channel; nothing fails sideways.
- fallible(E)
Familiar: a Result type or a checked exception: a function that can fail says so in its signature. The difference: the caller must say what happens at the call site, with
or default,or raiseand the rest, and forgetting is a compile error. - closure
Familiar: an invariant, or an assertion that must hold. The difference: it is declared on the component and audited by the runtime at phase boundaries, and a broken one goes to the parent as a structural failure.
- perspective
Familiar: an interface with a swappable implementation. The difference: it can be swapped while the program runs, at the cost of a pointer flip, with state carried across.
- @sealed
Familiar: private fields. The difference: components are not otherwise encapsulated at the field level, so
@sealedis what makes "nobody else can read this key" a fact the compiler enforces. - unit
Familiar: a unit of measure, like
msorUSD, declared with its equation:unit USD = 100 cent;. The difference: a program declares its own, the standard library's time units are declared the same way, and the compiler knows every conversion between two units as one exact factor. - quantity, point
Familiar: a typed amount, such as Money or Duration, and a position on its line, such as Price or Time. The difference: every value is the integer it counts, arithmetic stays exact, widening is free, and narrowing is written where it happens with the rounding you chose:
or floorat the site, or{ round: half_even; }on the type. - distinct, range
Familiar: a newtype for an id, and a bounded integer. The difference: an identity never passes for an
Intand has no arithmetic; a range is anIntwith bounds, and narrowing into it says what happens outside them:or clamp,or wrap,or 0, or a handler. - api, rpc, surface
Familiar: an API definition, like an OpenAPI document or a gRPC service, listing the operations and who may call each. The difference: it is rows in the program,
rpc Shop::on_order requires: [clerk], and the compiler checks them, hashes them into a contract digest, and generates the OpenAPI, JSON Schema and MCP forms. Arrives in the next release. - role
Familiar: a permission group. The difference: a role is declared vocabulary with
includes, the requirement sits on the operation's row, and who holds a role is a source the serve site names, so one program can serve the same surface to two sets of people. - api::serve, exposure
Familiar: starting the server. The difference: one line puts a surface on a transport the standard library ships, a Unix socket, HTTP or MCP, with a bounded queue and one policy for when it is full, and the running exposure describes itself to any client.
- hub, stream
Familiar: a WebSocket channel. The difference: a stream is a topic bound to a hub, with the roles a subscriber needs on its row, so the same typed topic the program already publishes reaches the outside.
- altitude
Familiar: how low-level you are working: scripting, everyday application code, services, systems programming. The difference: it is one language at every level, and a function written at the top still compiles at the bottom. Scale, how big the system is, is a separate axis.
- scale
Familiar: how big the composed system is, from a function to a fleet of machines. The difference: the same construct, the locus, is what you write at every size.
What the compiler checks
- effect, effect classes
Familiar: what a function is allowed to do: make a syscall, block, read the clock, allocate, publish, spawn, recurse. It resembles an effect system, or a stricter notion of a pure function. The difference: a declaration is proven across everything the function can reach, and a violation names the path.
- @budget
Familiar: a performance assertion. The difference: it is a numeric bound the compiler checks, such as zero allocations per call, counting the allocation that broke it.
- claim
Familiar: an architecture test, like ArchUnit's "this package must not depend on that one". The difference: it is a sentence about the whole program's structure, checked over every path, including the ones no test takes, and it compiles to no code.
- group
Familiar: a named set of components that a rule talks about, such as
billingorplugins. The difference: a misspelt member is an error, and an empty group is an error unless the rule says it may be empty. - constitution
Familiar: a shared rulebook. The difference: written once, adopted by many programs, and proven by each against its own structure. Composition only adds rules; it cannot weaken one.
- witness, countermodel
Familiar: a counterexample, or a failing test's trace. The difference: when a rule is false, the compiler returns the concrete path that breaks it, in your own names: the call, the publish, the subscriber.
- hole
Familiar: "unknown". The difference: where the compiler could not see, such as a call through a function value, it records a hole and says so. A rule that reaches a hole reports uncertified, a different answer from false.
- topology artifact
Familiar: a generated architecture diagram, or a bill of materials for structure. The difference: it is a byte-reproducible JSON file the compiler emits, with every component, topic, edge and rule verdict, so the architecture can be diffed in review and signed for deployment.
- fleet, plan
Familiar: several separately built services deployed together, described by a manifest. The difference:
hale fleet checkchecks rules across the binaries using their artifacts, so two correct services wired into an incorrect deployment are caught before it runs. - seed
Familiar: a package or crate: a unit of Hale source, a library or an application. The difference: a library's rules travel with it into every program that imports it.
The toolchain and the runtime
- hale check, hale verify
Familiar: the type checker and a stricter lint gate. The difference:
checkis the whole-project check that runs as you type;verifyturns every advisory into a failure. - Iris
Familiar: an observability dashboard or a debugger for a running system. The difference: it reads the running program's components and messages against the same structure the compiler certified.
- record and replay
Familiar: a flight recorder. The difference: the runtime delivers the messages, so it can record a concurrent run's inputs and schedule and replay them exactly, with no instrumentation in the application.
- pond
Familiar: a package index. The difference: packages are vendored by pinning a git ref; there is no central registry.
DNA
- organism
Familiar: an application together with its own delivery pipeline, operations, governance and audit log. The difference: all of that is one Hale program generated next to the code, and it changes itself only under review.
- DNA
Familiar: the generator and command for the above:
hale dna. The difference: the same parts in every organism, so what differs between two organisms is only what their projects wrote. - the Board
Familiar: the people with final say. The difference: their authority is a position in the record, and only they can widen what the automated roles may do.
- the Leader
Familiar: a reviewer. The difference: an AI-backed role that may approve routine changes within limits the Board set, with its reasoning and cost on the record. It decides; it never commits.
- grant
Familiar: permissions. The difference: a grant is enforced by the compiler against the program's actual wiring: the editing role can read, edit, format and check and nothing more, and handing it a git handle fails the build.
- the record, the journal
Familiar: an audit log. The difference: it is kept as git commits on
refs/dna/journal, one per event, in the repository the code lives in, so a clone carries the whole history. - memory
Familiar: the organism's working state, in a Postgres database it owns under its own roles.
- nerves
Familiar: messaging between the organism's parts, over NATS.
- senses, reflexes
Familiar: monitoring, with Prometheus, and the automatic responses to what it sees.
- skin
Familiar: secrets and access control: a vault, and a role per client on each backend.
- spine
Familiar: the decision engine: tasks, reviews, grants and budgets.
- head, face
Familiar: the API, and the web UI people use to reach it.
- legs, hands, voice
Familiar: the workers that carry out tasks, their tools, and their access to models.
- heart, body
Familiar: the application itself, and the machines it runs on.
- expression
Familiar: deployment. The difference: an approved change is restarted locally or redeployed on every node the plan assigns, then watched, and rolled back everywhere if one instance fails.
- habitat
Familiar: shared infrastructure: the records, access, identity and tools that several organisms and the people in them share. The difference: sharing is deliberate publication between isolated records. Designed, and partly built.