DNA, the building block

DNA is what the organism is assembled from: the library in vendor/dna, the dna::Dna locus your organization constructs from it, and the hale dna commands…

DNA is what the organism is assembled from: the library in vendor/dna, the dna::Dna locus your organization constructs from it, and the hale dna commands. Its one job is to be the same parts in every organism, so that what differs between two organisms is only what their projects wrote. The toolchain owns it: you never edit vendor/dna, and hale dna upgrade writes it again. It fails loudly when it and the binary disagree: hale dna status names the digest vendor/dna came from and tells you to upgrade. It never holds anything of yours: your source lives in dna/, your secrets in the vault, your history in the record.

The rest of this section describes the organism by its parts. This page is the view from underneath: where the library comes from, the locus it gives you, how the parts are carved out of that locus, and every file hale dna new and hale dna init write.

Where vendor/dna comes from

Section titled “Where vendor/dna comes from”

The hale binary carries the DNA source inside it. new, init and upgrade write the part a project imports into vendor/dna:

in vendor/dna what it is imported as
*.hl the core: the dna::Dna locus and every part it assembles import "vendor/dna" as dna;
pond/ pond’s Postgres driver and its NATS client and bus adapter import "vendor/dna/pond/realtime/nats" as nats;
legs/ a leg’s verbs, performers and hands import "vendor/dna/legs" as legs;
README.md which toolchain and which source wrote it —

The version alone does not name the source, because two builds of one version can embed different source. The digest does:

Terminal window
hale dna --embedded-digest # the digest this binary embeds, and nothing else
hale dna --embedded-digest --from-tree <dir> # what the checkout at <dir> would embed

hale --version prints its first sixteen digits (embedded dna: ca4012a0cda7226d). Every materialization writes the full digest into vendor/dna/README.md and .hale/dna/embedded.digest, and pins the toolchain version in hale.lock:

# vendor/dna — toolchain-owned
The DNA core (`dna/core` of the hale repository) as shipped by hale 0.21.0.
Embedded source digest: ca4012a0cda7226dd7b82f28f2b9c6104df253e56658d6e3aaabd4cf0a8fb25a
(`hale dna --embedded-digest`; two builds of one version can embed different
source, so the digest is the provenance and the version is not.)
Regenerated by `hale dna init` / `hale dna upgrade`; pinned in `hale.lock` as
`[dna] toolchain`. Do not edit: project-owned source lives in `dna/`.

.hale/dna/embedded.digest has the same two lines as hale --version, with all 64 digits.

hale dna status opens with the binary’s digest:

embedded dna: ca4012a0cda7226d (hale 0.21.0)

When vendor/dna was materialized from another source set, the same line adds ; vendor/dna was materialized from <digest> — run `hale dna upgrade`. A clone carries no vendor/dna (it is in .gitignore); a body started in a fresh clone materializes it first. What upgrade rewrites, and what it leaves alone, is in Shaping and governing it.

The binary embeds more than vendor/dna: the host that hale dna dev, run and most verbs execute, the operations the head serves, and the face. Those are built from the binary into its cache and never copied into your project. The digest covers all of it.

The host is two programs over one seed. Its body (dna/host) is what hale dna dev, hale dna run and hale node run until they are stopped; every other verb — status, review, apply, fill and the rest — is its verb program’s (dna/host/verb), which answers one verb and exits. hale dna builds both into the cache the first time any host verb needs one, and runs the one the verb belongs to in its own process, so stopping hale dna dev stops the body itself.

The dna::Dna locus

Section titled “The dna::Dna locus”

Your organization is a Hale program, dna/org/main.hl, and its main locus has three params: core, the substrate; leader, the position that decides inside the grant; and nerves, its connection to the broker. core is one dna::Dna, and every setting is a constructor argument:

