乐于分享
好东西不私藏

Claude Code 插件开发实战(二):6 大 Hook、Worker 与完整数据流架构解析

Claude Code 插件开发实战(二):6 大 Hook、Worker 与完整数据流架构解析

一、架构全景图 - 理解整体设计

1.1 核心组件关系图

claude-mem 的架构由四大核心组件构成:

┌─────────────────────────────────────────────────────┐
│  Claude Code CLI (主进程)                            │
│  ├─ 用户对话                                         │
│  ├─ 工具调用 (Read, Write, Edit, Bash...)          │
│  └─ 系统提示                                         │
└─────────────────────────────────────────────────────┘
           ↓ (触发 Hook)
┌─────────────────────────────────────────────────────┐
│  Plugin Hooks (6个生命周期拦截器)                   │
│  ├─ Setup Hook: 版本检查、Worker启动                │
│  ├─ SessionStart Hook: 会话初始化                   │
│  ├─ UserPromptSubmit Hook: 用户输入预处理           │
│  ├─ PreToolUse Hook: 工具调用前拦截                 │
│  ├─ PostToolUse Hook: 工具调用后拦截                │
│  └─ Stop Hook: 会话结束总结                         │
└─────────────────────────────────────────────────────┘
           ↓ (HTTP调用)
┌─────────────────────────────────────────────────────┐
│  Worker Service (独立进程)                          │
│  ├─ Express HTTP API (port 37700+)                 │
│  ├─ 异步队列 (后台处理AI生成)                       │
│  ├─ Claude Agent SDK (生成observation)             │
│  ├─ SQLite Service (持久化存储)                     │
│  ├─ Chroma Service (向量索引)                       │
│  └─ Session Manager (会话管理)                      │
└─────────────────────────────────────────────────────┘
           ↓ (存储数据)
┌─────────────────────────────────────────────────────┐
│  数据持久层                                          │
│  ├─ SQLite Database (~/.claude-mem/claude-mem.db)  │
│  │  ├─ sessions 表 (会话元数据)                     │
│  │  └─ observations 表 (观察记录)                   │
│  ├─ Chroma Vector DB (~/.claude-mem/chroma/)       │
│  │  ├─ 向量嵌入 (文本→向量)                         │
│  │  └─ 语义索引 (相似度搜索)                        │
└─────────────────────────────────────────────────────┘
           ↓ (检索注入)
┌─────────────────────────────────────────────────────┐
│  未来会话 (PreToolUse Hook)                         │
│  ├─ 查询Chroma (向量搜索相似文件)                   │
│  ├─ 查询SQLite (历史observation)                   │
│  └─ 注入到Claude系统提示                            │
└─────────────────────────────────────────────────────┘

1.2 进程模型详解

claude-mem 采用多进程架构,这是关键设计:

Claude Code 主进程

  • • 职责: 运行用户对话,执行工具调用
  • • 特点: 主进程,负责交互
  • • Hook影响: Hook在主进程中同步执行,必须快速响应

Worker 子进程

  • • 职责: 异步处理耗时操作(AI生成、数据存储)
  • • 特点: 独立进程,不阻塞主进程
  • • 端口: 37700 + (uid % 100)
  • • 管理: Bun runtime管理进程生命周期

为什么用 Worker 而不是直接处理?

直接处理
Worker架构
Hook中调用AI生成(耗时5-10秒)
Hook快速响应(<1秒)
Claude会话被阻塞,用户体验差
Claude继续工作,无感知
无法并发处理多个工具调用
后台队列并行处理
进程生命周期短暂
Worker持久运行,可重启

设计哲学: Hook是同步拦截点,必须快速响应;Worker是异步处理器,可以耗时操作。


二、6个生命周期Hook详解 - 触发时序和职责

2.1 Hook 触发时序图

完整的Hook触发顺序(从插件安装到会话结束):

[插件安装]
    ↓
Setup Hook (执行一次)
    ├─ 版本检查 (version-check.js)
    ├─ Bun/uv依赖安装
    └─ Worker进程启动
    ↓
