Hooks & Platform · how reflect captures and recalls across Claude + Codex
The short version — Three harnesses (Claude Code, Codex CLI, GitHub Copilot) wire the same hook scripts into different config files (
~/.claude/settings.jsonvs~/.codex/hooks.jsonvs~/.copilot/hooks/reflect.json) and share one on-disk knowledge base (~/.reflect/queue +~/.learnings/documents + GraphRAG index). SessionStart fires the baseline recall + the bg-drainer; UserPromptSubmit fires the intent-sharp recall with per-session dedupe; PreCompact, Stop, and PostToolUse capture learnings into the shared store. A codex session can enqueue a reflection that a later Claude session drains — and vice versa.No extra LLM/embedding config on any harness — recall/index use a local model (no key); capture reuses the harness LLM (
claude -p), so Codex/Copilot need theclaudeCLI on PATH for the drain. Copilot dropsuserPromptSubmittedhook output, so per-prompt recall there is manual/recall(SessionStart auto-recall works).
Architecture at a glance
Two harnesses, the same hook scripts, one shared knowledge base. Solid arrows are control flow; dashed clay arrows are recall (read into context); dashed olive arrows are capture (write to disk).
Session timeline · one coding session, end to end
A horizontal view of what fires when. Hook events show above the spine; data I/O below.
Storage layers · three tiers, one knowledge base
The pending queue, the learnings store, and the GraphRAG index. Append-only, grep-able, portable.
Platform · adapter interface and shared knowledge base
Each harness has its own adapter that wires hooks into its own config file. Both adapters point at the same hook scripts and the same shared knowledge base.
Install paths · Claude Code vs Codex CLI
Claude has a plugin runtime (/plugin install) so installation is two slash commands.
Codex has no plugin runtime, so the adapter does the autowire itself with one python
command.
The reflect plugin now ships from its own repo, stevengonsalvez/ainb-reflect-memory (plugin under plugin/), not this monorepo’s marketplace — so /plugin install reflect@agents-in-a-box no longer resolves. Clone that repo (or use ainb reflect bootstrap, which installs from it) and run the adapters from its plugin/adapters/ directory:
# Claude Code — install from the ainb-reflect-memory plugin/ dir via your# harness's plugin runtime (see the repo README).
# Codex CLI — adapter does the autowire (paths relative to the new repo's plugin/ dir)python plugin/adapters/codex/codex_adapter.py install# or skip the bg drain on codex-only machines without claude on PATH:python plugin/adapters/codex/codex_adapter.py install --no-bg-drainRecall · UserPromptSubmit primary, SessionStart baseline, per-session dedupe
SessionStart fires before the user has typed anything — its recall query has to be inferred from cwd, branch, and recent commits. UserPromptSubmit has the actual user prompt to query against, which gives much sharper hits. Both fire; UserPromptSubmit dedupes against learnings already injected this session so the same memory doesn’t re-inject on every prompt.
Dedupe state lives at ~/.reflect/session-injected/<session_id>.json — a per-session
set of learning IDs already injected. UserPromptSubmit recall queries the KB, intersects
with the dedupe set, and only injects new hits as additionalContext.
Capture · PostToolUse mini-learnings + Stop reflection enqueue
PreCompact handles the high-cost full reflection (claude -p /reflect). Two more hooks
cover gaps:
- PostToolUse captures cheap mini-learnings inline — on tool failure, arms a watcher
for the next user prompt; if the prompt looks like a correction (
"try X instead"), write a low-confidence learning directly to disk. No LLM run needed. - Stop catches short sessions that end before PreCompact ever fires. Enqueues the transcript on agent finish; dedupes against any PreCompact entry for the same session.
Status line · making recall + capture activity visible
Both harnesses give visual feedback, but through different mechanisms.
- Claude Code — hooks write
~/.reflect/last-event.json; the user’s~/.claude/statusline.shreads it and renders a persistent reflect fragment (🧠 3 recalled · 1 queued). - Codex CLI — hooks declare a
statusMessagefield inhooks.json. Codex shows it ephemerally during hook execution (🧠 recalling...). The static[tui] status_lineconfig can’t carry a custom token yet, so persistent codex-side counters wait on a codex API extension.
Worked example · “fix the OAuth redirect bug”
A concrete walkthrough showing every hook firing in order across a morning Claude session and an afternoon Codex session on the same repo.
- 09:14 · Claude SessionStart — recall on
cwd=auth-servicereturns L₁ (“OAuth state handling”). Injected as baseline. - 09:14 · UserPromptSubmit — user types “fix the OAuth redirect bug”. Sharp query
pulls L₂, L₃. L₁ skipped (already injected). Dedupe set:
{L₁, L₂, L₃}. - 09:18 · PostToolUse —
curlreturns 500. Mini-learning watcher arms. - 09:18 · UserPromptSubmit — user types “use
--insecurefor local dev”. Watcher sees correction pattern, writes mini-learning directly to disk. No LLM run. - 10:42 · PreCompact — context 90% full. Transcript path enqueued to
~/.reflect/pending_reflections.jsonl. - 11:05 · Stop — agent finishes.
stop_reflect.pychecks queue, sees PreCompact already enqueued this session_id → skips. - 14:30 · Codex SessionStart — different harness, same repo.
reflect-drain-bg.shstarts in background, finds the morning queue entry, spawnsclaude -p /reflectheadless. Writes L₄ (“OAuth state mismatch on redirect”). - 14:31 · Codex UserPromptSubmit — user types “add OAuth refresh tokens”. Recall pulls L₂, L₃, L₄ — including the learning Claude just wrote this morning.
The codex session benefits from the morning’s Claude work without anyone moving files around. The queue and the learnings store are the only handoff.
Files involved
The plugin-internal files below now live under plugin/ in the standalone stevengonsalvez/ainb-reflect-memory repo (extracted out of this monorepo); paths are relative to that plugin/ directory. The ~/.reflect/ and ~/.learnings/ entries are runtime state on the user’s machine, unchanged by the move.
| File | Role |
|---|---|
plugin/skills/recall/hooks/session_start_recall.py | SessionStart recall (baseline, cwd-based query) |
plugin/skills/recall/hooks/user_prompt_submit_recall.py | UserPromptSubmit recall (intent-sharp, with dedupe) |
plugin/hooks/precompact_reflect.py | PreCompact enqueue (full reflection deferred) |
plugin/hooks/stop_reflect.py | Stop enqueue (short-session fallback) |
plugin/hooks/posttooluse_minilearning.py | PostToolUse mini-learning capture |
plugin/hooks/reflect-drain-bg.sh | SessionStart bg-drainer (shells out to claude -p) |
plugin/.claude-plugin/plugin.json | Claude plugin autowire (installed from ainb-reflect-memory) |
plugin/adapters/codex/codex_adapter.py | Codex installer (writes ~/.codex/hooks.json) |
~/.reflect/pending_reflections.jsonl | Shared queue (any harness writes, any drains) |
~/.reflect/session-injected/<session_id>.json | Per-session dedupe state |
~/.reflect/last-event.json | Status line fragment source |
~/.learnings/documents/ | Markdown learnings + entity sidecars |
~/.learnings/graphrag/ | GraphRAG + vector index |
FAQ
Why doesn’t SessionStart recall use the user’s first prompt? SessionStart fires before the user has typed anything. Its query has to be inferred from cwd, branch, and recent commits — coarse but immediate. UserPromptSubmit fills the prompt-aware recall slot.
What stops UserPromptSubmit recall from re-injecting the same learning every prompt?
The per-session dedupe set at ~/.reflect/session-injected/<session_id>.json. Each hit
becomes a {learning_id: ts} entry; future prompts skip already-injected learnings
unless they’d be the top hit anyway.
Why does the drainer always shell out to claude instead of codex?
The /reflect skill is a Claude skill. Codex is the trigger (any SessionStart fires the
bg drainer), Claude is the worker. On codex-only machines without claude on PATH, pass
--no-bg-drain to the codex adapter to skip the drain hook.
Does Stop also fire on long sessions that hit PreCompact?
Yes, but stop_reflect.py dedupes against the queue by session_id. PreCompact gets in
first; Stop is a fallback for sessions that never compact.
Where does ~/.reflect/ live, and is it portable?
Under $HOME/.reflect/ by default; overridable via REFLECT_STATE_DIR. Contents are
JSONL/Markdown/YAML — fully grep-able, version-control friendly, and portable across
machines via filesystem sync.
Try it — see the standalone visual posters with the same diagrams:
- Prose explainer + small SVG: https://unfold-ledger-qjhe.here.now/
- Full platform poster: https://saffron-mesa-9nz2.here.now/