Skip to content

feat: capture reliability — filter, roll-up, multi-client hooks, proof - #7

Merged
elViRafa merged 6 commits into
mainfrom
claude/capture-filter-hooks-roadmap-dylnzy
Jul 16, 2026
Merged

feat: capture reliability — filter, roll-up, multi-client hooks, proof#7
elViRafa merged 6 commits into
mainfrom
claude/capture-filter-hooks-roadmap-dylnzy

Conversation

@elViRafa

Copy link
Copy Markdown
Owner

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.

  • Stage 0 — Capture filter (storage/capture.py): capture_commit skips noise commits by default (merges, [bot] authors, chore:/style:/ci:/build(deps) prefixes, lockfile-only changes) — audibly (skipped_reason, a warning, a commits_skipped counter), never silently. --no-filter opts back in.
  • Stage 1 — Episodic roll-up (storage/consolidation.py): ai-memory dream --mode deep folds episodic/commits/ daily files older than 14 days into weekly summaries, so passive capture's residual accumulation has a real destination instead of growing forever.
  • Stage 2 — Client lifecycle-hooks writer (client_hooks.py, new): ai-memory install --client <claude-code|gemini-cli|codex> --with-hooks wires 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-journal was printing its block reason to stdout only, but every client reads the exit-2 feedback from stderr exclusively.
  • Stage 3 — Capture-rate proof (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) and README.md (Capture Reliability section gets the demo pitch and the measured table).

Test plan

  • Full suite: 334 passed + 93 subtests (one pre-existing, unrelated failure in test_cross_process.py — reproduces on unmodified main in this container because it runs as root, not caused by this branch)
  • ruff check ., ruff format --check ., mypy src/memory_fabric all clean
  • Real end-to-end smoke tests for all three hook adapters: generated Stop/AfterAgent and PreCompact/PreCompress command strings executed via sh -c (not just the Python functions), confirming exit-2 + stderr blocking and a real non-blocking dream --mode light --apply checkpoint
  • scripts/capture_rate_benchmark.py run for real to produce the numbers cited in README/ROADMAP

🤖 Generated with Claude Code


Generated by Claude Code

claude added 6 commits July 16, 2026 13:00
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
@elViRafa
elViRafa merged commit 8cdd880 into main Jul 16, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants