Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Learn Claude Code Notes

The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.

模型是智能体,代码是 harness。构建优秀的 harness,智能体自会完成剩余工作。

关于本书

这是 learn-claude-code 项目的中文学习笔记。

原文档learn-claude-code/docs/zh/

核心思想

Agent = Model(模型即智能体)

Harness = Tools + Knowledge + Context + Permissions(工具 + 知识 + 上下文 + 权限)

Product = Agent + Harness(完整产品 = 智能体 + 环境)

学习路径

本书分为 4 个阶段,共 12 个 session:

阶段Sessions主题递进关系
Phase 1s01-s02The Loop - 基础循环与工具建立模型与世界的连接
Phase 2s03-s06Planning & Knowledge - 规划与知识管理让单 Agent 在有限上下文窗口内高效
Phase 3s07-s08Persistence - 持久化与并行为多 Agent 协作提供共享任务状态
Phase 4s09-s12Teams - 多 Agent 协作在共享状态上实现团队沟通与自组织

四阶段递进关系:

Phase 1: 循环基础
   ↓
Phase 2: 上下文管理 ──→ 单 Agent 能力完善
   ↓
Phase 3: 任务持久化 ──→ 为多 Agent 协作提供基础设施(任务图 + 并行执行)
   ↓
Phase 4: 多 Agent 协作 ──→ 基于共享状态的团队沟通、协议、自治

详细总结见:总结

核心概念

Agent、Model、Harness 的关系

这是学习本项目前最重要的概念区分:

Agent = Model(模型本身就是智能体)

Harness = Tools + Knowledge + Context + Permissions(工具 + 知识 + 上下文 + 权限)

Product = Agent + Harness(完整产品 = 智能体 + 环境)

层次图

┌─────────────────────────────────────────────────────────┐
│              Claude Code (完整产品)                        │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                Harness (环境/工具层)                   │ │
│  │  ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│  │  │  Tools  │ │ Knowledge│ │ Context  │ │Permission│ │ │
│  │  │ bash,   │ │ SKILL.md │ │ compression│ │ sandbox │ │ │
│  │  │ read... │ │ docs     │ │ subagent │ │ approval│ │ │
│  │  └─────────┘ └──────────┘ └──────────┘ └─────────┘ │ │
│  └─────────────────────────────────────────────────────┘ │
│                          +                                │
│  ┌─────────────────────────────────────────────────────┐ │
│  │              Agent = Model (智能体层)                  │ │
│  │           Claude (Anthropic 训练的 LLM)              │ │
│  │   - 决定何时调用工具                                  │ │
│  │   - 决定何时停止                                      │ │
│  │   - 推理和决策                                        │ │
│  └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

关键区分

概念是什么谁构建例子
Model训练出来的神经网络权重Anthropic/OpenAIClaude, GPT-4
Agent= Model(模型本身就是智能体)训练出来“Claude 这个模型”
Harness给 Agent 提供的环境/工具应用开发者Claude Code 的代码部分
ProductAgent + Harness产品团队Claude Code CLI

The Agent Pattern

所有 session 都基于这个核心循环:

flowchart TD
    A[User] --> B["messages[]"]
    B --> C[LLM]
    C --> D{"stop_reason == tool_use?"}
    D -->|yes| E[execute tools]
    D -->|no| F[return text]
    E --> G[append results]
    G --> B

Harness 的组成

Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions

    Tools:          file I/O, shell, network, database, browser
    Knowledge:      product docs, domain references, API specs, style guides
    Observation:    git diff, error logs, browser state, sensor data
    Action:         CLI commands, API calls, UI interactions
    Permissions:    sandboxing, approval workflows, trust boundaries

本项目的定位

This repository teaches you to build vehicles.

这个项目教你构建车辆(harness),而不是训练司机(agent)。

你学到的技能:

  • ✅ 实现工具(给 Agent 手和脚)
  • ✅ 管理上下文(给 Agent 清晰的记忆)
  • ✅ 控制权限(给 Agent 安全的边界)
  • ✅ 持久化任务(给 Agent 长远的目标)

需要做的:

  • ❌ 训练模型
  • ❌ 调整模型权重
  • ❌ 设计神经网络架构

Phase 1: The Loop

One loop & Bash is all you need

