← all docs · memory-proactive26 août 2026

Proactive memory — implementation plan

Status: Implemented (rev 3 — two rounds of cross-model plan review, fresh Fable then Codex gpt-5.6-sol high, 2026-08-26. Round 1: 4 blockers / 9 should-fix / 5 nits. Round 2: 7 blockers / 5 should-fix, including two features round 1 and the author had invented. All folded in; §Rev 3 corrections lists what changed and why)
Author: Claude (orch memory proactive)
Date: 2026-08-26
Repo: north-os, branch feat/memory-proactive
Related: docs/superpowers/specs/2026-08-26-memory-proactive-design.html

Shape

Five work packages, sequenced WP1 → (WP2 ∥ WP3 ∥ WP4) → WP5. WP1 is not just "the core": it carries every mechanical consumer fix its own deletions cause, so the tree typechecks before three workers touch it in parallel. WP5 runs last, after the other three merge — an orphan sweep over a moving tree is worthless.

No SQL migration. Three non-SQL prerequisites are real and were missing from rev 1: a scheduler_role INSERT grant on memory_rule, a TS enum widening on the ledger, and an advisory lock standing in for the unique constraint that does not exist. bun db:generate must be run and shown to be a no-op, so the "no migration" claim is verified rather than asserted.

WP1 — memory core + confirm removal + consumer fixes (backend worker, omp)

