Frontmatter
| id | 13893 |
| title | [msg-ingestion] Durable message WAL + drain daemon (local + cloud parity) |
| state | Closed |
| labels | enhancementaiarchitecturemodel-experience |
| assignees | [] |
| createdAt | Jun 23, 2026, 1:17 AM |
| updatedAt | Jun 23, 2026, 1:21 AM |
| githubUrl | https://github.com/neomjs/neo/issues/13893 |
| author | neo-opus-grace |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |
| blockedBy | [] |
| blocking | [x] 13895 [msg-ingestion] Drain crash-recovery replay + idempotency + retention + observability, [x] 13894 [msg-ingestion] Switch add_message to WAL-first + remove the synchronous model-hit (durability invariant) |
| closedAt | Jun 23, 2026, 1:21 AM |
Parent: Epic #13889 (daemon-based A2A message ingestion). The core durable pipe.
Problem / scope
A2A messages must persist durably without a synchronous model-hit. Mirror the memory ingestion pattern: a durable append-only message WAL + a daemon that drains it → graph-persist + embed, load-aware. This is the pipe the
add_messageflip (sibling sub) writes into.Acceptance Criteria
add_messagepersist mechanism (write-through to SQLite vs in-memory buffer) and where the durability gap is — empirical anchor: de1d9f0b (created,add_messagereturned an id, but absent fromNodes+GraphLog+ WAL). Shape the WAL boundary from that finding.memory-wal): one durable record peradd_message, written before the request returns.ai/daemons/embed/): drains the message WAL → graph-persist (Nodes+ SENT_BY/SENT_TO/DELIVERED_TO edges; the GraphLog insert-trigger fires) → embed. Load-aware — yields to heavy maintenance (REM / KB-sync), like the embed daemon.Contract Ledger
memory-wal) + the message-ingestion daemon (pattern:ai/daemons/embed/).GraphService.upsertNode/linkNodes), the embed pipeline.Sequencing
First sub of #13889 — the pipe. The add_message-flip + replay/robustness subs build on it.