feat: [ENG-2158] dual-write runtime signals to sidecar (3/6)#449
feat: [ENG-2158] dual-write runtime signals to sidecar (3/6)#449danhdoan merged 1 commit intoproj/runtime-signalfrom
Conversation
Third commit in the series. Every mutation site that previously touched scoring fields in context-tree markdown frontmatter now also mirrors the change to the RuntimeSignalStore sidecar introduced in commit 2. Markdown remains canonical; this commit populates the sidecar so commit 4 has real data to read from. Mutation sites dual-writing: - SearchKnowledgeService.flushAccessHits — mirrors importance/accessCount bumps and retiered maturity via batchUpdate - CurateTool executeAdd — seeds sidecar with default signals - CurateTool executeUpdate — mirrors importance +5, recency=1, updateCount +1, retiered maturity (updatedAt stays in markdown only) - CurateTool executeMerge — delegates to mergeScoring to keep the sidecar merge policy identical to markdown; drops source entry - CurateTool executeDelete — drops sidecar entry - FileContextTreeArchiveService.archiveEntry — drops sidecar entry - FileContextTreeArchiveService.restoreEntry — seeds defaults Wiring: runtimeSignalStore added to ToolServices and threaded through the tool registry (CODE_EXEC sandbox, CURATE, EXPAND_KNOWLEDGE). The daemon (agent-process.ts) constructs its own FileKeyStorage + RuntimeSignalStore so `brv search` and the dream-flow archive service both participate in dual-write. Invariants enforced at every site that bumps importance: - maturity recomputed via determineTier inside the atomic updater - updatedAt never written to sidecar (not a runtime signal) - sidecar failures wrapped in try/catch — markdown writes always complete - merge logic delegated to mergeScoring (no policy duplication) Service initializer reordered so keyStorage/runtimeSignalStore are constructed before ToolProvider — eliminates the prior updateServices late-bind workaround. Tests: 11 new tests covering dual-write invariants, hysteresis promotion, failure isolation, markdown/sidecar consistency, and a full pipeline integration test (ADD → UPDATE → flushAccessHits → MERGE → archive). 6476 total passing, 0 failing.
|
Claude finished @danhdoan's task in 2m 39s —— View job PR Review: feat: [ENG-2158] dual-write runtime signals to sidecar (3/6)Tasks:
Overall Assessment — Score: 4 / 5This is a well-structured, thoughtfully scoped PR. The dual-write pattern is applied consistently across all 7 mutation sites, failure isolation is uniformly enforced, and the test suite (unit + integration + failure-path) is thorough. The scope boundary (no read-path changes) is strictly observed. Issues below are mostly minor; one medium-severity gap around folder-delete orphans is worth resolving before commit 5 ships. Findingssuggestion — non-null assertions violate project coding standardFile: // curate-tool.ts mirrorCurateUpdate
const bumpedImportance = bumped.importance!
return {
...current,
importance: bumpedImportance,
recency: bumped.recency!,
updateCount: bumped.updateCount!,
}
// search-knowledge-service.ts mirrorHitsToSignalStore
const bumpedImportance = bumped.importance!
return {
...current,
accessCount: bumped.accessCount!,
importance: bumpedImportance,
}
Preferred pattern — use nullish coalescing and fall back to the pre-bump value: const bumpedImportance = bumped.importance ?? current.importance
return {
...current,
importance: bumpedImportance,
maturity: determineTier(bumpedImportance, current.maturity),
recency: bumped.recency ?? current.recency,
updateCount: bumped.updateCount ?? current.updateCount,
}This preserves the semantic intent (if a field is somehow missing, we keep the current value rather than crash) and removes the style-guide violation. The issue — folder DELETE leaves orphaned sidecar entriesFile:
This is an acceptable debt for commit 3 since markdown is still canonical, but it must be resolved before commit 5 (when the sidecar becomes canonical) — orphans from folder deletes would silently resurface as stale signals. The PR backlog already mentions Suggested fix for commit 3: after collecting // existing folder-delete path
const files = await DirectoryManager.listFiles(folderPath)
for (const f of files) {
await dropSidecar(runtimeSignalStore, relPathFromContextPath(f, basePath))
}
await DirectoryManager.deleteFolder(folderPath)nitpick —
|
Summary
flushAccessHits, curate ADD/UPDATE/MERGE/DELETE, archive/restore).ToolServicesgainsruntimeSignalStore; the daemon builds its own store via a localFileKeyStorage. Initializer reordered so the store is available toToolProviderat construction time.Type of change
Scope (select all touched areas)
Linked issues
Root cause (bug fixes only, otherwise write
N/A)Test plan
test/unit/agent/tools/curate-tool-sidecar-dual-write.test.ts— 8 teststest/unit/agent/tools/search-knowledge-flush-sidecar.test.ts— 4 teststest/unit/infra/context-tree/archive-service-sidecar-dual-write.test.ts— 4 teststest/integration/runtime-signals/dual-write-pipeline.test.ts— 1 end-to-end testupdatedAtnot mirroredmergeScoring, drops source entry, retiered maturitydraft→validatedat importance ≥ 65User-visible changes
None. Markdown is still canonical; the sidecar shadows it. Users will not observe any behavior change until commit 4 redirects reads.
Evidence
New dual-write tests:
Full repo suite: 6476 passing, 0 failing (commit 2 ended at 6459; +17 new tests across this series of PRs).
Checklist
npm test)npm run lint) — 0 errors, 202 pre-existing warnings (none new)npm run typecheck)npm run build)mainRisks and mitigations
importanceforgets to recomputematurityviadetermineTier. The store layer does not enforce the hysteresis invariant — it accepts any schema-valid combination. A bad updater would produce{importance: 90, maturity: 'draft'}which violates the tier rules.mergeScoring+determineTiertogether. Reviewer must check that any future sidecar updater follows the same pattern.updatedAtleaks into the sidecar.updatedAtreflects content change, not a runtime signal — must stay in markdown only.RuntimeSignalstype has noupdatedAtfield. By construction the type system blocks accidental mirroring. Verified at every updater by inspection.warnlog.RuntimeSignalStore.validateOrDefaultlogs corrupt-data cases but not write failures.dream.tsCLI path don't receive the store. Two peripheral code paths still write to markdown only.FileKeyStorageinagent-process.tscreates a second store instance targeting the same on-disk path as the agent-side instance. Both processes can race on the same signal record.IKeyStoragelayout, so writes are consistent on disk; only per-read-modify-write atomicity is in-process.Related
features/runtime-signals/backlog.md— swarm coverage, dream CLI path, sidecar failure logging, metrics