阶段概述

Phase 1 建立最核心的 Agent 循环和工具分发机制。这是所有后续 session 的基础。

Sessions

s01: The Agent Loop (Agent 循环)

一句话总结

“One loop & Bash is all you need” – 一个工具 + 一个循环 = 一个 Agent。

关键问题

1. Agent Loop 的核心思想是什么?

核心:建立模型与真实世界的连接。语言模型本身只能推理,无法执行实际操作。通过一个简单的循环,将模型的 tool_use 请求与实际执行连接起来。

2. Agent Loop 的工作流程?

+--------+      +-------+      +---------+
|  User  | ---> |  LLM  | ---> |  Tool   |
| prompt |      |       |      | execute |
+--------+      +---+---+      +----+----+
                    ^                |
                    |   tool_result  |
                    +----------------+

步骤

  1. 用户 prompt 作为第一条消息加入 messages 列表
  2. 发送 messages + tools 定义给 LLM
  3. 追加助手响应到 messages
  4. 如果 stop_reason != "tool_use",循环结束
  5. 执行每个 tool_use,收集结果
  6. 将结果作为 user 消息追加到 messages
  7. 回到步骤 2

3. stop_reason 的作用?

控制循环退出

  • stop_reason == "tool_use":模型请求调用工具,继续循环
  • stop_reason != "tool_use":模型完成工作,退出循环
if response.stop_reason != "tool_use":
    return  # 退出循环

4. 消息列表如何累积?

messages = [{"role": "user", "content": query}]

# 循环中:
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results})

工具结果格式

{
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": output,
}

❓ 易误解点

Q:stop_reason 是工具执行后返回的吗?

A:不是。stop_reason 是 LLM API 响应的一部分,由模型决定是否需要调用工具。工具执行后返回的是 tool_result,不会改变 stop_reason

🔗 原文位置

s02: Tool Use (工具使用)

一句话总结

“Adding a tool means adding one handler” – 加一个工具只加一个 handler,循环不用改,新工具注册进 dispatch map 就行。

关键问题

1. 为什么需要专用工具?

只有 bash 的问题

  • cat 截断不可预测
  • sed 遇到特殊字符容易出错
  • 每次 bash 调用都是不受约束的安全面
  • 无法在工具层面做路径沙箱

专用工具的优势

  • read_file: 可以控制读取行数(limit 参数)
  • write_file: 自动创建目录,路径安全检查
  • edit_file: 精确文本替换,避免 sed 的转义问题
  • 所有文件操作都经过 safe_path() 沙箱检查

2. Dispatch Map 的作用?

作用:将工具名映射到处理函数,避免 if/elif 链式判断。

TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

使用方式

handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"

优势

  • O(1) 时间复杂度查找
  • 工具注册简单:只需在字典中添加一项
  • 循环体完全不需要修改

3. 路径沙箱 (safe_path) 如何工作?

作用:防止文件操作逃逸工作区,增强安全性。

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

示例

safe_path("file.txt")      # OK: /workspace/file.txt
safe_path("../secret.txt") # Error: Path escapes workspace

🔗 原文位置

Phase 2: Planning & Knowledge

An agent without a plan drifts

阶段概述

Phase 2 解决一个核心问题:如何在有限上下文窗口内高效工作

核心策略:磁盘与上下文的分工——把不急需的数据放磁盘,上下文只保留当前需要的内容。

  • s03 TodoWrite - 进度存磁盘,不占上下文
  • s04 Subagents - 探索性工作隔离到独立上下文
  • s05 Skills - 知识存磁盘,按需加载
  • s06 Context Compact - 历史存磁盘,当前内容压缩

Sessions

s03: TodoWrite (待办写入)

一句话总结

“An agent without a plan drifts” — 没有计划的 agent 走哪算哪。通过带状态的 TodoManager 强制顺序聚焦,连续 3 轮不更新计划就注入 nag reminder 制造问责压力。

关键问题

1. 为什么需要 TodoWrite?

问题:多步任务中模型会丢失进度 – 重复做过的事、跳步、跑偏。对话越长越严重,工具结果不断填满上下文,系统提示的影响力被稀释。

