Claude Code 实战(一):核心闭环 — 从 0 搭建你的第一个 AI Agent
AI 教程教程进阶12 分钟阅读
学习路径:Claude Code 实战

Claude Code 实战(一):核心闭环 — 从 0 搭建你的第一个 AI Agent

从零手搓AI Agent!本篇覆盖Agent循环、工具使用、待办写入、子代理、技能系统、上下文压缩6大核心机制,每章附带完整Python代码。

Claude Code 实战(一):核心闭环 — 从 0 搭建你的第一个 AI Agent

本系列基于 Learn Claude Code 开源教程整理,带你从零手搓一个结构完整的 AI Agent。本文是第一篇,覆盖 Agent 最核心的 6 个机制。

前言

Claude Code 不只是一个聊天工具,它是一个完整的 Agent 系统。要真正理解它,最好的方式是从零开始搭建。

这个系列分 4 篇文章,对应 4 个阶段:

  1. 核心闭环(本文)— Agent Loop、工具使用、待办写入、子代理、技能系统、上下文压缩
  2. 系统加固 — 权限系统、Hook 系统、记忆系统、系统提示词、错误恢复
  3. 任务运行时 — 任务系统、后台任务、定时调度
  4. 多 Agent 平台 — Agent 团队、团队协议、自主代理、Worktree 隔离、MCP 与插件

第 1 章:Agent 循环

"一个工具 + 一个循环 = 一个 Agent"

问题

语言模型能推理代码,但碰不到真实世界 — 不能读文件、跑测试、看报错。没有循环,每次工具调用你都得手动把结果粘回去。你自己就是那个循环。

解决方案

+--------+      +-------+      +---------+
|  User  | ---> |  LLM  | ---> |  Tool   |
| prompt |      |       |      | execute |
+--------+      +---+---+      +----+----+
                     ^                |
                     |   tool_result  |
                     +----------------+
                     (loop until stop_reason != "tool_use")

一个退出条件控制整个流程。循环持续运行,直到模型不再调用工具。

核心代码

def agent_loop(query):
    messages = [{"role": "user", "content": query}]
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        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":
                output = run_bash(block.input["command"])
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})

不到 30 行,这就是整个 Agent。后面 11 个章节都在这个循环上叠加机制 — 循环本身始终不变。

关键理解

  • 消息历史不是聊天记录,而是模型下一轮要读的工作上下文
  • 工具结果必须重新进入消息历史,否则模型无法基于真实观察继续工作
  • 循环退出的唯一条件:stop_reason != "tool_use"

第 2 章:工具使用

"加一个工具,只加一个 handler"

问题

只有 bash 时,所有操作都走 shell。cat 截断不可预测,sed 遇到特殊字符就崩。专用工具(read_filewrite_file)可以在工具层面做路径沙箱。

关键洞察:加工具不需要改循环

解决方案

用 dispatch map 把工具名映射到处理函数:

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"]),
}

循环中按名称查找处理函数,循环体与第 1 章完全一致:

for block in response.content:
    if block.type == "tool_use":
        handler = TOOL_HANDLERS.get(block.name)
        output = handler(**block.input) if handler else f"Unknown tool: {block.name}"

路径安全

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

对比

之前 之后
1 个工具(仅 bash) 4 个工具(bash, read, write, edit)
硬编码 bash 调用 TOOL_HANDLERS 字典
无路径安全 safe_path() 沙箱
循环需要改 循环永远不变

第 3 章:待办写入(TodoWrite)

"可见计划不是装饰,而是防止会话漂移的稳定器"

问题

多步骤任务中,模型容易"忘记"自己在做什么。对话越长,模型越容易跳步或重复。

解决方案

给模型一个 todo_write 工具,让它自己维护待办清单:

  1. 把大任务拆成小步骤
  2. 标记每个步骤的状态(pending / in_progress / completed)
  3. 每次循环都能看到当前进度
todos = []

def run_todo_write(items: list) -> str:
    global todos
    todos = items
    return f"Updated {len(items)} todo items"

关键

  • 模型自己写 todo,不是外部系统强加
  • todo 列表作为上下文的一部分,每轮都可见
  • 防止模型跳步、重复或偏离目标
  • 这不是装饰,而是 agent 在长对话中保持方向的稳定器

第 4 章:子代理(Subagent)

"把探索性工作移进干净上下文后,父 agent 才能持续盯住主目标"

问题

主 agent 上下文越来越大。探索性任务(搜索代码、分析文件)会快速消耗上下文窗口,同时污染主对话流。

解决方案

启动子代理 — 一个独立的小型 agent 循环,有自己干净的上下文:

def spawn_subagent(task: str, tools: list = None) -> str:
    """在干净上下文中运行一个子任务"""
    sub_messages = [{"role": "user", "content": task}]
    sub_tools = tools or TOOLS

    while True:
        response = client.messages.create(
            model=MODEL, messages=sub_messages,
            tools=sub_tools, max_tokens=4000,
        )
        sub_messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return response.content[0].text
        # ... 执行工具,继续循环

