Hooks
MindStone for Claude Code runs entirely via four Claude Code hooks. Understanding how each works helps you customize the system and debug issues.
Hook overview
Section titled “Hook overview”| Hook | File | When it fires | What it does |
|---|---|---|---|
SessionStart | session_start.py | Claude Code session opens (or resumes after compaction) | Injects identity + memory context; replays the post-compaction handoff |
UserPromptSubmit | user_prompt_submit.py | User sends a message | Injects relevant semantic recall; danger-zone handoff directive |
Stop | session_end.py | A turn completes | Archives the transcript + increments memory hit counts (does not embed) |
PreCompact | pre_compact.py | Before context compaction | Archives the transcript + refreshes the .handoff.md tail |
Hooks are registered in ~/.claude/settings.json by the bootstrap script.
SessionStart
Section titled “SessionStart”File: orchestrator/hooks/session_start.py
Fires: At the start of every Claude Code session, regardless of which directory is open.
What it injects:
IDENTITY.mdcontent (always)USER.mdcontent (always)- Critical memory files (files with
critical: truein frontmatter — always injected) - Top-K weighted memory files by SCRI score (the most relevant memories given current context), including a project-match boost when
orchestrator/config/project_hints.tomlmaps the current directory to a project tag - Tail of
LOG.md(recent session log entries — provides continuity context)
Post-compaction replay: When the session start is triggered by a compaction (source == "compact"), the hook additionally replays the .handoff.md continuity note written by pre_compact.py and kicks off a deferred background embed so the just-compacted transcript gets vectorized without blocking the resumed session.
Output format:
The hook outputs JSON that Claude Code reads as pre-session context:
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "[full injected content as markdown]" }}Customization: Edit the TOP_K_MEMORIES constant and CRITICAL_ALWAYS_INJECT list in session_start.py to change how many memories are injected and which are always included.
UserPromptSubmit
Section titled “UserPromptSubmit”File: orchestrator/hooks/user_prompt_submit.py
Fires: When the user submits a message (before Claude Code processes it).
What it does:
- Embeds the user’s message using the configured embedding model (Ollama /
nomic-embed-textrunning locally by default) - Queries the SQLite-vec store for top-K semantically similar memory and transcript chunks
- Applies SCRI weighting (hits, prevented, recency)
- Injects the ranked results as additional context before the message is processed
- When context usage approaches the danger zone (~85%), also emits a handoff directive instructing the orchestrator to write a continuity note before the window fills
This is the autoRecall mechanism — it makes relevant past experience available before Claude Code generates a response, without the user having to explicitly ask.
Performance: The hook adds ~200–400ms latency per message (embedding call + vector query). This is the main performance cost of MS4CC.
Fallback: If the embedding call fails or the vector store is unavailable, the hook fails gracefully — the message is processed without semantic recall, with an error noted in the hook output.
PreCompact
Section titled “PreCompact”File: orchestrator/hooks/pre_compact.py
Fires: Before Claude Code compacts the context window.
What it does:
PreCompact is the compaction-handoff linchpin. At the compaction cliff it:
- Archives the live session transcript to
orchestrator/transcripts/(so nothing about to be compacted is lost) - Refreshes a
## RECENT TAILsection in.handoff.md— the continuity note thatsession_start.pyreplays after compaction completes
The pair (pre_compact.py writes the handoff, session_start.py replays it on source == "compact") is how a session survives a context-window compaction with continuity intact, without requiring the user to do anything.
Relationship to /checkpoint: PreCompact preserves continuity mechanically across a single compaction. It does not run the Dream Cycle. The full synthesis — judging what to preserve, updating IDENTITY.md and memory files, and embedding transcripts — happens at /checkpoint, which requires the orchestrator’s active participation and can’t be fully automated without losing the quality of the synthesis.
Stop (session_end.py)
Section titled “Stop (session_end.py)”File: orchestrator/hooks/session_end.py
Fires: When a turn completes (Claude Code’s Stop hook fires per turn, not only at session end).
What it does — archive only:
- Locates the most recent session transcript (JSONL from Claude Code’s session logs)
- Copies it to
orchestrator/transcripts/ - Increments
hitsandlast_appliedon memory files that were cited during the turn (auto-updating SCRI weights)
It does NOT embed. Earlier versions chunked and embedded on every Stop — but per-turn embedding pegged the local embedder, so embedding was moved out of the Stop hook entirely. The transcript vectorize and memory reindex happen at /checkpoint (the indexer run with CAIRN_CHECKPOINT_MODE=1), not per turn.
Why archive at Stop: Archiving every turn means the on-disk transcript record is always current, so /checkpoint and the post-compaction background embed always have the full session to draw from. The expensive vectorization is deferred to checkpoint time.
Registering hooks manually
Section titled “Registering hooks manually”If the bootstrap script can’t automatically merge settings.json (e.g., no jq), add the hooks manually:
{ "hooks": { "SessionStart": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 /path/to/orchestrator/hooks/session_start.py" } ] } ], "UserPromptSubmit": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 /path/to/orchestrator/hooks/user_prompt_submit.py" } ] } ], "PreCompact": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 /path/to/orchestrator/hooks/pre_compact.py" } ] } ], "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 /path/to/orchestrator/hooks/session_end.py" } ] } ] }}Replace /path/to/orchestrator with the absolute path to your orchestrator directory.