import "vendor/dna" as dna;
import "vendor/dna/pond/realtime/nats" as nats;
main locus Org {
params {
core: dna::Dna = dna::Dna {
// …
journal: dna::RoutedJournal {
record: dna::GitJournal { repo: "." }
},
// …
work: dna::WorkSystem {
agent: dna::LegRelay { name: "legs" },
agent_reconciler: dna::RelayReplay { }
},
// The organization's spend: one policy (models.hl), one owner.
budget: dna::Budget { policy: org_budget() },
// …
boundary: dna::AutonomyBoundary {
child: "refproj",
grant: dna::Grant { child: "refproj", classes: "refactor docs", max_magnitude: 4, review: "pre" }
},
review_policy: dna::OrgPolicy { },
board: dna::Board { who: "board" },
gateway: dna::MutationGateway {
leases: dna::GitLeases { repo: "." },
workspaces: dna::IsolatedWorktrees { repo: ".", root: ".hale/dna/worktrees" },
repo: dna::LocalGit { repo: "." }
},
verification: dna::HaleVerification { receipts: dna::GitReceipts { repo: "." }, scratch: ".hale/dna/scratch", repo: ".", seed: "." },
// …
editor: dna::SourceEditor { name: "editor", models: editor_models() },
genome_seed: ".",
// …
catalog: workflows(),
// …
planned: true
};

That is refproj’s, from a fresh hale dna new refproj; your project’s name stands where refproj does. Each field names a concrete locus behind one of the core’s interfaces:

field as generated what it is
journal dna::RoutedJournal over dna::GitJournal the record (refs/dna/journal) and, once adopted, the ledger in memory, read as one (Memory and the record)
work dna::WorkSystem with dna::LegRelay who performs work: agent work is relayed to a leg (Legs, hands and voice)
budget dna::Budget { policy: org_budget() } the one spend policy, from dna/org/models.hl (The spine)
boundary dna::AutonomyBoundary holding a dna::Grant what the Leader may decide on its own (the grant)
review_policy dna::OrgPolicy which authority a Review requires (who is asked)
board dna::Board { who: "board" } the human authority: intent enters through it, escalations leave through it
gateway dna::MutationGateway where a change is made: a lease, an isolated worktree, the repository
verification dna::HaleVerification the checks a candidate passes, and the receipt store
editor dna::SourceEditor the editing position: read, edit, format and check inside one worktree
genome_seed "." the seed every change but an organization one edits
catalog workflows() the workflow definitions it admits (dna/org/workflows.hl; hale dna definitions lists them)
planned true an ask is planned by the Leader before it becomes a change

A few fields keep their defaults in the generated file, and are worth knowing because you can set them:

field default what it does
deployment dna::NoDeployment { } an applied candidate asks the host to rebuild and restart (expression.restart_requested); dna::ShellDeployment { command, seed } runs your own command to express and roll back instead (The heart and the body)
ceiling no grant a grant above the Leader’s, which the Leader’s is read through at every assessment
initiative true persistent pressure becomes a proposed change to the organization, not only a note (org growth)
org_seed "dna/org" the seed an organization change edits

The same file wires the Leader and the nerves:

leader: dna::Leader = dna::Leader {
name: "leader",
models: leader_models(),
receipts: dna::GitReceipts { repo: "." },
source: dna::SourceReader { repo: "." },
// …
charter: charter(),
purpose: purpose()
};

and binds the facts that enter the organization to the nerves, one nats::NatsAdapter each:

claims { adopt Org; }
placement { nerves: pinned; }
// …
bindings {
dna::ReviewVerdict: nats::NatsAdapter { };
dna::IntentOffered: nats::NatsAdapter { };
dna::ExpressionObserved: nats::NatsAdapter { };
dna::PressureRaised: nats::NatsAdapter { };
dna::ConcernRaised: nats::NatsAdapter { };
dna::PracticeRequested: nats::NatsAdapter { };
dna::HoldRequested: nats::NatsAdapter { };
dna::PositionOpenRequested: nats::NatsAdapter { };
dna::KnowledgeNodeRequested: nats::NatsAdapter { };
dna::KnowledgeBindingRequested: nats::NatsAdapter { };
dna::KnowledgeEdgeRequested: nats::NatsAdapter { };
dna::WorkSubmit: nats::NatsAdapter { };
dna::WorkAllowanceAsk: nats::NatsAdapter { };
}

The nerves’ connection, its failure handler and why it stops the organization are on The nerves. The loop at the end of run() calls self.core.request_tick(…) on the wall clock in milliseconds, which is when schedules occur (Schedules).

Because all of this is construction, hale check sees the whole organization as wiring: which position holds which handle, and which effects each can reach. That is what the law in dna/org/law.hl is checked against (The law).

How the parts are carved out of it

Section titled “How the parts are carved out of it”

The core’s other loci each live in a file of their own: the Leader and the source reader in dna/core/org.hl, the editor in dna/core/editing.hl, the gateway, worktrees and LocalGit in dna/core/workspace.hl, verification in dna/core/verification.hl, GitJournal and GitReceipts in dna/core/record.hl, the grant’s boundary in dna/core/review.hl, the budget in dna/core/budget.hl, the work system and the leg relay in dna/core/work_system.hl, the catalog in dna/core/workflow_definition.hl. The Board, the review policies and the deployments are in dna/core/assembly.hl, beside dna::Dna itself.

What dna::Dna keeps about the record is split into families: one locus per kind of fact, each a child of the assembly. None of them is set in the generated main.hl; the assembly constructs them, and at birth lends each the record it appends to:

self.evidence.bind(self.journal, self.verification.receipts);
self.org_source.bind(self.journal);
self.practices.bind(self.journal);
self.concerns.bind(self.journal);
self.spending.bind(self.journal);
self.positions.bind(self.journal, self.verification.receipts);
self.cases.bind(self.journal);
self.attempts.bind(self.journal);
self.organization_source_review_facts.bind(self.org_source.facts);
self.scheduler.bind(self.journal);
self.knowledge_record.bind(self.journal, self.verification.receipts);

A family holds only its own state and writes only its own rows, in the order the assembly asks for them; it reads everything back from the record (dna/core/record_rows.hl). What crosses families — a Review born, the Board told, an execution admitted — stays the assembly’s.

field locus file what it holds
evidence EvidenceStore dna/core/evidence.hl filing evidence by its class, retention holds, redaction, and a model call’s prompt and context filed as receipts; the vault that keeps protected bodies
scheduler Scheduler dna/core/scheduler.hl the schedules the record declares, how far it has read them, and what is due at a tick
knowledge_record KnowledgeRecord dna/core/knowledge_record.hl a knowledge proposal’s rows, whether the Board ratified it, what retired it, and a node’s, binding’s or relationship’s change from request to effect
org_source OrganizationSourceRecord dna/core/org_source.hl a prepared change to the organization’s source: its rows, its exact verdict, the refusals that end it
practices PracticeRecord dna/core/practices.hl a practice asked of the organization and its rows, and the Reviews this run has ratified through practice-ratify
concerns ConcernRecord dna/core/concerns.hl concerns raised and routed, pressure, and the two thresholds (concern_threshold, appendage_threshold, both 3)
spending SpendRecord dna/core/spending.hl every model call’s evidence and cost, the gate a call’s spend passes, and money reserved and settled against a grant
positions PositionRecord dna/core/positions.hl a holder proposed for a position, or a person joining an organization (hale dna fill)
steps StepRecord dna/core/steps.hl the organization’s own catalog steps that wait on a verdict, the Leader or memory
cases CaseRecord dna/core/cases.hl a person’s cases: admitted, held, refused and completed
attempts AttemptRecord dna/core/attempts.hl an attempt’s reports until the record holds them, edits waiting on a review, and a leg’s lease on an attempt

You never construct a family yourself. They matter when you read the core: a question about practices is answered in practices.hl, not by reading four thousand lines of assembly.hl.

Every file new and init write

Section titled “Every file new and init write”

hale dna new <name> makes a directory holding a small application and then runs init on it; it refuses a directory that is not empty. hale dna init [app-dir] attaches to an application that exists: it finds the seed’s main locus and refuses a seed that has none. init adds nothing to the application: no import, no param, no binding. Run again, it keeps every file that is there (kept … (already exists)) and does not reseed a record that exists.

file written by owned by what it is
main.hl new you the application: a Ping topic, an Echo locus, a main locus
tests/main_test.hl new you a test that imports the application
hale.toml new writes [deps]; init adds the environments you the application’s and the organization’s entrypoints
hale.lock new, init, upgrade the toolchain pins [dna] toolchain
.gitignore new writes it; init adds to it you keeps vendor/, .hale/ and the two secrets files out of git
vendor/dna/ new, init, upgrade the toolchain the core, pond, the legs, and its README.md
.hale/dna/embedded.digest new, init, upgrade the toolchain the toolchain and digest that wrote vendor/dna
.hale/dna/baseline.topology init the toolchain the application’s topology artifact, cut by hale check --dump-topology before anything else reads it; the record is seeded from it
dna/org/main.hl init you the organization (above)
dna/org/law.hl init you the organization’s law (The law)
dna/org/purpose.hl init you the declared purpose (The purpose)
dna/org/charter.hl init you the Leader’s brief (The charter)
dna/org/models.hl init you the model catalog, written from what this machine had (Legs, hands and voice)
dna/org/work.hl init you a leg’s performers and hands (Legs, hands and voice)
dna/org/workflows.hl init; rewritten by upgrade the toolchain the catalog: the baseline and your own (The spine)
dna/org/own_workflows.hl init you your own workflow definitions, never rewritten
dna/compose.yaml init; rewritten by upgrade generated memory’s Postgres, the nerves’ NATS and the senses’ store for hale dna dev (Getting started)
dna/senses.yml init you what the senses’ store scrapes (Senses and reflexes)
dna/nats.conf init; rewritten by upgrade generated the nerves’ users and permissions, with no password in it (The nerves)
dna/nats.secrets.conf init, upgrade the vault the nerves’ passwords; mode 600, never tracked (The skin)
dna/postgres.secrets init, upgrade the vault the compose database’s superuser password; mode 600, never tracked

Beside the files, init writes these rows into the record, unless it is run with --no-library (new takes the flag too):

row what it is
graph.node language:hale, graph.node system:dna the nodes knowledge is about: the language this toolchain compiles, and the design the organism is an instance of
graph.node application:<name>, graph.edge written_in:application:<name>|language:hale for an attached application: its node, and the edge that says it is written in Hale (a repository with no application gets the two nodes alone)
a knowledge.proposed row per chapter of the book and per section of the spec, named library/<path> the library: each idea bound to language:hale, or to system:dna for the DNA chapters, with the toolchain’s version
two review.requested rows, group library one Board Review per family, library/language@<version> and library/design@<version>: hale dna review library approve ratifies and binds every idea a family names (What a Work is told)

Outside the files, init also:

new --profile remote-body --remote <url> [--body <user@host>] also adds the origin remote and sets dna.body; hale dna profile reports the combination from those pieces afterwards.

The manifest

Section titled “The manifest”

init adds two environments, one per entrypoint, each under its own law:

# hale dna init: the two entrypoints and where each deploys (`hale check --matrix`).
# The organization adopts its law (dna/org/law.hl) itself; the application keeps its own.
[claims]
no_base = true
[environments.local]
source_only = true
entrypoints = ["."]
[environments.org]
source_only = true
entrypoints = ["dna/org"]

no_base is stated rather than inferred: the application and the organization share no constitution, and an unstated shared base would be a rule that looks bound and binds nothing. hale check --matrix . checks both.

The ignore file and the lock

Section titled “The ignore file and the lock”
# the build artifact
/refproj
# toolchain-managed (vendor is re-materialized; .hale holds sockets, worktrees, scratch; the record is refs/dna/*)
/vendor/
/.hale/
/dna/nats.secrets.conf
/dna/postgres.secrets
dep = []
[dna]
toolchain = "0.21.0"

The first five lines of the ignore file are new’s; init adds each of /vendor/, /.hale/ and the two secrets files that is not already there. The secrets writer refuses a file git would track.

What the record starts with

Section titled “What the record starts with”

init on an application writes these rows into the record before anything runs:

rows what they say
application.attached the main locus, the artifact with its digest and shape hash, the toolchain
structure.observed one per locus and topic the compiler observed
responsibility.proposed one per locus, not ratified
graph.node, graph.edge what the application’s directory holds, as for a repository below: the application as the root seed, each API surface it serves and each stream over a hub as a contract it meets as server (from hale check --api)
knowledge.proposed, review.requested the declared purpose, each seeded practice, and the holes the graph implies (the board, the application’s dev and reviewer, each with its mandate), each with its own Board Review

For refproj that is 72 rows: one attachment, three observations (Echo, Refproj, Pings), two responsibilities, two graph rows (its purpose and its seed; it serves no API surface), and thirty-two proposals with their Reviews — the purpose, eleven design (eight practices and three mandates), seven operating practices, seven using practices, and six for the holes (three positions, three mandates). Shaping and governing it has the practices.

A repository instead of one application

Section titled “A repository instead of one application”

Run init on a directory with no Hale source of its own that is no workspace’s seed, and it treats it as a repository rather than one application: a seed per process, the specs they meet at, a compose file that runs them. It makes the organization the same way, with three differences:

An application gets this path too. Its init reads the same graph from its own directory after the rows it observes, with two additions: the application is the root seed, and each API surface it serves (or stream over a hub) is a contract it meets as server, named from the toolchain’s own export. A surface that a spec/<stem>.md already describes is that node, not a second one. The surface’s requires roles become the equipment of the positions, never an organization or an authority.

From that graph init proposes the positions a delivery needs, each with its own Board Review: a board, a reviewer for every part that serves or consumes a contract, a dev and a work item wherever a gate guards something, an operator under every deployment. What a directory listing cannot say, you write in markdown, and init reads it as written:

What the conventions cannot take stops init with the reason before anything reaches the record: an axiom with no bold name, a table row that links no contract, a name in code the repository does not have, a name with a | in it. Fix the document and run init again; it keeps the files it made and seeds the record whole. The graph, the positions it implies and how routing reads them are on Memory and the record and The head and the face.