# Hooks & MCP — How Agents Connect

> Agents connect via two surfaces: (1) MCP server exposing 53 tools over stdio or HTTP transport, and (2) shell hooks (prompt-submit, post-tool-use, session-start/end, pre-compact, stop) that fire as thin HTTP POSTs to the local REST API at :3111 — the hooks are agent-installed scripts that bridge the agent runtime event stream into the memory server without requiring code changes inside the agent.

- Repository: rohitg00/agentmemory
- GitHub: https://github.com/rohitg00/agentmemory
- Human wiki: https://grok-wiki.com/public/wiki/rohitg00-agentmemory-94f173bce1dc
- Complete Markdown: https://grok-wiki.com/public/wiki/rohitg00-agentmemory-94f173bce1dc/llms-full.txt

## Source Files

- `src/hooks/prompt-submit.ts`
- `src/hooks/post-tool-use.ts`
- `src/hooks/session-start.ts`
- `src/hooks/session-end.ts`
- `src/hooks/pre-compact.ts`
- `src/mcp/server.ts`
- `src/mcp/tools-registry.ts`
- `src/cli/connect/claude-code.ts`
- `src/triggers/api.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/hooks/prompt-submit.ts](src/hooks/prompt-submit.ts)
- [src/hooks/post-tool-use.ts](src/hooks/post-tool-use.ts)
- [src/hooks/session-start.ts](src/hooks/session-start.ts)
- [src/hooks/session-end.ts](src/hooks/session-end.ts)
- [src/hooks/pre-compact.ts](src/hooks/pre-compact.ts)
- [src/hooks/pre-tool-use.ts](src/hooks/pre-tool-use.ts)
- [src/hooks/stop.ts](src/hooks/stop.ts)
- [src/hooks/sdk-guard.ts](src/hooks/sdk-guard.ts)
- [src/hooks/subagent-start.ts](src/hooks/subagent-start.ts)
- [src/mcp/server.ts](src/mcp/server.ts)
- [src/mcp/tools-registry.ts](src/mcp/tools-registry.ts)
- [src/cli/connect/claude-code.ts](src/cli/connect/claude-code.ts)
</details>

# Hooks & MCP — How Agents Connect

Agentmemory provides two distinct integration surfaces for connecting an agent runtime to the memory server. The first is a **Model Context Protocol (MCP) server** that exposes a tool and resource catalog over HTTP transport, allowing an agent to directly call memory operations as first-class tools during its reasoning loop. The second is a **set of shell hook scripts** — thin Node.js processes installed alongside the agent — that fire as fire-and-forget HTTP POSTs to the local REST API on port 3111 whenever the agent runtime emits lifecycle events such as prompt submission, tool execution, session start, and session end.

These two surfaces serve different roles and are designed to coexist without coupling. Hooks operate passively in the background, bridging the agent's event stream into the memory server without any code change inside the agent or its prompts. MCP gives the agent explicit read/write access to memory during inference, so it can query past context or persist decisions at its own discretion. Understanding both is necessary to predict what memory is recorded, when it is available, and what would break if either surface were disabled.

---

## The Hook Layer

### How Hooks Work

Every hook is a standalone compiled `.mjs` script installed into the agent runtime's plugin directory. When the agent runtime fires a lifecycle event, it spawns the hook as a child process, writes a JSON object to the child's `stdin`, and moves on. The hook reads `stdin`, parses the JSON, and sends a single HTTP POST to the memory server — then exits. All hooks are fire-and-forget by design: timeouts are capped tightly, and all `catch` blocks are empty.

```
Agent Runtime  →  hook process (stdin JSON)  →  POST :3111/agentmemory/*
```

Each hook shares the same three constants:

```typescript
// src/hooks/prompt-submit.ts:9-10
const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
```

Authentication is bearer-token based when `AGENTMEMORY_SECRET` is set; otherwise the requests are unauthenticated. The server validates these tokens with a timing-safe compare to prevent timing-oracle attacks.

### SDK Recursion Guard

Every hook checks a recursion guard before doing anything else. When agentmemory uses its own Claude Agent SDK provider to run LLM calls (e.g., during session summarization), the spawned child session inherits the parent's hook environment. Without a guard, the child's `Stop` hook would call `/agentmemory/summarize`, which would spawn another SDK session, and so on indefinitely.

Two signals break the loop:

```typescript
// src/hooks/sdk-guard.ts:20-26
export function isSdkChildContext(payload: unknown): boolean {
  if (process.env.AGENTMEMORY_SDK_CHILD === "1") return true;
  if (!payload || typeof payload !== "object") return false;
  const p = payload as Record<string, unknown>;
  if (p["entrypoint"] === "sdk-ts") return true;
  return false;
}
```

The `AGENTMEMORY_SDK_CHILD=1` env var is set by the SDK provider before spawning; `payload.entrypoint === "sdk-ts"` is written by Claude Code into hook stdin when the session originates from the Agent SDK. If either condition is true, the hook exits without making any HTTP call.

### Hook Inventory

| Hook file | Fires on | REST endpoint | Notes |
|---|---|---|---|
| `session-start.ts` | Session begins | `POST /agentmemory/session/start` | Registers session; optionally writes project context to stdout |
| `prompt-submit.ts` | User submits a prompt | `POST /agentmemory/observe` (`hookType: "prompt_submit"`) | Records prompt text + cwd |
| `pre-tool-use.ts` | Before each tool call | `POST /agentmemory/enrich` | **No-op by default**; opt-in via `AGENTMEMORY_INJECT_CONTEXT=true` |
| `post-tool-use.ts` | After each tool call | `POST /agentmemory/observe` (`hookType: "post_tool_use"`) | Records tool name, input, truncated output; extracts base64 images |
| `stop.ts` | Agent stops responding | `POST /agentmemory/summarize` | 120s timeout; skipped in SDK child contexts |
| `pre-compact.ts` | Before context compaction | `POST /agentmemory/context` (budget 1500) | Writes context block to stdout for Claude Code to prepend |
| `session-end.ts` | Session terminates | `POST /agentmemory/session/end` | Optionally triggers consolidation pipeline and Claude bridge sync |
| `subagent-start.ts` | Subagent spawns | `POST /agentmemory/observe` (`hookType: "subagent_start"`) | 800ms timeout cap |

Sources: [src/hooks/session-start.ts:18-88](), [src/hooks/prompt-submit.ts:36-51](), [src/hooks/pre-tool-use.ts:9-23](), [src/hooks/post-tool-use.ts:40-59](), [src/hooks/stop.ts:43-51](), [src/hooks/pre-compact.ts:49-65](), [src/hooks/session-end.ts:36-76](), [src/hooks/subagent-start.ts:43-58]()

### Session Start: Two Paths

`session-start.ts` has two distinct execution paths depending on `AGENTMEMORY_INJECT_CONTEXT`:

**Default path** (`INJECT_CONTEXT=false`): calls `session/start` fire-and-forget with an 800ms timeout. The response is never read. This is pure telemetry.

**Inject path** (`INJECT_CONTEXT=true`): awaits the response and writes `result.context` to `stdout`. Claude Code reads PreToolUse and session-start stdout and prepends it to the next model turn, so the project context becomes the agent's first piece of context.

```typescript
// src/hooks/session-start.ts:62-71
if (!INJECT_CONTEXT) {
  fetch(url, {
    ...init,
    signal: AbortSignal.timeout(REGISTER_TIMEOUT_MS), // 800ms
  }).catch(() => {});
  return;
}
```

The comment in the code explains why injection is off by default: on Claude Pro, this silently added ~1000 tokens per tool-touch and burned entire allocations in a few messages (issue #143).

### Pre-Tool-Use: Default No-Op

The `pre-tool-use.ts` hook exits immediately unless `AGENTMEMORY_INJECT_CONTEXT=true`:

```typescript
// src/hooks/pre-tool-use.ts:36-38
if (!INJECT_CONTEXT) return;
```

When enabled, it fires only on file-touching tools (`Edit`, `Write`, `Read`, `Glob`, `Grep`), calls `/agentmemory/enrich` with the file paths and search terms, and writes any returned context to stdout — which Claude Code injects before the tool runs.

### Post-Tool-Use: Image Extraction

`post-tool-use.ts` includes base64 image detection and extraction logic. If the tool output is or contains a base64-encoded PNG/JPEG (detected by data URI prefix or magic bytes), it extracts the image separately and substitutes `"[image data extracted]"` in the text payload to avoid bloating the observation record:

```typescript
// src/hooks/post-tool-use.ts:62-68
function isBase64Image(val: unknown): val is string {
  return typeof val === "string" && (
    val.startsWith("data:image/") ||
    val.startsWith("iVBORw0KGgo") || // PNG magic
    val.startsWith("/9j/")           // JPEG magic
  );
}
```

Tool output is also truncated to 8000 characters before being sent.

### Session End: Optional Pipeline Triggers

When `CONSOLIDATION_ENABLED=true`, `session-end.ts` makes two additional calls after closing the session: one to `/agentmemory/crystals/auto` (auto-crystallization) and one to `/agentmemory/consolidate-pipeline`. When `CLAUDE_MEMORY_BRIDGE=true`, it additionally calls `/agentmemory/claude-bridge/sync` to write memories back to Claude Code's native `MEMORY.md` file.

Sources: [src/hooks/session-end.ts:46-76]()

---

## The MCP Layer

### Transport and Registration

The MCP server is registered as an HTTP transport over the same local REST API at `:3111`. Three endpoint groups form the MCP protocol surface:

| Endpoint | Method | Function ID | Purpose |
|---|---|---|---|
| `/agentmemory/mcp/tools` | GET | `mcp::tools::list` | Enumerate available tools |
| `/agentmemory/mcp/call` | POST | `mcp::tools::call` | Invoke a tool by name |
| `/agentmemory/mcp/resources` | GET | `mcp::resources::list` | List resources |
| `/agentmemory/mcp/resources/read` | POST | `mcp::resources::read` | Read a resource by URI |
| `/agentmemory/mcp/prompts` | GET | `mcp::prompts::list` | List prompt templates |
| `/agentmemory/mcp/prompts/get` | POST | `mcp::prompts::get` | Render a prompt template |

Sources: [src/mcp/server.ts:60-71](), [src/mcp/server.ts:1260-1264](), [src/mcp/server.ts:1313-1317](), [src/mcp/server.ts:1545-1552]()

Each endpoint is registered as an `sdk.registerFunction` + `sdk.registerTrigger` pair using the `iii-sdk` runtime. The tool call dispatcher is a large `switch` statement that maps tool names to internal `sdk.trigger()` calls — essentially proxying MCP tool calls into the internal function bus.

### Tool Visibility: `AGENTMEMORY_TOOLS`

The tools exposed to the agent are controlled by `getVisibleTools()`:

```typescript
// src/mcp/tools-registry.ts:944-948
export function getVisibleTools(): McpToolDef[] {
  const mode = process.env["AGENTMEMORY_TOOLS"] || "core";
  if (mode === "all") return getAllTools();
  return getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name));
}
```

In `core` mode (default), only 8 tools are exposed: `memory_save`, `memory_recall`, `memory_consolidate`, `memory_smart_search`, `memory_sessions`, `memory_diagnose`, `memory_lesson_save`, and `memory_reflect`. Setting `AGENTMEMORY_TOOLS=all` exposes all tools across every version tier.

Sources: [src/mcp/tools-registry.ts:920-948]()

### Tool Tiers

Tools are organized into version tiers that reflect the feature they belong to:

| Tier constant | Representative tools | Feature area |
|---|---|---|
| `CORE_TOOLS` | `memory_recall`, `memory_save`, `memory_file_history`, `memory_smart_search`, `memory_commit_lookup` | Core memory + commit tracing |
| `V040_TOOLS` | `memory_graph_query`, `memory_consolidate`, `memory_team_share`, `memory_audit`, `memory_governance_delete` | Knowledge graph, team, governance |
| `V050_TOOLS` | `memory_action_create/update`, `memory_frontier`, `memory_next`, `memory_lease`, `memory_signal_send/read` | Action graph, multi-agent coordination |
| `V051_TOOLS` | `memory_sentinel_create/trigger`, `memory_sketch_create/promote`, `memory_crystallize`, `memory_diagnose/heal` | Event-driven sentinels, ephemeral graphs |
| `V061_TOOLS` | `memory_verify` | Memory provenance |
| `V070_TOOLS` | `memory_lesson_save/recall`, `memory_obsidian_export` | Lessons, Obsidian export |
| `V073_TOOLS` | `memory_reflect`, `memory_insight_list` | Reflection, synthesized insights |
| `V010_SLOTS_TOOLS` | `memory_slot_list/get/create/append/replace/delete` | Persistent editable slots |

Sources: [src/mcp/tools-registry.ts:11-941]()

### MCP Resources

Six URI-addressable resources are available for read-only inspection by agents that support MCP resource access:

| URI | Content |
|---|---|
| `agentmemory://status` | Session count, memory count, health status |
| `agentmemory://project/{name}/profile` | Top concepts, file patterns, conventions |
| `agentmemory://project/{name}/recent` | Last 5 session summaries |
| `agentmemory://memories/latest` | Top 10 latest memories by type and strength |
| `agentmemory://graph/stats` | Node and edge counts by type |
| `agentmemory://team/{id}/profile` | Team shared item count |

Sources: [src/mcp/server.ts:1266-1304]()

### MCP Prompt Templates

Three prompt templates are registered for agents that support the MCP prompts capability:

- `recall_context` — searches observations + memories for a task description and returns a pre-formatted context block
- `session_handoff` — generates a handoff summary from a session ID for continuing work in a new session
- `detect_patterns` — runs pattern detection across sessions and formats the result

Sources: [src/mcp/server.ts:1554-1590]()

### Authentication

Both the hooks and MCP endpoints share the same bearer-token authentication model. When `AGENTMEMORY_SECRET` is configured, every request must include `Authorization: Bearer <secret>`. The server uses `timingSafeCompare` (constant-time string comparison) to prevent timing attacks. MCP endpoints that are called without a valid token receive a `401 { error: "unauthorized" }` response. Hook scripts read the secret from `AGENTMEMORY_SECRET` and include it in every request.

Sources: [src/mcp/server.ts:47-58](), [src/hooks/prompt-submit.ts:12-16]()

---

## Connection to Claude Code

The `agentmemory connect claude-code` CLI command writes the MCP server entry into `~/.claude.json` under `mcpServers.agentmemory`:

```typescript
// src/cli/connect/claude-code.ts:44-90
servers["agentmemory"] = AGENTMEMORY_MCP_BLOCK;
next.mcpServers = servers;
writeJsonAtomic(CLAUDE_JSON, next);
```

The adapter detects an existing installation (`entryMatches`) to avoid duplicate entries, backs up the existing file before modifying it, and verifies the write succeeded by re-reading the file. On first install it also creates the `~/.claude/` directory if it does not exist.

The install note from the adapter clarifies the dual-surface design:

```typescript
// src/cli/connect/claude-code.ts:38-39
protocolNote:
  "→ Using MCP. Hooks are also available — see docs/claude-code.md.",
```

Sources: [src/cli/connect/claude-code.ts:33-91]()

---

## Data Flow Overview

```text
Claude Code (agent runtime)
│
├── [session-start event] ──────────────────────→ POST :3111/agentmemory/session/start
│       └── if INJECT_CONTEXT: writes project context to stdout (prepended to first turn)
│
├── [user submits prompt] ──────────────────────→ POST :3111/agentmemory/observe
│       hookType: "prompt_submit"
│
├── [before tool call] ─────────────────────────→ POST :3111/agentmemory/enrich  (only if INJECT_CONTEXT=true)
│       writes file history to stdout for model context injection
│
├── [after tool call] ──────────────────────────→ POST :3111/agentmemory/observe
│       hookType: "post_tool_use" (tool name + input + truncated output)
│
├── [before context compaction] ────────────────→ POST :3111/agentmemory/context
│       writes condensed memory context to stdout for Claude Code to include
│
├── [agent stops] ──────────────────────────────→ POST :3111/agentmemory/summarize
│       triggers session summarization (120s timeout)
│
├── [session ends] ─────────────────────────────→ POST :3111/agentmemory/session/end
│       → (optional) POST :3111/agentmemory/crystals/auto
│       → (optional) POST :3111/agentmemory/consolidate-pipeline
│       → (optional) POST :3111/agentmemory/claude-bridge/sync
│
└── [MCP tool call by agent] ───────────────────→ GET/POST :3111/agentmemory/mcp/*
        Agent invokes memory_recall, memory_save, etc. as explicit tool calls
```

---

## Key Invariants and Failure Modes

**Hooks never block the agent.** Every hook uses `AbortSignal.timeout()` with short caps (800ms–3000ms for telemetry paths, 30–120s for session-end and stop). A slow or unreachable server causes hooks to silently fail, not to stall the agent.

**Hooks are fire-and-forget except where stdout is read.** `session-start` and `pre-compact` write to stdout, which Claude Code reads synchronously. These two hooks add latency to the agent's hot path if the server is slow; the 1500ms and 5000ms caps respectively bound that exposure.

**Pre-tool-use injection is a token consumption footgun.** When enabled via `AGENTMEMORY_INJECT_CONTEXT=true`, the hook fires on every file-touching tool call and prepends up to 4000 chars of context to each tool turn. On subscription-capped plans this has materially burned allocations (issue #143). The default is off.

**SDK recursion is a hard invariant.** If `isSdkChildContext()` is not checked and a hook re-enters the SDK provider, the result is unbounded token consumption and ghost session accumulation. All hooks check this guard unconditionally as their first operation. Sources: [src/hooks/sdk-guard.ts:1-26]()

**MCP tool visibility is all-or-nothing by tier.** `AGENTMEMORY_TOOLS=all` exposes all 53+ tools across every version tier; the default exposes only 8 essential tools. There is no per-tool selection. Agents that enumerate the tool list will see a very different surface depending on this setting.

**Tool calls that depend on optional features degrade gracefully.** Tools like `memory_graph_query`, `memory_consolidate`, `memory_team_share`, and `memory_mesh_sync` catch internal errors and return a `200` response with a descriptive error string (e.g., `"Knowledge graph not enabled. Set GRAPH_EXTRACTION_ENABLED=true"`) rather than a `5xx`. The MCP protocol sees a success, and the agent sees a human-readable explanation. Sources: [src/mcp/server.ts:449-461](), [src/mcp/server.ts:477-489]()

---

## Summary

The hook layer and MCP layer are architecturally independent: hooks run passively in the background, require no agent-side code, and tolerate server unavailability silently; MCP requires the agent to be MCP-aware and gives it active, synchronous control over memory operations. Together they ensure that even agents with no explicit memory calls accumulate observation history through hooks, while agents that understand the MCP protocol can query and shape their memory in real time. The shared `:3111` REST API and bearer-token authentication model mean both surfaces can be secured and deployed identically regardless of which agent runtime is in use.

Sources: [src/mcp/server.ts:42-65](), [src/hooks/session-start.ts:62-88]()
