Skip to content

Hooks

MindStone for Claude Code runs entirely via four Claude Code hooks. Understanding how each works helps you customize the system and debug issues.

HookFileWhen it firesWhat it does
SessionStartsession_start.pyClaude Code session opens (or resumes after compaction)Injects identity + memory context; replays the post-compaction handoff
UserPromptSubmituser_prompt_submit.pyUser sends a messageInjects relevant semantic recall; danger-zone handoff directive
Stopsession_end.pyA turn completesArchives the transcript + increments memory hit counts (does not embed)
PreCompactpre_compact.pyBefore context compactionArchives the transcript + refreshes the .handoff.md tail

Hooks are registered in ~/.claude/settings.json by the bootstrap script.

File: orchestrator/hooks/session_start.py

Fires: At the start of every Claude Code session, regardless of which directory is open.

What it injects:

  1. IDENTITY.md content (always)
  2. USER.md content (always)
  3. Critical memory files (files with critical: true in frontmatter — always injected)
  4. 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.toml maps the current directory to a project tag
  5. 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.

File: orchestrator/hooks/user_prompt_submit.py

Fires: When the user submits a message (before Claude Code processes it).

What it does:

  1. Embeds the user’s message using the configured embedding model (Ollama / nomic-embed-text running locally by default)
  2. Queries the SQLite-vec store for top-K semantically similar memory and transcript chunks
  3. Applies SCRI weighting (hits, prevented, recency)
  4. Injects the ranked results as additional context before the message is processed
  5. 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.

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:

  1. Archives the live session transcript to orchestrator/transcripts/ (so nothing about to be compacted is lost)
  2. Refreshes a ## RECENT TAIL section in .handoff.md — the continuity note that session_start.py replays 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.

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:

  1. Locates the most recent session transcript (JSONL from Claude Code’s session logs)
  2. Copies it to orchestrator/transcripts/
  3. Increments hits and last_applied on 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.

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.