capture what a session learned without asking it to - #33
Merged
Conversation
Between 2026-08-23 and 2026-09-02 the session-start briefing told every
session, in every project, to drain the capture queue: "run `echo-memory
pending`, read each, and write_episode what it states." In that window 16
memory files were written across eigen and dugout. The capture hook noticed
all 16. None became facts in the session that wrote them - they were ingested
by hand, ten days late, from a different project.
The instruction was correct and specific and lost 16 times out of 16. It was
competing with Claude Code's own file-based memory, which is a permanent
system-prompt section present on every turn, while this was one reminder at
session start among several hundred registered tools. Everything that worked
in that same window was a hook.
So capture stops asking:
- Stop hook (scripts/session-stop-hook.sh, cli/stop_gate.py) returns
{"decision": "block"} when this project still owes the graph, handing the
agent the file list as its next instruction. It fires at the one moment the
agent still holds the context to say what it learned. Bounded three ways so
it does not become the hook people disable: once per session via
stop_hook_active, scoped to the current project, silent when clean.
- reconcile (cli/reconcile.py) reads the filesystem rather than trusting the
hook fired. Three eigen files were found holding content the queue had never
seen; nothing detected that, because a queue only knows what it was told. It
runs from SessionStart and again inside the stop gate, so a missed capture
self-heals instead of being permanent. Same scope as the capture hook, so
the two cannot disagree about what counts as a memory.
- install-hooks (cli/hooks.py) registers all five in one command instead of
four JSON blocks merged by hand. That shape half-completes silently, and a
machine missing one hook is indistinguishable from a machine missing none.
Entries are matched by marker *and* by script name, so the first run on an
existing machine upgrades the hand-merged hooks rather than doubling them.
Also 0009: read_event has carried group_id since it shipped, which is the same
string for every Claude Code session in every repo. Asked "did the eigen
sessions read memory?" the table had no answer. It now records project and
session_id, both nullable, because an unattributed read is worth more than an
exception between the user and the agent.
387 existing tests still pass; 26 added.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
The measurement
From 2026-08-23 to 2026-09-02 the session-start briefing told every session in every project:
In that window 16 memory files were written across eigen and dugout. The capture hook noticed all 16. None became facts in the session that wrote them — they were ingested by hand, ten days late, from a different project.
The instruction was correct, specific, and lost 16 times out of 16. It was competing with Claude Code's own file-based memory, which is a permanent system-prompt section present on every turn, while this was one reminder at session start among several hundred registered tools. Everything that worked in that window was a hook.
What changed
Stop hook —
scripts/session-stop-hook.sh,cli/stop_gate.py. Returns{"decision": "block"}when this project still owes the graph, handing the agent the file list as its next instruction. It fires at the one moment the agent still holds the context to say what it learned and is no longer mid-task. Bounded three ways so it doesn't become the hook people disable:stop_hook_active), so a nudge never becomes a loopecho-memory reconcile— reads the filesystem instead of trusting that the hook fired. Three eigen files were found holding content the queue had never seen; nothing detected it, because a queue only knows what it was told. Runs from SessionStart and again inside the stop gate, so a missed capture self-heals. Deliberately the same scope as the capture hook — if the two disagreed, the sweep would either keep reopening files the hook ignores or keep missing files it queues.echo-memory install-hooks— registers all five hooks in one command instead of four JSON blocks merged by hand. That shape half-completes silently, and a machine missing one hook is indistinguishable from a machine missing none. Entries are matched by marker and by script name, so the first run on an existing machine upgrades the hand-merged hooks rather than doubling them (every hook firing twice, every file noticed twice).Migration 0009 —
read_eventhas carriedgroup_idsince it shipped one day ago, which is the same string for every Claude Code session in every repo. Asked "did the eigen sessions read memory?", the table had no answer: 12 rows, all identically labelled. It now recordsprojectandsession_id, both nullable, because an unattributed read is worth more than an exception between the user and the agent.Testing
Scope
The Stop gate only reaches clients that support hooks, so it helps Claude Code and not Claude Desktop. That limit is real and unchanged by this PR.