第 4 期:生命周期钩子 — Claude-Mem 如何「偷偷记笔记」
(申请发送: agentupdate)
本期场景:你在博客项目中让 Claude 修复一个评论功能的 Bug。在这 5 分钟内,Claude-Mem 在背后触发了十几次 Hook,记录了数条 Observation。问题是 —— 它是怎么知道该在什么时候出手的?
4.1 什么是 Lifecycle Hook?
Lifecycle Hook(生命周期钩子)是 Claude Code 插件系统提供的一种机制:
当 Claude Code 内部发生特定事件时,自动执行你注册的脚本。
就像你手机上的自动化快捷指令 —— 当你到家(事件)→ 自动开灯(脚本)。在 Claude-Mem 中,就是当 Claude 做了某个操作(事件)→ 自动记笔记(脚本)。
4.2 五大生命周期事件
Claude-Mem 注册了 5 个生命周期钩子和 1 个辅助脚本:
graph TD
subgraph "会话生命周期"
A["🟢 SessionStart"] -->|"你打开 Claude Code"| B["📝 UserPromptSubmit"]
B -->|"你发送指令"| C["🔧 PostToolUse"]
C -->|"Claude 使用工具"| C
C --> D["⏸ Stop"]
D -->|"Claude 停止活动"| E["🔴 SessionEnd"]
end
F["⚙️ Smart Install
(辅助脚本)"] -.->|"安装时运行"| A
style A fill:#10b981,color:#fff
style B fill:#6366f1,color:#fff
style C fill:#f59e0b,color:#000
style D fill:#94a3b8,color:#000
style E fill:#ef4444,color:#fff
style F fill:#64748b,color:#fff1️⃣ SessionStart — 会话启动
触发时机:你打开 Claude Code 的那一刻
做了什么:
- 向 Worker 发送
GET /api/context请求 - Worker 从 SQLite 中查询最近 10 个会话的 Observations 和 Summaries
- 将这些历史笔记注入到 Claude 的初始上下文中
效果:Claude 在你说第一句话之前,就已经"回忆起"了你之前做过什么。
[Claude 的内心独白]
"我看到上次会话:
- 项目是 my-blog(Next.js + Prisma)
- 实现了评论功能
- 遗留问题:评论通知还未完成
让我准备好帮你继续这个工作。"
2️⃣ UserPromptSubmit — 用户发送指令
触发时机:你每次按下回车发送一条消息
做了什么:
- 将你的提示词发送给 Worker
- Worker 存入
user_prompts表
意义:记录你的原始意图,方便后续回溯"当时你想让 Claude 做什么"。
3️⃣ PostToolUse — 工具使用后(🔑 最重要的钩子!)
触发时机:每当 Claude 使用一个工具(Read、Write、Bash、Search 等)之后
做了什么:
- 捕获工具的输入和输出(比如:读了哪个文件、写了什么内容、命令输出是什么)
- 将原始数据 POST 给 Worker
- Worker 使用 Claude Agent SDK 对原始数据进行 AI 压缩
- 生成结构化的 Observation(title, narrative, facts, concepts, type)
- 存入 SQLite + ChromaDB
这是 Claude-Mem 的核心引擎。你在一个会话中产生的大部分 Observations 都来自这个钩子。
4️⃣ Stop — 暂停
触发时机:Claude 停止主动活动(等待你的下一条指令)
做了什么:轻量级的状态同步,确保所有正在处理的 Observation 都已入库。
5️⃣ SessionEnd — 会话结束
触发时机:你关闭 Claude Code 或结束当前会话
做了什么:
- 通知 Worker 会话即将结束
- Worker 回顾本次会话所有的 Observations
- 生成 Session Summary(包含 request, investigated, completed, next_steps)
- 存入
session_summaries表
关键信息:next_steps 字段会在下次 SessionStart 时被读取并注入 —— 这就是跨会话连续性的秘密。
附:Smart Install(辅助脚本)
不是生命周期钩子,而是一个预检脚本。每次 Hook 触发前先运行它,检查依赖是否就位。如果 Bun 或 uv 缺失,它会自动补装。
4.3 钩子脚本的物理位置
~/.claude/plugins/marketplaces/thedotmack/plugin/hooks/
├── session-start.sh # SessionStart 钩子
├── user-prompt.sh # UserPromptSubmit 钩子
├── post-tool-use.sh # PostToolUse 钩子
├── stop.sh # Stop 钩子
├── session-end.sh # SessionEnd 钩子
└── pre-hook.sh # Smart Install (辅助)
这些都是 shell 脚本,内部调用了 Bun 来执行实际的 TypeScript 逻辑。
4.4 完整调用链追踪
下面这张时序图是 Claude-Mem 最核心的参考图。它追踪了从"你打开 Claude Code"到"你关闭会话"的完整数据流:
sequenceDiagram
participant User as 👤 用户
participant CC as 💻 Claude Code
participant Hook as 🪝 Hook 脚本
participant Worker as ⚙️ Worker (37777)
participant SDK as 🤖 Claude Agent SDK
participant DB as 💾 SQLite + ChromaDB
Note over User,DB: ── 会话开始 ──
User->>CC: 打开 Claude Code
CC->>Hook: 触发 SessionStart
Hook->>Worker: GET /api/context
Worker->>DB: 查最近 10 个会话
DB-->>Worker: 返回 Observations + Summaries
Worker-->>Hook: 返回上下文数据
Hook-->>CC: 注入历史记忆到 Claude
Note over User,DB: ── 用户开始工作 ──
User->>CC: "帮我修复评论的 Bug"
CC->>Hook: 触发 UserPromptSubmit
Hook->>Worker: POST /api/prompt
Worker->>DB: 存入 user_prompts 表
Note over User,DB: ── Claude 开始操作 ──
CC->>CC: 读取 src/api/comments.ts
CC->>Hook: 触发 PostToolUse (tool=Read)
Hook->>Worker: POST /api/observation (原始文件内容)
Worker->>SDK: 压缩提炼
SDK-->>Worker: {title, narrative, facts, type}
Worker->>DB: INSERT observations + 更新向量索引
CC->>CC: 修改 src/api/comments.ts
CC->>Hook: 触发 PostToolUse (tool=Write)
Hook->>Worker: POST /api/observation (修改内容)
Worker->>SDK: 压缩提炼
SDK-->>Worker: {title:"修复评论外键", type:"bugfix"}
Worker->>DB: INSERT observations + 更新向量索引
CC->>CC: 运行 npm test
CC->>Hook: 触发 PostToolUse (tool=Bash)
Hook->>Worker: POST /api/observation (测试输出)
Worker->>SDK: 压缩提炼
Worker->>DB: INSERT observations
Note over User,DB: ── 会话结束 ──
User->>CC: 关闭 Claude Code
CC->>Hook: 触发 SessionEnd
Hook->>Worker: POST /api/session/end
Worker->>Worker: 回顾本会话所有 Observations
Worker->>SDK: 生成 Session Summary
SDK-->>Worker: {request, investigated, completed, next_steps}
Worker->>DB: INSERT session_summaries4.5 关键理解:为什么是异步压缩?
你可能会问:每次 Claude 操作后都要用 AI 压缩,这不会很慢吗?
答案:不会,因为是异步的。
graph LR
A["Claude 执行操作"] --> B["Hook 发 HTTP 请求"]
B --> C["Worker 收到数据"]
C --> D["立即返回 200 OK"]
D --> E["Claude 继续工作
(不等待)"]
C --> F["后台异步压缩
(用 Agent SDK)"]
F --> G["压缩完成 → 存入 DB"]
style D fill:#10b981,color:#fff
style E fill:#6366f1,color:#fff
style F fill:#f59e0b,color:#000Hook 给 Worker 发完数据后立刻返回,不阻塞 Claude 的工作流。Worker 在后台慢慢压缩、存储。这就是为什么你完全感知不到 Claude-Mem 的存在。
实操练习
- 在博客项目中开一个新的 Claude Code 会话
- 让 Claude 做以下操作:
- 读取一个文件
- 修改一个文件
- 运行一条命令
- 在另一个终端窗口中,实时监控 Worker 日志:
tail -f ~/.claude-mem/logs/worker.log
- 观察每次 Claude 操作后,Worker 日志中出现的处理记录
- 结束会话后,检查 session_summaries 表中是否生成了摘要
下期预告
钩子负责捕获数据,但数据的压缩、存储和搜索,都是 Worker Service 在做。下一期我们深入 Worker 的内部 —— 它运行在 localhost:37777,用 Bun 驱动,提供 10 个 API 端点。我们会逐一讲解。