解决:TodoManager 提供外部记忆,将进度持久化到磁盘(不在上下文里)。Agent 不需要翻阅历史消息来回忆「做到哪了」,减少了对上下文的依赖。

2. TodoManager 的核心约束是什么?

同一时间只允许一个 in_progress – 强制顺序聚焦,避免多任务并行导致的注意力分散。

if in_progress_count > 1:
    raise ValueError("Only one task can be in_progress")

3. Nag Reminder 的工作机制?

Nag = 唠叨(英文:不停催促某人做某事)

触发条件:模型连续 3 轮以上不调用 todo 工具。

注入方式:在最后一个 user message 的 content 列表头部插入 <reminder> 标签。

if rounds_since_todo >= 3 and messages:
    last = messages[-1]
    if last["role"] == "user" and isinstance(last.get("content"), list):
        last["content"].insert(0, {
            "type": "text",
            "text": "<reminder>Update your todos.</reminder>",
        })

🔗 原文位置

s04: Subagents (子代理)

一句话总结

“Break big tasks down; each subtask gets a clean context” — 大任务拆小,每个小任务干净的上下文。Subagent 以 messages=[] 启动,运行独立循环,只返回摘要文本,保持父 Agent 上下文清洁。

关键问题

1. 为什么需要 Subagents?

问题:Agent 工作越久,messages 数组越胖。每次读文件、跑命令的输出都永久留在上下文里。

场景:“这个项目用什么测试框架?” 可能要读 5 个文件,但父 Agent 只需要一个词:“pytest”。

解决:Subagent 承担探索性工作,只返回浓缩的摘要。

2. Subagent 的上下文隔离如何实现?

独立 messages 数组

def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]  # 从零开始
    for _ in range(30):  # safety limit
        response = client.messages.create(...)
        sub_messages.append({"role": "assistant", "content": response.content})
        # ... 执行工具,追加结果 ...
    # 整个消息历史直接丢弃,只返回摘要
    return "".join(b.text for b in response.content if hasattr(b, "text"))

父 Agent 视角:Subagent 只是一个返回字符串的 task 工具。

3. 工具权限如何控制?

父 Agent 工具 = 子 Agent 工具 + task 工具

PARENT_TOOLS = CHILD_TOOLS + [
    {"name": "task",
     "description": "Spawn a subagent with fresh context.",
     "input_schema": {...}}
]

禁止递归:Subagent 没有 task 工具,防止无限生成子代理。

❓ 易误解点

Q:Subagent 的消息历史会保留吗?

A:不会。Subagent 的完整 messages[] 在运行结束后直接丢弃,只返回最终的摘要文本给父 Agent。这是保持父 Agent 上下文清洁的关键。

🔗 原文位置

s05: Skills (技能加载)

一句话总结

“Load knowledge when you need it, not upfront” — 用到什么知识,临时加载什么知识。两层注入策略:系统提示只放 Skill 名称(便宜),tool_result 按需加载完整内容(贵)。

关键问题

1. 为什么需要 Skills 机制?

问题:希望 Agent 遵循特定领域工作流(git 约定、测试模式、代码审查清单)。全塞进系统提示太浪费 – 10 个 Skill,每个 2000 token,就是 20,000 token,大部分跟当前任务无关。

解决:按需加载,模型开口要时才给。

2. 两层注入策略是什么?

Layer 1 - 系统提示(常驻,低成本)

You are a coding agent.
Skills available:
  - git: Git workflow helpers        ~100 tokens/skill
  - test: Testing best practices

Layer 2 - tool_result(按需,高成本)

<skill name="git">
  Full git workflow instructions...  ~2000 tokens
  Step 1: ...
</skill>

3. 技能文件如何组织?

skills/
  pdf/
    SKILL.md       # YAML frontmatter + 正文
  code-review/
    SKILL.md

设计:用目录名作为 Skill 标识,YAML frontmatter 存元数据(name, description),正文是完整指令。

🔗 原文位置

s06: Context Compact (上下文压缩)

一句话总结

“Context will fill up; you need a way to make room” — 上下文总会满,要有办法腾地方。三层压缩策略为无限会话腾出空间。

关键问题

1. 三层压缩分别指什么?