[用户启动Claude Code]
    ↓
SessionStart Hook (每个会话开始)
    ├─ 生成session_id (UUID)
    ├─ 记录开始时间戳
    └─ Worker创建session记录
    ↓
[用户输入问题]
    ↓
UserPromptSubmit Hook (可选,未使用)
    ├─ 输入预处理
    └─ 添加时间戳/标签
    ↓
[Claude处理用户输入,决定调用工具]
    ↓
[工具调用: Read]
    ↓
PreToolUse Hook (拦截Read工具)
    ├─ 查询Chroma: 找相似文件
    ├─ 查询SQLite: 找历史observation
    └─ 注入到Claude系统提示:
       "上次你读取app.ts时,还修改了config.ts..."
    ↓
[Claude执行Read工具]
    ↓
PostToolUse Hook (捕获Read输出)
    ├─ 拦截工具输出(file content)
    ├─ 发送到Worker队列
    ├─ Worker后台处理:
    │  ├─ Claude SDK生成observation
    │  ├─ SQLite存储observation
    │  └─ Chroma索引observation
    └─ 快速响应(job_id返回)
    ↓
[会话继续,多次工具调用...]
    ↓
[用户退出Claude Code]
    ↓
Stop Hook (会话结束)
    ├─ 查询session的所有observation
    ├─ Claude SDK生成session summary
    ├─ SQLite更新session结束时间
    └─ Chroma索引summary

2.2 Setup Hook - 插件初始化

触发时机

  • • 何时: 插件安装时执行一次
  • • 场景: 用户首次安装插件或版本更新

核心职责

  1. 1. 版本检查: 确保插件版本与已安装版本一致
  2. 2. 依赖安装: 检查Bun、uv是否安装,缺失则安装
  3. 3. Worker启动: 启动Worker进程,确保后续Hook可用

实际代码分析

从 hooks.json 提取的Setup Hook配置:

{
"Setup":[
{
"matcher":"*",
"hooks":[
{
"type":"command",
"shell":"bash",
"command":"... version-check.js",
"timeout":300
}
]
}
]
}

关键点:

  • • timeout: 300秒(5分钟),因为可能需要安装Bun/uv
  • • 独立脚本: Setup Hook不走bun-runner,直接执行version-check.js

version-check.js 核心逻辑

// plugin/scripts/version-check.js
const currentVersion = '13.3.0';

// 1. 检查已安装版本
const installedVersion = getInstalledVersion();

if (currentVersion !== installedVersion) {
console.error('Version mismatch, please reinstall');
exit(1); // 非阻塞错误,显示给用户
}

// 2. 检查Bun是否安装
if (!commandExists('bun')) {
console.log('Installing Bun...');
// 自动安装Bun
}

// 3. 检查uv是否安装(用于Chroma)
if (!commandExists('uv')) {
console.log('Installing uv...');
// 自动安装uv
}

// 4. 启动Worker进程
startWorkerProcess();

exit(0); // 成功

设计理念: Setup Hook确保插件环境就绪,后续Hook可以正常工作。


2.3 SessionStart Hook - 会话初始化

触发时机

  • • 何时: 每个Claude Code会话开始时
  • • matcherstartup|clear|compact (匹配特定命令)

核心职责

  1. 1. 生成session_id: UUID唯一标识会话
  2. 2. 记录时间戳: 会话开始时间
  3. 3. Worker初始化: 创建session记录,启动Worker

hooks.json 配置

{
"SessionStart":[
{
"matcher":"startup|clear|compact",
"hooks":[
{
"command":"... bun-runner.js ... start",
"timeout":60
},
{
"command":"... bun-runner.js ... context",
"timeout":60
}
]
}
]
}

两个Hook子命令:

  1. 1. start: 启动Worker进程(如果未运行)
  2. 2. context: 注入初始上下文到Claude

Worker处理流程

[SessionStart Hook]
    ↓ (调用Worker)
