如果你正在学习 Agent 开发,最容易陷入的误区是:把大量精力放在 Prompt 上,却没有理解 Agent 的运行时机制。本文基于 learn-claude-code 项目从 s01 到 s20 的递进实现,围绕 Claude Code 类编码 Agent 的核心机制,完整拆解一个 Agent 应该如何从零构建。
引言:Agent 不是“会调用函数的聊天机器人”
普通聊天应用的流程通常只有一次请求和一次响应:
用户输入 → LLM → 文本回答
Agent 则不同。它不仅要回答问题,还要观察环境、制定计划、调用工具、读取结果、修正策略,并持续执行到目标完成:
用户目标
↓
模型判断下一步
↓
调用工具改变或观察环境
↓
读取工具结果
↓
继续判断下一步
↓
完成任务或请求用户介入
以 Claude Code 为代表的 Coding Agent,本质上是一个由大语言模型驱动、由宿主程序控制的运行时系统。模型提供推理和决策能力,程序提供工具、状态、权限、上下文和执行保障。
因此,构建 Agent 的关键问题不是“如何写一个万能 Prompt”,而是如何设计以下几个部分:
- Agent 如何循环运行;
- 模型如何认识并调用工具;
- 如何限制高风险行为;
- 如何规划和推进长任务;
- 如何管理有限的上下文窗口;
- 如何在多轮会话之间保留知识;
- 如何应对 API 和工具执行失败;
- 如何拆分子任务并组织多个 Agent;
- 如何隔离并行代码修改;
- 如何动态接入外部系统。
learn-claude-code 项目的价值,正是把这些问题拆成 20 个递进模块,让我们能够看到一个 Agent 从 100 多行代码逐渐演变为完整运行时的过程。
一、先建立正确的心智模型
一个可工作的 Agent,可以抽象成五个相互配合的部分。
flowchart LR
U["用户目标"] --> O["编排器 Orchestrator"]
O --> L["LLM 决策"]
L --> T["工具系统"]
T --> E["外部环境"]
E --> T
T --> O
O --> S["状态与上下文"]
S --> L
O --> G["权限与安全"]
G --> T
1. LLM 是决策器
模型负责理解目标、分析上下文,并决定下一步应该回答、读取文件、执行命令还是委派任务。
2. Orchestrator 是控制器
宿主程序负责调用模型、保存消息、分发工具、处理异常,并判断循环何时结束。项目中的 agent_loop 就承担了这个角色。
3. Tools 是 Agent 的手和眼睛
模型本身不能直接读取本地文件或执行 Shell。它只能输出结构化的工具调用请求,再由宿主程序执行。
4. State 是持续工作的基础
对话消息、Todo、Task、Memory、后台任务和团队邮箱都属于状态。没有外部状态,Agent 很难稳定完成长任务。
5. Guardrails 决定 Agent 的能力边界
权限检查、路径限制、危险命令拦截、用户确认和 Worktree 隔离,共同决定 Agent 可以做什么,以及在什么条件下可以做。
下面按照实际开发顺序逐层构建这些能力。
二、第一层:实现最小 Agent Loop
项目的 s01_agent_loop 展示了最核心的 Agent 循环。其逻辑可以简化为:
def agent_loop(messages):
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
)
messages.append({
"role": "assistant",
"content": response.content,
})
if response.stop_reason != "tool_use":
return
results = []
for block in response.content:
if block.type != "tool_use":
continue
output = execute_tool(block.name, block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({
"role": "user",
"content": results,
})
这个循环包含四个关键协议动作。
第一步:把工具描述交给模型
工具不是只在 Python 中注册一个函数。模型还需要看到工具名称、用途和参数 Schema:
TOOLS = [{
"name": "bash",
"description": "Run a shell command",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string"}
},
"required": ["command"],
},
}]
工具描述会影响模型是否选择这个工具、何时使用,以及生成什么参数。它既是接口文档,也是模型行为提示。
第二步:完整保存模型响应
一次响应中可能同时存在文本块和多个 tool_use 块。宿主程序应保存完整的 response.content,而不是只提取文本。
第三步:通过 ID 配对调用和结果
每个 tool_result 必须携带对应的 tool_use_id。这让模型知道某个结果属于哪个调用,也是后续上下文压缩必须维护的重要协议关系。
第四步:由模型决定何时停止
Agent 不应该在执行一次工具后立即结束。工具结果要重新交给模型,由模型判断任务是否完成、是否需要验证,或者是否需要继续行动。
这就是 Agent 与普通 Function Calling 的主要差异:Function Calling 可能只调用一次函数,Agent Loop 则会围绕目标持续运行。
三、第二层:设计可靠的工具系统
只有 Bash 工具的 Agent 可以工作,但它很难被精细控制。s02_tool_use 将能力拆分为:
bash:执行命令;read_file:读取文件;write_file:创建或覆盖文件;edit_file:精确替换文本;glob:查找文件。
这种拆分有三个好处。
1. 工具语义更清晰
模型可以明确表达“读取文件”而不是自己拼接 cat 命令。宿主程序也能区分只读操作与写操作。
2. 权限可以按工具分类
读取文件可以自动允许,写入文件可以检查路径,危险 Bash 命令可以要求确认。
3. 参数和输出更容易标准化
专用工具可以控制编码、分页、输出长度和错误格式,不必依赖不同操作系统的 Shell 行为。
工具系统需要四层结构
一个成熟的 Tool Runtime 不应只有函数映射,而应包含:
Tool Schema
↓
参数校验与兼容转换
↓
权限判断
↓
Handler 执行与结果规范化
可以用统一注册表描述:
TOOL_REGISTRY = {
"read_file": {
"schema": READ_FILE_SCHEMA,
"handler": run_read_file,
"permission": "read",
"timeout": 30,
},
}
项目早期模块直接使用:
output = handler(**block.input)
这种写法简单,但模型一旦生成 Schema 之外的参数,Python 就可能抛出 TypeError。项目中 read_file 的 offset 问题就是一个真实案例。
模型有时会根据训练知识生成 offset 参数,即使早期 Schema 没有声明它。s19_mcp_plugin 最终采用了更稳健的分发方式:
params = inspect.signature(handler).parameters
safe_input = {
key: value
for key, value in block.input.items()
if key in params
}
try:
output = handler(**safe_input)
except TypeError as exc:
output = f"Tool error: {exc}"
对于生产实现,还应该使用 Pydantic 或 JSON Schema Validator 在进入 Handler 前完成严格校验。
需要记住一条原则:模型生成的工具参数属于外部输入,不能因为它来自 LLM 就默认可信。
四、第三层:把安全控制放在执行路径上
模型能调用工具之后,下一个问题不是如何让它做得更多,而是如何让它只做被允许的事情。
s03_permission 将操作分为三类:
明确安全 → 自动执行
明确危险 → 直接拒绝
无法确定 → 请求用户确认
例如:
- 读取工作区内文件通常可以直接允许;
rm -rf /、shutdown等命令应该拒绝;- 删除普通项目文件、部署或覆盖配置可能需要用户确认。
权限检查必须发生在工具执行之前
正确顺序应当是:
模型生成 tool_use
↓
校验参数
↓
检查权限
↓
执行 Handler
↓
记录和返回结果
如果只在系统提示词里告诉模型“不要执行危险操作”,并不能构成真正的安全边界。Prompt 是行为引导,权限层才是执行约束。
用 Hooks 保持主循环稳定
s04_hooks 把权限、日志、输出处理和上下文注入从 Agent Loop 中抽离出来:
register_hook("PreToolUse", permission_hook)
register_hook("PostToolUse", log_hook)
register_hook("UserPromptSubmit", context_inject_hook)
register_hook("Stop", summary_hook)
这样可以形成类似中间件的扩展结构:
flowchart LR
A["tool_use"] --> B["PreToolUse Hooks"]
B -->|允许| C["Tool Handler"]
B -->|拒绝| D["Blocked tool_result"]
C --> E["PostToolUse Hooks"]
E --> F["tool_result"]
Hook 机制的意义不仅是代码整洁。它还让权限、审计、可观测性和策略控制能够独立演进,而不必不断修改 Agent Loop。
五、第四层:让 Agent 学会规划和管理任务状态
模型可以直接连续调用工具,但面对复杂任务时容易出现三个问题:
- 忘记原始目标;
- 执行到一半遗漏步骤;
- 用户无法判断当前进度。
s05_todo_write 引入结构化 Todo:
[
{"content": "分析项目结构", "status": "completed"},
{"content": "修改核心代码", "status": "in_progress"},
{"content": "运行测试", "status": "pending"}
]
Todo 并不是装饰性的 UI,而是 Agent 的外部工作记忆。它把模型当前的执行计划变成宿主程序可以检查的状态。
运行时可以据此实现:
- 多步骤任务开始前要求创建计划;
- 限制同时只能有一个
in_progress项; - 工具调用多轮后仍未更新计划时提醒模型;
- 结束前检查是否仍有未完成任务。
Todo 和 Task 的区别
项目在 s12_task_system 又引入了持久化 Task,两者承担不同角色:
| 类型 | 生命周期 | 作用 |
|---|---|---|
| Todo | 当前会话或当前任务 | 描述执行步骤和进度 |
| Task | 可跨会话持久化 | 描述可领取、可依赖的工作单元 |
Task 包含 ID、状态、Owner 和 blockedBy:
@dataclass
class Task:
id: str
subject: str
description: str
status: str
owner: str | None
blockedBy: list[str]
有了依赖关系,Agent 才能判断某项工作是否可以开始。这是后续多智能体调度的基础。
六、第五层:通过子 Agent 隔离复杂子任务
当主 Agent 的任务越来越复杂,把所有探索过程都塞进同一个上下文会产生大量噪声。
s06_subagent 增加了 task 工具。主 Agent 可以把一个子问题交给新的 Agent Loop:
主 Agent
│
├── 发送任务描述
▼
子 Agent:使用全新 messages 和受限工具集
│
├── 独立读取、分析、执行
▼
只返回最终摘要
为什么子 Agent 要使用独立上下文
如果子 Agent 继承主会话全部消息,它仍然会受到大量无关信息影响。全新上下文可以让它专注于一个具体目标。
为什么只返回摘要
文件搜索、命令输出和中间推理可能非常长。主 Agent 通常只需要结论、证据和建议,而不需要复制完整过程。
为什么要禁止无限递归
项目没有给子 Agent 暴露 task 工具,并设置最大运行轮次。否则子 Agent 可以不断生成新的子 Agent,造成成本和资源失控。
因此,一个好的委派工具至少应限制:
- 子 Agent 可使用的工具;
- 最大模型调用轮次;
- 最大 Token 或成本;
- 最长执行时间;
- 返回结果的格式;
- 是否允许继续委派。
七、第六层:用 Skills 扩展能力,而不是无限膨胀系统提示词
随着 Agent 支持的任务越来越多,把所有知识和操作流程都写进系统提示词会迅速消耗上下文。
s07_skill_loading 使用 skills/*/SKILL.md 保存技能。每个技能通过 Frontmatter 描述名称和用途:
---
name: code-review
description: Review code changes and identify correctness risks.
---
# Code Review Workflow
...
启动时,系统只把技能目录放进 Prompt:
- code-review: Review code changes and identify correctness risks.
- agent-builder: Build a minimal coding agent.
- mcp-builder: Create an MCP integration.
当模型确认需要某项技能后,再调用 load_skill 读取正文。
这是一种渐进式披露机制:
先暴露能力索引
↓
模型判断相关性
↓
按需加载完整说明
Skills 适合承载领域知识、操作步骤、质量标准和工具使用规范,但不应该取代代码层的安全控制。
八、第七层:管理上下文,而不是被上下文窗口管理
长时间运行的 Agent 会不断积累:
- 用户消息;
- 模型回答;
- Tool Use;
- Tool Result;
- 大段文件内容;
- Shell 输出;
- 子 Agent 摘要。
如果不做处理,请求最终会超过模型上下文限制,或者因为无关历史过多而降低决策质量。
s08_context_compact 实现了四层处理策略。
1. 大型输出外置
超长工具结果写入磁盘,上下文中只保留摘要和文件路径:
[Tool result stored at .task_outputs/tool_results/tool_123.txt]
Summary: test output, 18 failures...
2. Micro Compact
保留消息结构,但用占位内容替换较早的长工具结果:
[Earlier tool result compacted. Re-run if needed.]
3. Snip Compact
保留对话头部和尾部,删除中间低价值消息。
4. Summary Compact
让模型总结历史,只保留目标、已完成工作、关键决策、用户约束和剩余事项。
压缩时最容易踩的坑
tool_use 和 tool_result 是协议配对,不能从中间切断:
assistant: tool_use(id=abc)
user: tool_result(tool_use_id=abc)
如果只保留其中一条,API 会认为消息序列非法。因此压缩器必须识别消息结构,而不是简单按数组下标删除。
成熟的 Agent 应同时具备主动压缩和响应式压缩:前者在接近阈值时提前执行,后者在 API 返回 Prompt Too Long 后作为恢复措施执行。
九、第八层:区分会话上下文和持久记忆
上下文解决的是“本轮模型能看到什么”,记忆解决的是“下次会话还应该知道什么”。
s09_memory 把记忆保存为 .memory 中的 Markdown 文件,并用 MEMORY.md 建立索引。记忆被分为:
user:用户偏好;feedback:用户对 Agent 行为的纠正;project:项目结构、命令和约定;reference:外部参考资料。
记忆系统需要解决三个问题
写什么
不是所有对话都值得持久化。适合写入的内容通常具有跨任务复用价值,例如项目测试命令、用户明确偏好和重要架构决策。
什么时候写
可以在用户明确要求、任务完成、收到纠正反馈或检测到稳定项目事实时触发,但应避免模型未经确认大量写入推测性内容。
什么时候读
不应把全部记忆塞入每次请求。系统应该根据当前任务检索相关记忆,再注入动态系统提示词。
因此,Memory 的正确形态不是不断增长的聊天记录,而是可检索、可更新、带来源的长期知识库。
十、第九层:动态组装系统提示词
早期模块使用固定 Prompt:
SYSTEM = "You are a coding agent. Act, don't explain."
但真实 Agent 的系统提示词通常由多个部分组成:
身份与行为准则
+ 当前工作目录
+ 可用工具说明
+ 项目约束
+ 当前任务状态
+ 相关 Skills
+ 相关 Memories
+ 团队角色
+ Worktree 信息
s10_system_prompt 将这些内容拆成 Section,再根据运行时 Context 组装:
def assemble_system_prompt(context):
sections = [
PROMPT_SECTIONS["identity"],
PROMPT_SECTIONS["tools"],
PROMPT_SECTIONS["workspace"],
]
if context.get("memories"):
sections.append(
f"Relevant memories:\n{context['memories']}"
)
return "\n\n".join(sections)
动态 Prompt 的核心不是拼接字符串,而是确保模型看到的环境描述与实际运行时保持一致。例如切换 Worktree 后,系统提示词中的工作目录也必须更新。
十一、第十层:为失败设计恢复机制
一个只在理想条件下运行的 Agent 很容易实现。真正困难的是处理以下情况:
- API 限流或临时不可用;
- 请求超时;
- 模型输出达到最大 Token;
- Prompt 超过上下文窗口;
- 工具超时或返回异常;
- 模型调用未知工具;
- Handler 参数不兼容。
s11_error_recovery 加入了指数退避、最大重试次数、备用模型和输出续写。
不同错误需要不同策略
| 错误类型 | 推荐策略 |
|---|---|
| 429 / 临时服务错误 | 指数退避并重试 |
| 连续服务过载 | 切换备用模型或停止重试 |
max_tokens |
注入续写指令继续生成 |
| Prompt Too Long | 执行响应式压缩后重试 |
| 工具参数错误 | 转成 tool_result 让模型修正 |
| 危险操作 | 拒绝或请求用户批准 |
尤其要避免工具异常直接终止 Agent 进程。工具失败本身也是一种环境观察,应该尽可能结构化地反馈给模型,让模型选择修正参数或更换方案。
十二、第十一层:支持后台任务和时间调度
有些操作不适合同步阻塞 Agent Loop,例如启动服务器、运行大型测试或等待构建结果。
s13_background_tasks 把耗时操作放到后台线程,立即向模型返回任务 ID:
[Background task bg_123 started]
任务完成后,运行时把结果作为通知重新注入消息:
[Background task bg_123 completed]
Exit code: 0
Output: 128 tests passed
这样模型可以在等待期间继续处理其他工作。
s14_cron_scheduler 又加入了定时任务,使 Agent 可以被时间事件唤醒。调度系统需要管理:
- Cron 表达式校验;
- 一次性与重复任务;
- 持久化;
- 避免同一分钟重复触发;
- Agent 忙碌时排队;
- Agent 空闲时自动消费。
从架构上看,用户输入、后台任务完成和 Cron 到期都可以统一为事件,再交给 Agent Loop 处理。
十三、第十二层:从子 Agent 进化为多智能体系统
子 Agent 更像一次性的函数调用:接收任务,返回摘要。多智能体系统中的 teammate 则具有更长的生命周期,需要持续通信、领取任务和等待反馈。
s15_agent_teams 引入 MessageBus,为每个 Agent 建立邮箱:
Lead Agent ──message──> Reviewer
Lead Agent <──message── Reviewer
Lead Agent ──message──> Backend Agent
Lead Agent <──message── Backend Agent
只有消息总线还不够
如果消息只是自然语言文本,系统很难判断一条回复对应哪个请求。s16_team_protocols 因此增加:
- 消息类型;
request_id;- 计划提交;
- 计划批准或驳回;
- Shutdown 请求;
- 协议状态。
例如计划审批流程:
sequenceDiagram
participant L as Lead
participant T as Teammate
L->>T: request_plan(request_id, task)
T->>L: submit_plan(request_id, plan)
L->>T: review_plan(request_id, approve/feedback)
T->>T: 执行或修改计划
T->>L: 汇报结果
s17_autonomous_agents 进一步允许 teammate 自主领取 Task、推进状态并汇报结果。
多智能体的核心不是并发,而是协调
构建多 Agent 系统时,至少需要回答:
- 谁负责分配工作;
- 如何避免两个 Agent 重复领取任务;
- 如何表达任务依赖;
- 如何把响应匹配到请求;
- 如何暂停或终止 Agent;
- 如何处理 Agent 崩溃;
- 多个 Agent 修改代码时如何避免冲突。
线程只是执行载体,Task、Mailbox、Protocol 和 Isolation 才是协作系统的核心。
十四、第十三层:用 Git Worktree 隔离代码修改
多个 Agent 如果在同一个工作目录修改文件,会出现非常实际的冲突:
- 一个 Agent 覆盖另一个 Agent 的修改;
git status无法区分改动归属;- 测试结果受到其他未完成修改影响;
- Agent 删除或重写彼此正在使用的文件。
s18_worktree_isolation 使用 Git Worktree 为任务建立独立目录和分支:
主仓库
├── 当前工作区
└── .worktrees/
├── backend-task/ → branch: wt/backend-task
└── review-task/ → branch: wt/review-task
Teammate 的系统提示词、文件工具和 Bash cwd 都绑定到自己的 Worktree。
Worktree 生命周期需要保护
创建 Worktree 只是第一步,还应处理:
- 名称和分支冲突;
- Task 与 Worktree 的绑定;
- 未提交改动检查;
- 保留现场供人工检查;
- 明确授权后才能丢弃修改;
- 最终合并或 Cherry-pick。
对于 Coding Agent,文件系统隔离和 Git 状态隔离是并发安全的重要组成部分。
十五、第十四层:通过 MCP 动态扩展工具
如果所有工具都静态写进 Agent,接入数据库、文档平台、部署系统和企业内部服务会变得越来越困难。
MCP 的作用,是让外部服务以统一方式向 Agent 暴露工具和资源。
s19_mcp_plugin 演示了一个简化流程:
Agent 调用 connect_mcp("docs")
↓
MCP Client 获取工具列表
↓
将工具 Schema 合并到模型可见工具池
↓
将 Handler 合并到本地分发表
↓
后续轮次可以调用新工具
def assemble_tool_pool():
tools = list(BUILTIN_TOOLS)
handlers = dict(BUILTIN_HANDLERS)
for client in mcp_clients.values():
tools.extend(client.tool_schemas())
handlers.update(client.tool_handlers())
return tools, handlers
项目中的 docs 和 deploy MCP 服务是教学 Mock,但已经体现出动态工具发现的关键思想:Agent 的能力集合不必在启动时完全确定。
真实 MCP 接入还需要处理连接方式、鉴权、工具命名冲突、超时、断线重连、返回内容大小和高风险工具审批。
十六、把所有部分组合成完整 Agent Runtime
最终的 s20_comprehensive 把前面的机制组合到同一个运行时中。
flowchart TD
EVENT["用户输入 / Cron / 后台通知 / 团队消息"] --> CTX["准备运行时上下文"]
CTX --> COMPACT["上下文预算与压缩"]
COMPACT --> PROMPT["组装动态系统提示词"]
PROMPT --> CALL["调用 LLM"]
CALL --> DECISION{"是否包含 tool_use"}
DECISION -->|否| DONE["输出结果并更新状态"]
DECISION -->|是| VALIDATE["参数校验"]
VALIDATE --> PERMISSION["Hooks 与权限检查"]
PERMISSION --> DISPATCH["工具分发"]
DISPATCH --> BUILTIN["文件 / Bash / Todo / Task"]
DISPATCH --> AGENTS["Subagent / Teammate"]
DISPATCH --> ASYNC["后台任务 / Cron"]
DISPATCH --> MCP["MCP 工具"]
BUILTIN --> RESULT["标准化 tool_result"]
AGENTS --> RESULT
ASYNC --> RESULT
MCP --> RESULT
RESULT --> CTX
CALL -->|API 异常| RECOVERY["重试 / 降级 / 响应式压缩"]
RECOVERY --> CALL
从这个结构可以看出,一个完整 Agent Runtime 可以划分为八层:
| 层 | 职责 | 项目对应实现 |
|---|---|---|
| 模型层 | 推理并生成下一步行动 | Anthropic Messages API |
| 循环层 | 维护 tool-use 循环 | s01 |
| 工具层 | Schema、Handler 与结果 | s02、s19 |
| 治理层 | 权限、Hooks、审计 | s03、s04 |
| 状态层 | Todo、Task、Memory | s05、s09、s12 |
| 上下文层 | Skill、Prompt、压缩 | s07、s08、s10 |
| 可靠性层 | 重试、后台任务、调度 | s11、s13、s14 |
| 协作层 | 子 Agent、团队、Worktree、MCP | s06、s15–s19 |
十七、Agent 开发者应该遵循的实现顺序
如果你准备从零实现自己的 Agent,不建议一开始就开发多智能体平台。更合理的路线是:
阶段一:跑通最小闭环
- 接入一个模型;
- 实现
tool_use/tool_result循环; - 提供一个只读工具和一个执行工具;
- 保存完整消息历史。
完成标准:Agent 能自己读取信息、调用工具并继续推理,直到给出结果。
阶段二:建立工具治理
- 工具 Schema 与 Handler 统一注册;
- 参数严格校验;
- 路径限制;
- 权限分级;
- 超时和输出长度限制;
- 所有异常转成结构化结果。
完成标准:错误工具调用不会导致主循环崩溃,高风险操作无法绕过权限层。
阶段三:支持复杂任务
- Todo;
- Task 和依赖;
- 子 Agent;
- Skills;
- 动态系统提示词。
完成标准:Agent 能拆解任务、委派子问题,并保持清晰进度。
阶段四:解决长时间运行问题
- 上下文预算;
- 大结果外置;
- 压缩和摘要;
- 持久记忆;
- API 重试和降级;
- 后台任务。
完成标准:Agent 可以连续工作较长时间,不因上下文或临时错误轻易中断。
阶段五:扩展为协作系统
- MessageBus;
- Agent 生命周期;
- 协议和请求匹配;
- Worktree 隔离;
- MCP 动态工具。
完成标准:多个 Agent 能在隔离环境中分工,并把结果可靠地交还给协调者。
十八、当前项目给出的工程启示
learn-claude-code 是一个教学项目。它通过复制上一阶段代码并增加新能力,使读者能够直观看到每一步变化,但这种结构不适合直接作为生产框架。
如果继续工程化,建议从 s20 中拆出以下组件:
agent_runtime/
├── loop.py # Agent Loop
├── model_client.py # 模型调用与错误恢复
├── tools/
│ ├── registry.py # 工具注册
│ ├── validation.py # 参数校验
│ ├── permissions.py # 权限策略
│ └── executor.py # 工具执行
├── context/
│ ├── prompt.py # 动态 Prompt
│ ├── compaction.py # 上下文压缩
│ ├── memory.py # 长期记忆
│ └── skills.py # Skills
├── tasks/
│ ├── todo.py
│ ├── task_store.py
│ ├── background.py
│ └── scheduler.py
├── agents/
│ ├── subagent.py
│ ├── team.py
│ ├── protocol.py
│ └── mailbox.py
├── isolation/
│ └── worktree.py
└── integrations/
└── mcp.py
还应补充自动化测试和可观测性,重点覆盖:
tool_use与tool_result的配对;- 多工具调用顺序;
- 未知参数和未知工具;
- 权限拒绝和用户确认;
- 压缩边界;
- Task 依赖和并发领取;
- API 重试;
- 后台通知注入;
- Agent 协议状态转换;
- Worktree 脏状态保护。
总结:真正的 Agent 能力来自运行时,而不只来自模型
通过这个项目可以看到,最初的 Agent Loop 很短,真正不断增长的是围绕模型建立的工程系统。
一个成熟 Agent 的构建逻辑可以概括为:
用 Agent Loop 建立行动闭环
↓
用 Tools 连接真实环境
↓
用 Permission 和 Hooks 约束行为
↓
用 Todo、Task 和 Memory 管理状态
↓
用 Skills、Prompt 和 Compact 管理上下文
↓
用 Retry、Background 和 Cron 提升可靠性
↓
用 Subagent、Team 和 Protocol 扩展协作
↓
用 Worktree 和 MCP 实现隔离与开放扩展
对于 Agent 开发学习者,最重要的不是一次性复刻所有功能,而是理解每一层为什么出现:
- 没有循环,模型只能回答一次;
- 没有工具,模型无法作用于环境;
- 没有权限,工具能力会变成风险;
- 没有状态,长任务会失控;
- 没有压缩和记忆,Agent 无法长期工作;
- 没有恢复机制,临时错误会终止任务;
- 没有协议和隔离,多智能体只会制造更多冲突。
Claude Code 类 Agent 的核心,并不是一个神秘的超级 Prompt,而是一套让模型能够安全、持续、可观察地行动的运行时机制。理解这套机制之后,你才真正具备了从零构建 Agent 的能力。







