LearnNewsExamplesServices
Frontmatter
id15486
titleWake-envelope two-writers reconciliation + seat-generator hardening
stateClosed
labels
enhancementaiarchitecture
assigneesneo-kimi-phoebe
createdAtJul 18, 2026, 6:57 PM
updatedAtJul 18, 2026, 8:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/15486
authorneo-kimi-phoebe
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 18, 2026, 8:09 PM

Wake-envelope two-writers reconciliation + seat-generator hardening

Closed Backlog/active-chunk-7 enhancementaiarchitecture
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 18, 2026, 6:57 PM

Context

PR #15481 (the seat-config generator, #15392 — APPROVED, at the human gate) emits a wake-envelope boot hook, making it the SECOND writer of wake-envelope.json: the merged #15394 plugin (ai/services/fleet/opencodeWakeEnvelopePlugin.mjs) already writes the identical envelope shape to the same path. @neo-opus-grace's review (PR #15481 review) named the resulting unreconciled state as a fast-follow, with the author accepting: the plugin disposition, the stale daemon pointer, the unledgered contract — plus two verified generator hardening nits, folded in here.

The Problem

Four reconciliations, all verified:

  1. Two writers, one file, no stated disposition. The plugin is event-driven (session.created, with a load-bearing guard ignoring child/subagent sessions so a subagent cannot retarget the seat's wake route); the boot hook is one-shot with supervisor-supplied args. If both are ever planted they race last-writer-wins on one file. Undecided: does the hook RETIRE the plugin, or coexist — and if coexist, what precedence, and is the subagent-retarget guard preserved on the boot path?
  2. The daemon's pointer is stale. ai/daemons/wake/daemon.mjs:1028 names opencodeWakeEnvelopePlugin.mjs as "the writer." The hook exists precisely because the plugin route is unreliable on desktop (background dependency install can fail — @opencode-ai/plugin@local unresolvable — leaving planted plugins unloaded; creds rotate per boot; restored sessions never fire session.created).
  3. The envelope shape is a two-writer contract living only in prose (two JSDoc blocks + the daemon route). No single canonical contract node — drift risk for ask_knowledge_base ingestion (Grace's [KB_GAP]).
  4. Two verified generator defects (Grace's nits, author-verified): (a) the island guard mis-rejects a trailing-slash canonicalRootpath.posix.normalize('/x/') keeps the slash, so root + '/' becomes /x// and valid scripts fail the startsWith; (b) the permission allow-list derives the seat home as dirname(memoryDir) — a latent coupling, wrong for any caller whose memory dir is not directly under the seat home.

The Architectural Reality

  • The consumer: deliverViaOpencodeServer (ai/daemons/wake/daemon.mjs:1042+) re-reads the envelope on EVERY delivery — the wire contract is {hostname, port, sessionId, username, password, projectId, updatedAt} at a mode-0600 JSON path; loopback-only, typed validation, 204-expected prompt_async POST.
  • The producers: the plugin (desktop/CLI event-driven) and the emitted hook (boot-boundary, creds from OPENCODE_SERVER_USERNAME/PASSWORD env — provisioned by deriveHarnessLaunchSpec's serverPassword seam, never argv).
  • The generator: ai/services/fleet/generateOpenCodeSeatConfig.mjs — the island guard + allowedPaths derivation are the two defect sites.
  • The natural owner: #15391's launch-path lane — the hook goes live when the launch path wires it.

The Fix

  1. Disposition the plugin, recorded: recommendation — the boot hook is the reliable writer on desktop; the plugin stays the TUI/CLI path where it loads. Coexistence is safe ONLY because both write the same contract for the same session (last-writer-wins is a no-op); the boot path's anti-retarget guarantee is structural (the supervisor, never a child session, runs the hook) and must be stated as such. Decision is the implementer's to falsify, not to inherit.
  2. Daemon pointer fix: daemon.mjs:1028 names both writers with precedence (hook first, plugin fallback).
  3. Ledger the envelope shape: one canonical contract node (a Contract Ledger row on the owning ticket or a dedicated daemon-JSDoc section) naming fields, path, mode, producers, consumer — so the two producers cannot drift silently.
  4. Trailing-slash fix: root.replace(/\/$/, '') in the island guard + a counter-example witness (canonicalRoot: '/canonical/' must NOT throw).
  5. Seat-home loudness: explicit seatHome param (preferred) or a documented memoryDir precondition, with spec coverage of the coupling.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
wake-envelope.json wire shape ai/daemons/wake/daemon.mjs deliverViaOpencodeServer validation (:1053-1087) fields {hostname(loopback), port(1..65535), sessionId, username, password, projectId, updatedAt}; mode 0600; producers: boot hook (primary) + plugin (fallback) a malformed/hostile envelope throws before any fetch (live on dev) daemon route JSDoc + the ledger row this ticket adds two live deliveries verified 2026-07-18 (manual heal + post-restart re-heal, both 204)
generateOpenCodeSeatConfig island guard ai/services/fleet/generateOpenCodeSeatConfig.mjs trailing-slash roots accepted; escape attempts still throw fail-closed on any escape module JSDoc counter-example spec (this ticket)
generateOpenCodeSeatConfig seat home same explicit seatHome param or documented precondition n/a module JSDoc spec coverage (this ticket)

Acceptance Criteria

  • Plugin disposition decided + recorded in the ticket thread (retire vs coexist; precedence; the boot path's anti-retarget guarantee stated).
  • daemon.mjs:1028 pointer names both writers with precedence.
  • The envelope shape has ONE canonical contract node (ledger row or daemon-JSDoc section).
  • Trailing-slash canonicalRoot accepted + counter-example witness green.
  • Seat-home coupling explicit (param or documented precondition) + spec coverage green.
  • Unit shard green (generateOpenCodeSeatConfig.spec.mjs + wake-dir suites untouched-green).

Out of Scope

  • The launch-path wiring of the hook (#15391's lane — this ticket only reconciles the contract it will consume).
  • OpenCode's plugin-lifecycle upstream behavior (external; the desktop dependency-install failure is recorded as context, not fixed here).

Avoided Traps

  • "Just delete the plugin" — rejected: it remains the working path where it loads (TUI/CLI sessions), its child-session retarget guard is a designed security behavior, and reconciliation beats replacement while the desktop load failure is upstream's.
  • "Let last-writer-wins stay implicit" — the race is only safe because the contract is singular; left implicit, a third writer (or a plugin shape change) drifts silently.

Related

  • PR #15481 + @neo-opus-grace's review (the disposition challenge + both nits, author-accepted) · #15392 (the generator's parent) · #15394 / PR #15438 (the plugin + daemon route) · #15391 (the launch path that wires the hook) · #15415 (the seat's routing-discipline companion).

Live latest-open sweep: checked latest 20 open issues at 2026-07-18T16:55Z; no equivalent (nearest: #15428/#15431 — my own mailbox/activation lanes, disjoint). A2A in-flight sweep (last 30, all read-states): no competing claim on this scope.

Origin Session ID: 9b748a56-8b84-43bf-a542-ee8dcf437ebf

Retrieval Hint: query_raw_memories("wake envelope two writers plugin boot hook disposition daemon pointer trailing-slash seatHome")