POST /api/session-init
    ↓ (Worker处理)
1. 生成session_id: UUID
2. SQLite INSERT:
   sessions (session_id, created_at, ...)
3. 返回session_id给Hook
    ↓ (Hook输出)
stdout: {"session_id": "abc-123"}
    ↓ (Claude读取)
Claude系统提示收到session_id

实际数据示例:

-- SQLite sessions表记录
INSERT INTO sessions VALUES (
'abc-123-def-456',  -- session_id
'2026-05-29T10:00:00Z'-- created_at
NULL,              -- ended_at (会话结束时更新)
NULL-- summary (会话结束时生成)
);

2.4 UserPromptSubmit Hook - 用户输入预处理(预留)

触发时机

  • • 何时: 用户输入提交前
  • • matcher: 无(所有用户输入)

设计意图(预留接口)

虽然hooks.json中配置了UserPromptSubmit Hook,但当前未实际使用,预留用于:

  • • 输入预处理: 添加时间戳、标签
  • • 隐私处理: 检测<private>标签,标记敏感内容
  • • 输入验证: 检查输入格式、长度

hooks.json 配置

{
"UserPromptSubmit":[
{
"hooks":[
{
"command":"... bun-runner.js ... session-init",
"timeout":60
}
]
}
]
}

注意: 配置中使用了session-init子命令,但实际功能未激活。

潜在应用场景

场景1: 隐私标签处理

// 未来实现: 检测<private>标签
if (userMessage.includes('<private>')) {
// 标记为隐私,不存储
markAsPrivate(sessionId);
}

场景2: 输入增强

// 未来实现: 添加时间戳
const enhancedMessage = `[${timestamp}${userMessage}`;
// 注入到Claude

2.5 PreToolUse Hook - 最关键的注入点

触发时机

  • • 何时: 工具调用前
  • • matcherRead (只拦截Read工具)

核心职责(关键功能)

这是claude-mem最核心的Hook,负责注入历史上下文:

  1. 1. 拦截Read工具: Claude要读取文件前触发
  2. 2. 查询Chroma: 向量搜索相似文件
  3. 3. 查询SQLite: 查找历史observation
  4. 4. 注入系统提示: 将历史信息注入到Claude

hooks.json 配置

{
"PreToolUse":[
{
"matcher":"Read",
"hooks":[
{
"command":"... bun-runner.js ... file-context",
"timeout":60
}
]
}
]
}

关键参数:

  • • matcher"Read" 只拦截Read工具,不拦截Write/Edit
  • • timeout: 60秒,查询Chroma可能耗时

完整注入流程详解

步骤1: Hook接收Read工具参数
// stdin输入(来自Claude Code)
{
"tool_name":"Read",
"tool_params":{
"file_path":"/src/app.ts"
},
"session_id":"abc-123"
}
步骤2: Worker查询Chroma(向量搜索)
// Worker处理
const filePath = '/src/app.ts';

// Chroma向量搜索
const similarFiles = await chromaCollection.query({
queryEmbeddings: [embedText(filePath)],
nResults5
});

// 返回结果示例:
[
  { file_path'/src/utils.ts'similarity0.85 },
  { file_path'/src/config.ts'similarity0.78 },
  { file_path'/src/router.ts'similarity0.72 }
]

向量搜索原理:

  • • 文件路径 /src/app.ts → 文本 → Embedding向量
  • • Chroma计算向量相似度(余弦距离)
  • • 返回语义相似的文件(即使路径不同)
步骤3: Worker查询SQLite(历史observation)
// SQLite查询
const observations = await sqlite.query(`
  SELECT observation_text, timestamp, tool_name
  FROM observations
  WHERE tool_params LIKE '%/src/app.ts%'
  ORDER BY timestamp DESC
  LIMIT 10
`
);

