diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md
index 1ae659a..f415bbd 100644
--- a/docs/USER_GUIDE.md
+++ b/docs/USER_GUIDE.md
@@ -29,6 +29,12 @@ connect clients to it, and use it day to day. If you want to work on the code it
- [Claude (CLAUDE.md)](#claude-claudemd)
- [ChatGPT (custom instructions)](#chatgpt-custom-instructions)
- [Other agents (AGENTS.md and similar)](#other-agents-agentsmd-and-similar)
+- [Automating memory with Claude Code hooks](#automating-memory-with-claude-code-hooks)
+ - [Prerequisites](#hooks-prerequisites)
+ - [Recall: inject memories on every prompt](#recall-inject-memories-on-every-prompt)
+ - [Capture: save the conversation at session end](#capture-save-the-conversation-at-session-end)
+ - [Wiring the hooks up](#wiring-the-hooks-up)
+ - [Hooks vs. prompting](#hooks-vs-prompting)
- [REST API reference](#rest-api-reference)
- [Backups and restore](#backups-and-restore)
- [Health and monitoring](#health-and-monitoring)
@@ -696,6 +702,227 @@ summary](./prompts/auto-capture.md), [research synthesis](./prompts/research-syn
[meeting synthesis](./prompts/meeting-synthesis.md). They're documentation only (no server changes)
and drive the same six tools.
+## Automating memory with Claude Code hooks
+
+[Prompting agents to use memory](#prompting-agents-to-use-memory) makes the memory tools
+*available* and *asks* Claude to use them — but the model still has to remember to. [Claude
+Code hooks](https://code.claude.com/docs/en/hooks-guide) close that gap: they run shell
+commands at fixed points in the request/response lifecycle, so memory recall and capture
+happen **deterministically**, every time, without relying on the model to choose to call a
+tool.
+
+Two hooks give you the loop the model can't be trusted to run on its own:
+
+- **`UserPromptSubmit`** fires right after you submit a prompt, before Claude sees it. A hook
+ here searches memserv for memories relevant to what you just asked and injects them into
+ Claude's context — "recall first," enforced.
+- **`SessionEnd`** fires when the conversation ends. A hook here ships the transcript to
+ memserv's REST `add` endpoint, whose LLM extracts the durable facts — "save what mattered,"
+ enforced.
+
+Both hooks talk to memserv over the **REST API** with the bearer token, completely independent
+of the MCP connector. They work whether or not you've run `claude mcp add`; if you have, the
+hook-injected recall and the six MCP tools happily coexist (the hook front-loads context, the
+tools let Claude read/write on demand mid-task).
+
+
+### Prerequisites
+
+- `curl` and [`jq`](https://jqlang.github.io/jq/) on your `PATH` (the scripts parse JSON with
+ `jq`).
+- Two environment variables exported in the shell that launches `claude`, so the hooks inherit
+ them:
+
+ ```bash
+ export MEM0_URL=https://mem0.your-domain.com # base URL, no /api or /mcp suffix
+ export MEM0_API_KEY=... # the same bearer token the server uses
+ ```
+
+ Put these in your shell profile (`~/.bashrc`, `~/.zshrc`) so every session has them. The
+ token lives in your environment and, for capture, the conversation is sent to *your* memory
+ server — fine for a self-hosted single-user store, but keep the usual "don't put secrets in
+ memory" discipline (see the recall/capture notes below).
+
+Save both scripts under `.claude/hooks/` (per-project) or `~/.claude/hooks/` (global), and make
+them executable with `chmod +x`.
+
+
+### Recall: inject memories on every prompt
+
+`.claude/hooks/mem0-recall.sh` — reads the prompt off stdin, semantically searches memserv, and
+prints the top matches back as `additionalContext`. Anything a `UserPromptSubmit` hook writes to
+stdout (or returns via `additionalContext`) is added to Claude's context for that turn. It
+**never blocks the prompt**: on any error, or if memserv is unreachable, it exits quietly and
+Claude proceeds as normal.
+
+```bash
+#!/usr/bin/env bash
+# mem0-recall.sh — Claude Code UserPromptSubmit hook.
+# Search memserv for memories relevant to the prompt and inject them into
+# Claude's context before it answers. Never blocks the prompt.
+set -euo pipefail
+
+INPUT=$(cat)
+PROMPT=$(printf '%s' "$INPUT" | jq -r '.prompt // empty')
+
+# Nothing to search on, or config missing → stay silent.
+[ -z "$PROMPT" ] && exit 0
+if [ -z "${MEM0_URL:-}" ] || [ -z "${MEM0_API_KEY:-}" ]; then exit 0; fi
+
+# Semantic search; --max-time keeps us well under the 30s UserPromptSubmit budget.
+RESPONSE=$(curl -sS --max-time 10 \
+ -X POST "$MEM0_URL/api/v1/memories/search" \
+ -H "Authorization: Bearer $MEM0_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d "$(jq -n --arg q "$PROMPT" '{query: $q, limit: 5}')") || exit 0
+
+# Format the hits as a bullet list; emit nothing if there are none.
+CONTEXT=$(printf '%s' "$RESPONSE" | jq -r '
+ (.results // []) | map(select(.memory))
+ | if length == 0 then empty
+ else "Relevant long-term memories from mem0 (use if helpful, ignore if not):\n"
+ + (map("- " + .memory) | join("\n"))
+ end' 2>/dev/null) || exit 0
+
+[ -z "$CONTEXT" ] && exit 0
+
+jq -n --arg ctx "$CONTEXT" \
+ '{hookSpecificOutput: {hookEventName: "UserPromptSubmit", additionalContext: $ctx}}'
+```
+
+The injected block is advisory ("use if helpful, ignore if not") so a stray semantic match
+doesn't derail the answer. Tune `limit` (how many memories to pull) to taste. To bias toward
+recent facts over the closest topical match, add `recency_weight` to the search body (see
+[Search memories](#search-memories--post-apiv1memoriessearch)); to recall by an exact term
+instead of meaning, use `"mode": "keyword"`.
+
+
+### Capture: save the conversation at session end
+
+`.claude/hooks/mem0-capture.sh` — a `SessionEnd` hook that flattens the transcript into a
+`messages` array and POSTs it to memserv. **memserv's own LLM does the fact extraction**, so the
+hook stays dumb: it doesn't decide what's worth remembering, it just hands over the conversation
+and lets the server distill it. Re-sending overlapping content across sessions is cheap —
+memserv fingerprints content and skips exact re-adds before the LLM runs (see
+[How memory works](#how-memory-works)).
+
+```bash
+#!/usr/bin/env bash
+# mem0-capture.sh — Claude Code SessionEnd (or Stop) hook.
+# Send the conversation to memserv, whose LLM extracts the durable facts.
+set -euo pipefail
+
+INPUT=$(cat)
+TRANSCRIPT=$(printf '%s' "$INPUT" | jq -r '.transcript_path // empty')
+
+if [ -z "$TRANSCRIPT" ] || [ ! -f "$TRANSCRIPT" ]; then exit 0; fi
+if [ -z "${MEM0_URL:-}" ] || [ -z "${MEM0_API_KEY:-}" ]; then exit 0; fi
+
+# Flatten the transcript JSONL into a mem0 messages array: user/assistant turns
+# and their text only (content may be a string or an array of blocks), capped at
+# the last 40 turns to bound payload and cost.
+MESSAGES=$(jq -rs '
+ [ .[]
+ | select(.message.role == "user" or .message.role == "assistant")
+ | { role: .message.role,
+ content: ( .message.content
+ | if type == "string" then .
+ else ([ .[]? | .text // empty ] | join("\n"))
+ end ) }
+ | select(.content != "")
+ ] | .[-40:]
+' "$TRANSCRIPT" 2>/dev/null) || exit 0
+
+# Empty conversation → nothing to save.
+[ "$(printf '%s' "$MESSAGES" | jq 'length')" -gt 0 ] || exit 0
+
+# agent_id is a write-only provenance tag; metadata.source follows the convention.
+curl -sS --max-time 30 \
+ -X POST "$MEM0_URL/api/v1/memories" \
+ -H "Authorization: Bearer $MEM0_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d "$(jq -n --argjson msgs "$MESSAGES" \
+ '{messages: $msgs, agent_id: "claude-code:hook", metadata: {source: "agent"}}')" \
+ >/dev/null 2>&1 || true
+
+exit 0
+```
+
+Notes and tradeoffs:
+
+- **`SessionEnd` fires on a graceful end** — `/clear`, `logout`, or exiting the prompt — but not
+ if the terminal is force-killed. If you want capture after *every* turn instead of once per
+ conversation, register the same script on the **`Stop`** event (fires each time Claude finishes
+ responding). That's more thorough but runs fact-extraction far more often.
+- **Cost.** Each capture that contains new content triggers one LLM fact-extraction call on
+ memserv. The dedup fingerprint makes re-runs over unchanged content free, but a fresh
+ conversation is genuinely new work. The 40-turn cap bounds the payload; lower it for tighter
+ cost control.
+- **`agent_id: "claude-code:hook"`** tags these writes for provenance. It's write-only — it does
+ **not** scope future searches (every client shares one store) — but it lets you later find or
+ bulk-delete hook-captured memories via the REST filters.
+- **Secrets.** The hook ships the whole (tailed) conversation to memserv, and memserv's LLM may
+ distill anything in it into a memory. Don't paste credentials into a session you're
+ auto-capturing.
+
+
+### Wiring the hooks up
+
+Register both in a Claude Code [settings file](https://code.claude.com/docs/en/hooks-guide) —
+`.claude/settings.json` in the project root (shareable, commit it) or `~/.claude/settings.json`
+(applies everywhere). Use `$CLAUDE_PROJECT_DIR` for project-scoped scripts, or an absolute path
+like `~/.claude/hooks/...` for global ones:
+
+```json
+{
+ "hooks": {
+ "UserPromptSubmit": [
+ {
+ "hooks": [
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/mem0-recall.sh" }
+ ]
+ }
+ ],
+ "SessionEnd": [
+ {
+ "hooks": [
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/mem0-capture.sh" }
+ ]
+ }
+ ]
+ }
+}
+```
+
+Verify with `/hooks` inside Claude Code (it lists registered hooks per event). If a hook doesn't
+fire, confirm the scripts are executable, that `MEM0_URL`/`MEM0_API_KEY` are exported in the
+launching shell, and that `jq`/`curl` are installed; then run `claude --debug-file /tmp/claude.log`
+and check the log for the hook's exit code, stdout, and stderr. You can sanity-check a script
+outside Claude Code by piping it a sample payload:
+
+```bash
+echo '{"prompt": "where do we deploy?"}' | .claude/hooks/mem0-recall.sh
+```
+
+
+### Hooks vs. prompting
+
+Hooks and the [prompt-based approach](#prompting-agents-to-use-memory) solve the same problem —
+"actually use memory" — from opposite ends, and combine well:
+
+| | Hooks (this section) | Prompting / MCP tools |
+|---|---|---|
+| **Runs** | Deterministically, every time | When the model decides to |
+| **Recall** | Injects top-K matches for *every* prompt | Model searches when it judges it useful |
+| **Capture** | Ships the raw conversation; memserv's LLM distills it | Model chooses *what* to save, as clean single-facts |
+| **Setup** | `curl`/`jq` scripts + `settings.json` | A paragraph in `CLAUDE.md` |
+
+A good middle ground: use the **recall hook** (deterministic front-loading beats hoping the model
+searches) alongside **prompt-based capture** via the
+[auto-capture prompt pack](./prompts/auto-capture.md) (the model writes cleaner, more selective
+single-fact memories than a raw transcript dump). Or run all four for belt-and-suspenders. They
+read and write the same shared store, so nothing conflicts.
+
## REST API reference
All endpoints live under `/api/v1` and require `Authorization: Bearer `. Request and