LearnNewsExamplesServices
Frontmatter
id16526
titleThe wake daemon reads the whole graph from a host path the deployment does not serve
stateOpen
labels
bugaiarchitecture
assigneesneo-opus-grace
createdAtAug 5, 2026, 12:04 AM
updatedAtAug 8, 2026, 4:52 AM
githubUrlhttps://github.com/neomjs/neo/issues/16526
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

The wake daemon reads the whole graph from a host path the deployment does not serve

Open Backlog/active-chunk-13 bugaiarchitecture
neo-opus-grace
neo-opus-grace commented on Aug 5, 2026, 12:04 AM

Context

Found while implementing #16513 (turn-presence writes bypassing the deployment). That ticket's fix moves the harness hooks' write onto the Memory Core's MCP surface. Tracing what else touches AGENT_TURN_PRESENCE surfaced a second, larger participant that could not ride the same PR.

Live latest-open sweep at filing: #16495#16519 window checked; #16488/#16495 own the SDK-boundary/module-scope-import class, #16514/#16515 own the ai/ duplication and census lanes. None covers the wake daemon's store binding.

The Problem

ai/daemons/wake/daemon.mjs opens the graph from a host config leaf and serves every read from it:

:2866  DB_PATH = memoryCoreConfig.storagePaths.graph;
:2896  db      = initializeDatabase(DB_PATH);

Consumers of that one handle, measured:

line read
:554 getGraphLogEntries
:579 getActiveShapeCSubscriptions
:584-585 getNodesData / getEdgesData
:638 getDbNode
:742 DELIVERED_TO edge probe
:842-852 isMessageReadFor (message read-state)
:1994, :2006 findTurnPresenceAfter — the Codex delivery proof
:2802 getActiveHarnessPresence

In a containerized deployment that path is not the served store. healthcheck reports plane.dataRoot: /app/.neo-ai-data, and ai/deploy/docker-compose.yml:193 mounts shared-sqlite-data:/app/.neo-ai-data/sqlite where shared-sqlite-data is declared bare at :483 — a Docker-managed named volume, inside the Docker Desktop VM on macOS. The contrast in the same file is the control: :327 mounts backups as ${NEO_HOST_BACKUP_ROOT:-${HOME}/.neo-ai/backups}, a real host bind, which is why backups are host-reachable and the graph is not.

So there is nothing host-visible for storagePaths.graph to point at, and every read above answers from a diverged file — successfully, because a stale SQLite file serves reads correctly. That is the same failure shape PR #16401 ruled on from the subscription side: the contract was right and the in-repo precedent was wrong.

The Architectural Reality

  • This is not a stray bypass. Unlike the hook writer in #16513 — one call site that could be lifted cleanly — the daemon is a wholesale host-store reader. Migrating a single read would leave the process reading most things from one store and one thing from another, which is a worse state than the coherent-but-wrong one it is in now. The unit of repair is the process, not the call site.
  • Currently dormant, and that is a sequencing fact rather than a reason to skip it. daemon.mjs is not running on this deployment: launchctl shows com.neomjs.agent-os-wake bound to ai/daemons/wake/receiver.mjs (the manifest-driven route dispatcher), and healthcheck reports features.wake.daemonRunning: false. Wakes are delivered today by the receiver, which reads a routes manifest and never touches the graph.
  • #16513 leaves the correlation key ready. record_turn_presence now accepts and persists wakeSubmitNonce, so once this daemon reads the served store, findTurnPresenceAfter's nonce match works against beacons the hooks are already writing there.

The Latent Regression, stated plainly

With #16513 merged and this ticket open, starting daemon.mjs would degrade scheduleCodexTurnStartProof to wake-submit-unknown for every delivery: hooks write the served store, the daemon polls the host file, and the nonce never matches. Today those two agree — both are wrong together, which is why the proof "works".

This is latent, not live, because the process does not run. It is recorded here so that starting it is a decision made with the constraint visible, rather than a surprise.

The Fix

  1. Inventory the eight read sites and decide per-read whether it belongs on the MCP surface, on a served-store binding, or is genuinely host-local (the daemon's own state files are not in question).
  2. Move the process to the deployment's store as one change, so it never runs half-migrated. ai/daemons/wake/readSubscriptionsOverMcp.mjs is the in-tree precedent for the subscription read; several other reads have no tool surface yet and may need one, which is the real sizing risk.
  3. Prove the delivery proof end-to-end: a wake submit followed by a nonce-correlated wake-submit-started, with hook and daemon on different filesystems — the case a colocated setup cannot exercise and therefore the one that must be constructed deliberately.

Acceptance Criteria

  • Every AGENT_TURN_PRESENCE, message, subscription, and presence read in daemon.mjs resolves against the store the deployment serves, or is documented as deliberately host-local with the reason.
  • No intermediate commit leaves the daemon reading two stores.
  • A spec proves wake-submit-started on a nonce-correlated interval when hook and daemon do not share a filesystem.
  • An unreachable store makes the daemon fail visibly; it never falls back to a path it can open.

Out of Scope

  • #16513 itself (the hook write path; merged or in flight).
  • The deployment topology decision — two orchestrators across two data roots is an operator call, and this ticket makes the daemon correct under either outcome.
  • The wake receiver, which is manifest-driven and does not read the graph.

Decision Record impact

none — applies an already-adopted mitigation to a process that predates it.

Related

#16513 (the writer half, same root cause), PR #16401 (the ruling this re-confirms), #16510.