// 返回结果示例:
[
  {
observation_text'读取了app.ts,包含Router配置',
timestamp'2026-05-28T15:00:00Z',
tool_name'Read'
  },
  {
observation_text'修改了app.ts,增加了新路由',
timestamp'2026-05-27T10:00:00Z',
tool_name'Edit'
  }
]
步骤4: 构建注入文本
// 构建系统提示注入
const injectionText = `
历史上下文:
- 相似文件: utils.ts, config.ts (上次你读取app.ts时还查看了这些文件)
- 历史操作:
  * 2026-05-28: 读取了app.ts,包含Router配置
  * 2026-05-27: 修改了app.ts,增加了新路由
`
;

// 输出到stdout
console.log(JSON.stringify({
type'context_injection',
text: injectionText
}));
步骤5: Claude系统提示接收

Claude的系统提示被更新:

原始系统提示:
"You are Claude, a helpful AI assistant..."

注入后系统提示:
"You are Claude, a helpful AI assistant...

历史上下文:
- 相似文件: utils.ts, config.ts (上次你读取app.ts时还查看了这些文件)
- 历史操作:
  * 2026-05-28: 读取了app.ts,包含Router配置
  * 2026-05-27: 修改了app.ts,增加了新路由"
步骤6: Claude使用历史信息

现在Claude读取/src/app.ts时,会:

  • • 理解关联: 知道app.ts与utils.ts、config.ts相关
  • • 参考历史: 知道之前做过Router配置
  • • 提供精准建议: 基于历史提供更准确的修改建议

实际体验示例:

用户: "帮我优化app.ts的路由性能"

Claude (看到历史上下文):
"根据历史记录,你在app.ts中配置了Router,上次还修改了config.ts。
我建议优化路由性能可以从以下方面入手:
1. config.ts中的路由缓存设置...
2. utils.ts中的路由工具函数..."

2.6 PostToolUse Hook - 最关键的数据收集点

触发时机

  • • 何时: 工具调用后
  • • matcher* (拦截所有工具)

核心职责(关键功能)

这是另一个核心Hook,负责捕获工具输出并生成observation:

  1. 1. 拦截所有工具: Read、Write、Edit、Bash等
  2. 2. 捕获工具输出: 工具执行结果
  3. 3. 发送到Worker: 添加到后台队列
  4. 4. Worker后台处理: AI生成observation + 存储 + 索引

hooks.json 配置

{
"PostToolUse":[
{
"matcher":"*",
"hooks":[
{
"command":"... bun-runner.js ... observation",
"timeout":120
}
]
}
]
}

关键参数:

  • • matcher"*" 拦截所有工具,不限制
  • • timeout: 120秒(2分钟),因为需要等待Worker快速响应

完整数据收集流程详解

步骤1: Hook接收工具输出
// stdin输入(来自Claude Code)
{
"tool_name":"Read",
"tool_params":{
"file_path":"/src/app.ts"
},
"tool_output":"import express from 'express';\nconst app = express();\n...",
"session_id":"abc-123",
"timestamp":"2026-05-29T10:00:00Z"
}

注意tool_output 可能很大(文件内容),Hook不处理内容,直接转发Worker。

步骤2: Hook调用Worker API
// PostToolUse Hook脚本
const response = awaitfetch('http://127.0.0.1:37700/api/observation', {
method'POST',
bodyJSON.stringify({
tool_name'Read',
tool_params: { file_path'/src/app.ts' },
tool_output'file content...'// 可能很大
session_id'abc-123',
timestamp'2026-05-29T10:00:00Z'
  })
});

// Worker快速响应(<1秒)
const result = await response.json();
// { status: 'queued', job_id: 'xyz-789' }

快速响应设计:

  • • Worker收到请求后立即返回job_id
  • • 不等待AI生成完成(耗时5-10秒)
  • • Hook收到响应后立即退出
步骤3: Hook快速退出
// Hook输出job_id(可选)
console.log(`Observation queued: ${result.job_id}`);

// 快速退出,不阻塞Claude
process.exit(0);
步骤4: Worker后台队列处理
[Worker收到请求]
    ↓
