真正自主的系统,不只在被问到时才思考——它在你看向别处时也在学习。
16.1 核心问题
第 15 章的 Coding Agent 虽然完整,但它有一个根本局限:它只在用户主动提问时才运作。用户输入 → Agent 响应 → 等待下一次输入。Agent 本身不会主动做任何事。
但现实中,有很多有价值的事情可以"顺手"完成:
用户刚问完"解释一下认证流程"
↓
对话历史里出现了新的架构知识
↓ ← 这里可以做什么?
等待下一条输入
用户离开 5 分钟回来
↓ ← 这里可以做什么?
"欢迎回来,您在继续实现登录功能..."
Agent 连续处理了 10 个会话后
↓ ← 这里可以做什么?
记忆文件里出现了大量重复条目
Claude Code 对这三个问题的回答,都用同一个设计模式解决:forked Agent。
16.2 原理讲解
16.2.1 forked Agent 模式
forked Agent 是一种设计模式,让主 Agent 在完成一轮工作后,"悄悄"启动一个与自己共享上下文的子 Agent 去做后台工作:
主 Agent 完成一轮(stop_reason = "end_turn")
│
├─ 响应展示给用户
│
└─ [同时/之后] fork 子 Agent(异步,不阻塞用户输入)
│
├─ 共享父 Agent 的 system prompt + messages(prompt cache 命中)
├─ 接收一条新的"任务指令"消息
└─ 独立执行(写文件、调 API、整理记忆...)
关键特性:
- 不阻塞:子 Agent 在后台运行,用户可以立即继续输入
- Cache 共享:子 Agent 的首次 API 请求和父 Agent 使用完全相同的 system prompt + messages 前缀,命中父 Agent 的 prompt cache,接近零额外成本
- 结果不回传:子 Agent 通过写文件或修改状态影响系统,不直接在对话中显示结果
16.2.2 CacheSafeParams:Cache 共享的关键
Anthropic API 的 prompt cache 键由以下五部分组成:
cache_key = hash(system_prompt + tools + model + messages_prefix + thinking_config)
forkedAgent.ts 定义了 CacheSafeParams,它精确携带这五个部分:
// src/utils/forkedAgent.ts
export type CacheSafeParams = {
systemPrompt: SystemPrompt // 与父 Agent 完全相同的 system prompt
userContext: { [k: string]: string }
systemContext: { [k: string]: string }
toolUseContext: ToolUseContext // 含 tools + model + thinkingConfig
forkContextMessages: Message[] // 父 Agent 的完整消息历史(作为前缀)
}
每轮结束后,saveCacheSafeParams() 保存当前参数到模块级变量。子 Agent 通过 getLastCacheSafeParams() 取出,确保参数与父 Agent 一致。
16.2.3 触发机制:postSamplingHooks
src/utils/hooks/postSamplingHooks.ts 提供了一个事件系统:在主 Agent 每轮采样完成后调用所有注册的 Hook:
// 注册 Hook(在各模块的 init 函数中调用)
registerPostSamplingHook(async (ctx: REPLHookContext) => {
// ctx.messages = 完整对话历史
// ctx.systemPrompt = 当前 system prompt
// ctx.toolUseContext = 含工具列表和模型
// ... 在这里检测条件,决定是否 fork
})
// 每轮采样后由 QueryEngine 调用
executePostSamplingHooks(messages, systemPrompt, ...)
16.2.4 四个实际应用
Claude Code 用 forked Agent 模式实现了四个后台智能系统:
① extractMemories(记忆提取)
- 文件:
src/services/extractMemories/extractMemories.ts - 触发:每轮
stop_reason = "end_turn"(非工具调用轮) - 条件门:新增了 ≥ N 条消息 + 主 Agent 自身没有写过记忆
- 任务:fork 子 Agent,分析最近 N 条消息,找出值得长期保留的事实,写入
~/.claude/projects/<path>/memory/下的 Markdown 文件 - 工具限制:仅允许
Read、Glob、Grep、只读 Bash、Edit/Write(仅限记忆目录)
// 核心提示词策略(buildExtractAutoOnlyPrompt):
// "分析最近 ~N 条消息,找出值得持久化的事实,
// 第 1 轮:并行 Read 所有可能要更新的文件
// 第 2 轮:并行 Write/Edit 所有需要更改的文件
// 不要跑去验证源码,直接处理消息里的信息。"
② autoDream(记忆整合)
- 文件:
src/services/autoDream/autoDream.ts - 触发:时间门(≥24 小时)+ 会话数门(≥5 个新会话)
- 任务:fork 子 Agent,整合最近 N 个会话的所有记忆,去重、归类后重写
- Lock 机制:
consolidationLock.ts用文件锁防止多个进程同时整合
门控逻辑(cheapest-first):
1. stat(lock_file).mtime → 时间是否 ≥ minHours ← 最快,一次 stat()
2. readdir(sessions/) → 新会话数 ≥ minSessions ← 次快,一次 readdir
3. try_acquire_lock() → 无其他进程在整合 ← 需要 atomic write
③ Magic Docs(文档自动更新)
- 文件:
src/services/MagicDocs/magicDocs.ts - 触发:
FileReadTool读取到含# MAGIC DOC: [title]首行的文件时注册监听;每轮 postSamplingHook 检查已注册的文件 - 任务:fork 子 Agent,根据当前对话上下文,用
FileEditTool更新文档内容
<!-- ARCHITECTURE.md 的首行 -->
# MAGIC DOC: System Architecture
_Keep this document up to date with key architectural decisions._
<!-- Agent 读取此文件后,每轮结束自动检查是否需要更新 -->
④ Speculation(推测性预执行)
- 文件:
src/services/PromptSuggestion/speculation.ts - 触发:主 Agent 响应完毕,用户开始输入时(检测到键盘活动)
- 任务:预测用户下一条指令,提前执行只读工具(Read/Glob/Grep/LSP)
- 结果合并:若预测命中,把工具结果注入下一轮,节省 TTFT(首 Token 时间)
- 安全限制:只允许只读工具,禁止任何写操作
MAX_SPECULATION_TURNS = 20
SAFE_READ_ONLY_TOOLS = { 'Read', 'Glob', 'Grep', 'ToolSearch', 'LSP', ... }
WRITE_TOOLS = { 'Edit', 'Write', 'NotebookEdit' } ← 禁止
16.2.5 Away Summary(返回摘要)
Away Summary 不用 forked Agent,而是直接调用小模型:
// src/services/awaySummary.ts
const recent = messages.slice(-30) // 最近 30 条消息
recent.push(createUserMessage({ content: buildAwaySummaryPrompt(memory) }))
await queryModelWithoutStreaming({
model: getSmallFastModel(), // 小模型,省 Token
skipCacheWrite: true, // 一次性查询,不写缓存
// ...
})
触发条件:用户离开(最后活跃时间 > 阈值)再回来时,在 UI 上显示"您离开时正在做:..."的提示卡片。
16.2.6 为什么不用普通的 setTimeout?
后台任务用 forked Agent 而不是简单的 setTimeout + LLM 调用,原因是 prompt cache:
普通方式(无 cache 复用):
重新构建 system prompt ─────────────────────────────────── 全部 Token 入 cache
重新上传完整消息历史 ─────────────────────────────────── 全部 Token 入 cache
API 调用开销 ≈ 一次完整对话 ← 昂贵
forked Agent 方式(共享 parent cache):
复用父 Agent 的 system prompt + messages prefix
API 调用开销 ≈ 只有新增的任务指令(1 条消息) ← 接近免费
这使得后台任务的实际 API 成本极低,可以在每轮结束后都触发而不心疼。
16.3 源码索引
| 文件 | 关键函数/类型 | 作用 |
|---|---|---|
src/utils/forkedAgent.ts |
CacheSafeParams、runForkedAgent()、saveCacheSafeParams()、getLastCacheSafeParams() |
forked Agent 核心框架 |
src/utils/hooks/postSamplingHooks.ts |
registerPostSamplingHook()、executePostSamplingHooks()、REPLHookContext |
采样后 Hook 注册与执行 |
src/services/extractMemories/extractMemories.ts |
initExtractMemories()、createAutoMemCanUseTool() |
每轮记忆提取 |
src/services/extractMemories/prompts.ts |
buildExtractAutoOnlyPrompt() |
记忆提取子 Agent 的提示词 |
src/services/autoDream/autoDream.ts |
initAutoDream() |
定期记忆整合(时间+会话双门控) |
src/services/autoDream/consolidationLock.ts |
tryAcquireConsolidationLock()、readLastConsolidatedAt() |
文件锁防止并发整合 |
src/services/MagicDocs/magicDocs.ts |
detectMagicDocHeader()、initMagicDocs() |
Magic Doc 检测与自动更新 |
src/services/PromptSuggestion/speculation.ts |
runSpeculation()、SAFE_READ_ONLY_TOOLS |
推测性预执行(只读工具) |
src/services/awaySummary.ts |
generateAwaySummary() |
返回摘要(小模型,无 fork) |
16.4 最小化产出物
代码骨架位于
../chapters/16/src/,参考实现位于../chapters/16/solution/。 前置条件:需要ANTHROPIC_API_KEY环境变量(npm start需要)。
本章要实现什么
在 ../chapters/16/src/forked.ts 中完成 forked Agent 框架。
接口规范(已提供,不要修改):
export interface CacheSafeParams {
systemPrompt: string
messages: Anthropic.MessageParam[]
model: string
}
export type PostSamplingHook = (params: CacheSafeParams) => Promise<void>
export function registerPostSamplingHook(hook: PostSamplingHook): void
// 将 hook 追加到内部数组
export async function executePostSamplingHooks(params: CacheSafeParams): Promise<void>
// 并发执行所有 Hook,捕获每个 Hook 的错误
export async function runForkedAgent(
client: Anthropic,
cacheSafeParams: CacheSafeParams,
taskMessage: string,
label: string,
): Promise<string>
// 运行 forked 子 Agent,共享父 Agent 的 prompt cache
你需要实现:
registerPostSamplingHook():追加到 hooks 数组executePostSamplingHooks():Promise.all并发执行,每个 hook 用.catch()捕获错误runForkedAgent():- 构造 messages = [...cacheSafeParams.messages, { role: 'user', content: taskMessage }]
- 调用 client.messages.create(使用父 Agent 的 systemPrompt 和 model)
- 提取文本内容并返回
关键约束:
executePostSamplingHooks()中单个 Hook 失败不能影响其他 HookrunForkedAgent()使用与父 Agent 完全相同的 systemPrompt + messages 前缀,这是命中 prompt cache 的关键
验收
cd docs/chapters/16
npm install
npm test
卡住时查看 ../chapters/16/solution/forked.ts。
16.5 本章小结
forked Agent 的本质
forked Agent 不是一个新的架构概念,而是对两个已知问题的组合解:
- 后台任务:用异步不阻塞的方式触发附加工作
- Cache 共享:通过复用父 Agent 的消息前缀,让附加工作接近免费
设计约束 解决方案
──────────────────────────────────────────────
不能阻塞用户输入 → void Promise(fire and forget)
后台 LLM 调用成本高 → 共享 prompt cache(CacheSafeParams)
后台任务可能并发 → 文件锁(consolidationLock)
后台任务需要访问工具 → 受限的 canUseTool(仅只读 + 记忆目录)
结果不直接显示给用户 → 写文件(MEMORY.md / Magic Doc)
四个系统的对比
| 系统 | 触发时机 | 频率 | 成本 | 结果 |
|---|---|---|---|---|
| extractMemories | 每轮 end_turn | 高 | 极低(cache 共享) | MEMORY.md 条目 |
| autoDream | 24h + 5 会话 | 极低 | 中(整合多文件) | MEMORY.md 重写 |
| Magic Docs | 每轮(有追踪文件时) | 中 | 低(cache 共享) | 文档文件更新 |
| Speculation | 用户开始输入时 | 高 | 低(只读工具) | 工具结果预加载 |
| Away Summary | 用户返回时 | 低 | 低(小模型) | UI 提示卡片 |
进化路径
第 7 章 Agentic Loop ────────────────────────── 主动响应用户请求
+
第 13 章 AgentTool ──────────────────────────── 并行执行子任务
+
第 16 章 forked Agent ───────────────────────── 后台持续学习与优化
↓
真正自主的 Coding Agent:
- 执行任务(第 7 章)
- 并行分解(第 13 章)
- 持续进化(第 16 章)
这是整个系列的终点:从"能响应"到"会学习",从"工具调用"到"持续智能"。
验收命令
cd chapters/16
npm install
# 启动 Agent
npx tsx src/main.ts
# 场景 A:记忆提取验收
# 输入:"我们项目用 React 18 + TypeScript 5,后端 FastAPI,数据库 PostgreSQL + Redis"
# 等待 Agent 回复后,查看记忆文件:
cat ~/.mini-agent/memory/MEMORY.md
# 预期:自动写入了技术选型相关的记忆条目
# 再次输入:"我们的测试框架用 Vitest,CI/CD 用 GitHub Actions"
# 再次查看记忆(应追加新条目):
cat ~/.mini-agent/memory/MEMORY.md
# 场景 B:Magic Doc 验收
# 输入(第一次):"请读取 src/ARCHITECTURE.md 文件"
# → Agent 读取后,后台检测到 MAGIC DOC 头,注册追踪
# 输入:"我们采用六边形架构,核心域不依赖任何框架,通过 Adapter 对接外部系统"
# 等待 Agent 回复后,查看文档是否被自动更新:
cat src/ARCHITECTURE.md
# 预期:ARCHITECTURE.md 内容已根据对话自动更新
# 重启验证记忆持久化
# 输入 /exit 退出,重新运行:
npx tsx src/main.ts
# 预期:启动时显示"已有记忆",Agent 在回答中体现对之前技术选型的记忆
进阶选修章结语
至此,整个系列的 16 章全部完成。
你从一个空白的
main.ts出发,经历了进程 I/O、终端 UI、CLI 路由、工具引擎、流式 LLM、对话管理、Agentic Loop、权限系统、上下文注入、记忆持久化、会话存档、斜杠命令、多 Agent 调度、MCP 插件、完整集成,最终到达后台持续学习。这不只是 Claude Code 的源码解读——这是一幅现代 AI Agent 系统的完整架构图。