Unified Agentic Memory Across Harnesses (via Hooks)¶
This page captures an architectural pattern proposed by Tomaz Bratanic (Neo4j) in a Towards Data Science post from May 2026: how to give multiple coding-agent "harnesses" — Claude Code and OpenAI Codex CLI — a single shared, portable memory layer by using each harness's lifecycle hooks as the integration mechanism, with Neo4j as the persistent graph store. The same pattern generalises beyond coding agents.
Verification note (2026-05-19): The hook contract described here is verified against primary documentation for Claude Code (Anthropic) and OpenAI Codex CLI (OpenAI). Bratanic's original article names Cursor as a third harness, but Cursor's hook vocabulary differs significantly (see Limitations). The reference implementation at tomasonjo/agent-memory-hooks-neo4j covers Claude Code and Codex only — there is no Cursor wiring in the repo.
What it is¶
A harness is the scaffolding around a model: the agent loop, tool definitions, context management, memory, prompts, workflows. Claude Code, Codex CLI, Cursor, and similar tools are all harnesses. A running debate in the AI coding-tool space is whether committing to a specific harness creates vendor lock-in — and memory is the sharpest edge of that question: if your agent's memory lives inside a closed harness or a proprietary API, you don't really own it, and switching costs add up fast.
The post argues for keeping the memory layer outside the harness and letting any harness plug into it. The result is a single, shared memory store that follows the user across tools — switch from Claude Code to Codex mid-project and pick up exactly where you left off, with the agent's understanding of who you are and what you're working on intact (Bratanic, TDS).
Why hooks, not MCP tools¶
MCP servers are the default answer for giving agents access to external systems, and you can expose a Neo4j database as an MCP tool. But MCP tools are agent-initiated: the model has to decide to call them, and remember when and why. That means:
- The agent has to "remember to remember" — proactively decide to store things worth recalling later.
- No consistency guarantee — one session might log everything, the next might log nothing.
- You're relying on the model's judgement about what's important, in real time, while it's busy doing other work.
What you really want for memory is passive, deterministic logging — capturing every session event regardless of what the model is doing, without consuming any of its context or attention. That is what hooks give you. Hooks are shell commands that fire automatically on lifecycle events; the agent doesn't decide to call them, they run programmatically.
The hook contract — verified for Claude Code and Codex CLI¶
Both Claude Code and OpenAI Codex CLI support the same five lifecycle events with identical names. Codex deliberately mirrors Claude's JSON-over-stdin/stdout contract — it even sets CLAUDE_PLUGIN_ROOT and CLAUDE_PLUGIN_DATA environment variables explicitly for compatibility with existing Claude Code hook scripts. This parity is empirical (OpenAI chose to copy Claude Code's schema), not governed by any formal cross-vendor spec.
| Event | When it fires | Claude Code | Codex CLI |
|---|---|---|---|
SessionStart |
Agent session begins (fires before system prompt is read) | ✅ | ✅ |
UserPromptSubmit |
User sends a message | ✅ | ✅ |
PreToolUse |
Before each tool call; can block execution | ✅ | ✅ |
PostToolUse |
After each tool call succeeds | ✅ | ✅ |
Stop |
Session ends | ✅ | ✅ |
The contract is simple: the hook receives a JSON payload on stdin (session ID, event name, tool details, user prompt) and can emit JSON on stdout to inject additional context back into the conversation. The same Python scripts handle both clients; thin shell wrappers with a --client flag are the only per-harness glue.
Two of the events are injection points:
SessionStartfires before the agent reads its system prompt, so anything the hook emits there gets prepended to the system prompt — this is the channel for persistent, agent-level memory.UserPromptSubmitfires just before the user message is sent, so emissions there get appended to the user prompt — the channel for turn-level, query-relevant memory retrieval.
The other events (PreToolUse, PostToolUse, Stop) are write-side: they log events into the store.
Architecture: three layers¶
The proposed design has three components, two of which are online and one offline.
[Harness: Claude Code | Codex CLI]
| hooks (stdin JSON in, stdout JSON out)
v
[Hook scripts: log every event + inject memories]
|
v
[Neo4j memory store] <-- (offline) Dream phase: batch summarise into markdown wiki
^
|
[(optional) MCP server over Neo4j for on-demand agent CRUD]
1. Hooks (online)¶
Hooks passively log every event into Neo4j as a linked list per session. Each agent session is a node, connected to a chain of typed event nodes (one per hook invocation: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop). The result is a complete audit trail of every session across every harness. Crucially, hooks run outside the harness's model session, so they cannot reuse the LLM the agent is talking to. If you want LLM-powered work inside a hook you have to make your own model call — which adds latency to every event. The design therefore keeps hooks doing only two things: log events and inject pre-computed memories. They stay fast and deterministic.
2. Dream phase (offline)¶
The actual memory work — extracting facts from sessions, summarising what happened, updating the graph — happens in a separate batch job called the dream phase. It runs every few hours, reads events since the last watermark, hands them to Claude along with the current memory store, and asks the model to write back a small set of durable notes.
Crucially, the notes themselves imitate a markdown wiki — "the same shape Karpathy and others have been gravitating toward for personal LLM memory and the same shape Anthropic's skills already use." Each memory is a file at a semantic path: profile/role.md, tools/bash/common-flags.md, project/neo4j-skills.md, etc., with YAML frontmatter on top and prose underneath. Claude is told to merge rather than append, so a path is a living document rather than a log; if new events contradict an old note, the old note gets rewritten. The result is a tree of small, self-contained markdown files indistinguishable in form from an Anthropic Skill — just authored by the dream phase instead of by hand.
This is directly relevant to the agent-skills-authoring and karpathy-llm-wiki pages: the same wiki-of-markdown shape is converging across multiple memory designs.
3. Injection (online)¶
On the next session start in any harness, profile memories are loaded into context via the SessionStart hook. On each user prompt, relevant memories are searched and appended via UserPromptSubmit. Append-only is a real constraint here — hooks cannot edit or remove tokens already in the prompt.
Hooks plus MCP tools, together¶
The author notes that in practice you will almost always want both:
| Channel | Strengths | Weaknesses |
|---|---|---|
| Hooks | Deterministic, always fire, no model attention needed, work cross-harness with same script | Append-only injection; can't manipulate the rest of the prompt; cannot reuse the in-session LLM |
| MCP tools | Agent can search, store, update, delete memories on demand (CRUD over the markdown abstraction) | Agent-initiated, so depends on the model deciding to call them; consumes context |
In the reference project, only hooks are used; the official Neo4j MCP server can be plugged in for on-demand graph exploration.
Why this matters¶
"If you don't own your memory, you don't own your agent. Every harness today builds its own walled garden of context, preferences, and session history. Switch them and you start from zero."
The architectural takeaway is portable beyond coding agents: any agentic workflow that wants to be tool-agnostic at the orchestration level can externalise memory by writing through lifecycle hooks and reading on session start. The cost is operational (you run your own store and dream phase) but the lock-in profile flips: the harness becomes replaceable, the memory becomes durable.
Limitations¶
- Cursor is NOT a third harness in this pattern. The original article names Cursor alongside Claude Code and Codex, but Cursor's hook vocabulary is different: it uses
beforeSubmitPrompt,beforeShellExecution,afterFileEdit,stop, etc. — not theSessionStart/PreToolUse/PostToolUsenames. Most critically, Cursor has noSessionStartevent, which is the injection point for persistent memory at session boot. The reference implementation (tomasonjo/agent-memory-hooks-neo4j) contains only.claude/and.codex/directories — no Cursor wiring. Extending to Cursor would require adapting the scripts to Cursor's event names and accepting that memory injection must happen per-prompt viabeforeSubmitPromptrather than once at session start. - Convergence is empirical, not standardised. The Claude Code ↔ Codex parity is a deliberate OpenAI choice, not a formal cross-vendor specification. AGENTS.md (Linux Foundation's Agentic AI Foundation) governs instruction file formats, not hook lifecycle events. Future harness releases may diverge.
- Single vendor-affiliated author. This is one engineer's design (from a Neo4j employee, with explicit disclosure) published on Towards Data Science — a community publication, not peer-reviewed. The pattern is plausible and the hook contracts are verified against primary docs, but production hardening and multi-team adoption are not externally validated. The repo was brand new as of May 2026 with minimal community uptake.
- Latency in hooks. Every event fires a subprocess; LLM calls inside hooks add user-visible latency. The design keeps hooks fast specifically to avoid this, but that means the interesting memory work has to be deferred to the batch dream phase, which introduces freshness lag.
- Append-only injection. Hooks cannot edit or remove tokens already in the prompt; they can only add to it. Pruning or rewriting requires MCP tools or harness-side support.
- Storage choice is opinionated. Neo4j is used because the author works there; the design itself does not require a graph database. A relational store with a session/event schema would work.
Sources¶
- Tomaz Bratanic, "Unified Agentic Memory Across Harnesses Using Hooks", Towards Data Science, 8 May 2026 — primary article; reference implementation linked in post.
- Hooks reference — Claude Code Docs (Anthropic) — primary vendor documentation for Claude Code lifecycle hooks; all five events confirmed with exact names and JSON stdio contract.
- Hooks — Codex CLI Docs (OpenAI) — primary vendor documentation for Codex CLI hooks; identical event names, explicitly sets
CLAUDE_PLUGIN_ROOTfor Claude Code compatibility. - tomasonjo/agent-memory-hooks-neo4j (GitHub) — Bratanic's reference implementation; contains
.claude/and.codex/directories only.
Changelog¶
2026-05-18 — Page created from https://towardsdatascience.com/unified-agentic-memory-across-harnesses-using-hooks/ (Type T, confidence 65). Single-source, vendor-affiliated author — confidence reduced accordingly. 2026-05-19 — Deep-research verification run. Hook contract confirmed against Anthropic and OpenAI primary docs. Codex CLI deliberately mirrors Claude Code schema (CLAUDE_PLUGIN_ROOT compatibility env var). Cursor corrected: different event vocabulary, no SessionStart, not covered by reference repo. Confidence raised to 75 (moderate). "Three harnesses same contract" framing removed; Cursor section moved to Limitations. Four sources added.