快速响应: {status: 'queued', job_id: 'xyz-789'}
    ↓
添加到后台队列:
Queue.add({
  job_id: 'xyz-789',
  tool_name: 'Read',
  tool_output: 'file content...',
  session_id: 'abc-123'
})
    ↓
[Claude继续工作,不受影响]
    ↓
[Worker后台处理,5-10秒]
步骤5: Claude SDK生成observation
// Worker后台队列处理
asyncfunctionprocessObservation(job) {
// 1. 调用Claude Agent SDK生成一句话总结
const prompt = `
Analyze the following tool usage and generate a one-line observation:

Tool: ${job.tool_name}
File: ${job.tool_params.file_path}
Output: ${job.tool_output.substring(01000)}

Generate a concise summary of what was done.
`
;

const observation = await claudeSDK.generate(prompt);

// observation示例: "读取了app.ts,包含Express Router配置"

// 2. 存储到SQLite
await sqlite.insert('observations', {
observation_id: job.job_id,
session_id: job.session_id,
tool_name: job.tool_name,
observation_text: observation,
timestamp: job.timestamp
  });

// 3. 索引到Chroma
await chroma.addEmbedding({
id: job.job_id,
text: observation,
metadata: {
session_id: job.session_id,
tool_name: job.tool_name,
file_path: job.tool_params.file_path
    }
  });
}
步骤6: 数据持久化完成
-- SQLite observations表记录
INSERT INTO observations VALUES (
'xyz-789',         -- observation_id
'abc-123',         -- session_id
'Read',            -- tool_name
'{"file_path":"/src/app.ts"}'-- tool_params
'读取了app.ts,包含Express Router配置'-- observation_text
'2026-05-29T10:00:00Z'-- timestamp
NULL-- metadata
);

Chroma向量索引:

Observation文本 → Embedding向量 → Chroma存储
"读取了app.ts,包含Express Router配置"
    ↓ (text-embedding-ada-002)
[0.123, -0.456, 0.789, ...] (1536维向量)
    ↓ (Chroma.add)
向量索引,支持语义搜索

2.7 Stop Hook - 会话总结

触发时机

  • • 何时: 会话结束(用户退出Claude Code)
  • • matcher: 无(所有会话结束)

核心职责

  1. 1. 查询session observations: 获取会话的所有observation
  2. 2. 生成session summary: Claude SDK生成会话总结
  3. 3. 更新session结束时间: SQLite更新ended_at字段
  4. 4. 索引summary: Chroma索引会话总结

hooks.json 配置

{
"Stop":[
{
"hooks":[
{
"command":"... bun-runner.js ... summarize",
"timeout":120
}
]
}
]
}

timeout: 120秒,因为需要等待AI生成summary。

会话总结流程

[Stop Hook触发]
    ↓
查询SQLite:
SELECT observation_text FROM observations
WHERE session_id = 'abc-123'
ORDER BY timestamp
    ↓
返回observation列表:
[
  '读取了app.ts,包含Router配置',
  '修改了config.ts,增加了环境变量',
  '调试了API调用,修复了bug'
]
    ↓
调用Claude SDK生成summary:
"本次会话修改了配置文件,调试API,修复bug"
    ↓
更新SQLite:
UPDATE sessions SET
  ended_at = '2026-05-29T11:00:00Z',
  summary = '本次会话修改了配置文件...'
WHERE session_id = 'abc-123'
    ↓
索引到Chroma:
会话summary也加入向量索引

三、Worker Service架构 - HTTP API核心

3.1 Express HTTP API设计

Worker Service是独立进程,提供HTTP API供Hook调用。

端口计算公式

// src/shared/SettingsDefaultsManager.ts
constWORKER_PORT_BASE = 37700;
const uid = process.getuid(); // Unix用户ID
const port = WORKER_PORT_BASE + (uid % 100);

示例计算:

  • • uid=1000 → port=37700 + (1000%100)=37700
  • • uid=1001 → port=37700 + (1001%100)=37701
  • • uid=2050 → port=37700 + (2050%100)=37750

