Context
Lane A of the FM-week co-plan (#14560, lane-map comment) reconnects the Fleet cockpit's read/write seams to the dockerized Agent OS plane. Leaf 1 (#16324, PR #16329, merged at a0e6c8f — commit a0e6c0… see PR) moved the mailbox, compose, and catch-up seams onto an identity-verified persistent MCP client (ai/services/fleet/planeMailboxClient.mjs) against the containerized Memory Core, with a fail-closed boot decision in the entrypoint. This leaf migrates the remaining S2 wake-state observation seams, which still read pre-hard-cut host truths.
Live latest-open sweep: checked latest 20 open issues at 2026-08-02T12:29:35Z; no equivalent found. Adjacent-not-duplicate: #16331 / PR #16340 (wake-status semantics convergence across readers — orthogonal axis: status interpretation vs. which truth store is opened; merge-order coordination flagged A2A). A2A in-flight claim sweep (last ~60 min): claims cover #16336, #16343, #15252-R2, #16331 — no overlap with this scope.
The Problem
Post-hard-cut, the wake-state axes wired at ai/services/fleet/devFleetServer.mjs:132-136 measure a plane that no longer exists on the host:
- Subscription axis:
readActiveWakeSubscriptionIdentities opens the host GraphService SQLite. The live WAKE_SUBSCRIPTION rows are written by the containerized Memory Core into the container plane volume — the host graph holds pre-cut relic rows (the class the wake-manifest builder deliberately skips). This is the same split-brain-read defect class leaf 1 removed for the mailbox seams.
- Daemon-liveness axis:
pidFilePath points at the legacy local wake daemon's PID file under the memory-core wakeDaemon.dataDir. No such process runs post-cut (verified: ps finds no daemons/wake/daemon.mjs; delivery is now container-Memory-Core leg 1 → signed host receiver leg 2, per ADR 0019 §10.7). A missing PID file is OBSERVED not-running by the adapter's contract → alive: false.
- Terminal-receipt axis:
deliveryFailureFilePath reads the same retired daemon's receipt file.
Composed through resolveAgentWakeState's truth table, host-relic-active identities render suppressed (intent on + "delivery off") and genuinely-subscribed post-cut identities render off. The cockpit's wake column currently fabricates a fleet-wide blind-switch incident and hides real subscriptions — exactly the failure class the fail-honest taxonomy exists to prevent, produced by stale wiring rather than a broken adapter.
The Architectural Reality
ai/services/fleet/fleetWakeStateAdapter.mjs — already seam-shaped: injected readers, entrypoint-owned config resolution, capability envelope, row-local honest degradation. The taxonomy and truth table need no change.
ai/services/fleet/readActiveWakeSubscriptionIdentities.mjs:43-59 — durable host-SQLite ACTIVE scan; correct for a host plane, split-brain against the containerized one. Its module doc already names why the fleet read is deliberately NOT the caller-owner management API.
ai/services/memory-core/WakeSubscriptionService.mjs (manage_wake_subscription) — every action derives its owner from RequestContextService; list filters to the caller and returns full subscription rows ({id, ...props}) for that owner. The plane surface today cannot answer "which identities hold an active subscription" for a fleet-scoped observer, and full rows would over-disclose if it did.
ai/services/fleet/planeMailboxClient.mjs — leaf 1's identity-verified client already exposes the generic proven-session callTool(name, args) (the catch-up seam rides it, devFleetServer.mjs:198-204).
- Admission precedent: the mailbox-mirror read (leaf 1) — a fleet-wide read admitted by the Memory Core's own permission-grant machinery, decided plane-side, attributed to the verified boot viewer.
- ADR 0019 §10.7 — wake delivery disposition: container Memory Core owns heartbeat/coalescing; the signed host receiver is the final mile; the Shape-C local daemon is a pre-cleanup relic.
The Fix
Memory-Core side (the truth owner):
- Add a fleet-scope observation read to the wake-subscription surface: deduplicated holder identity strings only for ACTIVE subscriptions — deliberately not full rows (owner rows carry endpoint/config/key-adjacent material a roster read has no business seeing). Durable-first SQL mirroring the established ACTIVE-scan shape.
- Admission: the
who_is_online telemetry class (resolved 2026-08-02 against the registered permission kinds, as this body reserved): PermissionService's model is strictly pairwise grantee→owner edges (CAN_READ_INBOX_OF shape) with AGENT:* structurally always-true as a target — a fleet-scoped resource fits neither, and per-seat N-grants would turn missing grants into roster holes (a new fabrication class). The truth-owning service already exposes an authenticated-caller fleet-telemetry read: who_is_online IS WakeSubscriptionService.whoIsOnline. The fleet-identities read is its narrow sibling — same service, same authenticated-caller gate, and strictly less disclosure than the host-side fleet scan has always had (identities only, no rows). Any plane-side refusal (unauthenticated/unadmitted caller) still degrades honestly in the adapter.
- Surface shape (resolved): a new
fleet-identities action on the existing manage_wake_subscription tool — no new MCP tool (#16217 bloat discipline, handler-census friendly), no change to who_is_online's DTO. #16320 note: clients with pinned pre-change schemas may not see the new action value until reconnect; the FM plane client connects fresh per boot, and server-side validation governs.
Fleet side:
devFleetServer.mjs: in plane mode, FleetManager.wakeStateOptions.listActiveSubscriptionIdentities binds to the plane read through the verified client, replacing the host-graph scan; the host-graph scan remains the in-process-mode binding (correct for a host plane). The boot log names the wake-seam plane decision (mirror of leaf 1's witness line).
- Plane mode wires no Shape-C PID/receipt path (they measure a retired daemon). The delivery-liveness axis binds to what the plane can actually vouch for (healthcheck/heartbeat surface if exposed — V-B-A at implementation), else stays honestly
unknown with a named reason. No fabricated on, no fabricated suppressed.
fleetWakeStateAdapter.mjs gains only what that binding needs (an injectable delivery-liveness seam beside the PID-file path) — additive, truth table unchanged.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
manage_wake_subscription fleet-identities action |
ai/services/memory-core/WakeSubscriptionService.mjs (sibling of whoIsOnline) |
identities-only ACTIVE scan, authenticated-caller telemetry class (who_is_online precedent), riding the converged status policy (PR #16340) |
plane refusal of any kind → adapter's honest unknown |
tool description + service JSDoc |
unit specs: refusal × identities-only shape × policy-module consumption |
FleetManager.wakeStateOptions binding |
ai/services/fleet/devFleetServer.mjs:132 (boot entrypoint, leaf-1 plane decision) |
plane mode → plane-backed bulk reader + plane-vouched liveness; in-process mode → existing host bindings |
reader throw → adapter's row-local honest unknown (existing contract) |
entrypoint module doc, step-5 extension |
boot log witness naming the wake-seam plane |
fleetWakeStateAdapter options |
ai/services/fleet/fleetWakeStateAdapter.mjs |
additive injectable delivery-liveness seam |
seam absent → current PID-file behavior |
adapter JSDoc |
existing specs stay green + new seam specs |
Decision Record impact: aligned-with ADR 0019 (§10.7 wake-delivery disposition; any config touch stays leaf-read-at-use-site clean). No new ADR.
Acceptance Criteria
Out of Scope
- Throttle axis:
FleetManager.throttleStateOptions was never wired anywhere (the adapter documents that no platform throttle truth source exists yet) — nothing to migrate.
- Host receiver observability and wake-election policy (S2 read-projection over turn presence) — separate lanes (#16310-adjacent).
- Retiring the Shape-C daemon code itself (hard-cut cleanup owns deletion).
- Cockpit UI changes (#14560 design lane).
- Status-semantics convergence across wake readers — that is #16331 / PR #16340 (Ada); this ticket only changes WHICH store the fleet's plane-mode read opens.
Avoided Traps
- Reading the container SQLite volume from the host process: bypasses the MCP tool contract and tenant admission — the exact authority-model violation the plane exists to end.
- Exposing the fleet scan as full rows through the caller-owner
list action: the owner-scoped verb and the fleet telemetry read have different disclosure contracts; identities-only enumeration matches the established who_is_online telemetry surface, full rows never cross fleet-wide.
- A per-seat pairwise grant model (
CAN_OBSERVE_WAKE_OF): consistent-looking with the permission model, but a missing grant would render as a roster hole — a new fabrication class — and every seat onboarding would carry a silent grant obligation.
- Returning full subscription rows fleet-wide: over-discloses endpoint/config/key-adjacent material to a read that needs identity strings.
- Treating "plane MCP answers" as delivery-lane liveness: presence/reachability and wake-route health are independent signals; MCP reachability must not be inferred into
on.
Related
#13015 (parent epic) · #16324 / PR #16329 (leaf 1: pattern + client) · #16331 / PR #16340 (status semantics, merge-order coordination) · #16310 (wake boot-arming, receiver side) · #16320 (pinned tool schemas) · #14560 (cockpit epic) · D#16304 (merged code vs. running containers)
Origin Session ID: 5892cb00-bb3f-467b-9346-81c509834503
Retrieval Hint: query_raw_memories("fleet wake-state plane migration split-brain subscription scan"); commit anchor: leaf 1 merge (PR #16329).
Context
Lane A of the FM-week co-plan (#14560, lane-map comment) reconnects the Fleet cockpit's read/write seams to the dockerized Agent OS plane. Leaf 1 (#16324, PR #16329, merged at a0e6c8f — commit
a0e6c0… see PR) moved the mailbox, compose, and catch-up seams onto an identity-verified persistent MCP client (ai/services/fleet/planeMailboxClient.mjs) against the containerized Memory Core, with a fail-closed boot decision in the entrypoint. This leaf migrates the remaining S2 wake-state observation seams, which still read pre-hard-cut host truths.Live latest-open sweep: checked latest 20 open issues at 2026-08-02T12:29:35Z; no equivalent found. Adjacent-not-duplicate: #16331 / PR #16340 (wake-status semantics convergence across readers — orthogonal axis: status interpretation vs. which truth store is opened; merge-order coordination flagged A2A). A2A in-flight claim sweep (last ~60 min): claims cover
#16336,#16343,#15252-R2,#16331— no overlap with this scope.The Problem
Post-hard-cut, the wake-state axes wired at
ai/services/fleet/devFleetServer.mjs:132-136measure a plane that no longer exists on the host:readActiveWakeSubscriptionIdentitiesopens the host GraphService SQLite. The live WAKE_SUBSCRIPTION rows are written by the containerized Memory Core into the container plane volume — the host graph holds pre-cut relic rows (the class the wake-manifest builder deliberately skips). This is the same split-brain-read defect class leaf 1 removed for the mailbox seams.pidFilePathpoints at the legacy local wake daemon's PID file under the memory-corewakeDaemon.dataDir. No such process runs post-cut (verified:psfinds nodaemons/wake/daemon.mjs; delivery is now container-Memory-Core leg 1 → signed host receiver leg 2, per ADR 0019 §10.7). A missing PID file is OBSERVED not-running by the adapter's contract →alive: false.deliveryFailureFilePathreads the same retired daemon's receipt file.Composed through
resolveAgentWakeState's truth table, host-relic-active identities rendersuppressed(intent on + "delivery off") and genuinely-subscribed post-cut identities renderoff. The cockpit's wake column currently fabricates a fleet-wide blind-switch incident and hides real subscriptions — exactly the failure class the fail-honest taxonomy exists to prevent, produced by stale wiring rather than a broken adapter.The Architectural Reality
ai/services/fleet/fleetWakeStateAdapter.mjs— already seam-shaped: injected readers, entrypoint-owned config resolution, capability envelope, row-local honest degradation. The taxonomy and truth table need no change.ai/services/fleet/readActiveWakeSubscriptionIdentities.mjs:43-59— durable host-SQLite ACTIVE scan; correct for a host plane, split-brain against the containerized one. Its module doc already names why the fleet read is deliberately NOT the caller-owner management API.ai/services/memory-core/WakeSubscriptionService.mjs(manage_wake_subscription) — every action derives its owner fromRequestContextService;listfilters to the caller and returns full subscription rows ({id, ...props}) for that owner. The plane surface today cannot answer "which identities hold an active subscription" for a fleet-scoped observer, and full rows would over-disclose if it did.ai/services/fleet/planeMailboxClient.mjs— leaf 1's identity-verified client already exposes the generic proven-sessioncallTool(name, args)(the catch-up seam rides it,devFleetServer.mjs:198-204).The Fix
Memory-Core side (the truth owner):
who_is_onlinetelemetry class (resolved 2026-08-02 against the registered permission kinds, as this body reserved):PermissionService's model is strictly pairwise grantee→owner edges (CAN_READ_INBOX_OFshape) withAGENT:*structurally always-true as a target — a fleet-scoped resource fits neither, and per-seat N-grants would turn missing grants into roster holes (a new fabrication class). The truth-owning service already exposes an authenticated-caller fleet-telemetry read:who_is_onlineISWakeSubscriptionService.whoIsOnline. The fleet-identities read is its narrow sibling — same service, same authenticated-caller gate, and strictly less disclosure than the host-side fleet scan has always had (identities only, no rows). Any plane-side refusal (unauthenticated/unadmitted caller) still degrades honestly in the adapter.fleet-identitiesaction on the existingmanage_wake_subscriptiontool — no new MCP tool (#16217bloat discipline, handler-census friendly), no change towho_is_online's DTO. #16320 note: clients with pinned pre-change schemas may not see the new action value until reconnect; the FM plane client connects fresh per boot, and server-side validation governs.Fleet side:
devFleetServer.mjs: in plane mode,FleetManager.wakeStateOptions.listActiveSubscriptionIdentitiesbinds to the plane read through the verified client, replacing the host-graph scan; the host-graph scan remains the in-process-mode binding (correct for a host plane). The boot log names the wake-seam plane decision (mirror of leaf 1's witness line).unknownwith a named reason. No fabricatedon, no fabricatedsuppressed.fleetWakeStateAdapter.mjsgains only what that binding needs (an injectable delivery-liveness seam beside the PID-file path) — additive, truth table unchanged.Contract Ledger Matrix
manage_wake_subscriptionfleet-identitiesactionai/services/memory-core/WakeSubscriptionService.mjs(sibling ofwhoIsOnline)who_is_onlineprecedent), riding the converged status policy (PR #16340)unknownFleetManager.wakeStateOptionsbindingai/services/fleet/devFleetServer.mjs:132(boot entrypoint, leaf-1 plane decision)unknown(existing contract)fleetWakeStateAdapteroptionsai/services/fleet/fleetWakeStateAdapter.mjsDecision Record impact: aligned-with ADR 0019 (§10.7 wake-delivery disposition; any config touch stays leaf-read-at-use-site clean). No new ADR.
Acceptance Criteria
unknownwith reason), never an empty-fleet fabrication.unknownwith a named reason — nosuppressedfabricated from the retired daemon's absence.Out of Scope
FleetManager.throttleStateOptionswas never wired anywhere (the adapter documents that no platform throttle truth source exists yet) — nothing to migrate.Avoided Traps
listaction: the owner-scoped verb and the fleet telemetry read have different disclosure contracts; identities-only enumeration matches the establishedwho_is_onlinetelemetry surface, full rows never cross fleet-wide.CAN_OBSERVE_WAKE_OF): consistent-looking with the permission model, but a missing grant would render as a roster hole — a new fabrication class — and every seat onboarding would carry a silent grant obligation.on.Related
#13015 (parent epic) · #16324 / PR #16329 (leaf 1: pattern + client) · #16331 / PR #16340 (status semantics, merge-order coordination) · #16310 (wake boot-arming, receiver side) · #16320 (pinned tool schemas) · #14560 (cockpit epic) · D#16304 (merged code vs. running containers)
Origin Session ID: 5892cb00-bb3f-467b-9346-81c509834503
Retrieval Hint:
query_raw_memories("fleet wake-state plane migration split-brain subscription scan"); commit anchor: leaf 1 merge (PR #16329).