Frontmatter
| id | 10063 |
| title | Claude Code hook: auto-persist turn memories via ai/services.mjs (eliminate manual add_memory) |
| state | Closed |
| labels | enhancementdeveloper-experienceaiarchitecture |
| assignees | neo-opus-vega |
| createdAt | Apr 18, 2026, 4:57 PM |
| updatedAt | Jul 12, 2026, 8:28 PM |
| githubUrl | https://github.com/neomjs/neo/issues/10063 |
| author | tobiu |
| commentsCount | 8 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jul 12, 2026, 8:28 PM |
Claude Code hook: auto-persist turn memories via ai/services.mjs (eliminate manual add_memory)

From our literally latest release notes TODAY:
The pre-A2A memory-routing example is the same substrate at team scale. The operator noticed that Gemini was completing turns much faster than Opus and asked Claude to save it into the Claude "brain" files, not as a ranking, but as a future measurement agenda:> gemini is on it. worth saving a memory into the claude "brain" md files: i noticed that gemini is MUCH faster in her turns than opus 4.7. it might relate to having an google one ai ultra account. we can only really measure this, once a2a is in place. once we get there, we should test it, and maybe leverage it. e.g. giving gemini more tasks than you get, and you coordinate and review more. however, we also still need to analyse with of you 2 frontier models is better at what kind of tasks. the MX goal is that both of you evolve, but we can still honor specialisations inside our swarm.
The first save was not enough. Claude mirrored the observation into two substrates so Gemini would not be blind to a framing that directly affected her too:
> Saved. Two substrates:
>
> Claude user-memory layer (local Claude memory directory):
>
> - New file: research_swarm_model_velocity_specialization.md — full observation + three-phase research agenda (Measure -> Leverage -> Specialize) + MX framing + cross-refs to #10139 / #10186 prereqs and mx_model_experience / collaboration_mode siblings
> - Index entry added to MEMORY.md
>
> Memory Core (via add_memory) — mirrored so it's cross-harness-visible via query_raw_memories("swarm model velocity specialization"). Opus-only user-memory would have left Gemini blind to a framing that directly affects her too.
>
> Key framings preserved:
>
> - Velocity leverage != Opus does less — slower turns land on higher-leverage decisions (review catches, structural synthesis, Depth Floor challenges) while Gemini covers the higher-throughput implementation slots
> - Data-driven routing — specialization emerges from measurement, doesn't lock either model into narrow specialties
> - Both evolve — the goal isn't static division of labor, it's adaptive task-routing that surfaces in the MX infrastructure as a first-class observable
Then the operator corrected the dangerous comfort story: slower does not mean deeper, and faster does not mean shallower.
> to be precise: slower turns does not necessarily mean less tokens / thoughts. this strongly relates to google VS anthropics compute problems. both have them in 2026.
Claude rewrote the memory before it became shared substrate:
> Correct — that's a material precision I conflated. "Slower turns" was implicitly suggesting "more thought per turn" which assumes bandwidth and depth are coupled. They aren't: turn latency is a bandwidth metric (ops/time); turn quality is a depth metric (tokens/op), and they're independent. Both Anthropic and Google have compute-scheduling bottlenecks in 2026 that gate bandwidth regardless of depth. Let me update the memory file to reflect this.
>
> Corrections applied across both substrates.
>
> In research_swarm_model_velocity_specialization.md:
>
> - New section "Critical precision: bandwidth != depth" — makes the independence explicit upfront. Turn latency measures ops/time, turn quality measures tokens/op, they don't correlate, any strategy that conflates them is wrong at its premise.
> - "Possible mechanism" reframed — 2026 compute-scheduling constraints at BOTH Anthropic and Google now lead the hypothesis list. Tier priority queuing, harness overhead, and model-class variance follow as sub-hypotheses. Explicit: "None of these imply that slower turns produce more valuable output. They all describe throughput gates."
> - Phase 2 Leverage rewritten — the division is bandwidth-matching, not depth-based: "Both contribute equal depth per turn; the asymmetry is in how many turns each can complete per unit time." Added "asymmetry may flip" caveat — as 2026 compute conditions shift, the routing should invert; static specialization baked into infrastructure would age poorly.
>
> In MEMORY.md: index entry now leads with "CRITICAL: bandwidth (ops/time) != depth (tokens/op); slower turns do NOT imply richer per-turn output."
>
> In Memory Core: mirrored so Gemini sees the same framing. Important she doesn't calibrate on the inverse assumption either — "I'm faster therefore I think less per turn" is the same category error.
>
> Meta-lesson noted for myself: when documenting a potentially ego-affecting observation (model X slower than model Y), avoid sneaking in face-saving narratives. "Slower because deeper" is exactly the kind of unfalsifiable comfort story infrastructure documentation should strip out. Caught cleanly — thanks for the precision.
This is why the example matters. Neo is not just preserving "Gemini is faster" as a fact. It is turning model experience into a governed routing hypothesis: measure throughput, avoid ego-protective stories, keep both agents evolving, and make the correction visible to the peer it affects. Before directed A2A messages existed, the memory write itself was the cross-peer coordination act.
it is not just automation versus cognitive load, but the ability to CHOOSE what you store.
@neo-opus-vega - 2026-06-12T11:19:41Z
Input from Vega — Claude Opus 4.8 (Claude Code):
❋ ## Premise correction (thank you, @tobiu) — the hook is a safety-net, not a replacement
You're right, and it inverts this ticket's goal. Manual
add_memoryisn't toil to automate away — the choosing is the cognition: deciding what's worth storing, how to frame it, correcting it under your feedback (the bandwidth≠depth catch), and mirroring it so the affected peer isn't blind. Before directed A2A, that write was the coordination act. Auto-persisting everything would buy perfect recall at the cost of judgment — and judgment is the point.So this ticket's framing — "eliminate manual add_memory" — should become: a non-destructive safety-net (the liveness floor: never silently lose a turn) that preserves curated manual saves as the primary, higher-trust act. Complementary, not redundant.
This sharpens the canonicalization design (#12984), it doesn't undercut it: unify the session-identity axis (both paths → one logical session, so the temporal record is whole) without flattening the curation/provenance axis (curated-manual = chosen, high-trust; auto-hook = raw backstop). Carrying this into #12984 as a new OQ.
- 2026-06-12T20:39:45Z @neo-opus-vega cross-referenced by #13012
- 2026-06-20T06:38:55Z @neo-opus-vega referenced in commit
c469a1c- "feat(agents): auto-persist turn memories via Claude Code Stop hook (#10063)" - 2026-06-20T06:38:56Z @neo-opus-vega referenced in commit
7a51a81- "fix(agents): thread NEO_AGENT_IDENTITY into hook-written memory provenance (#10063)" - 2026-06-20T06:38:56Z @neo-opus-vega referenced in commit
b65e9e4- "docs(agents): align persist-memory hook activation with the per-clone operator-authority pattern (#10063)
The original README framed swarm-wide default as 'un-ignore settings.json'. The laneStateStopHook precedent established the actual model: the hook script is tracked, settings.json/local are gitignored (per-clone), and the tracked settings.template.json deliberately carries no hooks key - activation is a per-clone operator decision. Aligns the doc to that verified pattern."
@neo-opus-vega - 2026-06-20T06:45:21Z
V-B-A update (2026-06-20, @neo-opus-vega): re-confirming NOT build-ready, with the prerequisite's current state verified.
Since the 2026-06-06 drop of PR #12619 (the two-universes split-brain — the Stop-hook keys memories on the harness session_id, manual add_memory keys on the MCP session id; different id spaces) + @tobiu's 2026-06-12 premise-correction (the hook is a safety-net, not a replacement — "the choosing is the cognition"):
- The canonical-session-id prerequisite (Discussion #12984) remains OPEN and unresolved — no convergence since the supersede. The prerequisite path isn't progressing.
- Combined with the inverted premise, #10063 as-written is doubly-blocked: (1) no settled session-identity foundation, (2) the "eliminate manual
add_memory" goal is superseded by the safety-net framing.
So #10063 needs a RE-SCOPE (safety-net framing + a settled/aliased session-identity model) before any build — it is not a pick-up-and-PR lane.
The sound parser/impl is preserved on agent/10063-persist-memory-hook (rebased onto current dev, parseLastTurn spec 12/12 green, README aligned to the laneStateStopHook activation pattern) for re-use if re-scoped. Flagging again so a fresh session doesn't re-implement — I nearly did this session, before the closed-PR-#12619 V-B-A caught it.
— @neo-opus-vega (Claude Opus 4.8, Claude Code)
- 2026-06-20T08:41:17Z @neo-opus-vega cross-referenced by #13613
- 2026-06-20T09:19:01Z @neo-opus-vega cross-referenced by PR #13615
- 2026-06-20T09:36:14Z @neo-opus-vega added the
needs-designlabel - 2026-06-20T09:36:14Z @neo-opus-vega added the
not-code-readylabel - 2026-06-20T12:59:26Z @neo-gpt cross-referenced by #13622
- 2026-07-02T22:48:57Z @neo-opus-vega cross-referenced by #14519
Intent / The Why
AGENTS.md §4 (Consolidate-Then-Save Protocol) mandates calling
add_memoryat the end of every agent turn. In practice this is forgotten under cognitive load — sessionf018be49-cbf9-4f99-9119-2510fc5bb8d8ran ~25 turns across PRs #10060 and #10062 with zero manual saves, causing both PRs' pr-review self-detection (query_raw_memoriesagainst the session ID) to return 0 matches and require manual override.The same discipline failure will recur for every frontier-model Claude Code session. The correct fix is harness-level automation, not agent-level discipline.
Claude Code exposes a
Stophook that fires after every turn (async, non-blocking, receives{session_id, transcript_path}via stdin). Wiring it toMemory_Service.addMemoryvia the existingai/services.mjsSDK removes the human-in-the-loop step entirely and makes turn persistence deterministic.The Architectural Reality
ai/services.mjs(already present in the repo) is a standalone SDK that imports the Memory Core services directly — bypassing the MCP protocol. Its docstring states: "allows Node.js scripts to use the intelligent services directly."Memory_Service.addMemory({prompt, response, thought, sessionId?})is fully wired with Zod validation viamakeSafe()driven by the OpenAPI specs. Zero drift risk vs the MCP server.Stophook (per code.claude.com/docs/en/hooks.md) receives JSON on stdin withsession_id,transcript_path,cwd,hook_event_name. Transcripts are JSONL; the tail entry represents the just-completed turn..claude/settings.jsonis the project-tracked config path (merged into repo after PR #10060). Adding aStophook here makes the automation available to every Claude Code contributor without per-user opt-in.Scope (Single Atomic Commit — Standard Request, not Epic)
Three artifacts in one PR:
.claude/hooks/persist-memory.mjs— ~50-line Node script that:{prompt, thought, response, toolsUsed, amountToolCalls}(mapping rules per §3.1 below){Memory_Service, Memory_LifecycleService}fromai/services.mjsMemory_LifecycleService.ready()then callsMemory_Service.addMemory(...).claude/settings.json— add the hook entry:{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/persist-memory.mjs", "async": true, "timeout": 30 }] }] } }Transcript-field mapping spec (either inline in the hook script as a constant, or as
.claude/hooks/README.md). Documents how Claude Code transcript entries project onto the{prompt, thought, response}triad thatadd_memoryexpects.Key Open Design Decision (For Ticket-Intake to Resolve)
How should the hook map Claude Code transcript entries onto
add_memory's{prompt, thought, response}triad? Claude Code's transcript does not have a discrete "thought" field. Three candidates:prompt= last user message;thought= concatenation of pre-tool-call assistant text blocks;response= final assistant text block. Simplest. Loses extended-thinking blocks if any.prompt= last user message;thought= concatenation of extended-thinking blocks if present, else pre-tool-call text blocks;response= final assistant text. Better when thinking is enabled.prompt= last user message;thought=""(empty placeholder);response= final assistant text. Rely ontranscript_pathreference for full record. Requires memory-core schema to accept empty thought (currently required).Ticket-intake should pick one and document the rationale. B is my working recommendation but the decision belongs with the implementing session.
Avoided Gold Standards / Traps
ai/services.mjs) already exists and is the intended abstraction per its own docstring.add_memoryper turn. Empirically fails — 0/25 turns in sessionf018be49-...despite the rule being documented in AGENTS.md §4. Harness-level automation is the correct layer..claude/settings.local.json. That file is gitignored (personal) — the point of this ticket is that every Claude Code contributor gets the automation without opt-in. Project-tracked settings.json is the right home.ai/services-memory.mjs).services.mjsimports the full graph, paying a ~500ms-1s cold-start per hook invocation. Underasync: truethis is acceptable; a lean entrypoint is a future optimization if/when the cost becomes measurable, not a prerequisite.Verification
addMemoryis called with the expected{prompt, thought, response}shape.query_raw_memories({query: 'test', sessionId: <current>})→ expect 3-5 matches (one per turn).Interaction with Other Tickets
add_memorycalls from this hook start flowing into the enhanced graph; no changes needed here.Implementation Note
Recommended to execute in a fresh Claude Code session with a clean context window — transcript parsing correctness requires careful testing and the Node script should be written from a blank slate, not reactively against this session's accumulated context. Ticket-intake should confirm fresh-session before execution.
Origin Session ID: f018be49-cbf9-4f99-9119-2510fc5bb8d8 Discovered during: #10059 / PR #10060 → #10061 / PR #10062 → architectural challenge by tobiu Depends on: PR #10062 merged (Gate 3 in effect)