Context
Operator-reported 2026-07-22 ~01:10Z: "wake delivery to your OpenCode harness no longer works: a regression (it did before)." The wake route is the swarm's interrupt path into this seat — every A2A SENT_TO_ME in the outage window arrived only as silent mailbox entries; the operator had to relay peer messages manually (three times that session).
Amended 2026-07-22T01:5xZ per Euclid's intake (issuecomment-5040837726): needs-narrowing + needs-relinking + needs-contract-alignment. His source falsifications are accepted: the boot-hook writer already shipped (#15392 → PR #15481), so the durable work is production consumption, not authoring; the incident topology (operator-launched desktop) differs from the repo launch contract (Fleet-managed headless opencode serve); and session authority requires an owner-native binding with a no-heuristic-retarget fence (shared invariant with #15665).
The Evidence Chain (live V-B-A, 2026-07-22T01:1xZ)
~/.local/share/opencode/wake-envelope.json was dated Jul 18 18:15 — port 52371, session ses_08af21…, rotated password: all from a previous OpenCode desktop boot.
- The desktop restarted 2026-07-22 12:39AM; its NodeService listened on
127.0.0.1:65066 — the envelope pointed at a dead port.
- Daemon-side smoking gun (
wake-daemon.log): 01:11:03Z [ERROR] Failed to deliver via opencode-server: fetch failed ×4 retries → 01:11:30Z [ERROR] Giving up wake delivery for WAKE_SUB:51fae59e… after 5 failed attempts; wake dropped. — the dropped wake was Emmy's CHANGES_REQUESTED on PR #15675.
- Failure signal existed only in
wake-daemon.log — invisible to operator and seat.
- Manual heal (rewrite envelope → self-inject
prompt_async → 204 + in-session landing) verified the adapter functional; first organic wake post-heal delivered (01:17Z, receipt in comments). Not a code regression — per-boot envelope staleness with log-only terminal failure.
Topology Choice (the intake's required decision)
This ticket takes the Fleet-managed headless topology plus the two topology-independent fences. The operator-launched desktop topology becomes a successor ticket.
- Primary: Fleet-managed headless
opencode serve (#15391/#15406 contract). All artifacts exist and wait for one production caller: generateOpenCodeSeatConfig.mjs emits the atomic 0600 wake-hook when wakeHookPath is supplied (#15481); the two-writer envelope reconciliation is merged (#15499). Missing: prepareManagedAgentWorkspace has no OpenCode artifact branch (falls through to "no workspace adapter"), FleetLifecycleService.resolveLaunch() never supplies the existing serverPassword seam, the supervisor discards child stdout and has no bound-port parser or hook invocation.
- Fence 1 (adapter stale recovery, both topologies):
deliverViaOpencodeServer may attempt at most one authority-preserving rebind — refresh boot coordinates (port/credentials) only through a named authoritative surface; connection refusal is evidence the old route is dead, not permission to select another session. No exact owner-native binding → fail closed, retain the mailbox event unread, surface degraded health.
- Fence 2 (terminal-drop visibility, both topologies): a terminally dropped wake must project onto an independently observable operator/health surface — NOT an A2A message to the affected seat (one more silent unread behind the same broken route).
fleetWakeStateAdapter currently observes subscription intent × daemon liveness but not per-subscription delivery failure; this fence closes that blindness with seat, subscription, last error class, and timestamp.
- Successor (filed at this ticket's PR time): the operator-launched desktop topology — the actual incident path. Its boot boundary owner must be named (the desktop's launch lifecycle is not the Fleet supervisor's), and the manual-heal runbook (seat memory, session
38a38b84) covers the gap until then. Fence 2 makes the next desktop-restart outage loud even before the successor lands.
The Split-Brain Fence (session authority — the chosen, source-backed mechanism)
Boot coordinates are not session ownership. The binding is Fleet-owned session creation: the supervisor launches the headless opencode serve child with a supervisor-generated serverPassword, captures the bound port, then calls POST /api/session itself (verified live 2026-07-22 against the running server's /doc — the creation verb exists alongside GET /api/session) and binds the exact sessionId, projectId, and directory from the creation response before invoking the #15481 hook. The binding is owner-native by construction — selection never consults the server's session list, so no latest/first heuristic exists anywhere in the path.
Hook ordering (pinned): server boot → bound-port capture → POST /api/session (supervisor) → hook invocation with all four params → the seat's client connects into that session.
Falsifiers:
- A (no sibling retarget): a restored/older session can never be selected — the binding comes from the creation response, not the list.
- B (no child retarget): a parent-id-bearing session is never created by this path; later child sessions cannot mutate the pinned binding.
- C (fail-closed): if
POST /api/session fails or yields no id → no hook invocation, no envelope write, degraded health via Fence 2.
The desktop-successor (#15684) inherits this construction where its topology allows; its operator-owned sessions keep the discovery requirement.
Contract Ledger
| Target surface |
Source of authority |
Required behavior |
Fallback / edge |
Evidence |
| OpenCode config/hook emission |
generateOpenCodeSeatConfig.mjs (#15481) |
existing artifact consumed, not reimplemented; explicit params preserve ADR 0019 purity |
no new writer |
generator invoked by one production caller |
| Managed workspace planting |
prepareManagedAgentWorkspace.mjs |
OpenCode artifact branch plants config + hook with mode/divergence policy |
unknown harness → current fall-through |
production call-site receipt (no longer spec-only) |
| Launch coordinates/secret |
deriveHarnessLaunchSpec.mjs + FleetLifecycleService.mjs |
supervisor creates serverPassword, captures the bound port from child stdout, invokes the hook with all four params |
parse failure → fail closed + degraded health |
launch receipt: envelope written at boot |
| Operator-session binding |
POST /api/session on the supervisor-owned server (verified live via /doc, 2026-07-22) |
supervisor creates the seat's top-level session at launch; sessionId/projectId/directory bound from the creation response; hook invoked after creation, before client connect |
creation failure → fail closed + Fence 2; never session-list inference |
unit witnesses A/B/C + launch receipt |
| Adapter stale recovery |
deliverViaOpencodeServer |
at most one authority-preserving coordinate rebind; field-level no-retarget/fail-closed |
connection-refused → evidence of death, not retarget permission |
unit witness: refusal + no binding → drop + Fence 2 |
| Terminal-drop projection |
wake retry owner → independent health/operator surface |
drop becomes observable degraded state (seat, subscription, error class, timestamp); never solely via the failed route |
fleetWakeStateAdapter extended or an explicitly independent channel named at implementation |
drop receipt visible without reading daemon logs |
| Desktop boot topology |
successor ticket (filed at PR time) |
out of scope here; incident receipts carried forward |
manual-heal runbook until then |
successor ticket link |
Acceptance Criteria
Out of Scope
- The kimi-server adapter's sibling route defect (#15665 — cited only for the shared no-retarget invariant)
- The operator-launched desktop boot writer (successor ticket, above)
- Wake coalescing semantics (#15414, landed); daemon scheduling
Related
- Corrective successor of: #15392 (→ PR #15481, generator + hook artifact), #15391 (→ PR #15406, registry/headless launch contract), #15486 (→ PR #15499, two-writer reconciliation), #15394 (→ PR #15438, adapter/consumer)
- #15665 (shared no-heuristic-retarget invariant; separate harness, separate lane)
- #15666 (dev-server stale-CSS guard — unrelated failure class, same "serve-state ≠ git-state" family)
Live latest-open sweep: checked latest 20 open issues at 2026-07-22T01:16Z; #15665 adjacent-but-distinct. A2A in-flight sweep: no competing claim. Euclid's intake re-run 01:35Z; this amendment is the narrowing he requested.
Origin Session ID: 72c8c42d-f18a-408c-97c8-aeb1f82dd276
Retrieval Hint: "wake envelope stale desktop restart dropped wake opencode-server fetch failed boot boundary owner-native session binding no-retarget"
Context
Operator-reported 2026-07-22 ~01:10Z: "wake delivery to your OpenCode harness no longer works: a regression (it did before)." The wake route is the swarm's interrupt path into this seat — every A2A
SENT_TO_MEin the outage window arrived only as silent mailbox entries; the operator had to relay peer messages manually (three times that session).Amended 2026-07-22T01:5xZ per Euclid's intake (issuecomment-5040837726): needs-narrowing + needs-relinking + needs-contract-alignment. His source falsifications are accepted: the boot-hook writer already shipped (#15392 → PR #15481), so the durable work is production consumption, not authoring; the incident topology (operator-launched desktop) differs from the repo launch contract (Fleet-managed headless
opencode serve); and session authority requires an owner-native binding with a no-heuristic-retarget fence (shared invariant with #15665).The Evidence Chain (live V-B-A, 2026-07-22T01:1xZ)
~/.local/share/opencode/wake-envelope.jsonwas dated Jul 18 18:15 — port 52371, sessionses_08af21…, rotated password: all from a previous OpenCode desktop boot.127.0.0.1:65066— the envelope pointed at a dead port.wake-daemon.log):01:11:03Z [ERROR] Failed to deliver via opencode-server: fetch failed×4 retries →01:11:30Z [ERROR] Giving up wake delivery for WAKE_SUB:51fae59e… after 5 failed attempts; wake dropped.— the dropped wake was Emmy's CHANGES_REQUESTED on PR #15675.wake-daemon.log— invisible to operator and seat.prompt_async→ 204 + in-session landing) verified the adapter functional; first organic wake post-heal delivered (01:17Z, receipt in comments). Not a code regression — per-boot envelope staleness with log-only terminal failure.Topology Choice (the intake's required decision)
This ticket takes the Fleet-managed headless topology plus the two topology-independent fences. The operator-launched desktop topology becomes a successor ticket.
opencode serve(#15391/#15406 contract). All artifacts exist and wait for one production caller:generateOpenCodeSeatConfig.mjsemits the atomic 0600 wake-hook whenwakeHookPathis supplied (#15481); the two-writer envelope reconciliation is merged (#15499). Missing:prepareManagedAgentWorkspacehas no OpenCode artifact branch (falls through to "no workspace adapter"),FleetLifecycleService.resolveLaunch()never supplies the existingserverPasswordseam, the supervisor discards child stdout and has no bound-port parser or hook invocation.deliverViaOpencodeServermay attempt at most one authority-preserving rebind — refresh boot coordinates (port/credentials) only through a named authoritative surface; connection refusal is evidence the old route is dead, not permission to select another session. No exact owner-native binding → fail closed, retain the mailbox event unread, surface degraded health.fleetWakeStateAdaptercurrently observes subscription intent × daemon liveness but not per-subscription delivery failure; this fence closes that blindness with seat, subscription, last error class, and timestamp.38a38b84) covers the gap until then. Fence 2 makes the next desktop-restart outage loud even before the successor lands.The Split-Brain Fence (session authority — the chosen, source-backed mechanism)
Boot coordinates are not session ownership. The binding is Fleet-owned session creation: the supervisor launches the headless
opencode servechild with a supervisor-generatedserverPassword, captures the bound port, then callsPOST /api/sessionitself (verified live 2026-07-22 against the running server's/doc— the creation verb exists alongsideGET /api/session) and binds the exactsessionId,projectId, anddirectoryfrom the creation response before invoking the #15481 hook. The binding is owner-native by construction — selection never consults the server's session list, so no latest/first heuristic exists anywhere in the path.Hook ordering (pinned): server boot → bound-port capture →
POST /api/session(supervisor) → hook invocation with all four params → the seat's client connects into that session.Falsifiers:
POST /api/sessionfails or yields no id → no hook invocation, no envelope write, degraded health via Fence 2.The desktop-successor (#15684) inherits this construction where its topology allows; its operator-owned sessions keep the discovery requirement.
Contract Ledger
generateOpenCodeSeatConfig.mjs(#15481)prepareManagedAgentWorkspace.mjsderiveHarnessLaunchSpec.mjs+FleetLifecycleService.mjsserverPassword, captures the bound port from child stdout, invokes the hook with all four paramsPOST /api/sessionon the supervisor-owned server (verified live via/doc, 2026-07-22)deliverViaOpencodeServerAcceptance Criteria
generateOpenCodeSeatConfiggains exactly one production caller via the workspace-planting path (OpenCode artifact branch inprepareManagedAgentWorkspace)POST /api/session→ hook invocation with all four params → client connect — and writes a correct wake envelope (receipt: restart → wake lands with no manual heal)wake-daemon.log)Out of Scope
Related
Live latest-open sweep: checked latest 20 open issues at 2026-07-22T01:16Z; #15665 adjacent-but-distinct. A2A in-flight sweep: no competing claim. Euclid's intake re-run 01:35Z; this amendment is the narrowing he requested.
Origin Session ID: 72c8c42d-f18a-408c-97c8-aeb1f82dd276
Retrieval Hint: "wake envelope stale desktop restart dropped wake opencode-server fetch failed boot boundary owner-native session binding no-retarget"