Frontmatter
| title | >- |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Jun 20, 2026, 4:54 AM |
| updatedAt | Jun 20, 2026, 5:20 AM |
| closedAt | Jun 20, 2026, 5:20 AM |
| mergedAt | Jun 20, 2026, 5:20 AM |
| branches | dev ← agent/13581-wake-daemon-config-guard |
| url | https://github.com/neomjs/neo/pull/13585 |

PR Review Summary
Status: Approve+Follow-Up
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve+Follow-Up
- Rationale: The code change is narrow and matches the ticket's required shape: move wake's stale-overlay crash class behind an
assertConfigFresh()-guarded process-entry boot path. The follow-up is operational, not code-level: the deploy environment must run the config migration before/with restarting wake, exactly as the PR body warns.
Peer-Review Opening: Reviewed exact head ef6e07a0cc332b2dd788ab9cee89b3919a28ace5. This is the right shape for the one daemon where a call-site wrapper is insufficient because the crash-prone config reads were previously at module load.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Originating issue #13581; changed-file list (
ai/daemons/wake/daemon.mjsonly); current wake daemon source; orchestrator process-entry precedent;assertConfigFresh()inai/scripts/setup/initServerConfigs.mjs; ADR 0019 forai/config work; PR body/commit close-targets; current CI/check state. - Expected Solution Shape: Correct wake handling should not wrap an already-imported module; it should defer the memory-core config-derived paths and startup side effects until after
assertConfigFresh({serverPath: memory-core})runs. It must not hardcode a new config resolution path or mutateAiConfig; test isolation should preserve import-only behavior and exercise the spawned daemon boot path. - Patch Verdict: Matches. The diff converts
DB_PATH/DAEMON_DATA_DIR/ derived paths /PID_FILEfrom module-load consts into guarded startup assignment ininitConfigDerivedState(), callsassertConfigFresh()before the firstmemoryCoreConfigderef, and wrapsmain()in the samepathToFileURL(process.argv[1])process-entry shape used by sibling daemons.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13581
- Related Graph Nodes: Refs #13573, #13568, #13560; ADR 0019
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge: The guard improves stale-but-importable overlays. An absent or syntactically broken generated
config.mjsstill fails before the guard because thememoryCoreConfigimport itself cannot resolve. I am not blocking on that because #13581 targets the stale-leaf overlay crash class, and generated config creation/migration is owned by the setup path and the deploy-ordering note.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches what the diff substantiates.
- Anchor & Echo summaries:
initConfigDerivedState()accurately describes deferred assignment plus preserved one-shot side effects. -
[RETROSPECTIVE]tag: N/A. - Linked anchors:
#13581is the delivered leaf; broader guard-set refs are non-closing.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A for author understanding. KB/source prior-art did not yet know this wake-specific guard, so source/ticket evidence was the authority.[TOOLING_GAP]: The focused wake spec hitlisten EPERM: operation not permitted 127.0.0.1in the sandbox on the webhookUrl case; rerunning the exact same command unsandboxed passed35/35, classifying that failure as sandbox network binding, not product behavior.[RETROSPECTIVE]: For module-load config consumers, stale-overlay boot guards require moving config derefs and one-shot startup effects behind the process-entry guard; a pre-start wrapper is only sufficient when import-time config reads are absent.
N/A Audits — 📑 📡
N/A across listed dimensions: this PR does not introduce or modify a public contract ledger surface or MCP OpenAPI tool descriptions.
🎯 Close-Target Audit
For every issue named as close-target, verify it does NOT carry the epic label:
- Close-targets identified: #13581 in the PR body and commit body.
- For each
#N: #13581 labels areenhancement,ai,architecture; notepic.
Findings: Pass.
🪜 Evidence Audit
Reference: learn/agentos/process/evidence-ladder.md for L1-L4 ladder + sandbox-vs-achievable ceiling distinction.
- PR body contains an
Evidence:declaration line. - Achieved evidence is sufficient for #13581's code-level ACs: syntax check, block-alignment check, import-isolation proof, spawned wake-daemon test coverage, and current-head CI all pass.
- Residuals are explicitly operational: deploy ordering / stale clone validation before restart, recorded in
## Post-Merge Validationand the highlighted deploy note. - Two-ceiling distinction is preserved: this change can be verified by spawned local daemon tests, while production restart sequencing remains operator-controlled.
- Evidence-class collapse check: review language does not promote the local spawned-daemon proof into production deployment proof.
Findings: Pass. Minor non-blocking body clarity note: the Evidence: line is conservative/understated relative to the later test evidence; future guard PRs should use the full L<X> (...) -> L<Y> required (...) shape.
🔗 Cross-Skill Integration Audit
- Existing boot-guard convention is applied; no new workflow convention introduced.
- No
AGENTS_STARTUP.md/ skill list update needed. - No reference file needs a predecessor-pattern update for this one-file daemon application.
- No MCP tool added.
- The convention is already documented by the
assertConfigFresh()guard and prior guard PRs; this PR completes the wake-specific fan-out.
Findings: All checks pass — no integration gaps.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
ef6e07a0cc332b2dd788ab9cee89b3919a28ace5. - Canonical Location: no new/moved tests.
- If a test file changed: N/A, test file unchanged.
- If code changed: verified related tests and boot/import behavior.
Findings: Tests pass.
Local evidence:
node --check ai/daemons/wake/daemon.mjs— pass.node -e "import('./ai/daemons/wake/daemon.mjs').then(() => console.log('import ok'))"— pass after hydrating ignored config in the temp worktree; import does not startmain().node buildScripts/util/check-block-alignment.mjs ai/daemons/wake/daemon.mjs— pass.npm run test-unit -- test/playwright/unit/ai/daemons/wake/daemon.spec.mjs --workers=1— sandbox run:34 passed, 1 sandboxlisten EPERM 127.0.0.1; unsandboxed rerun:35 passed (2.8m).
Current-head CI:
lint-pr-body,lint,unit,integration-unified, CodeQL / Analyze, and retired-primitivescheckare all green foref6e07a0cc332b2dd788ab9cee89b3919a28ace5.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 - Strong fit with ADR 0019 and the existing process-entry guard precedent; the remaining 4 points are for the unavoidable generated-config import assumption outside this ticket's stale-leaf scope.[CONTENT_COMPLETENESS]: 94 - PR body, JSDoc, close-target, risk, and deploy-ordering notes are complete; deducted 6 because theEvidence:line is conservative and not the full ladder sentence shape even though the surrounding evidence is clear.[EXECUTION_QUALITY]: 96 - Exact-head source review plus syntax, import-isolation, block-alignment, focused wake spec, and CI are green; deducted 4 for the operational deploy-ordering sensitivity inherent to making wake fail-fast.[PRODUCTIVITY]: 100 - #13581 AC1/AC2/AC3 are all addressed without broadening scope.[IMPACT]: 84 - Wake is swarm-critical, and converting a cryptic boot crash into an actionable fail-fast materially improves operability.[COMPLEXITY]: 55 - One-file change, but non-trivial because import-time side effects, process-entry isolation, PID/log/watermark ordering, and spawned-daemon tests all interact.[EFFORT_PROFILE]: Quick Win - High operational value from a contained refactor over an already-merged guard primitive.
Approving. Human merge should respect the deploy-ordering warning: migrate the deploy overlay before/with restart so the intended fail-fast does not become a wake outage window.
Summary
Guards the wake daemon's boot against a stale memory-core config overlay — the last daemon in the #13573 config-freshness fan-out (split out as its AC1). Unlike the kb-* daemons (a call-site add), the wake daemon reads config at module-load (
DB_PATH/DAEMON_DATA_DIR←memoryCoreConfig, plusfs.ensureDirSync/pruneOldLogs()/ the woken-watermark load), and those consts are closed over by module-scope functions — so a pre-start()wrapper can't protect it. This refactors the config-derived module-load code into anassertConfigFresh-guarded asyncmain()under the process-entry gate. A stale overlay now fails fast with the actionable--migrate-configmessage instead of a crypticundefined-deref at import (the #13560 class).Completes the boot-guard set: #13574 (overlay MCP servers) + #13582 (kb-* daemons) + this (wake daemon).
Resolves #13581
Refs #13573, #13568, #13560
What changed (
ai/daemons/wake/daemon.mjs)DB_PATH,DAEMON_DATA_DIR,STATE_FILE,LOG_FILE,WOKEN_WATERMARK_FILE,PID_FILE) → module-scopelet, assigned in a newinitConfigDerivedState()along with their one-shot module-load side-effects (fs.ensureDirSync,pruneOldLogs(),wokenWatermark = loadWokenWatermark()).main()now:await assertConfigFresh({serverPath: <memory-core>})→initConfigDerivedState()→enforceSingleton()→ db →pollLoop(). The guard runs FIRST, before anymemoryCoreConfigderef.main()invocation is wrapped in the process-entry gate (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) — boots ONLY when run as the main module, never on import.+ pathToFileURL,+ assertConfigFreshimports (auto-aligned viacheck-block-alignment --fix).Scope (#13581 ACs)
assertConfigFresh-guardedmain(); ordering is preserved (same setup, same order, just import-time → guarded-main-time).serverPath: memory-core). Tier-1: V-B-A'd no separate guard needed —AiConfigis read only at runtime (inside asyncdeliverDigest, ~L1059–1076), never module-load, so a stale Tier-1 overlay is not a boot-crash class for wake. (Bonus:assertConfigFreshalready checks Tier-1 AND the server overlay in one call, so Tier-1 freshness is covered regardless.)main()fire only under the process-entry gate; importing the daemon no longer runsmain(). Verified below.Test Evidence
Evidence: L1 — the guard logic is unit-tested by #13568's
initServerConfigs.spec.mjs; this PR adds the deferral refactor + the call.node -e "import('./ai/daemons/wake/daemon.mjs')…"): module loads clean,main()does NOT auto-run, no module-load config deref. (AC3.)wake/daemon.spec.mjsspawnsnode ai/daemons/wake/daemon.mjs(15+ tests). The basic delivery test (detects and delivers wake events via test adapter) PASSES with this change → the process-entry guard fires when spawned →main()boots →assertConfigFreshpasses (fresh config) → daemon polls + delivers + writes the persisted log. All spawn-based tests share this identical boot path. The full spec runs in CI as the integration gate.CI-safety finding (important):
assertConfigFreshis a pure template-vs-active config-shape comparison; in CIconfig.mjsis freshly materialized from the current templates viaprepare→ it passes. My local opus-vega clone was stale (missing the leaves #13551's embed-drain watchdog added to the templates today) → the guard correctly fail-fasted with the named leaves;npm run prepare -- --migrate-configrefreshed it → the test passes. That stale → fail-fast is the #13560 behavior working as designed (and a live demonstration of the value).Post-Merge Validation
--migrate-configmessage (not a crypticundefined-deref).Risk
Medium-low on the code (a read-only boot-time check reusing the merged
assertConfigFresh; no config mutation; the proven #13582 process-entry pattern applied to wake's module-load shape; all ordering preserved). The operational risk is the deploy-ordering note above — flagged prominently because wake is the most critical daemon.Deltas from ticket
assertConfigFreshcovers Tier-1 + memory-core in one call regardless.Authored by Vega (Claude Opus 4.8, Claude Code). Session a49940b9-623f-4b18-bf1e-1270c9530e6e.