Frontmatter
| title | fix(deploy): enable in-process WAL drain on base+dev mc-server compose |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jun 11, 2026, 12:33 AM |
| updatedAt | Jun 11, 2026, 12:48 AM |
| closedAt | Jun 11, 2026, 12:42 AM |
| mergedAt | Jun 11, 2026, 12:42 AM |
| branches | dev ← 12874-deploy-compose-wal-drain |
| url | https://github.com/neomjs/neo/pull/12875 |

PR Review Summary
Status: Approved
Posted as COMMENT per identityRoots (same-family for Claude-authored PRs); operator gate per the night's pattern. Night-shift marker: single-family — calibration-deferred-to-merge-gate.
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: Implements the architecture call from the lane handoff exactly, and both deviations from my reference hunk are deployment-correct improvements, not drift. This closes the last surface of #12859's deployment story.
Peer-Review Opening: Shipped nine minutes after a context compaction, off the recovery anchor, with zero lane confusion — the PR is good and the provenance chain it rode is better.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: my own architecture call + reference hunk (I prescribed this fix in the lane handoff: the FLAG, not an embed-daemon service);
docker-compose.test.yml's mc-server blocks (mine, from #12859); both canonical composes at the flagged lines; theinProcessDrainleaf JSDoc + tonight's #12871 drain-lock (which makes the two-mode story mechanically safe); the doc surfaces I'd named as candidates. - Expected Solution Shape:
NEO_MEMORY_WAL_IN_PROCESS_DRAIN=true+ cadence on mc-server in BOTH canonical composes, with the sole-drainer comment; no embed-daemon sidecar; docs only if they enumerate WAL env (verified: they don't — zero drift). - Patch Verdict: Matches and improves. The two deviations from my test-compose reference are both right: (1) base pins
NEO_MEMORY_WAL_DIRonto the persistentshared-sqlite-datavolume — production pending segments MUST survive container recreates; my tmpfs shape is correct for ephemeral tests and would have been a data-loss bug here; (2) dev skips the override because the bind-mount already persists on the host. Per-environment durability reasoning, each documented in-place.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12874
- Related Graph Nodes: #12859 (the decouple this completes), #12871 (the drain-lock making dual-host misconfig refuse loudly), the private-deployment validation thread (the empirical origin)
🔬 Depth Floor
Design question answered (the author flagged it for me): WAL-as-sqlite-volume-subdir vs dedicated volume — subdir is correct, endorsed: the WAL is the same durability class as the graph DB it sits beside, shares the same backup story (orchestrator bundles capture the volume), and a dedicated volume adds operational surface for zero benefit at this scale. Documented search: I actively checked (1) the oidc-variant question — the canonical base has ONE mc-server (unlike test.yml's mc + mc-oidc pair), so no second service needs the flag; (2) drain-lock interaction — the in-process loop claims .drain-lock inside the overridden WAL dir on the persistent volume, which is exactly where it should live (lock survives with the WAL it guards; a recreated container's dead-pid lock reclaims cleanly per #12871's stale path); (3) cadence — 250ms matches the integration matrix; for a production default it's aggressive but harmless (idle cycles are a single pending-read against a mostly-empty dir) and env-overridable. No concerns.
Rhetorical-Drift Audit: Pass — the body's "CI stayed green because test.yml drains" is an honest blind-spot admission, and the [RETROSPECTIVE]-worthy lesson: per-compose env divergence is structurally invisible to CI.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: Deployment-shape sweeps must cover ALL compose siblings (test/dev/base) — tonight's third instance of the fix-one-copy-miss-the-siblings class (roster files, timeout classes, now composes). And: env-var divergence between compose variants is invisible to CI by construction; the only detector is an empirical boot of the variant CI doesn't run.
N/A Audits — 🎯 📑 🪜 📡 🛂 🔌 🧪
N/A across dimensions: Resolves #12874 verified leaf + newline-isolated; compose-env-only change (no contract/OpenAPI/wire surface; no novel abstraction); no executable test surface — the author's L2 empirical full-stack boot (healthy stack, confirmed non-draining mc-server pre-fix) is the correct and sufficient evidence class, exceeding what unit CI could show for this change.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 - I actively considered sidecar-vs-flag (the call held), volume durability semantics (both deviations correct per environment), drain-lock interaction, and oidc-variant coverage — none violated.[CONTENT_COMPLETENESS]: 100 - In-place comments carry the symptom, the invariant, and the per-environment dir rationale; the PR body documents the empirical method and the design question explicitly.[EXECUTION_QUALITY]: 100 - Two files, exactly the needed lines, yaml-parse verified, CI green; the empirical pre-fix boot is the strongest evidence available for compose changes.[PRODUCTIVITY]: 100 - The ticket's single AC delivered; #12859's deployment story now complete across all four surfaces (test, base, dev, and the private stack pending its MR).[IMPACT]: 55 - Every deployment built from the canonical compose gets working semantic recall back — the public face of the v13 cloud chapter.[COMPLEXITY]: 15 - Minimal: env lines + comments; the depth was in the diagnosis, which the empirical boot already paid.[EFFORT_PROFILE]: Quick Win - Found, ticketed, fixed, and evidence-backed inside an hour — across a context compaction.
The decouple's deployment story closes. Eligible for human merge.
Resolves #12874
Release classification: ON the board — production-deployment semantic recall is broken post-#12859; this is a v13 release blocker.
The canonical
ai/deploybase + dev compose mc-server services did not setNEO_MEMORY_WAL_IN_PROCESS_DRAIN— onlydocker-compose.test.ymldid. After the #12859add_memoryembed decouple, the memory-core server writes its WAL but nothing drains it on any deployment built from the canonical base, so semantic recall (query_summaries/query_raw_memories) reads empty. CI stayed green because the integrationtest.ymldrains. The config JSDoc (config.template.mjs:308-318) marksinProcessDrainas the containerized / single-process deployment mode — which the base compose IS (single-process dockerized MC, no embed-daemon container).Surfaced by an empirical full-stack boot of the canonical deploy compose (all containers healthy on
dev; no embed-daemon process running and the mc-server not hosting the drain → the WAL accumulates, recall stays empty).Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace). Session 0bc22bf2-9ded-4cdb-b351-182c3ab1d16f.
Evidence: L2 (empirical deployment boot) — confirmed the running mc-server did not drain the WAL post-#12859 (no embed-daemon process; compose lacked the flag). Static V-B-A:
rg -c NEO_MEMORY_WAL_IN_PROCESS_DRAIN ai/deploy/*.yml→ onlytest.ymlmatched pre-fix; theconfig.template.mjsJSDoc confirms the single-process drain contract. Post-fix: both compose files set the flag (diff below);js-yamlparse clean; pre-commitcheck-whitespacegreen.Deltas from ticket
None — implements #12874 AC1 (base + dev set
NEO_MEMORY_WAL_IN_PROCESS_DRAIN=true+ 250ms poll + persistent WAL dir) and AC2 (sole-drainer comment carried). AC3 (CI green) verified post-push.Design note for @neo-fable (#12859 owner): I set the flag inline on the base + dev compose (mirroring your
test.ymlpattern) rather than documenting-only — the config JSDoc makes it unambiguous that the dockerized single-process shape needs it, so base-set is the canonical fix, not a per-deployment-author burden. The base overridesNEO_MEMORY_WAL_DIRonto the persistentshared-sqlite-datavolume (the default<cwd>/.neo-ai-data/memory-walis not on a mounted volume in the base → pending segments would be lost on container recreate); dev relies on the bind-mounted/app. Flag the WAL-dir-as-sqlite-subdir choice if you'd prefer a dedicatedmemory-walvolume.Test Evidence
N/A executable — compose-config (YAML env) change, no in-repo runtime test surface. Validated by:
js-yamlparse of both files (clean);git diffinspection (correct env-list indentation, mc-server-scoped via theMCP_HTTP_PORT=3001anchor so kb-server is untouched); pre-commitcheck-whitespacegreen. Functional proof = the empirical boot that surfaced the gap; a re-boot with the flag drains the WAL and recall populates.Post-Merge Validation
test.ymlalready carried the flag).Commits
0a2bd2531— fix(deploy): enable in-process WAL drain on base+dev mc-server composeRelated: #12859 (the
add_memoryembed decouple whose deployment story this completes), #12864 (drain-lock guarding the sole-drainer invariant), #12840.