feat: capture reliability — filter, roll-up, multi-client hooks, proof - #7
Merged
Merged
Conversation
capture_commit now skips merges (parent count or subject prefix), [bot] authors, chore:/style:/ci:/build(deps) prefixes, and lockfile-only commits. Skips are audible, never silent: skipped_reason in the result, a warning naming the commit, and a commits_skipped counter surfaced by ai-memory status. Opt out with capture --no-filter; idempotency per commit hash is unchanged. The diff-elision list moves from finalize.py to storage/_shared.py so the diff budget and the capture filter share one notion of noise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
Deep dream now folds memory-store/episodic/commits/ daily files older than 14 days into weekly week-<iso-year>-w<ww>.md summaries, marked review_status: consolidated. Gives the pending capture backlog a real destination instead of unbounded accumulation. Idempotent: a daily file is deleted only once its content is appended, so a re-run with nothing newly eligible is a no-op; a malformed daily file is skipped with a warning and left for a future retry. Light dream is unaffected. Reuses the existing candidate/snapshot/apply pipeline in consolidation.py — no new snapshot mechanism needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
…nts (Stage 2) New client_hooks.py registers a HOOK_ADAPTERS dispatcher, deliberately separate from clients.py/installer.py's MCP-config engine since each client's hook mechanism (SessionStart/Stop/PreCompact and friends) has its own event model that doesn't share a json/toml/cli split. A client with no adapter gets an honest supported:false result naming what's actually supported, instead of a silent no-op. ai-memory install --client claude-code --with-hooks writes .claude/settings.json: SessionStart (session-start --hook-format claude-code, marks the session + injects a short additionalContext reminder), Stop (guard-journal, already exits 2), PreCompact (non-blocking dream --mode light --apply checkpoint). Verified the live hooks schema against current docs before writing the merge logic and found two real gaps the plan didn't anticipate: guard-journal was printing its block reason to stdout only, but Claude Code reads exit-2 feedback from stderr exclusively (fixed); and PreCompact's block path gives no stderr feedback to Claude at all, so it runs a best-effort checkpoint instead of attempting to enforce journaling a second way. Commands carry a trailing '# memory-fabric-managed' marker so install/uninstall touch only memory-fabric's own entries and leave anything a user added by hand alone. Moved the ai-memory binary resolver from lifecycle.py (git hooks) to clients.py as resolve_cli_binary() so both hook mechanisms share one resolution. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
Researched hook/lifecycle-extension capability across Cursor, Codex CLI, Windsurf, VS Code Copilot, Gemini CLI, and Cline against current official docs (not training-data recall) before picking what to build next. Gemini CLI came back highest-confidence and closest match to the Claude Code design, so it's implemented now; Codex CLI is next (higher ceiling — its PreCompact can actually block compaction — but only secondary-sourced so far); Cursor/VS Code held on open bugs/preview status; Windsurf/Cline lack the mechanism entirely. Findings recorded in ROADMAP_CAPTURE_HOOKS.md §2.1. _install_gemini_cli_hooks writes .gemini/settings.json: SessionStart, AfterAgent (guard-journal, unchanged - same exit-2/stderr contract), PreCompress (non-blocking checkpoint, unchanged - same advisory-only constraint as Claude Code's PreCompact). Gemini CLI's lifecycle hooks are flat hook-definition lists with no matcher wrapper, unlike Claude Code's matcher-based blocks, so this needed its own merge/remove logic rather than reusing the claude-code adapter's - confirming the registry was right not to assume a shared format engine. Extracted the truly shared JSON safe-write scaffolding (read/backup/finalize-write) out of the claude-code adapter so it's shared by both instead of duplicated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
Every official Codex hooks doc URL 403'd on fetch (same as the earlier client survey found), so this went straight to Codex's own Rust source on GitHub instead of guessing from secondary sources: codex-rs/hooks/ src/lib.rs, codex-rs/config/src/hook_config.rs (the literal hooks.json wire schema), codex-rs/hooks/src/events/common.rs (matcher semantics), and the session_start/stop/compact test suites (exact I/O behavior). hooks.json turned out structurally identical to Claude Code's settings. json hooks (same matcher+hooks blocks), so the merge/remove logic is shared rather than duplicated a second time - refactored into _merge_matcher_block_hooks/_remove_matcher_block_hooks, used by both claude-code and codex now. Codex's matcher syntax supports exact pipe-separated alternation, letting SessionStart and PreCompact each cover both sources in one block instead of Claude Code's two. Stop's exit-2-plus-stderr contract is confirmed identical from a test literally named exit_code_two_uses_stderr_feedback_only, so guard-journal needed no changes. New, source-confirmed finding this adds real value on: Codex gates hooks behind a hash-based trust check - a newly added/modified hook is discovered but not executed until trusted. The exact end-user trust command wasn't found in the source checked, so the installer surfaces an explicit warning naming the gap rather than asserting a guessed command or silently implying the install is fully done. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
scripts/capture_rate_benchmark.py scripts a fully non-cooperative simulated agent through 20 sessions per mode: 0% of sessions journaled with no enforcement, 100% with the Stop hook wired in, commit capture steady at 100% in both modes since passive capture runs off the git post-commit hook rather than the client-side session hooks. This is a mechanism proof (the enforcement primitive can't be silently skipped), not a statistical study of real agent compliance - that stays Phase 5's separate, larger benchmark work. Regression-guarded in tests/test_capture_rate_benchmark.py so the number can't silently drift. Updates README's Capture Reliability section with the demo pitch and the measured table, and closes out the loop in both roadmaps: ROADMAP.md Phase 3 section 5.2/5.3 marked done with the client-hook survey and capture-rate metric, section 10's success-metrics row updated with the measured number, and ROADMAP_CAPTURE_HOOKS.md's all four stages now closed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvZySU2imvkpciVzx83JvG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes out ROADMAP.md Phase 3 (capture reliability) in four stages, each verified against real code/docs before shipping rather than assumed. Full design record in
ROADMAP_CAPTURE_HOOKS.md.storage/capture.py):capture_commitskips noise commits by default (merges,[bot]authors,chore:/style:/ci:/build(deps)prefixes, lockfile-only changes) — audibly (skipped_reason, a warning, acommits_skippedcounter), never silently.--no-filteropts back in.storage/consolidation.py):ai-memory dream --mode deepfoldsepisodic/commits/daily files older than 14 days into weekly summaries, so passive capture's residual accumulation has a real destination instead of growing forever.client_hooks.py, new):ai-memory install --client <claude-code|gemini-cli|codex> --with-hookswires SessionStart (mark + inject context), Stop (blocking journal enforcement), and a pre-compaction checkpoint into each client's own hook config. Each schema was verified independently — official docs for Gemini CLI, source code for Codex CLI since every doc URL 403'd — rather than assumed from Claude Code's shape, even where they turned out to match. Found and fixed along the way:guard-journalwas printing its block reason to stdout only, but every client reads the exit-2 feedback from stderr exclusively.scripts/capture_rate_benchmark.py): scripts a fully non-cooperative simulated agent through 20 sessions per mode — 0% session-journal rate with no enforcement, 100% with the Stop hook wired in, commit capture steady at 100% either way. Regression-guarded so the number can't silently drift.Also updates
ROADMAP.md(§5.2/§5.3 marked done, §10 metrics row updated, header refreshed) andREADME.md(Capture Reliability section gets the demo pitch and the measured table).Test plan
test_cross_process.py— reproduces on unmodifiedmainin this container because it runs as root, not caused by this branch)ruff check .,ruff format --check .,mypy src/memory_fabricall cleanStop/AfterAgentandPreCompact/PreCompresscommand strings executed viash -c(not just the Python functions), confirming exit-2 + stderr blocking and a real non-blockingdream --mode light --applycheckpointscripts/capture_rate_benchmark.pyrun for real to produce the numbers cited in README/ROADMAP🤖 Generated with Claude Code
Generated by Claude Code