TacidOS
DevelopersGitHub

Decision 0042

The body is declared at birth, and the first boot reads it back

Architecture decision record 0042: The body is declared at birth, and the first boot reads it back

Status
accepted
Date
2026-09-05

Context

ADR-0022 said what the first boot is for: the agent introduces itself, verifies the hardware against the facts, offers models, and shows one suggestion. Until now the first boot was a boot: the installed machine came up, blessed itself, and said nothing, and what the installer had found — the vendor, the product, the processor — was a comment at the top of body/hardware.nix that nothing could read back. A genome is portable and a body is not; the plan’s own word for moving a genome to another machine is reincarnation. A machine whose declaration says what body it was written for, and that reads the declaration back against the body it wakes up in, is the one that can say so.

Prior decisions

ADR-0007 splits the genome into body/, regenerated per machine, and mind/, which moves. ADR-0028 makes the facts a structured system.* API read from the kernel. ADR-0004 makes the keeper the process that writes things down, and ADR-0021 says nothing is shown for its own sake.

Decision

The installer declares what the body was at birth; the keeper reads it back at the first boot; the machine introduces itself once, on the console, and at any time when asked.

The installer writes the birth as data beside the declaration, body/born.json, not as a comment and not as a value in the system:

{ "vendor": "Framework", "product": "Laptop 13", "processor": "Intel(R) Core Ultra 7",
  "cores": 16, "memoryMiB": 32000 }

A file rather than an option, because a value rendered into the system would make the system a function of the birth: a live image that builds a machine’s first system from its genome must build the very system the declaration names, and the vendor’s name is no part of what a machine is built from. The keeper reads the file where the genome is. At the keeper’s first start on a machine — the start that finds nothing in the journal — it reads the same facts the installer read and writes one line: first boot: this is <hostname>, generation 1; the body is as it was born: …, or …; the body differs from its birth: 16 cores then, 2 now. What was not declared is not compared, and memory is compared with a sixteenth of tolerance, because what the kernel keeps for itself moves between kernels. The line is said once, because the journal is the memory of having said it.

tacid introduce says who the machine is, what its body is, what the keeper found at the first boot, and one thing to ask it. tacid-first-boot.service runs it once, after the boot is complete and the daemon is up, with its output on the console as well as in the journal, and leaves a mark in its own state directory so that no later boot repeats it. The graphical session’s version of the same moment is the shell’s to build when there is a shell.

Offering models is not part of this record. The machine says what it has — a rules model that answers offline — and the offer is the installer’s infer profile and the agent’s to make.

What the first run found

The birth was first written as tacid.hardware.born options and rendered into the keeper’s environment. The installation test’s first run with it failed at the installation: the live image evaluated the genome it had written, with the machine’s own vendor, processor and memory in it, to a system other than the one the host had built from the same declaration with the host’s — and, with no network, set out to build curl, Python and the manual from source. A value in the system is a value the system is built from; the birth became a file.

Three more runs found three smaller things. The live image introduced itself — “this is installer, no generation yet; the body’s birth is not declared” — which is honest and beside the point: genesis mode is the one state in which there is no first boot to speak of, and the keeper now reads the marker before the note. The machine that was installed said its piece on the console exactly as designed; the test then waited for tacid-first-boot.service to be active, which a oneshot that has done its one thing never is again, and now waits for its result. And the memory a machine reports is what its kernel counts, a little under what the board holds, so the birth and the first boot agree on a number that is neither’s idea of the truth — the assertion reads a range rather than a constant that would move with a kernel.

Consequences

  • body/hardware.nix carries five options a person can read and correct, and the keeper’s first-boot line is a fact about the machine rather than a greeting: a genome moved to another body says so in its journal before anyone asks.
  • The installation test asserts the introduction on the console after the first boot, the keeper’s line in the history, tacid introduce afterwards, and silence at the second boot. The live image in that test is given the cores its target has, so that the birth it declares is the body the first boot finds; a live image on a real machine has that by construction.
  • A machine installed before this record has no born and is told so: “the body’s birth is not declared”, rather than compared with nothing.

Alternatives rejected

  • A greeting from the daemon at every start. Noise; and the first boot is the one moment a machine has a reason to speak unasked.
  • Comparing the body from the facts alone, without a declaration. There is nothing to compare against; the point is the declaration.
  • A marker of “first boot” in the keeper’s state. The journal is the record, and a keeper that has written nothing has not started before.