为什么需要子代理

  • 上下文隔离:子代理有独立的 messages,不污染父上下文
  • 资源控制:子代理可以用更小的 max_tokens,节省成本
  • 并行执行:父 agent 可以启动多个子代理处理不同子任务
  • 结果过滤:子代理只返回最终结果,中间过程不污染主对话

第 5 章:技能系统(Skills)

"专门知识不该一开始全部塞进上下文,而该在需要时被轻量发现"

问题

不同任务需要不同的专业知识(API 文档、编码规范、工具用法)。全部塞进 system prompt 会让上下文爆炸。

解决方案

技能系统 = 按需加载的专业知识模块:

  1. 用户提出任务
  2. 系统扫描可用技能,发现相关的
  3. 只加载相关技能的内容到上下文
  4. 模型根据技能知识完成任务
SKILLS_DIR = Path("skills")

def discover_skills(task: str) -> list:
    """根据任务描述,找到相关技能"""
    skills = []
    for skill_dir in SKILLS_DIR.iterdir():
        if skill_dir.is_dir():
            manifest = json.loads((skill_dir / "manifest.json").read_text())
            if is_relevant(task, manifest):
                skills.append(manifest)
    return skills

技能目录结构

skills/
├── api-docs/
│   ├── manifest.json    # 技能元数据
│   └── instruction.md   # 技能内容
├── coding-style/
│   ├── manifest.json
│   └── instruction.md
└── ...

关键

  • 技能 = 可复用的知识包(markdown + 配置)
  • 按需发现,按需加载,不浪费上下文
  • 社区可以贡献和共享技能

第 6 章:上下文压缩(Context Compact)

"压缩的目标不是删历史,而是保住连续性和下一步所需的工作记忆"

问题

对话越来越长,上下文窗口有限。直接截断会丢失关键信息,agent 会"失忆"。

解决方案

智能压缩:用模型自己总结已完成的工作,保留关键信息:

def compact_messages(messages: list, max_tokens: int = 4000) -> list:
    """压缩消息历史,保留关键上下文"""
    # 1. 保留最近几轮完整对话
    recent = messages[-6:]

    # 2. 让模型总结早期对话
    summary_prompt = "请简洁总结以下对话中完成的工作和关键发现:"
    old_messages = messages[:-6]

    summary_response = client.messages.create(
        model=MODEL,
        messages=[{"role": "user", "content": summary_prompt + str(old_messages)}],
        max_tokens=max_tokens,
    )

    # 3. 用摘要替代早期对话
    return [
        {"role": "user", "content": f"[之前工作的摘要] {summary_response.content[0].text}"},
        *recent
    ]

压缩策略

策略 说明
保留 system prompt 系统指令永不压缩
保留最近几轮 最近的工作最重要
摘要早期对话 关键发现不能丢
保留 todo 列表 任务进度必须保持
保留关键文件内容 当前正在处理的文件

关键

  • 压缩不等于删除,而是用更少的 token 保留等价信息
  • 模型自己知道哪些信息重要,让它做摘要比规则截断好
  • 压缩后 agent 必须还能继续工作,不能"失忆"

总结

核心闭环的 6 个机制构成了 AI Agent 的基础骨架:

章节 机制 核心价值
s01 Agent 循环 模型能行动的基础
s02 工具使用 扩展能力的边界
s03 待办写入 防止任务漂移
s04 子代理 上下文隔离
s05 技能系统 按需知识加载
s06 上下文压缩 长对话可持续

下一篇我们将进入"系统加固"阶段,学习如何给这个 Agent 加上权限、Hook、记忆、提示词工程和错误恢复。


常见问题

Q: Agent 循环和普通聊天机器人有什么区别?

普通聊天机器人是一次性问答:用户问一句,模型回一句。Agent 循环是持续工作:模型可以自己决定调用工具、查看结果、继续下一步,直到任务完成。区别就像"问一句答一句"和"给个任务自己干完"。

Q: 子代理和主代理共享上下文吗?

不共享。子代理有自己独立的上下文窗口,只接收任务描述,只返回最终结果。这是设计上的隔离,避免子代理的操作污染主代理的上下文。

Q: 上下文压缩会不会丢重要信息?

会丢一些细节,但压缩的目标是保留"对当前任务仍然有价值"的信息。实现方式通常是让辅助模型提炼摘要,而不是粗暴截断。对于代码文件,agent 可以随时重新读取原文。

Q: 技能系统和普通函数有什么区别?

技能是一组预打包的知识 + 模板 + 脚本。普通函数只做一件事,技能可以包含多步操作流程、注意事项、常见陷阱。技能更像是一本"操作手册"而不只是一个工具。

Q: 我需要用 Claude 才能跑这些代码吗?

代码示例用的是 Anthropic 的 API 格式,但核心概念(Agent 循环、工具使用、权限检查)适用于任何 LLM。你可以替换成 OpenAI、Gemini 或本地模型,只需改 API 调用部分。更多配置方法见 Claude Code 完整配置教程


本文基于 Learn Claude Code 开源项目(GitHub: shareAI-lab/learn-claude-code)整理改编。

相关阅读: