The mechanisms you can use to make memory routing deterministic.
Rules in .claude/rules/ with paths: frontmatter are mechanically enforced by the harness. They re-inject every time Claude accesses a matching file — unlike CLAUDE.md triggers which depend on Claude "remembering."
# .claude/rules/api-conventions.md
---
paths:
- "src/api/**/*.ts"
- "src/routes/api/**"
---
# API Rules
- All endpoints must validate input at the handler level
- Use typed error responses, never raw strings
- Read memory/api-patterns.md for the full conventions
# .claude/rules/deploy-safety.md
---
paths:
- "**/deploy*"
- "**/Dockerfile"
- "**/.github/workflows/**"
- "**/*.service"
---
# Deploy Rules
- Read memory/deploy-guide.md before any deploy changes
- Use systemctl for systemd services — never manual kill/start
Rules without paths: frontmatter load unconditionally at session start. Use sparingly — they consume context every session.
The most useful hooks for memory management:
| Event | Use for Memory | Can Block? |
|---|---|---|
SessionStart | Inject rules + context at every session start (including after compaction) | No |
PreToolUse | Inject context before specific tools run | Yes |
PostToolUse | Inject context after tool results | No |
FileChanged | React when watched files change on disk | No |
InstructionsLoaded | Track which instruction files loaded and why | No |
// ~/.claude/settings.json
{
"hooks": {
"SessionStart": [{
"matcher": "startup|resume|compact",
"hooks": [{
"type": "command",
"command": "/path/to/session-context.sh",
"timeout": 5
}]
}]
}
}
#!/bin/bash
# session-context.sh — inject always-on rules + git state
RULES="## Session Context (auto-injected)
### Always-On Rules
1. One fix at a time — only the approved change, no extras
2. Verify claims — label uncertainty, offer to verify before acting
3. Evidence-first debugging — cite logs/timestamps, not speculation
4. No magic strings — use constants or enums for property checks"
GIT_INFO=""
if git rev-parse --git-dir >/dev/null 2&1; then
BRANCH=$(git branch --show-current 2>/dev/null)
RECENT=$(git log --oneline -3 2>/dev/null)
GIT_INFO="
### Git State
Branch: $BRANCH
Recent:
$RECENT"
fi
jq -n --arg ctx "$RULES$GIT_INFO" '{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": $ctx
}
}'
| Field | Effect |
|---|---|
additionalContext | Injected into Claude's context |
initialUserMessage | Sets the first user message |
watchPaths | Array of absolute file paths to monitor for FileChanged events |
| Variable | Available In | Value |
|---|---|---|
CLAUDE_PROJECT_DIR | All hooks | Real repo root (stable) |
CLAUDE_ENV_FILE | SessionStart, CwdChanged, FileChanged | Path to temp .sh file — write exports here to persist env vars across the session |
Hooks receive JSON on stdin:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "SessionStart",
"source": "startup" // or "resume", "compact"
}
Exit codes: 0 = success (stdout parsed as JSON), 2 = block action (stderr shown to Claude), other = non-blocking error.
if Field (for Tool Hooks)Pre-execution filter using permission rule syntax. Avoids spawning the hook process for non-matching commands:
{
"type": "command",
"command": "/path/to/check-git.sh",
"if": "Bash(git *)" // only fires for git commands
}
Only works for tool events (PreToolUse, PostToolUse, etc.), not SessionStart.
Give Claude an explicit search_memories() tool:
| Server | Backend | Privacy |
|---|---|---|
@modelcontextprotocol/server-memory | JSONL files | Local |
memorious-mcp | ChromaDB | Local |
mcp-server-qdrant | Qdrant | Local/cloud |
mcp-memory-service | Knowledge graph | Local |
// ~/.claude/.mcp.json
{
"mcpServers": {
"memory-search": {
"command": "uvx",
"args": ["memorious-mcp"],
"env": { "MEMORIOUS_DB_PATH": "~/.claude/semantic-memory" }
}
}
}