1a. The confirm interrupt goes (this is the ask's most visible violation)

save_memory and update_memory raise an in-tool confirmArtifact interrupt — a "Save this note?" card the lawyer must accept — before every write (tools/save-memory.ts:157-175, tools/update-memory.ts:154-171), and their descriptions say "The user confirms first." (save-memory.ts:232, update-memory.ts:228). Delete the interrupt in both. The NetDocuments guard and the re-check at write time stay; that re-check becomes the only guard. Add a test asserting saveMemoryTool never interrupts.

The concurrency classifications stay. Rev 2 listed tools/concurrency.ts:257-258 as part of the confirm chain. It is not — the classifier is about mutation, not interruption (concurrency.ts:9-13,20-25,94-101), and both memory tools still mutate tenant state after the card is gone. save_memory: "serial" and update_memory: "serial" stay, and stay in MUST_STAY_SERIAL; concurrency.test.ts:42-55,92-98,191-195 pins them and a further test asserts every dispatchable tool is classified, so removing them is both a correctness regression and a red gate. Only the comment implying interruption was their reason gets rewritten.

The resume path is a compatibility problem, not just a deletion. artifacts/confirm.ts:46-66 persists the confirm payload into the LangGraph checkpoint, chat-runtime/src/run.ts:1960-1992 refuses every fresh message while a non-ask_user interrupt is pending, and chat-ui/src/assistant-ui/thread.tsx:421-429,780-790 renders the resumable widget only while the tool is in CONFIRM_TOOL_NAMES. Delete all of that at once and every thread already paused on a "Save this note?" card is permanently wedged — no button, and every new message returns thread_awaiting_input. Blue/green makes it worse: the old colour keeps minting such checkpoints during the release skew. So: the tools stop calling interrupt(), and the memory action, the action→tool map and the confirm widget stay for one release as an explicitly deprecated resume-only shim, with a regression test that seeds a pre-deploy paused save_memory checkpoint and proves it resumes and writes exactly once. The shim is removed in a later change, once the count of legacy pauses is zero.

What WP1 does cut in the same package, or nothing compiles: agent-runtime/src/artifacts/confirm.ts comments. The client half is WP4's (below), and the coupling is literal: chat-ui/src/assistant-ui/working-notes.ts:267-285 matches the tools' result strings character for character, so WP1 must publish its exact new strings for WP4 to match, or the working-note collapse breaks silently.

1b. The gate

1c. Prerequisites the sweep needs

Tests: rewrite disposition/rules, suggestions, access.integration, both tool test files; add "save_memory never interrupts"; add the shared-matter pin — a matter memory written under user A is returned by loadActiveMemories for user B in the same org while A's user_private memory is not, plus the inverse: B archiving A's matter memory hides it for A too.

WP2 — worker-memory (backend worker, omp)

Tests: mine.test.ts / backfill.test.ts assert active and that low is skipped; trust-profile tests prove (a) a delete-heavy window mints exactly one rule + one ledger row, (b) a second run mints nothing, (c) deleting the rule does not resurrect it, (d) inaction alone never fires.

WP3 — MCP + Cowork surface (backend worker, omp)

WP4 — Settings → Memory + chat confirm card (frontend worker, claude opus)

Tests: apps/web/lib/memory/router.test.ts (removed routes 404; edit and delete work), memory-source-link.test.ts:30 fixture drops calibrationReason; a ui-test pass at the end.

WP5 — legacy script, evals, docs, orphan sweep (backend worker, omp) — runs last

Rev 3 corrections — what plan review round 2 changed

Round 2 (Codex, gpt-5.6-sol high) reviewed rev 2 and found seven blockers. Three of them were the author's own inventions or errors rather than gaps, which is worth recording:

Round 2 also confirmed the load-bearing claim rev 2 asserted without proof: suggested_action is a Drizzle text(..., {enum}), i.e. TypeScript inference metadata over a plain text column with no PG enum and no CHECK, so widening it emits no SQL. bun db:generate stays a gate and must leave the migrations directory byte-for-byte unchanged.

Decisions taken at plan review

QuestionDecision
firm_global blast radiusA — all scopes auto-active. Retrieval reads only the personal and matter buckets, so a firm memory steers nobody's answer today; the risk is Settings clutter, not a firm-wide steer. Standing condition: the change that ever adds a firm bucket to retrieval re-opens sign-off.
What filters the unattended miner once Layer 3 is goneNothing does. An earlier draft had synthesis/backfill skip low-confidence captures; review cut it as invented scope — silent non-persistence with no recovery surface is a retention policy, not a confirmation removal. Every capture is saved; confidence stays as audit metadata that nothing reads.
Legacy confirmed ledger rowsDeleted with the rest. Their action vocabulary would block buckets from earning the rule they deserve.
Archive verbDELETE only; the PATCH arm goes and the UI call switches.
Auto-rule visibilityThe rule itself, in Settings → Memory, deletable. No provenance line — it cannot be computed accurately from this schema and was not asked for.
Low-confidence mined candidatesSaved active like everything else. confidence stays as audit metadata; a discard threshold is a separate change with data behind it.
Threads paused on a confirm card at deploy timeResume-only shim for one release, with a seeded-checkpoint regression test. Removed once legacy pauses reach zero.

Risks

RiskAnswer
A wrong memory steers answers with no human in the loopVisible and deletable in Settings → Memory; the prompt fence still marks memory as untrusted data deferring to the live conversation; repeated deletes mint a never_remember rule; and writes are refused inside headless automation runs, where there is no lawyer to correct them
Volume grows without a gateRetrieval is capped per bucket (20 personal / 15 matter) with semantic ranking past the cap; dedup and near-duplicate enrichment untouched
Legacy activation floods the firm at onceDry-run counts first, human decides (WP5)
A lawyer is silently stopped from getting memoriesCohort-and-floor signal instead of an all-time keep-rate. There is no provenance copy on the rule — review cut it as unbackable by this schema; the lawyer's recourse is deleting the rule, which the ledger makes permanent
Two sweeps write duplicate rulesAdvisory lock around check+insert

Gates

bun run lint + bun run typecheck from the root, plus tests for @workspace/agent-runtime, @workspace/chat-ui, @workspace/chat-runtime, apps/worker-memory, apps/mcp-server and apps/web. bun db:generate must produce nothing. Retrieval and memory behaviour change, so bun run eval:gate:cheap runs before the PR. Dual adversarial review of the diff to convergence, including an explicit deletions audit (git diff origin/main -U0 | grep '^-') — this change removes far more than it adds, which is exactly where a silent drop hides.