设计目的: 避免多用户或多账号场景下的端口冲突。

核心API路由

Worker提供以下HTTP API:

路由
方法
作用
调用时机
/api/session-init
POST
初始化会话
SessionStart Hook
/api/observation
POST
生成observation
PostToolUse Hook
/api/file-context
POST
文件上下文注入
PreToolUse Hook
/api/summarize
POST
会话总结
Stop Hook
/health
GET
健康检查
bun-runner启动前

3.2 异步处理机制 - 核心设计

Worker的核心设计是异步队列,这是让Hook不阻塞Claude的关键。

快速响应模式

// Worker收到Hook请求
app.post('/api/observation'async (req, res) => {
const job_id = uuidv4();

// 1. 快速响应(<1秒)
  res.json({
status'queued',
job_id: job_id,
message'Observation generation started'
  });

// 2. 后台异步处理(不阻塞响应)
  observationQueue.add({
    job_id,
tool_name: req.body.tool_name,
tool_output: req.body.tool_output,
session_id: req.body.session_id
  });
});

时间对比:

  • • Hook → Worker HTTP调用: ~50ms
  • • Worker快速响应: ~100ms
  • • Hook阻塞时间: ~150ms (总)
  • • Worker后台AI生成: ~5-10秒 (异步)

后台队列实现

// Worker后台队列
classObservationQueue {
privatequeueJob[] = [];
privateisProcessingboolean = false;

add(jobJob): void {
this.queue.push(job);
if (!this.isProcessing) {
this.processQueue(); // 启动后台处理
    }
  }

privateasyncprocessQueue(): void {
this.isProcessing = true;

while (this.queue.length > 0) {
const job = this.queue.shift();

try {
// 1. Claude SDK生成observation (耗时5-10秒)
const observation = await claudeSDK.generate(job);

// 2. 存储到SQLite
await sqlite.insert('observations', observation);

// 3. 索引到Chroma
await chroma.addEmbedding(observation);

      } catch (error) {
console.error('Job failed:', error);
// 错误处理,继续下一个任务
      }
    }

this.isProcessing = false;
  }
}

3.3 Claude Agent SDK集成

Worker使用Claude Agent SDK生成observation和summary。

SDK调用示例

importAnthropicfrom'@anthropic-ai/sdk';

const client = newAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});

// 生成observation
asyncfunctiongenerateObservation(toolOutputstring): string {
const prompt = `
Analyze the following tool output and generate a one-line observation:
${toolOutput.substring(01000)}
`
;

const message = await client.messages.create({
model'claude-sonnet-4-5',
max_tokens200,
messages: [{ role'user'content: prompt }]
  });

return message.content[0].text;
}

模型选择: claude-sonnet-4-5 (平衡速度和质量)


四、数据流详解 - 完整路径追踪

4.1 一个Read工具调用的完整数据流

让我们追踪Claude执行Read /src/app.ts的完整数据流:

[Claude决定调用Read工具]
    ↓ (1)
[PreToolUse Hook触发]
    ↓ (2) stdin接收
{
  "tool_name": "Read",
  "tool_params": {"file_path": "/src/app.ts"},
  "session_id": "abc-123"
}
    ↓ (3) Hook调用Worker
POST http://127.0.0.1:37700/api/file-context
    ↓ (4) Worker处理
Chroma向量搜索: "app.ts"
返回相似文件: [utils.ts, config.ts]
    ↓ (5) Worker查询SQLite
SELECT * FROM observations
WHERE tool_params LIKE '%app.ts%'
返回历史: ["读取了app.ts...", "修改了app.ts..."]
    ↓ (6) Worker返回Hook
{
  "similarFiles": ["/src/utils.ts", "/src/config.ts"],
  "observations": [
    {"text": "读取了app.ts,包含Router", "time": "2026-05-28"},
    {"text": "修改了app.ts,增加路由", "time": "2026-05-27"}
  ]
}
    ↓ (7) Hook输出到Claude
