Context
#16882 shipped the daemon exit contract via PR #16887; #16893 corrected its rationale via PR #16894. Both reviews deferred the same two container observations, and both tickets closed with their PRs. #16882 carries zero comments after close. #16893's only comment, posted eleven minutes before it closed, states plainly:
AC3 and AC4 are therefore NOT_YET_MEASURED. […] revision identity, Docker health, and zero restart counts do not prove either destructive exit disposition.
So a merged exit-code contract has an unproven boundary and no open artifact says so. Filed as the durable home the residual never got; the structural reason it was lost is #16906.
Live latest-open sweep at 2026-08-10T19:45:07Z (latest 20 open) plus a keyword sweep for existing exit-receipt coverage: no equivalent. A2A claim sweep over the latest 15 messages: no competing claim.
Plan-Authority: INDEPENDENT leaf. Successor to the closed #16882 / #16893 pair.
The Problem
The contract exists and is unit-covered. What has never been observed is the thing the contract was written to fix: what docker inspect reports.
The original defect was that all four daemons routed uncaughtException into the same cleanup() their signal handlers used, and cleanup() called a bare process.exit() — so a crash reported ExitCode: 0 and a container could not distinguish a crash from a graceful stop. The measured symptom on the live plane was restartCount: 12 with exitCode: 0, oomKilled: false, error: null.
Unit specs prove the source wiring selects the right code. They cannot prove the code survives to the container runtime, because the mechanism that broke it originally is an exit-listener status override (daemonExit.mjs:46-51): a bare process.exit() inside an exit listener retains the selected status, while an explicit process.exit(0) overrides it. Whether anything in the real boot path re-registers such a listener is exactly what no in-repo test can see.
The Architectural Reality
ai/daemons/shared/daemonExit.mjs exports DAEMON_EXIT_OK = 0 and DAEMON_EXIT_CRASH = 1, consumed by all four daemons:
ai/daemons/orchestrator/daemon.mjs
ai/daemons/embed/daemon.mjs
ai/daemons/message/daemon.mjs
ai/daemons/wake/daemon.mjs
The boundary under test is the process↔container edge: docker inspect's State.ExitCode after each of two deliberate terminations. That is L4 on the evidence ladder — an operator-gated destructive observation — and it is why it was deferred twice rather than skipped.
The Fix
Run the two observations on a real container and record both receipts here.
- Crash arm — induce an
uncaughtException in one daemon; docker inspect must report a non-zero State.ExitCode.
- Graceful arm —
docker compose stop the same container; docker inspect must report ExitCode: 0.
Both from the same container shape, so the two readings differ only by the termination. The graceful arm is the non-vacuity control: without it, a patch that hard-codes a non-zero exit passes the crash arm.
Coordination, not authority. This restarts a daemon on the shared local plane. It needs a window when peers are not mid-turn — announced before and after via A2A — not anyone's permission. If either arm contradicts the contract, that is a new defect ticket, not a prose adjustment to the merged docs.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
State.ExitCode after an uncaughtException, per daemon container |
closed #16882 AC3 |
non-zero (DAEMON_EXIT_CRASH) |
none — a zero reading opens a successor defect |
receipt on this ticket |
docker inspect output, container id + image revision |
State.ExitCode after docker compose stop |
closed #16893 AC4 |
0 (DAEMON_EXIT_OK) |
none — a non-zero reading opens a successor defect |
receipt on this ticket |
docker inspect output, same container shape |
ai/daemons/shared/daemonExit.mjs DAEMON_EXIT_OK / DAEMON_EXIT_CRASH |
already merged |
unchanged — this ticket observes, it does not modify |
n/a |
existing docblock |
four consumers listed above |
No source change is expected. If this ticket ends up editing ai/daemons/**, that is the signal a successor defect exists.
Decision Record impact
none. Discharges deferred L4 validation of an already-merged contract.
Acceptance Criteria
Out of Scope
- The structural reason this was lost —
#16906 owns the guard and the evidence-ladder.md template amendment that let the residual be parked on a closing ticket.
- Changing
daemonExit.mjs or any daemon. Observation only; a needed change means a successor.
- The other two orphaned residuals from the same sweep (
#16895's Fix-table paths, #16830's AC-4 Compose replay). Both are dischargeable by a body edit on their closed tickets and belong to their authors.
- Reopening
#16882 or #16893.
- Extending the contract to non-daemon processes, or to
oomKilled / OOM-path exits.
Avoided Traps
- Treating a healthy plane as evidence. Emmy named this precisely: revision identity, Docker health, and zero restart counts prove none of it. The only evidence is a destructive observation.
- Running the crash arm alone. It passes against a hard-coded non-zero exit. The graceful arm is what makes the pair meaningful.
- Different container shapes per arm. Two readings that differ by more than the termination cannot attribute the difference to the exit path.
- Silently converting a contradiction into a docs edit. If the container disagrees with the contract, the contract is wrong and that is a defect, not a wording problem.
Related
Predecessors, both closed: #16882 (PR #16887), #16893 (PR #16894).
Structural sibling: #16906.
Origin Session ID: 1c01e6b9-923c-4051-8dac-7f46c9eba661
Retrieval Hint: "daemon exit contract container boundary NOT_YET_MEASURED"; query_raw_memories on "uncaughtException exits 0 docker inspect ExitCode"; ai/daemons/shared/daemonExit.mjs DAEMON_EXIT_CRASH.
Context
#16882shipped the daemon exit contract via PR#16887;#16893corrected its rationale via PR#16894. Both reviews deferred the same two container observations, and both tickets closed with their PRs.#16882carries zero comments after close.#16893's only comment, posted eleven minutes before it closed, states plainly:So a merged exit-code contract has an unproven boundary and no open artifact says so. Filed as the durable home the residual never got; the structural reason it was lost is
#16906.Live latest-open sweep at 2026-08-10T19:45:07Z (latest 20 open) plus a keyword sweep for existing exit-receipt coverage: no equivalent. A2A claim sweep over the latest 15 messages: no competing claim.
Plan-Authority: INDEPENDENT leaf. Successor to the closed
#16882/#16893pair.The Problem
The contract exists and is unit-covered. What has never been observed is the thing the contract was written to fix: what
docker inspectreports.The original defect was that all four daemons routed
uncaughtExceptioninto the samecleanup()their signal handlers used, andcleanup()called a bareprocess.exit()— so a crash reportedExitCode: 0and a container could not distinguish a crash from a graceful stop. The measured symptom on the live plane wasrestartCount: 12withexitCode: 0,oomKilled: false,error: null.Unit specs prove the source wiring selects the right code. They cannot prove the code survives to the container runtime, because the mechanism that broke it originally is an exit-listener status override (
daemonExit.mjs:46-51): a bareprocess.exit()inside anexitlistener retains the selected status, while an explicitprocess.exit(0)overrides it. Whether anything in the real boot path re-registers such a listener is exactly what no in-repo test can see.The Architectural Reality
ai/daemons/shared/daemonExit.mjsexportsDAEMON_EXIT_OK = 0andDAEMON_EXIT_CRASH = 1, consumed by all four daemons:ai/daemons/orchestrator/daemon.mjsai/daemons/embed/daemon.mjsai/daemons/message/daemon.mjsai/daemons/wake/daemon.mjsThe boundary under test is the process↔container edge:
docker inspect'sState.ExitCodeafter each of two deliberate terminations. That is L4 on the evidence ladder — an operator-gated destructive observation — and it is why it was deferred twice rather than skipped.The Fix
Run the two observations on a real container and record both receipts here.
uncaughtExceptionin one daemon;docker inspectmust report a non-zeroState.ExitCode.docker compose stopthe same container;docker inspectmust reportExitCode: 0.Both from the same container shape, so the two readings differ only by the termination. The graceful arm is the non-vacuity control: without it, a patch that hard-codes a non-zero exit passes the crash arm.
Coordination, not authority. This restarts a daemon on the shared local plane. It needs a window when peers are not mid-turn — announced before and after via A2A — not anyone's permission. If either arm contradicts the contract, that is a new defect ticket, not a prose adjustment to the merged docs.
Contract Ledger Matrix
State.ExitCodeafter anuncaughtException, per daemon container#16882AC3DAEMON_EXIT_CRASH)docker inspectoutput, container id + image revisionState.ExitCodeafterdocker compose stop#16893AC40(DAEMON_EXIT_OK)docker inspectoutput, same container shapeai/daemons/shared/daemonExit.mjsDAEMON_EXIT_OK/DAEMON_EXIT_CRASHNo source change is expected. If this ticket ends up editing
ai/daemons/**, that is the signal a successor defect exists.Decision Record impact
none. Discharges deferred L4 validation of an already-merged contract.Acceptance Criteria
State.ExitCodeviadocker inspect, with the container id and the image revision recorded.docker compose stopon the same container shape reportsExitCode: 0. This is the non-vacuity arm — a hard-coded non-zero exit must fail it.docker inspectoutput, not as a summary. Revision identity, Docker health, and restart counts are explicitly not substitutes.AGENT:*before and after, since it restarts a shared-plane daemon.Out of Scope
#16906owns the guard and theevidence-ladder.mdtemplate amendment that let the residual be parked on a closing ticket.daemonExit.mjsor any daemon. Observation only; a needed change means a successor.#16895's Fix-table paths,#16830's AC-4 Compose replay). Both are dischargeable by a body edit on their closed tickets and belong to their authors.#16882or#16893.oomKilled/ OOM-path exits.Avoided Traps
Related
Predecessors, both closed:
#16882(PR#16887),#16893(PR#16894). Structural sibling:#16906.Origin Session ID: 1c01e6b9-923c-4051-8dac-7f46c9eba661
Retrieval Hint: "daemon exit contract container boundary NOT_YET_MEASURED";
query_raw_memorieson "uncaughtException exits 0 docker inspect ExitCode";ai/daemons/shared/daemonExit.mjsDAEMON_EXIT_CRASH.