Hooks
What total-agent-memory runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add vbcherepanov/total-agent-memory > /plugin install total-agent-memory@vbcherepanov
Ships with total-agent-memory. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
- Matches
startup|resume|clear${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt-submit.sh
PreToolUse
- Matches
Edit|Write|MultiEdit|NotebookEdit${CLAUDE_PLUGIN_ROOT}/hooks/pre-edit.sh
PostToolUse
- Matches
Edit|Write|MultiEdit|NotebookEdit|Bash${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use.sh
PostToolUseFailure
- Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/on-bash-error.sh
Stop
${CLAUDE_PLUGIN_ROOT}/hooks/on-stop.sh
SessionEnd
${CLAUDE_PLUGIN_ROOT}/hooks/session-end.sh
Where it lives
- hooks/auto-capture.ps1GitHub
- hooks/auto-capture.shGitHub
- hooks/codex-notify.ps1GitHub
- hooks/codex-notify.shGitHub
- hooks/memory-trigger.ps1GitHub
- hooks/memory-trigger.shGitHub
- hooks/on-bash-error.ps1GitHub
- hooks/on-bash-error.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # PostToolUse hook for Bash — v7.0 learn_error trigger # # Fires on non-zero bash exit with a distinguishable root cause. # Emits a reminder to call learn_error(...) — N≥3 similar patterns # auto-consolidate into a rule on the MCP side. # =========================================== source "$(dirname "$0")/lib/common.sh" TOOL=$(hook_get '.tool_name') [ "$TOOL" != "Bash" ] && exit 0 EXIT_CODE=$(hook_get '.tool_response.exit_code') # Empty or zero → no error, skip [ -z "$EXIT_CODE" ] || [ "$EXIT_CODE" = "0" ] && exit 0 COMMAND=$(hook_get '.tool_input.command' | head -c 200) STDERR=$(hook_get '.tool_response.stderr' | head -c 500) # Skip noise: user aborts, interactive prompts, benign warnings case "$STDERR" in *"permission denied by user"*|*"User denied"*|*"SIGINT"*) exit 0 ;; esac # Only react if stderr has actionable signal [ -z "$STDERR" ] && exit 0 cat <<EOF <system-reminder> v7.0 learn_error trigger: bash exited $EXIT_CODE. If the root cause is reproducible and fixable, call: learn_error( file="<path if relevant>", error="<short stderr>", root_cause="<what actually failed>", fix="<what resolves it>", pattern="<short slug, e.g. sqlite-locked-during-ddl>" ) Skip if this is user-aborted, interactive, or benign. After N≥3 same patterns it auto-consolidates into a rule — do not re-log if you just fixed it and the root cause is identical to an earlier call this turn. </system-reminder> EOF exit 0 - hooks/on-stop.ps1GitHub
- hooks/on-stop.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # On Stop Hook — Portable version # # Saves session context when Claude stops # (context limit, error, user stop, etc.) # # 1. Saves git state + context to recovery file # 2. Reminds about saving knowledge # 3. Cleans old recovery files # # Hook: Stop (matcher: "") # =========================================== source "$(dirname "$0")/lib/common.sh" source "$(dirname "$0")/lib/memory-nudge.sh" STOP_ACTIVE=$(hook_get 'stop_hook_active') [ "$STOP_ACTIVE" = "true" ] && exit 0 TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') DATE_SHORT=$(date '+%Y%m%d-%H%M%S') CTX=$(hook_context) CWD=$(hook_get 'cwd') [ -z "$CWD" ] && CWD="$PWD" PROJECT=$(basename "$CWD") SESSION_ID=$(hook_get 'session_id') [ -z "$SESSION_ID" ] && SESSION_ID="${CLAUDE_SESSION_ID:-unknown}" # Recovery directory mkdir -p "$HOOK_RECOVERY_DIR" # Collect git context for recovery (read-only commands only) GIT_BRANCH="" GIT_STATUS="" GIT_RECENT="" if [ -d "$CWD/.git" ] || git -C "$CWD" rev-parse --git-dir >/dev/null 2>&1; then GIT_BRANCH=$(cd "$CWD" 2>/dev/null && git rev-parse --abbrev-ref HEAD 2>/dev/null) GIT_STATUS=$(cd "$CWD" 2>/dev/null && git status --short 2>/dev/null | head -20) GIT_RECENT=$(cd "$CWD" 2>/dev/null && git log --oneline -5 2>/dev/null) fi # Save recovery file RECOVERY_FILE="$HOOK_RECOVERY_DIR/pending-${DATE_SHORT}.md" cat > "$RECOVERY_FILE" <<EOF # Session Recovery - ${TIMESTAMP} ## Context - **Project**: ${PROJECT} - **Path**: ${CWD} - **Branch**: ${GIT_BRANCH:-N/A} - **Stopped**: ${TIMESTAMP} - **Reason**: Session stopped (likely context limit) ## Git State ### Modified files: \`\`\` ${GIT_STATUS:-No git changes detected} \`\`\` ### Recent commits: \`\`\` ${GIT_RECENT:-No recent commits} \`\`\` ## Recovery Action 1. Read this file to understand what was being worked on 2. Use memory_recall() to find related knowledge 3. Ask user what needs to be continued 4. Save any unrecovered knowledge to memory EOF # Keep only last 5 recovery files ls -t "$HOOK_RECOVERY_DIR"/pending-*.md 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null # Notify user hook_notify "$CTX | Stopped - context saved for recovery" "Claude Memory | Recovery" "Basso" hook_log "STOPPED: $CTX - recovery saved to $RECOVERY_FILE" echo "Session stopped at $TIMESTAMP" echo "Recovery context saved to: $RECOVERY_FILE" echo "" # Emit nudge summary based on this session's write/save counters. # Suppressed when nothing relevant happened (writes==0 and saves==0). if [ "${MEMORY_NUDGE_DISABLE:-0}" != "1" ]; then SUMMARY_LINE=$(nudge_summary "$SESSION_ID" "$PROJECT") if [ -n "$SUMMARY_LINE" ]; then echo "$SUMMARY_LINE" echo "" fi fi echo "MEMORY_WARNING: Session ending. Before closing:" echo " 1. Save important knowledge with memory_save(project=\"${PROJECT}\")" echo " 2. Record a reflection: self_reflect(reflection=\"...\", task_summary=\"...\", project=\"${PROJECT}\")" echo "" echo "IMPORTANT: Session context was auto-saved for recovery." echo "On next session, pending knowledge will be restored." # Prune nudge state files older than 7 days to keep the dir small. find "$NUDGE_STATE_DIR" -name "nudge-*.json" -mtime +7 -delete 2>/dev/null - hooks/post-tool-use.ps1GitHub
- hooks/post-tool-use.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # PostToolUse Hook — observation capture + memory_save nudges # # Runs AFTER a tool call. Two responsibilities: # # 1. (opt-in) when MEMORY_POST_TOOL_CAPTURE=1, enqueue a deferred # observation for the extractor. # 2. (always-on) bump per-session counters (writes / edits / saves) # and, when the writes-without-saves ratio crosses a threshold, # echo a nudge line to stdout so Claude sees it on the next turn. # This addresses the "model never calls memory_save on its own" # pattern (reported 2026-05-14 by client running Sonnet). # # Env: # MEMORY_POST_TOOL_CAPTURE — "1" to enable observation capture # MEMORY_NUDGE_DISABLE — "1" to disable nudges entirely # MEMORY_NUDGE_SOFT/HARD/STEP — tune thresholds (see memory-nudge.sh) # CLAUDE_MEMORY_INSTALL_DIR — install root (auto-resolved) # CLAUDE_MEMORY_DIR — memory storage (~/.claude-memory) # # Hook: PostToolUse (matcher: "*") # =========================================== CLAUDE_MEMORY_INSTALL_DIR="${CLAUDE_MEMORY_INSTALL_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)}" CLAUDE_MEMORY_DIR="${CLAUDE_MEMORY_DIR:-$HOME/.claude-memory}" HOOK_PYTHON="${CLAUDE_MEMORY_INSTALL_DIR}/.venv/bin/python" if [ ! -x "$HOOK_PYTHON" ]; then HOOK_PYTHON="python3" fi SRC_DIR="${CLAUDE_MEMORY_INSTALL_DIR}/src" # Cache stdin so the background process can read it after this shell exits. TMP_INPUT="$(mktemp -t cmm-pthook.XXXXXX)" cat > "$TMP_INPUT" # ---------- Nudge counter + emission (synchronous, in-shell) ---------- # Runs BEFORE the opt-in capture exit so it fires unconditionally. The # python below is short and reads only the cached temp file — no DB I/O, # no network, well under 50ms typical. if [ "${MEMORY_NUDGE_DISABLE:-0}" != "1" ]; then NUDGE_LINE=$( "$HOOK_PYTHON" - "$TMP_INPUT" "$CLAUDE_MEMORY_DIR" <<'PY' 2>/dev/null import json, os, sys, time, pathlib tmp_path = sys.argv[1] memory_dir = sys.argv[2] try: data = json.loads(pathlib.Path(tmp_path).read_text() or "{}") except Exception: sys.exit(0) tool = (data.get("tool_name") or "").strip() if not tool: sys.exit(0) sid_raw = data.get("session_id") or os.environ.get("CLAUDE_SESSION_ID") or "unknown" sid = "".join(c if c.isalnum() or c in "._-" else "_" for c in sid_raw) project_cwd = data.get("cwd") or os.getcwd() project = os.path.basename(project_cwd) or "unknown" state_dir = pathlib.Path(memory_dir) / "state" state_dir.mkdir(parents=True, exist_ok=True) state_path = state_dir / f"nudge-{sid}.json" try: state = json.loads(state_path.read_text()) except Exception: state = {"session_id": sid_raw, "started_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "writes": 0, "edits": 0, "bashes": 0, "memory_saves": 0, "last_nudge_writes": 0, "nudge_count": 0} # Classify the tool. memory_save fires for any MCP tool whose name # contains "memory_save" or the dedicated save_decision/save_intent # entrypoints — covers all three save flavours. field = None t_lower = tool.lower() if "memory_save" in t_lower or t_lower.endswith("save_decision") or t_lower.endswith("save_intent"): field = "memory_saves" elif tool in ("Edit", "MultiEdit") or t_lower.endswith("__edit"): field = "edits" elif tool == "Write" or t_lower.endswith("__write"): field = "writes" elif tool == "Bash" or t_lower.endswith("__bash"): field = "bashes" if field: state[field] = int(state.get(field, 0)) + 1 state_path.write_text(json.dumps(state)) writes_total = int(state.get("writes", 0)) + int(state.get("edits", 0)) saves = int(state.get("memory_saves", 0)) last = int(state.get("last_nudge_writes", 0)) SOFT = int(os.environ.get("MEMORY_NUDGE_SOFT", "3")) HARD = int(os.environ.get("MEMORY_NUDGE_HARD", "7")) STEP = int(os.environ.get("MEMORY_NUDGE_STEP", "3")) # Don't nudge on save/bash events themselves — only after write/edit. if field not in ("edits", "writes"): sys.exit(0) # Recently saved → back off until divergence grows again. if saves > 0 and (writes_total - last) < STEP * 2: sys.exit(0) if writes_total < SOFT: sys.exit(0) # Hard threshold escalation always fires (after one prior soft nudge): # the throttle should never prevent the urgent message. escalating = writes_total >= HARD and saves == 0 and last < HARD if not escalating and writes_total - last < STEP: sys.exit(0) if writes_total >= HARD and saves == 0: msg = ( f"MEMORY_NUDGE [hard]: {writes_total} significant edits this session, " f"0 memory_save calls. Save decisions/solutions NOW while context is " f"fresh: memory_save(content=..., type='decision'|'solution', " f"project='{project}', tags=['reusable', ...]). " f"Skipping saves is the #1 cause of session amnesia." ) elif saves == 0: msg = ( f"MEMORY_NUDGE [soft]: {writes_total} edits without memory_save. " f"When the next decision/fix is finalized, call " f"memory_save(project='{project}'). Don't batch for end of session." ) else: msg = ( f"MEMORY_NUDGE: {writes_total} writes vs {saves} saves. " f"If a non-trivial new fact landed, memory_save now while it's hot." ) print(msg) state["last_nudge_writes"] = writes_total state["nudge_count"] = int(state.get("nudge_count", 0)) + 1 state_path.write_text(json.dumps(state)) PY ) if [ -n "$NUDGE_LINE" ]; then echo "$NUDGE_LINE" fi fi # Opt-in guard — observation capture only runs when explicitly enabled. if [ "${MEMORY_POST_TOOL_CAPTURE:-0}" != "1" ]; then rm -f "$TMP_INPUT" 2>/dev/null exit 0 fi ( "$HOOK_PYTHON" -c ' import json, os, sys from pathlib import Path src_dir = sys.argv[1] memory_dir = sys.argv[2] tmp = sys.argv[3] if src_dir not in sys.path: sys.path.insert(0, src_dir) os.environ.setdefault("CLAUDE_MEMORY_DIR", memory_dir) try: raw = Path(tmp).read_text() except - hooks/pre-edit.ps1GitHub
- hooks/pre-edit.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # PreToolUse hook for Write|Edit — v7.0 file_context guard # # Emits a reminder to call file_context(path) BEFORE editing a file. # The agent then calls the tool itself and reads warnings/risk_score. # =========================================== source "$(dirname "$0")/lib/common.sh" TOOL=$(hook_get '.tool_name') FILE_PATH=$(hook_get '.tool_input.file_path') # Only guard Write/Edit, not NotebookEdit case "$TOOL" in Write|Edit) ;; *) exit 0 ;; esac [ -z "$FILE_PATH" ] && exit 0 # Skip trivial / dotfile / small paths case "$FILE_PATH" in */.git/*|*/node_modules/*|*/.venv/*|/tmp/*) exit 0 ;; esac cat <<EOF <system-reminder> v7.0 pre-edit guard: before editing \`$FILE_PATH\`, call file_context(path="$FILE_PATH") If risk_score > 0.3, read the returned warnings (past errors / hot spots) and incorporate them into the edit. Skip if file_context was already called for this path in the current turn. </system-reminder> EOF exit 0 - hooks/session-end.ps1GitHub
- hooks/session-end.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # Session End Hook — Portable version # # 1. Saves recovery context from transcript # 2. Calls auto_session_save.py for context preservation # 3. Calls auto_episode_capture.py for episode capture # 4. Cleans old recovery files # # Hook: SessionEnd (matcher: "") # =========================================== source "$(dirname "$0")/lib/common.sh" REASON=$(hook_get 'reason') CTX=$(hook_context) TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') DATE_SHORT=$(date '+%Y%m%d-%H%M%S') CWD=$(hook_get 'cwd') [ -z "$CWD" ] && CWD="$PWD" PROJECT=$(basename "$CWD") # Human-readable reason case "$REASON" in clear) REASON_TEXT="User ran /clear" ;; compact) REASON_TEXT="User ran /compact" ;; logout) REASON_TEXT="User logged out" ;; prompt_input_exit) REASON_TEXT="User exited" ;; bypass_permissions_disabled) REASON_TEXT="Permissions changed" ;; *) REASON_TEXT="Session ended ($REASON)" ;; esac # Recovery directory mkdir -p "$HOOK_RECOVERY_DIR" # Try to extract info from transcript TRANSCRIPT=$(hook_get 'transcript_path') TRANSCRIPT_SUMMARY="" LAST_USER_MSGS="" LAST_ASSISTANT="" if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then LINES=$(wc -l < "$TRANSCRIPT" 2>/dev/null | tr -d ' ') # Extract last user messages and assistant context via Python EXTRACTED=$("$HOOK_PYTHON" -c " import json, sys transcript = '$TRANSCRIPT' user_msgs = [] assistant_msgs = [] try: with open(transcript) as f: for line in f: try: d = json.loads(line.strip()) role = d.get('role', '') content = d.get('content', '') if isinstance(content, list): text = ' '.join(c.get('text', '') for c in content if c.get('type') == 'text') elif isinstance(content, str): text = content else: continue if not text.strip(): continue if role == 'user' and d.get('type') == 'human': user_msgs.append(text.strip()[:500]) elif role == 'assistant': assistant_msgs.append(text.strip()[:500]) except: continue # Output last 5 user messages print('USER_MSGS_START') for msg in user_msgs[-5:]: print(msg) print('USER_MSGS_END') print('ASSISTANT_START') for msg in assistant_msgs[-3:]: print(msg[:2000]) print('ASSISTANT_END') except: pass " 2>/dev/null) LAST_USER_MSGS=$(echo "$EXTRACTED" | sed -n '/^USER_MSGS_START$/,/^USER_MSGS_END$/p' | sed '1d;$d') LAST_ASSISTANT=$(echo "$EXTRACTED" | sed -n '/^ASSISTANT_START$/,/^ASSISTANT_END$/p' | sed '1d;$d' | head -c 2000) TRANSCRIPT_SUMMARY="~${LINES} transcript events" # Save transcript recovery with context if [ -n "$LAST_USER_MSGS" ] || [ -n "$LAST_ASSISTANT" ]; then RECOVERY_FILE="$HOOK_RECOVERY_DIR/pending-${DATE_SHORT}.md" cat > "$RECOVERY_FILE" <<EOF # Session Recovery - ${TIMESTAMP} ## Context - **Project**: ${PROJECT} - **Path**: ${CWD} - **Reason**: ${REASON_TEXT} - **Transcript**: ${TRANSCRIPT_SUMMARY} ## Last User Requests ${LAST_USER_MSGS:-No user messages extracted} ## Last Assistant Context ${LAST_ASSISTANT:-No assistant content extracted} ## Recovery Action 1. Review what was being discussed 2. Use memory_recall() to find related knowledge 3. Continue where left off or save summary to memory EOF hook_log "SESSION_END: Recovery saved to $RECOVERY_FILE" fi fi # Keep only last 5 recovery files ls -t "$HOOK_RECOVERY_DIR"/pending-*.md 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null # ======= AUTO-SAVE SESSION CONTEXT (via auto_session_save.py) ======= if [ -n "$LAST_USER_MSGS" ]; then hook_run_script "auto_session_save.py" \ --project "$PROJECT" \ --cwd "$CWD" \ --reason "$REASON_TEXT" \ --user-context "$LAST_USER_MSGS" \ --assistant-context "$(echo "$LAST_ASSISTANT" | head -c 1000)" hook_log "AUTO_SESSION_SAVE: Started for project $PROJECT" fi # ======= AUTO EPISODE CAPTURE (via auto_episode_capture.py) ======= EXTRACT_SESSION_ID="" if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then EXTRACT_SESSION_ID=$(basename "$TRANSCRIPT" .jsonl) else # Fallback: find latest transcript by CWD PROJECT_HASH=$(echo "$CWD" | sed 's|^/||;s|/|-|g') TRANSCRIPT_DIR="$HOME/.claude/projects/-${PROJECT_HASH}" FALLBACK_TRANSCRIPT=$(ls -t "$TRANSCRIPT_DIR"/*.jsonl 2>/dev/null | head -1) if [ -n "$FALLBACK_TRANSCRIPT" ] && [ -f "$FALLBACK_TRANSCRIPT" ]; then EXTRACT_SESSION_ID=$(basename "$FALLBACK_TRANSCRIPT" .jsonl) fi fi if [ -n "$EXTRACT_SESSION_ID" ]; then hook_run_script "auto_episode_capture.py" \ --session-id "$EXTRACT_SESSION_ID" \ --project "$PROJECT" hook_log "AUTO_EPISODE: Started capture for session $EXTRACT_SESSION_ID (project: $PROJECT)" fi # Build notification STATS="" [ -n "$TRANSCRIPT_SUMMARY" ] && STATS=" | $TRANSCRIPT_SUMMARY" NOTIFY_MSG="$CTX | $REASON_TEXT$STATS" hook_notify "$NOTIFY_MSG" "Claude Memory | Done" "Submarine" hook_log "SESSION_END: $NOTIFY_MSG" echo "Session ended: $TIMESTAMP ($REASON_TEXT)" echo "" echo "MEMORY_AUTO_SAVE: Session context auto-saved to recovery + memory." # ======= CLEAN UP ERROR-FIX STATE ======= if [ -d "$HOOK_STATE_DIR" ]; then rm -f "$HOOK_STATE_DIR"/last-error-* 2>/dev/null fi - hooks/session-start.ps1GitHub
- hooks/session-start.shRunsGitHub
Read the script
#!/usr/bin/env bash # =========================================== # Session Start Hook — Portable version # # 1. Prints memory recall hint for current project # 2. Checks for pending recovery files # 3. Loads SOUL rules summary # 4. Shows project knowledge count # 5. Cleans old error-fix state # # Hook: SessionStart (matcher: "") # =========================================== source "$(dirname "$0")/lib/common.sh" CWD=$(hook_get 'cwd') [ -z "$CWD" ] && CWD="$PWD" PROJECT=$(hook_project_name) BRANCH=$(hook_git_branch) MODEL=$(hook_model_short) CTX=$(hook_context) SOURCE=$(hook_get 'source') # Human-readable source case "$SOURCE" in startup) SOURCE_TEXT="New session" ;; resume) SOURCE_TEXT="Resumed" ;; clear) SOURCE_TEXT="After /clear" ;; compact) SOURCE_TEXT="After /compact" ;; *) SOURCE_TEXT="Started" ;; esac # Notification NOTIFY_MSG="$CTX | $SOURCE_TEXT | $MODEL" hook_notify "$NOTIFY_MSG" "Claude Memory | Session" "Glass" hook_log "SESSION_START: $NOTIFY_MSG" echo "Session: $NOTIFY_MSG" echo "" # ======= MEMORY HINT ======= HINT="MEMORY_HINT: Project: ${PROJECT}" [ -n "$BRANCH" ] && HINT="${HINT}, Branch: ${BRANCH}" HINT="${HINT}. Use memory_recall(query=\"your task\", project=\"${PROJECT}\") to search past knowledge." HINT="${HINT} Also run self_rules_context(project=\"${PROJECT}\") to load behavioral rules." echo "$HINT" echo "" # ======= RECOVERY CHECK ======= mkdir -p "$HOOK_RECOVERY_DIR" PENDING_FILES=$(ls -t "$HOOK_RECOVERY_DIR"/pending-*.md 2>/dev/null | head -3) if [ -n "$PENDING_FILES" ]; then RECOVERY_COUNT=$(echo "$PENDING_FILES" | wc -l | tr -d ' ') echo "RECOVERY_ALERT: Found $RECOVERY_COUNT pending recovery file(s) from previous session(s)!" echo "" # Show the most recent recovery file LATEST_RECOVERY=$(echo "$PENDING_FILES" | head -1) echo "--- LATEST RECOVERY ($LATEST_RECOVERY) ---" cat "$LATEST_RECOVERY" echo "--- END RECOVERY ---" echo "" echo "ACTION_REQUIRED: Review the recovery context above and:" echo " 1. Save any important knowledge to MCP memory (memory_save)" echo " 2. Delete recovery files after processing: rm $HOOK_RECOVERY_DIR/pending-*.md" echo "" hook_notify "Recovery files found! Check session start output." "Claude Memory | Recovery" "Sosumi" fi # ======= SOUL RULES CHECK ======= MEMORY_DB="$CLAUDE_MEMORY_DIR/memory.db" if [ -f "$MEMORY_DB" ]; then # Check active SOUL rules RULES_INFO=$("$HOOK_PYTHON" -c " import sqlite3, os db_path = '$MEMORY_DB' try: db = sqlite3.connect(db_path) # Check if rules table exists tables = [r[0] for r in db.execute(\"SELECT name FROM sqlite_master WHERE type='table'\").fetchall()] if 'rules' in tables: active = db.execute(\"SELECT COUNT(*) FROM rules WHERE status='active'\").fetchone()[0] if active > 0: print(f'SOUL_RULES: {active} active behavioral rule(s) loaded.') rows = db.execute( \"SELECT priority, substr(content, 1, 80) FROM rules WHERE status='active' ORDER BY priority DESC LIMIT 5\" ).fetchall() for p, c in rows: print(f' - [P{p}] {c}') print(f' -> Call self_rules_context(project=\"$PROJECT\") at session start') # Check error patterns if 'errors' in tables: patterns = db.execute(''' SELECT category, COUNT(*) AS cnt FROM errors WHERE status != 'insight_extracted' AND created_at > datetime('now', '-30 days') GROUP BY category HAVING cnt >= 3 ''').fetchall() if patterns: print(f'PATTERN_ALERT: {len(patterns)} error pattern(s) detected (3+ in 30 days).') for cat, cnt in patterns[:5]: print(f' - {cat} ({cnt}x)') print(' -> Call self_patterns(view=\"full_report\") to analyze') # Project knowledge summary if 'knowledge' in tables and '$PROJECT' and '$PROJECT' != os.path.basename(os.path.expanduser('~')): kcount = db.execute( \"SELECT COUNT(*) FROM knowledge WHERE project=? AND status='active'\", ('$PROJECT',) ).fetchone()[0] scount = db.execute( \"SELECT COUNT(*) FROM knowledge WHERE project=? AND status='active' AND type='solution'\", ('$PROJECT',) ).fetchone()[0] if kcount > 0: print(f'PROJECT_MEMORY: \"{PROJECT}\" has {kcount} knowledge records ({scount} solutions)') print(f' -> memory_recall(query=\"<task>\", project=\"$PROJECT\") before starting work') else: print(f'PROJECT_MEMORY: No prior knowledge for \"{PROJECT}\". Start fresh.') db.close() except Exception as e: pass " 2>/dev/null) if [ -n "$RULES_INFO" ]; then echo "" echo "$RULES_INFO" fi fi # ======= CLEAN UP OLD STATE ======= if [ -d "$HOOK_STATE_DIR" ]; then # Remove error states older than 1 hour find "$HOOK_STATE_DIR" -name "last-error-*" -mmin +60 -delete 2>/dev/null fi - hooks/user-prompt-submit.ps1GitHub
- hooks/user-prompt-submit.shRunsGitHub
All 20 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Persistent memory for your facts, decisions and working practices. Persistent, local memory for AI coding agents: Claude Code, Codex CLI, Cursor, any MCP client.
Repo: vbcherepanov/total-agent-memory

