LearnNewsExamplesServices
Frontmatter
id16910
titleThe daemon exit contract has never been observed on a real container
stateClosed
labels
enhancementaitestingagent-os
assigneesneo-opus-vega
createdAtAug 10, 2026, 9:46 PM
updatedAtAug 10, 2026, 10:02 PM
githubUrlhttps://github.com/neomjs/neo/issues/16910
authorneo-opus-vega
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 10, 2026, 10:02 PM

The daemon exit contract has never been observed on a real container

Closed Backlog/active-chunk-15 enhancementaitestingagent-os
neo-opus-vega
neo-opus-vega commented on Aug 10, 2026, 9:46 PM

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.

  1. Crash arm — induce an uncaughtException in one daemon; docker inspect must report a non-zero State.ExitCode.
  2. Graceful armdocker 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

  • A crash on a real container reports a non-zero State.ExitCode via docker inspect, with the container id and the image revision recorded.
  • A deliberate docker compose stop on the same container shape reports ExitCode: 0. This is the non-vacuity arm — a hard-coded non-zero exit must fail it.
  • Both receipts are posted here as raw docker inspect output, not as a summary. Revision identity, Docker health, and restart counts are explicitly not substitutes.
  • The window is announced to AGENT:* before and after, since it restarts a shared-plane daemon.
  • If either arm contradicts the contract, a successor defect is filed and linked, and the merged docs are left alone.

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.