stdout: {
  "type": "context_injection",
  "text": "上次读取app.ts时,还查看了utils.ts..."
}
    ↓ (8) Claude系统提示更新
[Claude收到历史上下文]
    ↓ (9) Claude执行Read
Read /src/app.ts
返回文件内容: "import express..."
    ↓ (10) [PostToolUse Hook触发]
    ↓ (11) stdin接收
{
  "tool_name": "Read",
  "tool_output": "import express...",
  "session_id": "abc-123"
}
    ↓ (12) Hook调用Worker
POST http://127.0.0.1:37700/api/observation
    ↓ (13) Worker快速响应
{status: "queued", job_id: "xyz-789"}
    ↓ (14) Hook退出
process.exit(0)
    ↓ (15) Claude继续工作
[不受阻塞]
    ↓ (16) Worker后台处理
[Claude SDK生成observation]
"读取了app.ts,包含Express Router配置"
    ↓ (17) SQLite存储
INSERT INTO observations ...
    ↓ (18) Chroma索引
向量嵌入 → Chroma.add
    ↓ (19) 完成
日志: Observation processed: xyz-789

时间统计:

  • • 步骤1-8(PreToolUse): ~1-2秒
  • • 步骤9(Read执行): ~0.5秒
  • • 步骤10-14(PostToolUse响应): ~0.15秒
  • • 步骤15(Claude继续): 0秒(无阻塞)
  • • 步骤16-19(Worker后台): ~5-10秒(异步)

4.2 一个会话的完整生命周期数据流

从会话开始到结束的完整数据:

[会话开始: SessionStart Hook]
    ↓
session_id: "abc-123"
created_at: "2026-05-29T10:00:00Z"
SQLite: INSERT INTO sessions ...

[会话期间: 多次PostToolUse Hook]
    ↓
observation_1: "读取了package.json,版本13.3.0"
observation_2: "修改了README.md,添加新章节"
observation_3: "调试了API调用,修复bug"
observation_4: "读取了app.ts,包含Router配置"
SQLite: INSERT INTO observations ...
Chroma: 向量索引每个observation

[会话结束: Stop Hook]
    ↓
查询observations:
SELECT observation_text FROM observations
WHERE session_id = 'abc-123'
返回: [observation_1, observation_2, ...]
    ↓
Claude SDK生成summary:
"本次会话读取了配置文件,修改文档,调试API,修复bug"
    ↓
SQLite更新:
UPDATE sessions SET
  ended_at = '2026-05-29T11:00:00Z',
  summary = '本次会话...'
WHERE session_id = 'abc-123'
    ↓
Chroma索引summary:
会话总结也加入向量索引

五、实践任务 - 验证架构理解

任务1: 观察 Hook触发顺序

目标: 实际观察Hook的触发时序。

步骤:

# 1. 启动Claude Code
claude-code

# 2. 打开Worker日志
tail -f ~/.claude-mem/logs/worker.log

# 3. 在Claude Code中执行操作
# 输入: "请读取package.json文件"

# 4. 观察日志输出(应该看到):
[SessionStart] Session initialized: abc-123
[PreToolUse] Intercepting Read: package.json
[Worker] Chroma search: query="package.json"
[Worker] SQLite query: file_path LIKE '%package.json%'
[Hook] Context injection: "上次读取package.json..."
[PostToolUse] Observation queued: job_id=xyz-789
[Queue] Processing job...
[Claude SDK] Generating observation...
[SQLite] Observation stored
[Chroma] Observation indexed

分析要点:

  • • SessionStart最先触发
  • • PreToolUse在Read前触发
  • • PostToolUse在Read后触发
  • • Worker后台异步处理observation

任务2: 分析 SQLite数据

目标: 查看Hook生成的实际数据。

步骤:

# 1. 打开SQLite
sqlite3 ~/.claude-mem/claude-mem.db

