LearnNewsExamplesServices
Frontmatter
id13890
titleMirror memoryWal drain topology for A2A messages
stateClosed
labels
bugaiarchitecturemodel-experience
assigneesneo-gpt
createdAtJun 23, 2026, 1:08 AM
updatedAtJun 23, 2026, 12:35 PM
githubUrlhttps://github.com/neomjs/neo/issues/13890
authorneo-gpt
commentsCount0
parentIssue13889
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 13892 Replay message WAL into mailbox graph idempotently
closedAtJun 23, 2026, 12:35 PM

Mirror memoryWal drain topology for A2A messages

Closed v13.1.0/archive-v13-1-0-chunk-5 bugaiarchitecturemodel-experience
neo-gpt
neo-gpt commented on Jun 23, 2026, 1:08 AM

Context

Parent epic: #13889

A2A message ingestion needs the same local-vs-cloud topology discipline that the existing memory WAL drain already has. The parent incident is an acknowledged add_message write that returned a MESSAGE:* id but never durably landed in Nodes, GraphLog, or WAL. Grace added the operator parity constraint on #13889, and the epic-review closeout matrix now treats parity as a required row.

This child exists to make that parity deliverable explicit instead of leaving it as review prose.

Release classification: boardless - data-integrity hardening under an active epic, but not currently attached to a release board.

Live latest-open sweep: checked the latest 20 open issues immediately before creation on 2026-06-22T23:06Z; no equivalent child ticket found. Scope-specific live sweep for message WAL + local + cloud + drain returned only parent epic #13889. A2A in-flight sweep checked the latest 30 messages and found no competing lane-claim for this child scope.

The Problem

A WAL-first message path can still fail the parent epic if its drain only works in one deployment reality. Local maintainer setups use native processes and repo-local .neo-ai-data, while cloud deployments use separate containers for Memory Core, model serving, and vector storage. If the message drain invents its own placement, config leaves, lock behavior, or volume/reachability assumptions, it can pass locally while failing in the cloud stack, or the reverse.

The existing memory WAL drain already solved this topology split. Message ingestion should mirror that shape, not create a parallel one.

The Architectural Reality

The current memory WAL drain documents two host modes in ai/daemons/embed/drainCycle.mjs:248: supervised daemon process for the local/orchestrator-managed profile, and memoryWal.inProcessDrain for containerized/single-process deployments.

ai/mcp/server/memory-core/Server.mjs:251 hosts the in-process drain loop inside the Memory Core server when configured, while continuing to serve if another host already owns the drain lock.

ai/daemons/embed/drainLock.mjs:1 owns the per-WAL-directory .drain-lock contract and fail-loud refusal when a second live host attempts to drain the same WAL directory.

ai/mcp/server/memory-core/config.template.mjs:371 through ai/mcp/server/memory-core/config.template.mjs:424 define the memory WAL daemon config leaves and NEO_MEMORY_WAL_IN_PROCESS_DRAIN host-mode toggle.

The Fix

Define and implement the A2A message-ingestion drain topology by mirroring the memory WAL host-mode contract:

  • Local profile: a supervised message-ingestion drain host with daemon data/log/pid state comparable to the memory embed daemon.
  • Containerized/single-process profile: an in-process Memory Core drain mode for message WAL when no external orchestrator-supervised daemon exists.
  • Mutual exclusion: exactly one message WAL drain host per message WAL directory, with a fail-loud lock/refusal path comparable to the existing .drain-lock discipline.
  • Config overlay: template leaves and env overrides sufficient for both local and cloud deployments, with stale-overlay diagnostics comparable to memory WAL leaves.
  • Deployment wiring: document or adjust the cloud/container profile so the message WAL drain can reach the WAL, graph, and embedding/vector surfaces with the same topology assumptions as memory WAL.

Do not implement the add_message WAL append contract here unless it is a small prerequisite; that belongs in a separate child if it grows beyond this topology leaf.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Message WAL drain host mode memoryWal drain topology in drainCycle.mjs and Server.mjs Message drain supports local supervised host and containerized in-process host Fail loud if the configured host mode cannot start safely Config/template comments and ticket/PR body L2 unit/config tests plus L3 smoke or declared cloud residual
Message WAL drain lock drainLock.mjs sole-drainer invariant A second live message drain host refuses to start or logs loud without double-draining Stale dead-holder lock can be reclaimed JSDoc on lock helper or reuse docs L2 lock/refusal tests
Message drain config leaves config.template.mjs memoryWal leaves Env-backed leaves cover directory, cadence, batch/retry, data dir, and in-process mode as needed Stale overlay gets actionable diagnostics Config template comments L1 static check plus L2 startup/config test
Cloud/container wiring parent epic parity note and existing Memory Core deployment shape Cloud deployments can run exactly one reachable message drain host Operator-declared residual if real cloud smoke is outside CI PR body Evidence line and deployment docs if touched L3 smoke or explicit residual

Decision Record impact

Aligned with the existing memory WAL drain substrate. No ADR change expected for this child.

Decision Record: Not needed.

Acceptance Criteria

  • Message ingestion has an explicit local host mode and an explicit containerized/single-process host mode, both modeled on the existing memory WAL drain topology.
  • The implementation prevents two live hosts from draining the same message WAL directory, with a fail-loud/refusal path covered by tests.
  • Config/template leaves and env overrides cover the message-drain host modes without hidden defaults that split WAL locations.
  • Evidence covers both realities: L2 unit/config coverage for the two host modes, and L3 smoke or an operator-declared residual for real cloud deployment wiring.
  • The PR body states whether A2A-message Chroma/search population is handled by this drain or deliberately deferred to #10150.
  • add_message remains free of synchronous chat/embed/model calls after this topology work; any model-dependent ingestion stays daemon/drain-owned.

Out of Scope

  • The core WAL-first add_message acceptance contract, unless a tiny seam is required to wire topology tests.
  • Full graph replay/idempotency semantics for every MESSAGE edge type.
  • A2A message semantic-search UX beyond declaring whether it is handled or deferred to #10150.
  • The separate fade/apoptosis MESSAGE-exemption hardening noted on the parent epic.

Avoided Traps

  • Do not create a message-specific deployment topology that diverges from memory WAL without explicit rationale.
  • Do not make local success the only evidence; cloud/container parity is the point of this leaf.
  • Do not reintroduce synchronous model calls in add_message to satisfy embedding/search convenience.
  • Do not allow a double-drain race; marker/replay semantics need a single owner per WAL directory.

Related

Parent: #13889

Related: #10150

Origin Session ID: db5b2ecf-db91-4b7d-9498-ccef00426a1c

Handoff Retrieval Hints: A2A message ingestion daemon local cloud parity memoryWal drain topology; message WAL drain host mode inProcessDrain sole-drainer lock

tobiu referenced in commit 0593fa9 - "feat(ai): add message WAL drain topology (#13890) (#13906) on Jun 23, 2026, 12:35 PM
tobiu closed this issue on Jun 23, 2026, 12:35 PM