三层压缩

  1. Micro Compact(微型压缩): 替换旧 tool_result 为占位符(每轮静默执行)
  2. Auto Compact(自动压缩): token 超限时,保存对话 + LLM 总结(自动触发)
  3. Manual Compact(手动压缩): 模型调用 compact 工具时执行压缩(手动触发)

关键理解:Layer 2 和 Layer 3 是同一套逻辑(都调用 auto_compact),区别只是触发方式(自动 vs 手动)。

2. Micro Compact 做了什么?

目标:替换旧的 tool_result 为占位符,减少 token 消耗。

策略

  • 保留最近 3 个 tool_result(KEEP_RECENT = 3
  • 更早的结果替换为 "[Previous: used {tool_name}]"
  • 例外read_file 的结果不压缩(原文档未提及——因为文件内容是参考资料,压缩后会导致模型重新读文件,浪费 token)
def micro_compact(messages: list) -> list:
    # 遍历所有 user message 中的 tool_result
    for msg_idx, msg in enumerate(messages):
        if msg["role"] == "user" and isinstance(msg.get("content"), list):
            for part_idx, part in enumerate(msg["content"]):
                if isinstance(part, dict) and part.get("type") == "tool_result":
                    # 压缩 tool_result 内容
                    # ...

防御性编程:使用 isinstance 检查类型,避免崩溃。

3. Auto Compact 何时触发?

触发条件:当 estimate_tokens(messages) > 50000 时自动触发。

执行流程

  1. 保存对话:完整消息历史保存到 .transcripts/transcript_{timestamp}.jsonl
  2. LLM 总结:让模型总结对话(包含:已完成什么、当前状态、关键决策)
  3. 替换消息:所有 messages 替换为一条摘要消息
def auto_compact(messages: list) -> list:
    # 1. 保存完整对话到磁盘
    TRANSCRIPT_DIR.mkdir(exist_ok=True)
    transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
    # 2. 让 LLM 总结对话
    summary = client.messages.create(..., 
        "Summarize this conversation for continuity. Include: "
        "1) What was accomplished, 2) Current state, 3) Key decisions made.")
    # 3. 替换所有消息为摘要
    return [{"role": "user", 
             "content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}"}]

效果

压缩前:messages = [msg1, msg2, ..., msg100]  # ~50000 tokens
压缩后:messages = [summary_msg]              # ~500 tokens

4. Manual Compact 如何触发?

触发方式:模型主动调用 compact 工具。

# 模型调用 compact 工具
{"type": "tool_use", "name": "compact", "input": {"focus": "当前任务进度"}}

# agent_loop 检测到 manual_compact
if manual_compact:
    print("[manual compact]")
    messages[:] = auto_compact(messages)  # 和 Auto Compact 相同逻辑
    return  # 压缩后退出,下一轮从摘要继续

使用场景

  • 用户主动要求:“上下文太长了,压缩一下”
  • 模型判断需要压缩时

5. Message Schema 详解?(补充)

为什么需要理解这个?

micro_compact 需要用 isinstance 检查,因为 API 的 content 类型是动态的:

Message 结构

{
    "role": "user" | "assistant",
    "content": str | list  # 字符串或 ContentBlock 列表
}

ContentBlock 类型

位置type说明content 类型
Usertext文本消息字符串
Userimage图片对象 (base64)
Userdocument文档 (PDF/TXT)对象 (base64/text)
Usertool_result工具结果字符串 列表
Assistanttext文本回复字符串
Assistanttool_use工具调用对象

嵌套层级(最多 3 层):

Level 0: message.content (列表)
   │
   └─ Level 1: content[n] (ContentBlock)
         │
         └─ tool_result.content (列表,仅多模态时)
               │
               └─ Level 2: content[m] (原子元素,不能再是列表)

为什么 micro_compact 需要 isinstance 检查?

  • content 可能是字符串(普通消息)或列表(多工具/多模态)
  • tool_result.content 可能是字符串或列表(多模态结果)
  • 需要安全地遍历,避免对非列表类型调用列表操作

6. 三层压缩流程图?

每轮循环
   │
   ├─→ [Layer 1: micro_compact] ──→ 替换旧 tool_result 为占位符(静默)
   │
   ├─→ [检查:tokens > 50000?]
   │       │
   │       ├─ no → 继续
   │       │
   │       └─ yes → [Layer 2: auto_compact]
   │                   ├─ 保存到 .transcripts/
   │                   ├─ LLM 总结
   │                   └─ 替换 messages 为摘要
   │
   └─→ [模型调用 compact 工具?]
           │
           └─ yes → [Layer 3: manual compact]
                       └─ 同 Layer 2 逻辑

📚 官方文档参考


❓ 易误解点

Q:message.content 是什么类型?

A:可能是字符串或列表。当一次调用多个工具时,结果是列表;普通文本消息是字符串。

Q:tool_result.content 是什么类型?

A:同样可能是字符串或列表。多模态结果(如图片 + 文字)是列表,纯文本结果是字符串。micro_compact 需要用 isinstance 检查来安全处理这两种情况。

Q:为什么 read_file 的结果不压缩?

A:因为 read_file 的输出是参考资料(文件内容),压缩后会丢失信息,导致模型需要重新读取文件,反而浪费 token。这是代码中的设计巧思,原文档未提及。

🔗 原文位置

Phase 3: Persistence(持久化)

Break big goals into small tasks, order them, persist to disk

阶段概述

Phase 3 解决两个问题:

  1. 如何让任务图持久化到磁盘?(s07 Task System)
  2. 如何让慢操作不阻塞 Agent?(s08 Background Tasks)

与 Phase 2 的区别:

  • Phase 2 的磁盘 = 暂存(进度/知识/历史,可重建)
  • Phase 3 的磁盘 = 状态(任务图/依赖关系,丢失会导致协作失败)

Sessions

s07: Task System (任务系统)

一句话总结

“Break big goals into small tasks, order them, persist to disk” — 大目标拆成小任务,排好序,记在磁盘上。文件持久化的任务图(DAG),用 blockedBy 表达依赖关系,为多 Agent 协作打基础。

关键问题

1. 为什么需要 Task System?

s03 TodoManager 的局限

  • 扁平清单,没有顺序、没有依赖
  • 只活在内存里,上下文压缩(s06)后丢失
  • 状态只有“做完/没做完“

真实需求:任务 B 依赖任务 A,任务 C 和 D 可以并行,任务 E 要等 C 和 D 都完成。

2. 任务图如何表示?

每个任务是一个 JSON 文件

{
  "id": 4,
  "subject": "Write tests",
  "status": "pending",
  "blockedBy": [2, 3],
  "owner": ""
}

DAG 示例

                 +----------+
            +--> | task 2   | --+
            |    | pending  |   |
+----------+     +----------+    +--> +----------+
| task 1   |                          | task 4   |
| completed| --> +----------+    +--> | blocked  |
+----------+     | task 3   | --+     +----------+
                 | pending  |
                 +----------+

3. 依赖如何解除?

完成任务时自动解锁后续任务

def _clear_dependency(self, completed_id):
    for f in self.dir.glob("task_*.json"):
        task = json.loads(f.read_text())
        if completed_id in task.get("blockedBy", []):
            task["blockedBy"].remove(completed_id)
            self._save(task)  # 自动解锁

4. 任务图回答的三个问题?

  • 什么可以做?status == "pending"blockedBy == []
  • 什么被卡住?blockedBy 非空
  • 什么做完了?status == "completed"

🔗 原文位置

s08: Background Tasks (后台任务)

一句话总结

“Run slow operations in the background; the agent keeps thinking” — 慢操作丢后台,agent 继续想下一步。守护线程跑命令,完成后注入通知队列,循环保持单线程,只有子进程 I/O 被并行化。

关键问题

1. 为什么需要 Background Tasks?

问题:有些命令要跑几分钟(npm installpytestdocker build)。阻塞式循环下模型只能干等。

场景:用户说 “装依赖,顺便建个配置文件”,阻塞模式下 Agent 只能一个一个来。

解决:后台执行 + 通知队列,Agent 可以继续做其他事。

2. 通知队列如何工作?

class BackgroundManager:
    def __init__(self):
        self.tasks = {}
        self._notification_queue = []  # 线程安全的队列
        self._lock = threading.Lock()

执行流程

  1. run() 启动守护线程,立即返回
  2. 子进程完成后,结果进入通知队列
  3. 每次 LLM 调用前排空队列,注入到 messages

3. 结果如何注入?

def agent_loop(messages: list):
    while True:
        # 1. 排空通知队列
        notifs = BG.drain_notifications()
        if notifs:
            notif_text = "\n".join(
                f"[bg:{n['task_id']}] {n['result']}" for n in notifs)
            messages.append({"role": "user",
                "content": f"<background-results>\n{notif_text}\n</background-results>"})
        
        # 2. 正常 LLM 调用
        response = client.messages.create(...)

时间线

Agent --[spawn A]--[spawn B]--[other work]----
             |          |
             v          v
          [A runs]   [B runs]      (并行执行)
             |          |
             +-- results injected before next LLM call --+

🔗 原文位置

Phase 4: Teams

When the task is too big for one, delegate to teammates

阶段概述

Phase 4 实现多 Agent 协作系统:

  1. s09: 创建队友,建立通信机制
  2. s10: 定义通信协议和状态机
  3. s11: 队友自主认领任务
  4. s12: worktree 隔离执行环境

Sessions

s09: Agent Teams (Agent 团队)

一句话总结

“When the task is too big for one, delegate to teammates” — 任务太大,分给队友。Lead + teammates + JSONL 收件箱,建立多 Agent 协作基础。

关键问题

1. 发送和读取权限?

每个 agent 可以:
✓ 给任何 teammate 发送消息(只要知道名字)
✓ 只能读取自己的 inbox

2. Lead 和队友的工具有何区别?

Lead 有 9 个工具

TOOLS = [
    {"name": "bash", ...}, {"name": "read_file", ...},
    {"name": "write_file", ...}, {"name": "edit_file", ...},
    {"name": "spawn_teammate", ...}, {"name": "list_teammates", ...},
    {"name": "send_message", ...}, {"name": "read_inbox", ...},
    {"name": "broadcast", ...},
]

队友只有 6 个工具(少了 spawn_teammatelist_teammatesbroadcast):

def _teammate_tools(self) -> list:
    return [
        {"name": "bash", ...}, {"name": "read_file", ...},
        {"name": "write_file", ...}, {"name": "edit_file", ...},
        {"name": "send_message", ...}, {"name": "read_inbox", ...},
    ]

3. 队友如何知道其他成员?

队友没有 list_teammates 工具,由 Lead 在 spawn 的 prompt 中告知:

spawn_teammate(
    name="alice",
    role="coder",
    prompt="You are alice, the coder. Bob is the reviewer. "
           "When you finish coding, send bob a message for review."
)

4. 完整通信图?

┌─────────────────────────────────────────────────────────┐
│                      Lead (领导)                         │
│  工具:spawn_teammate, list_teammates, send_message     │
│        read_inbox, broadcast                            │
└───────────────┬─────────────────────┬───────────────────┘
                │                     │
      send_message         send_message
      to="alice"           to="bob"
                │                     │
                v                     v
┌───────────────────────┐ ┌───────────────────────┐
│    Alice (Coder)      │ │   Bob (Reviewer)      │
│  工具:send_message   │ │  工具:send_message   │
│        read_inbox     │ │        read_inbox     │
└───────────────────────┘ └───────────────────────┘
                │                     │
                └──────────┬──────────┘
                           │
                 send_message
                 to="lead"
                           ↓
              ┌────────────────────────┐
              │  lead.jsonl (Lead 邮箱) │
              └────────────────────────┘

5. JSONL 的作用?

JSONL = 每行是一个完整 JSON。

{"type": "message", "from": "alice", "content": "hello"}
{"type": "message", "from": "bob", "content": "hi"}

优势:追加写入无需读改写整个文件。


🔗 原文位置

s10: Team Protocols (团队协议)

一句话总结

“Teammates need shared communication rules” — 队友需要共享通信规则。请求审批 FSM + 队友状态机,建立标准化协作协议。

关键问题

1. FSM 是什么?s10 中有哪几个 FSM?

FSM = Finite State Machine(有限状态机) — 系统在一组有限状态间转换的数学模型。

s10 中有两个 FSM

FSM状态流转归属
协议 FSMpending → approved/rejected请求(request)
Teammate FSMworking/idle → shutdown队友(teammate)

关系:协议 FSM 驱动 Teammate FSM

shutdown 请求 approved → teammate 状态变为 shutdown

2. 计划模式由谁决定触发和审批?

触发Teammate 自主决定(LLM 根据 prompt 判断)
审批Lead 自主决定 approve/reject
用户通过指挥 Lead 间接控制

3. 通信结构?

User → Lead ↔ Teammate 1
            ↕
       Teammate 2

❓ 易误解点

Q:s10 中有几个 FSM?

A:两个。一个是协议 FSM(请求状态:pending → approved/rejected),归属于请求本身;另一个是Teammate FSM(队友状态:working/idle → shutdown),归属于队友。协议 FSM 的转换会驱动 Teammate FSM 的转换。

🔗 原文位置

s11: Autonomous Agents (自治 Agent)

一句话总结

“Teammates scan the board and claim tasks themselves” — 队友扫描看板自己认领任务。IDLE 轮询 + 自动认领 + 身份重注入,实现自组织团队。

关键问题

1. 自治性的体现?

s10 vs s11 的核心区别

特性s10s11
任务分配Lead 手动指派队友自己扫描看板认领
空闲行为轮询收件箱 + 任务看板
工具集12 个14 个 (+idle, +claim_task)
超时机制60 秒空闲自动 shutdown

自治性体现

  • 队友进入 IDLE 状态后,自动扫描 .tasks/ 目录查找未认领的任务
  • 找到 pending 状态、无 owner、未被阻塞的任务时,自动调用 claim_task 认领
  • 无需 Lead 逐个分配任务,实现自组织

2. IDLE 阶段工作机制?

def _idle_poll(self, name, messages):
    for _ in range(IDLE_TIMEOUT // POLL_INTERVAL):  # 60s / 5s = 12 次
        time.sleep(POLL_INTERVAL)  # 每 5 秒轮询一次

        # 1. 检查收件箱
        inbox = BUS.read_inbox(name)
        if inbox:
            messages.append({"role": "user",
                "content": f"<inbox>{inbox}</inbox>"})
            return True  # 有新消息,恢复 WORK 状态

        # 2. 扫描任务看板
        unclaimed = scan_unclaimed_tasks()
        if unclaimed:
            claim_task(unclaimed[0]["id"], name)
            messages.append({"role": "user",
                "content": f"<auto-claimed>Task #{unclaimed[0]['id']}: "
                           f"{unclaimed[0]['subject']}</auto-claimed>"})
            return True  # 认领到任务,恢复 WORK 状态

    return False  # 60 秒超时,进入 SHUTDOWN

轮询内容

  1. 收件箱:检查是否有新消息
  2. 任务看板:扫描 .tasks/task_*.json 文件

认领条件

  • status == "pending"
  • owner 为空
  • blockedBy 为空

3. 身份重注入解决了什么问题?

问题:Context Compact (s06) 后,消息列表被压缩,Agent 可能忘记自己的身份。

实现

if len(messages) <= 3:  # 上下文过短
    messages.insert(0, {"role": "user",
        "content": f"<identity>You are '{name}', role: {role}, "
                   f"team: {team_name}. Continue your work.</identity>"})

4. 队友生命周期?

+-------+
| spawn |  ← Lead 创建
+---+---+
    |
    v
+-------+   tool_use     +-------+
| WORK  | <------------- |  LLM  |
+---+---+                +-------+
    |
    | stop_reason != tool_use
    v
+--------+
|  IDLE  |  每 5 秒轮询,持续最多 60 秒
+---+----+
    |
    +---> 收件箱有消息 -------------> 恢复 WORK
    |
    +---> 有未认领任务 -------------> 认领 -> 恢复 WORK
    |
    +---> 60 秒超时 ----------------> SHUTDOWN

🔗 原文位置

s12: Worktree Isolation (Worktree 隔离)

一句话总结

“Each works in its own directory, no interference” — 各干各的目录,互不干扰。任务管目标,worktree 管目录,用任务 ID 绑定,永不碰撞的并行执行通道。

关键问题

1. 为什么需要 Worktree 隔离?

问题:到 s11,Agent 已经能自主认领和完成任务。但所有任务共享一个目录。两个 Agent 同时重构不同模块 – A 改 config.py,B 也改 config.py,未提交的改动互相污染。

解决:给每个任务一个独立的 git worktree 目录,用任务 ID 把两边关联起来。

2. 控制平面与执行平面如何绑定?

控制平面 (.tasks/)                  执行平面 (.worktrees/)
+------------------+                +------------------------+
| task_1.json      |                | auth-refactor/         |
|   status: in_progress  <------>   branch: wt/auth-refactor
|   worktree: "auth-refactor"   |   task_id: 1             |
+------------------+                +------------------------+

绑定同时写入两侧状态:
def bind_worktree(self, task_id, worktree):
    task = self._load(task_id)
    task["worktree"] = worktree
    if task["status"] == "pending":
        task["status"] = "in_progress"  # 自动推进状态
    self._save(task)

3. Worktree 生命周期?

创建

WORKTREES.create("auth-refactor", task_id=1)
# -> git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD

执行

subprocess.run(command, shell=True, cwd=worktree_path, ...)

收尾(二选一):

  • worktree_keep(name) – 保留目录
  • worktree_remove(name, complete_task=True) – 删除目录 + 完成任务

4. 事件流的作用?

每个生命周期步骤写入 .worktrees/events.jsonl

{
  "event": "worktree.remove.after",
  "task": {"id": 1, "status": "completed"},
  "worktree": {"name": "auth-refactor", "status": "removed"},
  "ts": 1730000000
}

作用:崩溃后从 .tasks/ + .worktrees/index.json + events.jsonl 重建现场。

🔗 原文位置

总结

12 个 Session 回顾

Session主题一句话总结
s01The Agent LoopOne loop & Bash is all you need
s02Tool UseAdding a tool means adding one handler
s03TodoWriteAn agent without a plan drifts
s04SubagentsBreak big tasks down; each subtask gets a clean context
s05SkillsLoad knowledge when you need it, not upfront
s06Context CompactContext will fill up; you need a way to make room
s07Task SystemBreak big goals into small tasks, order them, persist to disk
s08Background TasksRun slow operations in the background; the agent keeps thinking
s09Agent TeamsWhen the task is too big for one, delegate to teammates
s10Team ProtocolsTeammates need shared communication rules
s11Autonomous AgentsTeammates scan the board and claim tasks themselves
s12Worktree IsolationEach works in its own directory, no interference

四个阶段

Phase 1: THE LOOP                    Phase 2: PLANNING & KNOWLEDGE
==================                   ==============================
s01  The Agent Loop                  s03  TodoWrite
     while + stop_reason                  TodoManager + nag reminder
     |                                    |
     +-> s02  Tool Use                  s04  Subagents
              dispatch map                   fresh messages[]
                                              |
                                         s05  Skills
                                              SKILL.md via tool_result
                                              |
                                         s06  Context Compact
                                              3-layer compression

Phase 3: PERSISTENCE                 Phase 4: TEAMS
==================                   =====================
s07  Tasks                           s09  Agent Teams
     file-based CRUD + deps graph        teammates + JSONL mailboxes
     |                                    |
s08  Background Tasks                s10  Team Protocols
     daemon threads + notify queue       shutdown + plan approval FSM
                                          |
                                     s11  Autonomous Agents
                                          idle cycle + auto-claim
                                     |
                                     s12  Worktree Isolation
                                          task coordination + isolated lanes

核心公式

Agent = Model(模型即智能体)

Harness = Tools + Knowledge + Context + Permissions

Product = Agent + Harness

The Agent Pattern

flowchart TD
    A[User] --> B["messages[]"]
    B --> C[LLM]
    C --> D{"stop_reason == tool_use?"}
    D -->|yes| E[execute tools]
    D -->|no| F[return text]
    E --> G[append results]
    G --> B

关键洞察

  1. 循环不变,工具插件化 – 从 s01 到 s12,Agent Loop 本身从未改变,变化的是周围的 harness 机制

  2. 上下文是稀缺资源 – s04(Subagent 隔离)、s05(Skills 按需加载)、s06(Context Compact)都在解决同一个问题:保持上下文清洁

  3. 持久化是协作的基础 – s07(Task System)和 s12(Worktree)将状态写入磁盘,使多 Agent 协作成为可能

  4. 状态机驱动协作 – s10-s12 用 FSM 管理队友状态和请求审批,实现自组织的团队


The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.