# 2. 查询session
SELECT session_id, created_at, ended_at, summary FROM sessions;

# 应该看到:
abc-123 | 2026-05-29T10:00:00Z | NULL | NULL

# 3. 查询observation
SELECT observation_id, tool_name, observation_text FROM observations LIMIT 5;

# 应该看到:
xyz-789 | Read | 读取了package.json,版本13.3.0
...

# 4. 分析关系
SELECT s.session_id, o.observation_text
FROM sessions s
JOIN observations o ON s.session_id = o.session_id;

# 理解session和observation的关系

任务3: 追踪 PreToolUse注入

目标: 验证PreToolUse Hook的上下文注入。

步骤:

# 1. 在Claude Code中读取一个文件
# 输入: "请读取src/services/worker-service.ts"

# 2. 观察Worker日志
grep "file-context" ~/.claude-mem/logs/worker.log

# 应该看到:
[Worker] Chroma search: query="worker-service.ts"
[Worker] Similar files: [worker-cli.ts, server-beta-service.ts]
[Worker] SQLite query: file_path LIKE '%worker-service%'
[Worker] Observations: ["读取了worker-service...""修改了worker-service..."]

# 3. Claude的响应应该包含历史信息
# Claude可能说: "根据历史记录,上次读取worker-service.ts时..."

任务4: 测试 Worker健康检查

目标: 验证Worker进程管理。

步骤:

# 1. 查看Worker端口
grep "Worker running on port" ~/.claude-mem/logs/worker.log

# 输出: Worker running on port 37700

# 2. 测试健康检查API
curl http://127.0.0.1:37700/health

# 应该返回:
{
"status""healthy",
"checks": {
"sqlite"true,
"chroma"true,
"queue"true
  },
"uptime": 3600
}

# 3. 如果Worker未运行,bun-runner会自动启动
# 手动停止Worker:
pkill -f worker-service

# 重新启动Claude Code,观察bun-runner启动Worker

六、关键学习点总结

6.1 Hook vs Worker的职责分离

对比项
Hook
Worker
执行方式
同步执行,必须快速
异步处理,可以耗时
生命周期
短暂(几秒)
持久(一直运行)
进程位置
Claude Code主进程
独立子进程
职责
拦截、转发、快速响应
AI生成、存储、索引
阻塞影响
会阻塞Claude
不阻塞Claude

设计哲学: Hook是快速拦截器,Worker是重型处理器


6.2 数据流是单向的

完整的数据流路径:

Hook → Worker → SQLite → Chroma → 注入

特点:

  • • 单向流动: 数据从Hook流向存储,再流向注入
  • • 没有反向: Worker不会调用Hook
  • • 清晰职责: 每个阶段职责明确,易于理解

6.3 PreToolUse是最关键的注入点

为什么PreToolUse Hook最关键?

  • • 时机关键: 在Claude执行工具前,可以影响Claude的决策
  • • 信息注入: 查询历史,注入上下文,让Claude"记住"过去
  • • 精准增强: 只拦截Read工具,精准注入文件相关历史

实际价值: Claude看到历史信息后,能提供更精准的建议。


6.4 PostToolUse是最关键的数据收集点

为什么PostToolUse Hook最关键?

  • • 时机关键: 在工具执行后,捕获所有输出
  • • 数据收集: 拦截所有工具(Read、Write、Edit等)
  • • 持久化基础: 生成的observation是未来注入的数据源

实际价值: 没有PostToolUse收集数据,PreToolUse就没有历史可注入。


6.5 Worker的异步设计是核心创新

为什么Worker必须异步?

  • • AI生成耗时: Claude SDK生成observation需要5-10秒
  • • 用户体验: 如果Hook等待,Claude会阻塞10秒
  • • 并发处理: Worker队列可以并行处理多个observation

对比: 如果没有异步设计,用户体验会是灾难性的。


下一步: 第三章将深入Hook系统的实现细节,学习hooks.json配置、bun-runner进程管理、Hook脚本编写技巧。