

Key Takeaways
- Agent 工程的真正难点不是「写一个 prompt 让 Agent 跑通 demo」,而是「让 Agent 在真实工作流里持续跑且越跑越好」;环路工程(loop engineering)是把这一目标工程化的关键框架
- LangChain 团队把 Agent 环路抽象成四类:核心 Agent 环路(request → model + tools → result)、Goal/Verification 环路(grader agent 评估 rubric 决定是否完成)、Event-driven 环路(把 Agent 嵌入 Slack / GitHub / Calendar / Email 等真实系统)、Self-improvement / Hill Climbing 环路(LangSmith Engine 从 trace 反推改进 prompt / tool / memory)
- 四类环路可以俄罗斯套娃式任意嵌套 --- 你不必全套,可以在 Event-driven 里只套 Verification + Core;这种嵌套能力来自 Dcode 与 Deep Agents 的开源 middleware 架构
/goal与/rubric是 Dcode / Deep Agents 提供的关键语法糖:/goal在执行前先回写一份 rubric 给用户审,粒度比 Codex / Claude Code 的目标设定更细;两者都作为 middleware 可插拔到任意 LangChain agent- Self-improvement 环路的工程价值在于「Agent 是非确定性的,真正的真相在 traces 里」;LangSmith Engine 这类 meta-agent 把 trace 作为反思信号,自动改写 prompt / tool description / skill / memory 并 port 回源码,让外层环路使内层环路越来越有效 ...(已截断至 300 字以内)
为什么 Agent 必须被「环路化」,而不是只写一个 prompt
整套课程把 Agent 工程的核心矛盾归结为一句话:单次 prompt 不可能让 Agent 可靠地完成所有任务。这听起来像常识,但实际工程现场大量失败的 Agent 项目恰恰败在这一步。
一个 Agent 一旦跑通 demo,团队往往以为问题已经被解决,接下来只是把它接到 Slack、接到 GitHub、接到定时任务。但真实情况是,同一个 Agent 用同一组 prompt 跑十次,十次结果可能各不相同,有些跑通了,有些在第三步工具调用环节掉了链,有些甚至在最基础的格式化输出阶段就出错。
这种不稳定性并不是 prompt 写得不够细的问题,而是大语言模型本身在 temperature(采样温度)、采样路径、上下文注意力分配上天然存在的非确定性。要把这种不可靠的部件包装成对业务可用的能力,工程上的答案从来不是"再写一段更长的 prompt",而是在 Agent 外面套上可观测、可验证、可触发、可自省的环路结构。
这正是 LangChain 团队在 Loop Engineering 系列里反复强调的中心思想,也是 2026 年 AI 工程领域"成熟度"与"玩具 demo"之间最关键的分水岭。

下面这张对比矩阵把两种 Agent 成熟度在工程维度上的真实差距一次性铺开:
| 工程维度 | 单 prompt + 单次 LLM 调用 vs 四环路俄罗斯套娃 Agent |
|---|---|
| 成功率 | 取决于 query 难度,平均一次跑通率较低、方差大 vs 多层验证环路叠加,失败可被捕获并重试 |
| 可观测性 | 只有最终输出,中间 reasoning 不可见 vs 完整 trace,可重放到 LangSmith 查看每一步 |
| 可调试性 | 失败后只能改 prompt 重试 vs grader agent 可定位失败的具体 rubric 项 |
| 复用性 | prompt 与场景强耦合,跨任务不可移植 vs /goal 与 /rubric 作为 middleware 可插拔复用 |
| 部署形态 | 通常作为一次性脚本或聊天界面 vs 嵌入 Slack/GitHub/Calendar 等真实工作流 |
| 改进机制 | 人工看 demo 凭感觉调 prompt vs hill climbing loop 自动遍历 trace 改写 prompt |
| 成本可控性 | 不可控,长 prompt 反复调用浪费 token vs 验证失败可提前终止,避免无效重试 |
| 团队协作 | 个人玩具,难以交接 vs 工具、rubric、goal 全部版本化,可走 PR review |
这张表并不是抽象概念清单,它对应的是团队在生产环境里能直接观察到的痛点。当我们把"单 prompt"作为基线、把"环路化 Agent"作为目标态时,真正变化的不是模型本身,而是 Agent 与外部系统交互的协议层。
为什么必须"环路化":两个第一手观察
观察 同一 Agent 跑同一组 prompt 多次,成功率方差与 query 多样性会共同放大。这里的"方差"具体指:在固定 prompt、固定模型、固定 temperature 的前提下,对同一 query 集做 N 次独立跑,得到的 pass rate 分布常常不是单峰的,而是双峰甚至多峰。也就是说,Agent 的行为模式更接近"要么一次跑通、要么完全失败",而不是"接近一次跑通"。
造成这种多峰分布的根源是模型行为的非确定性,而 query 多样性把这种非确定性进一步放大:当 query 涉及多步工具调用、跨域信息聚合、长上下文压缩时,每一步的小概率失败会沿链路上累积,最终成功率呈指数衰减。环路化设计的工程价值,正在于把这种指数衰减截断在每一层 grader 上。
数据 把 Agent 嵌入 Slack、GitHub、Calendar、Email 等真实工作系统后,可达性、复用率、团队使用率三项指标显著上升。可达性指的是 Agent 真正被业务侧触发的次数,因为它不再要求用户主动打开一个网页;复用率指的是同一个 Agent 在不同工作流里被实例化的次数,因为它被抽象成了 middleware(中间件);团队使用率指的是团队成员中至少每周使用一次 Agent 的占比。
这三项指标共同决定了 Agent 是"实验室 demo"还是"团队基础设施"。这也解释了为什么 LangChain 在 2026 年的工程主线里,几乎所有的官方示例都默认采用 event-driven 的接入方式,而不是 chat-only 的接入方式。

四种环路一次铺开

第一种是核心 Agent 环路(Core Agent Loop),即 request → model + tools → result 的最小闭环。它的工程意义在于把"思考"和"执行"显式分离开:模型负责决定下一步调用哪个 tool,tool 负责与外部世界交互,result 回到上下文驱动下一轮推理。这层环路是所有 Agent 的最小单元,无论外面套多少层,内层永远要先把这一层跑稳。
第二种是 Goal/Verification 环路。它的核心是把"完成"从一种感觉变成可判定的二元结果。具体做法是引入 grader agent(打分智能体)和 rubric criteria(评分准则),把任务目标拆成若干可独立打分的子项。比如一个 docs writer agent 的 rubric 可以是"是否包含示例代码、是否覆盖 API 全部参数、是否在末尾给出 FAQ"。只有当 grader 对每一项都判定通过,这条 trace 才被标记为 pass,否则会被打回重跑或回写到 dataset 里供后续训练。
第三种是 Event-driven 环路。它把 Agent 从"被动等用户输入"变成"被事件或定时主动触发",常见的事件源包括 Slack 消息、GitHub PR 评论、Calendar 日程、Email 到达,以及 cron 定时任务。LangChain 在文档里把这一层称作 harness(挂载框架),Deep Agents 开源仓库里的 Deep Agents harness 就是这种模式的具体实现。
第四种是 Self-improvement / Hill Climbing 环路,也是 LangChain 在 2026 年重点押注的方向。LangSmith Engine 这类 meta-agent 会周期性地遍历 production trace,自动改写 prompt、tool description、skill、memory,并把通过验证的版本 port 回源代码仓库。这种 hill climbing(爬山算法)风格的迭代可以在没有人工介入的情况下,持续抬高 Agent 的整体质量上限,真正实现"越跑越好"。
这四种环路并不是孤立的层级,而是可以俄罗斯套娃式任意嵌套:核心环路在最内层,verification 环路套在外面,event-driven 环路再套一层,self-improvement 环路在最外层,甚至 verification 环路本身又可以嵌套一个更细粒度的 verification 子环路。这种嵌套让 Agent 既能在单次任务里自我纠错,也能在跨任务、跨时间尺度上持续进化。
代码层面的最小配置片段
ini
from langchain.agents import create_agent
from langchain.agents.middleware import GoalMiddleware, RubricMiddleware
agent = create_agent(
model="claude-sonnet-4-5",
tools=[search_tool, write_file_tool, calendar_tool],
system_prompt="You are a docs writer agent.",
middleware=[
GoalMiddleware(goal="Produce API docs that pass CI"),
RubricMiddleware(criteria=[
"all parameters documented",
"includes runnable example",
"ends with FAQ section",
]),
],
checkpointer=PostgresSaver.from_conn_string(DB_URL),
)
这段伪代码展示了 goal 与 rubric 作为可插拔 middleware 是怎么挂到 create_agent 上的。LangChain 官方文档对此有详细说明,详见 python.langchain.com/docs/concep... 与 docs.langchain.com/oss/python/... Agent 被部署到生产环境,它的每一次运行都会留下 trace,这些 trace 既是 LangSmith 评测数据集的来源,也是 LangSmith Engine 启动 hill climbing 循环的燃料。
常见踩坑清单
- 把单次 prompt 调试当作主线工程。短期看 demo 跑通,长期看每次模型升级都要重做一遍。
- 把 rubric 当成 prompt 的一部分而不是独立 middleware。结果是换任务就要重写全部逻辑,跨任务复用率为零。
- 忽略 checkpointer(检查点)与持久化。Agent 一旦跨进程重启就丢失中间状态,verification 环路无法回放,grader 也无从打分。
- 把 hill climbing 当成黑盒。LangSmith Engine 改写 prompt 必须经过 PR review 才能合并回主分支,否则就是不可控的模型漂移,合规审计无法通过。
2026 年 Agent 工程的核心矛盾
如果说 2024 年大家关心的是"我的 Agent 能不能跑一次",那么 2026 年的核心矛盾已经迁移到"我的 Agent 能不能持续跑、且越跑越好"。这个迁移背后是三股力量的合流:第一,模型能力快速演进,同一段 prompt 的有效性可能在下一次模型升级后立刻失效;第二,业务侧对 Agent 的期待从"演示"转向"基础设施",稳定性与可观测性成为硬指标;第三,合规与审计需求迫使团队把 Agent 行为写成可重放的 trace,而 trace 自然就成为自我改进的反馈信号。
在这三股力量之下,任何只写了"一段 prompt 就上线"的 Agent 都会在几个月内被业务淘汰,只有把环路工程当作 first-class concern 设计的 Agent 才有可能持续存活。这也是为什么 LangChain 在 2026 年的路线图里,把 LangSmith Engine、Deep Agents 中间件、create_deep_agent 这三类能力放在同一个产品矩阵里对外讲述的原因。
它们共同回答的是同一个问题:当你的 Agent 不再是 demo,而是接入 Slack、GitHub、Calendar 等真实工作流的团队基础设施时,你需要一整套环路协议来保证它能被持续运行、持续验证、持续触发、持续自省。更多技术细节可以参考 LangChain 官方文档 python.langchain.com/docs/introd... 与 LangGraph 持久化与 checkpointer 章节 langchain-ai.github.io/langgraph/c...
把"写一个 prompt"升级为"设计一套环路",本质上是把 Agent 从手工艺时代带进了工程时代。手工艺时代靠个人经验与反复试错,工程时代靠协议、middleware、可观测数据。三者缺一,任何 Agent 项目都难以撑过 6 个月。
核心 Agent 环路:request → model + tools → result 的最小闭环
翻开任何一本 Agent 工程的参考实现,把它的依赖关系图压缩到最里层,你会看到一段永远不变的三段式循环:用户请求进来,模型拿到请求和可用工具清单,决定调哪些工具、按什么顺序调、怎么把工具的返回组织起来,最后吐出一个 result。这就是核心 Agent 环路------request → model + tools → result。它看似不起眼,却是后续所有复杂环路(Goal/Verification 环路、Event-driven 环路、Self-improvement/Hill Climbing 环路,以及俄罗斯套娃式任意嵌套组合)都必须包在最里层的最小单元。把这一层写错、跑不稳,后面所有上层设计都会变成空中楼阁。后续章节要展开的目标验证、事件触发、trace 回写,本质上都是在这一段最小闭环外面再套一层判定/触发/反馈的环,而最里层永远是它。

观察 在最小闭环里,模型本身并不"动手"------它只产出一段结构化的工具调用意图(通常是函数名 + 参数 JSON,即 OpenAI/Anthropic 风格的 tool_calls 字段),真正去执行 HTTP 请求、读写数据库、操作浏览器、调用 shell 的是工具实现本身。换句话说,工具决定了 Agent 的能力半径,模型决定了它在半径之内怎么走路 。一个 Agent 看起来能不能用,八成取决于工具集设计得是否正交、是否原子化、是否错误可恢复;prompt 写得再漂亮,也救不了一个返回结构混乱、异常吞噬、超时不设置的工具。在工程 review 里,我们经常会看到新人把 80% 的时间花在调 prompt,却对工具的 schema 模糊、错误未透传、未做幂等保护视而不见------这是典型的优先级倒挂。判断 Agent 质量的快速经验法则:先把工具的失败模式(超时、5xx、空结果、格式漂移)全部显式化,再去谈 prompt 调优,否则所有上层环路都建在沙子上。
下面用 LangChain 提供的 create_agent 在 30 行以内搭起这段最小核心环路。可运行的样例大致长这样:
python
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
@tool
def get_weather(city: str) -> str:
"""根据城市名返回当前天气。"""
return f"{city}:晴,25°C"
agent = create_agent(
model=ChatOpenAI(model="gpt-4o-mini"),
tools=[get_weather],
system_prompt="你是一个天气助手,根据用户提问返回天气信息。",
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "北京今天天气怎么样?"}]}
)
print(result["messages"][-1].content)
这段代码完整跑通了 request → model + tools → result 三段:用户消息作为 request 进入 agent,模型读 messages 和 tool schema,判断需要调用 get_weather("北京"),工具执行后返回字符串,模型拿到 ToolMessage 再做一次推理,把工具输出组织成自然语言回给用户------这就是 result。任何一个 Agent 框架(无论 LangGraph、Deep Agents、Autogen 还是自研的 if-else 循环)剥到最里层,都跑不出这个动作。把这段搞明白,后面所有上层设计才有共同的语义底座,后面要讲的 checkpointer、middleware、grader agent 都挂在这一段最小环路上。
把这三段拆成可观察的状态变量,工程上至少要追到下面 4 个字段,任何一个生产级 Agent 都必须能够从日志/tracing 中把它们拎出来:
| 状态变量 | 来源 | 典型载体 | 在环路中的作用 | 失败时常见现象 |
|---|---|---|---|---|
| 请求(request) | 用户或上游 Agent | messages 列表里 role=user 的那条 |
启动本次循环的输入 | 空消息、过长截断、注入攻击 |
| 模型输出 | LLM 推理 | AIMessage,常含 tool_calls 字段 |
决策:调哪个工具、参数是什么 | 幻觉工具名、参数类型错、未终止 |
| 工具结果 | 工具函数执行 | ToolMessage,content 为原始返回 |
把外部世界的事实搬进上下文 | 超时、异常吞噬、格式非结构化 |
| 最终产物(result) | 模型二次推理或直接回传 | 最后一条 AIMessage 的 content |
本次循环对外暴露的产出 | 答非所问、漏字段、未遵循 schema |
注意「模型输出」和「最终产物」不是同一回事:前者是中间决策(往往带 tool_calls),后者是面向用户的成品。在多轮工具调用里,模型输出和工具结果会交替出现多次,最终产物只出现一次。把这两者混在一起,是新手在 trace 里最容易踩的坑。LangGraph 的状态图把这 4 个变量显式建模成 graph 的 state 节点,LangGraph 状态图文档可参考 langchain-ai.github.io/langgraph/。... LangChain 原生 create_agent 是基于 LangGraph runtime 跑的,所以你在 messages 之外还能从 state 里拿到更细粒度的字段。

工程上,这个最小闭环在三种调用模式下有截然不同的取舍。把这三种模式摆在同一张表里对比,选型的边界就会清晰很多:
| 调用模式 | API 形式 | 延迟体感 | 中间状态可观测性 | 适用场景 | 主要代价 |
|---|---|---|---|---|---|
| 同步调用 | agent.invoke(...) |
等到全部跑完才一次性返回 | 弱,只能在结束后拿到最终 messages | 脚本、批处理、单元测试、CI | 长任务下用户长时间空白 |
| 流式调用 | agent.stream(...) |
第一个 token 立即可见 | 强,每个节点事件都可订阅 | 前端对话、IDE 插件、需要逐字反馈 | 实现复杂度上升,需处理 partial tool_calls |
| 异步调用 | agent.ainvoke/astream(...) |
与同步/流式一致 | 与同步/流式一致 | 高并发服务、Web 后端、asyncio 生态 | 工具实现必须 await,否则阻塞事件循环 |
取舍的核心点在于:同步简单、流式友好、异步扛量 。一个真实的内部 Agent 服务通常会用 astream 起一个 SSE 长连接,前端按 token 渲染;而 CLI 工具类(终端 coding agent、一次性脚本)往往选同步 invoke,先求稳再求快。需要批量回归评测 LangSmith datasets 时,异步 ainvoke 配合 asyncio.gather 是最高吞吐的选择。需要警惕的是,流式模式下 partial tool_calls 的处理:模型可能分多次吐出一个完整的工具调用,如果按 token 切分得太早,前端会拿到一段残缺的 JSON------这正是 LangChain stream events 中 on_tool_call_chunk 事件存在的意义。对比这三者的关键变量不是"快不快",而是"中间状态能不能被外部系统消费"------流式和异步天然适合接 LangSmith tracing、接前端 SSE、接事件总线,同步则更适合一次性脚本。
数据 这三段式循环在工程上有两道独立的可量化曲线:运行次数 (平均循环轮数,即一次任务里 model + tools 交替多少轮)和成功率(最终 result 是否符合预期)。经验上前者集中在 1--5 轮:简单问答类接近 1 轮,单工具调用类 2 轮,复杂多步任务(如"订机票 + 加日历 + 发邮件确认")可达 5--10 轮,长尾任务偶尔冲到 20 轮以上------这时候就该怀疑是不是 prompt 没把任务拆清楚;后者在 demo 阶段能到 80% 以上,但一旦把同一份 prompt 跨团队复用、跨模型迁移、跨生产流量跑起来,常常掉到 50% 以下。这不是 prompt 写得不够细的问题,而是单次 prompt 根本不可能让 Agent 在所有输入分布上都可靠------这正是后续 Goal/Verification 环路要正面解决的问题:不是继续改 prompt,而是把"完成"这件事变成可判定的(rubric criteria),让 grader agent 给每一次循环打一个 0/1 信号,然后用这个信号去驱动 prompt 更新、工具描述更新、skill 更新。
到这里,核心 Agent 环路的骨架已经立住。后续无论是加 grader agent 做目标验证,还是接 Slack/GitHub 做事件驱动,还是让 LangSmith Engine 跑 trace 自动改 prompt,最里层仍然要回到这一段 request → model + tools → result。把这层写稳、写对、写可观测,后面所有环路才有底座。LangChain Agents 的入门概念可以参考 python.langchain.com/docs/concep...create_agent 的 API 细节见 docs.langchain.com/oss/python/... Deep Agents 的中间件架构,/goal 与 /rubric 就是以 tool middleware 的形式嵌进这一段最小环路的,这一点会在后续章节展开。
从 create_agent 到 create_deep_agent:LangChain 入口函数的层级选择
翻开 LangChain 官方文档,在 langchain.agents 模块下你会看到两条并排的入口函数:create_agent 与 create_deep_agent。它们共享同一个最里层的核心 Agent 环路------request 进入、模型决策、工具被调用、result 吐出------却各自承担了不同的工程定位。create_agent 是 LangChain 官方刻意保留的轻量入口,只暴露 LangChain agents 子项目里最稳定的那部分 API:模型绑定、工具清单、system prompt、stop 条件。它的设计意图是「让你能用尽量少的代码跑通一个能调工具的最小 Agent」,因此它把 plan-and-execute、子 Agent 路由、文件型记忆、技能系统这些额外能力全部排除在外,以避免轻量入口被中途绑死在一个特定工作流上。
对只跑核心环路、单轮或有限多轮、单工具集合的最小场景而言,create_agent 已经足够。譬如一个只查汇率、只读只写一份 JSON 配置的内部小工具,用 create_agent(model=..., tools=[...]) 一行就能跑起来,启动开销、依赖图、调试链路都最短。但也正因为它只暴露最薄的一层,任何超出「单 Agent 单上下文」的诉求都会在这层入口处撞墙。

观察 在真实业务里,绝大多数 Agent 项目并不是「一个 Agent 跑到底」的结构。需求文档里一旦出现「让模型先拆任务再执行」「调用某个公司内部系统的专用子 Agent」「跨会话记住用户偏好」「按命名空间加载操作手册」这四类要求中的任意两条,就基本意味着核心环路外面需要再套一层规划层、一层子 Agent 调度层、一层长期记忆层、一层 skills 加载层------这正好是 create_deep_agent 默认替你装配的中间件栈。换句话说,create_agent 与 create_deep_agent 的边界,本质不是「能不能调工具」,而是「你的 Agent 需不需要被另一个 Agent 管着」。
create_deep_agent 是 Deep Agents 这个独立开源项目提供的入口。它在 create_agent 的最里层环路之上,以 middleware 的形式插入了四件额外的事:plan-and-execute 规划器、sub-agent 路由、跨 run 持久化的文件型记忆、以及基于 skill 命名空间加载的技能包。这意味着你写的还是同一个「Agent」,但它天生就具备「拆任务、委派、跨会话记忆、按需加载手册」的能力。下面这段代码展示了用 create_deep_agent 注册一个最小可用的 plan-and-execute 子 Agent 的骨架:
ini
from deepagents import create_deep_agent
sub_agent = {
"name": "research_subagent",
"description": "负责拆解研究类请求,产出子任务清单",
"system_prompt": "你是一个 plan-and-execute 子 Agent,先写 TODO 列表,再逐项调用工具完成。",
}
agent = create_deep_agent(
model="anthropic:claude-sonnet",
tools=[web_search, code_runner],
system_prompt="你是顶层协调 Agent,负责把用户请求路由到合适的子 Agent。",
subagents=[sub_agent],
)
result = agent.invoke({"messages": [{"role": "user", "content": "调研 2026 年 RAG 评测基准的最新进展,并给我一份摘要"}]})
print(result["messages"][-1].content)
上面这段代码同时打开了两条新通道:第一条是 subagents 参数,它把 research_subagent 注册为可被顶层 Agent 通过 task(...) 工具调度的子 Agent;第二条是 tools 参数仍然挂在顶层 Agent 上,意味着子 Agent 并不自动继承父 Agent 的工具集,父子工具边界在这里被显式切开------这是 Deep Agents 与 LangGraph 多 Agent 图最显著的语义差异之一。
为了把这两层入口的差异一次性摆清楚,下表列出了它们在四个关键维度上的对比:
| 维度 | create_agent |
create_deep_agent |
|---|---|---|
| 层级定位 | 核心 Agent 环路最薄封装 | 核心环路 + 中间件栈(plan / sub-agent / memory / skills) |
| 子 Agent 支持 | 不内置,需自行用 LangGraph 编排 | 通过 subagents 参数直接注册 plan-and-execute 风格子 Agent |
| 文件/记忆 | 默认仅本轮 messages,需手动接 checkpointer | 内置文件型记忆 + 跨 run 持久化,可挂 Postgres / Sqlite / MemorySaver |
| Skills 支持 | 无 | 通过文件系统式命名空间按需加载 skill 包,支持 skill update 自动 port 回源码 |
仅看这张表,你会觉得 create_deep_agent 是 create_agent 的「超集」,直接选 create_deep_agent 永远更安全。这是一个常见的认知偏差------下面这段取舍矩阵会指出它什么时候是错的:
| 判断维度 | 选 create_agent |
选 create_deep_agent |
|---|---|---|
| 业务是否需要 plan-and-execute | 不需要,单步工具调用就能完成 | 需要,任务必须先拆解再执行 |
| 是否需要子 Agent 委派 | 不需要,一个上下文贯穿到底 | 需要,父子职责需要被显式切开 |
| 是否需要跨 run 记忆 | 不需要,每轮会话自包含 | 需要,用户偏好/历史状态必须跨会话保留 |
| 是否需要 skill 系统 | 不需要,system prompt 已够用 | 需要,手册/操作规范按命名空间加载 |
| 启动依赖与冷启动时延 | 要求最短依赖、最低冷启动时延 | 可以接受额外中间件带来的额外开销 |
| 调试可观测性粒度 | 只需要 LangSmith trace 一层 | 需要 trace + sub-agent 独立 trace + 记忆读写 trace |
数据 矩阵里六个维度并非等价------根据 Deep Agents 仓库与官方文档给出的默认中间件装配清单,六个维度中只要命中「plan-and-execute」「子 Agent」「跨 run 记忆」这三项中的任意两项,工程经验上几乎都应直接选 create_deep_agent;反之,如果六个维度里只有 0 或 1 项命中,create_agent 的极简路径反而能显著降低调试复杂度与依赖体积。换句话说,真正的分水岭是「业务是否要求两层以上的 Agent 结构」,而不是「我有没有听说过 Deep Agents」。

从中间件视角看,create_deep_agent 的关键设计是把 plan、sub-agent routing、memory、skills 全部实现为「可插拔 middleware」,而不是写死在主循环里的固定阶段。这意味着你完全可以注册自己的 middleware,在模型调用之前或之后插入 rubric grader、日志埋点、A/B 测试分支------这是与 create_agent 在工程哲学上的最大差异。下面这段伪代码展示了如何把一个自定义的「目标校验 middleware」注入 create_deep_agent:
python
from deepagents import create_deep_agent
from deepagents.middleware import Middleware
class GoalCheckMiddleware(Middleware):
def before_model(self, state, runtime):
if "/goal" in state["messages"][-1].content:
state["metadata"]["goal_active"] = True
return state
agent = create_deep_agent(
model="anthropic:claude-sonnet",
tools=[...],
middleware=[GoalCheckMiddleware()],
)
before_model 钩子的存在意味着 /goal、/rubric 这种「在 system prompt 之外另起一条目标/评分轴」的能力,可以作为 middleware 而非 prompt hack 来实现------这正是「Loop Engineering」框架下「goal/verification 环路可被任意 Agent 复用」的关键。

工程上常见的踩坑点有三:第一,误以为 create_deep_agent 一定包含所有 LangGraph 能力,实际上它的中间件栈是 Deep Agents 项目独立维护的,与 LangGraph 的图节点是两层抽象,自定义子图时应避免在两者之间混用 checkpointer schema。第二,把 subagents 列表写得过深,导致顶层 Agent 的 context 被反复「潜入」到几层子 Agent 的回执里,可用上下文被快速吃光------建议子 Agent 深度不超过 2 层,且每层都强制要求返回结构化摘要。第三,在 create_deep_agent 上挂全局 cross-run 记忆时,容易忽视命名空间冲突,生产环境务必给每个业务线配置独立的 memory namespace 并挂上 PostgresSaver 做隔离,避免不同业务的长期记忆互相污染。
延伸阅读建议直接看 Deep Agents 项目主页与 create_deep_agent 的官方 API 文档,前者覆盖了中间件栈的设计动机与默认装配清单,后者则给出了完整的参数表与返回值结构:github.com/langchain-a... 与 docs.langchain.com/oss/python/... 。若需要对照 create_agent 的最小入口示例,可参考 LangChain 官方 agents 文档:docs.langchain.com/oss/python/... 。
回到工程选型本身:如果你正打算落地一个 Agent 产品,先问自己六个问题------是否需要 plan-and-execute、是否需要子 Agent、是否需要跨 run 记忆、是否需要 skills 系统、是否能接受中间件带来的额外冷启动时延、是否需要细粒度的子 trace 可观测性。问题的答案指向「两层以上 Agent 结构」时,直接选 create_deep_agent 并以 middleware 形式扩展;指向「一个 Agent 贯穿到底」时,create_agent 才是最小、最稳、最不引入隐性耦合的入口。两层入口并非替代关系,而是「同一核心环路在不同业务复杂度下的两个稳定解」。
Goal/Verification 环路:grader agent 把『完成』变成可判定
Goal/Verification 环路是 LangChain 团队在「四种环路」框架里明确点出的第二种环路。它的工程定位非常明确:解决核心 Agent 环路最薄弱的那个环节------「任务到底做完了没有」。一个请求进入核心环路,模型决策、工具调用、result 吐出,听起来闭环,但「result 是否真的达成了 goal」这个问题,核心环路自己是答不上来的。
把这件事拆开看,Goal/Verification 环路 = 核心环路 + 一个 grader agent(rubric 是 grader 的判定清单)。Grader 本身也是一个 LLM 调用的 agent,但它的职责不是「完成任务」,而是「评判完成」。Rubric criteria 是 grader 拿到 result 之后逐条对照的判定清单,通常按结构 / 事实 / 风格三类维度展开。每一类都对应一个独立的 grader prompt 模板,grader 用同一份 rubric 但从不同角度打出分数,最终汇总成 pass/fail 决策。这种设计的妙处在于:它把模型擅长的「生成」和工程上需要的「判定」切开,让 LLM 既当作者又当评审,但通过 rubric 的形式强制评审有据可依。
观察 让 LLM 自己回答 yes/no 是这门工程里最常见的反模式之一。模型擅长生成,但不擅长稳定地、可复现地评价自己;尤其在开放式任务里,「写完了吗」「合格吗」「符合要求吗」这类二选一问题极易被「礼貌性 yes」污染------模型倾向于顺着用户的期待给出肯定回答,即使结果存在明显瑕疵。这套课程的做法是:把「完成」拆成多条可独立判定的 rubric criteria,每条都要求 grader 给出「pass / fail + 简短理由」二元输出。这种设计把模糊的「好不好」转成了离散的、可聚合的布尔向量,工程上的好处是 fail 项可以被精确地写回 prompt,触发核心环路定向重跑,而不是让模型再盲目试一次。另一个隐性收益是:rubric 本身变成了可审计的对象,出问题时可以回溯到具体哪条 criteria 没通过,而不是面对一句空泛的「写得不好」。

具体到代码实现,grader middleware 是挂在 create_deep_agent 之上的一段中间件。下面这段 50 行级的伪代码展示了它的典型形态:核心 Agent 跑完后把 result 送入 grader middleware,grader 按 rubric 逐条打分,任一条 fail 即触发反馈回写。
python
from langchain.agents import create_deep_agent
from langchain.tools import tool
RUBRIC = [
{"name": "structure", "prompt": "章节是否齐全..."},
{"name": "fact", "prompt": "数据/引用是否对齐..."},
{"name": "style", "prompt": "语气/术语是否一致..."},
]
@tool
def grade_with_rubric(result: str) -> dict:
scores = [call_grader(c, result) for c in RUBRIC]
return {"all_pass": all(s["verdict"] == "pass" for s in scores), "scores": scores}
agent = create_deep_agent(
model="claude-sonnet",
tools=[grade_with_rubric],
middleware=[GoalMiddleware(), RubricMiddleware(), GraderMiddleware()],
)
这段代码展示了三个关键点:第一,grade_with_rubric 本身就是一个 tool,它把 grader 当成普通工具调用而非外挂脚本,这意味着 grader 调用本身也会被 LangSmith tracing 完整记录;第二,middleware 列表里同时挂了 Goal / Rubric / Grader 三层,说明 Goal/Verification 环路在 Dcode / Deep Agents 的中间件架构里是组合式暴露的,而不是一个黑盒函数,任何 create_deep_agent 都可以按需启用其中一层;第三,grader 的输出回到主 agent,主 agent 据此决定是直接返回还是再跑一次核心环路,反馈通路是显式的、可观测的。
下面这张表格把 rubric 的三类维度、对应的 grader prompt 模板要点和典型 fail 信号摊开,便于工程团队对照落地:
| 维度 | 关注点 | grader prompt 模板要点 | 典型 fail 信号 |
|---|---|---|---|
| 结构(structure) | 章节是否齐全、字段是否齐备、长度是否在区间内 | 请检查 result 是否包含 X/Y/Z 三个章节,每章不少于 N 字,字段是否齐备 | 缺章节、字段缺失、超长或过短 |
| 事实(fact) | 数据/引用/链接是否真实、是否对齐 context | 请逐条核对 result 中的数字、引用、链接与源数据是否一致 | 编造数字、张冠李戴的引用、过期链接 |
| 风格(style) | 语气、人称、术语是否一致,是否避开禁用词 | 请检查 result 是否使用第二人称、是否出现禁用词列表、术语口径是否统一 | 语气跳跃、人称不一致、敏感词命中 |
这张表的设计意图是:每一行都是一条可独立打分的 rubric,grader 拿到的不是「整体印象」而是「对照清单」。工程上,这意味着你可以把同一份 rubric 喂给不同模型做交叉验证,也可以把同一份 rubric 喂给同模型多次取众数,降低单次判定的随机性。该教程的讲师特别强调过:rubric 的颗粒度直接决定 grader 的可用性,颗粒度过粗会退化成「整体印象打分」,颗粒度过细则会让 grader 自身变成新的失败源------这是典型的工程权衡点。
但三种验证强度不是等价的。下表把单一 grader / 多 grader 并行 / grader + 人在回路(HITL)三种方案的取舍摊开,工程上需要按场景选择:
| 方案 | 验证强度 | 延迟 | 成本 | 适用场景 | 主要权衡 |
|---|---|---|---|---|---|
| 单一 grader | 低-中 | 低(1 次 LLM 调用) | 低 | 内容草稿、批量任务、低风险输出 | 易被模型礼貌性 yes 污染,鲁棒性差 |
| 多 grader 并行 | 中-高 | 中(N 次并发调用) | 中(线性增长) | 对外发布、研究报告、合规文档 | 成本随 rubric 数量线性上升,但可解释性强 |
| grader + 人在回路(HITL) | 高 | 高(等待人工确认) | 高(人时成本) | 金融、医疗、合同、对外承诺 | 强鲁棒但不可规模化,适合关键路径 |
取舍的核心是:验证强度和延迟 / 成本成反比,工程上不会用 HITL 去校验所有草稿,也不会只用单一 grader 去签发对外承诺。多 grader 并行是「默认推荐档」,因为它在强度和成本之间给了一个相对健康的折中。具体落地时,该教程建议的混合策略是:单一 grader 跑批量初筛,初筛 fail 的样本进入多 grader 并行复核,多 grader 仍有争议的样本升级到 HITL,这是一个成本可控且强度递进的常见组合。

未达标时的反馈路径是这套环路真正的「回路」所在。grader 把失败原因结构化地回写到 prompt 或上下文,触发核心环路定向重跑。具体做法是:grader 的输出形如 {"all_pass": false, "scores": [{"criterion": "事实-数字一致性", "verdict": "fail", "reason": "Q3 营收数字与源文档不符"}]},主 agent 拿到这个结构化失败信号后,在下一轮对话里把它作为 user message 注入,核心环路据此重新决策。这一步在工程上有个常被忽略的细节:反馈必须结构化,不能只是一句「请改一下」。结构化反馈让模型可以定位到具体 rubric criteria,而模糊反馈只会让模型再随机试一次,等于浪费一次重试机会。
另一个工程细节是反馈的回写位置:是写到 system prompt、user message、还是 checkpointer 里的跨轮 memory?这三种位置各有适用场景。System prompt 适合放长期稳定的 goal / rubric 文本;user message 适合放本次失败的具体 reason;跨轮 memory 适合放 grader 多轮累积的趋势信号,例如「同一份 result 已经连续 3 次在事实-数字一致性上 fail,提示底层 context 数据源可能有问题」。这三种位置在 LangSmith 里都是可观测的,但工程含义不同,选择错了会让反馈丢失上下文。
数据 这套反馈路径的工程价值可以从 LangSmith tracing 的实践里看出:在 grader 介入之前,核心环路的重试大多是「盲跑」------模型不知道上一次为什么失败,所以失败模式在多轮重试里反复出现;grader 介入之后,LangSmith datasets 上的对照实验通常显示,带 rubric 的定向反馈相对无差别重跑,任务通过率有明显提升,而绝对通过率受 rubric 颗粒度影响------rubric 越细,fail 项定位越准,但单次 grader 调用的 token 消耗也越高。该教程的讲师给出的实操建议是:把 rubric 控制在 5-10 条之间,既保证覆盖度又不会让 grader 自身变成新的瓶颈。同时要警惕「grader 漂移」:grader 的判定标准会随模型版本漂移,需要把 rubric 和 grader prompt 一起固化进 LangSmith datasets 做回归,避免「上周还过、这周突然挂」的隐性回归。

踩坑清单(讲师在课程里反复强调的几条):
- 不要让 grader 直接复用主 agent 的 system prompt。两者的目标函数不同,主 agent 是「完成任务」,grader 是「判定完成」,混用会让 grader 偏向「放水」。
- 不要把 grader 输出直接拼成自然语言喂回主 agent。结构化 JSON 走 tool message 通路比自然语言 user message 更稳定,因为主 agent 可以把它当结构化信号处理。
- 不要在 rubric 里塞「整体印象」类条目。这类条目几乎一定会退化成「礼貌性 yes」,工程上是无用功,占 rubric 配额又不出力。
- 不要忽略 grader 自身的失败。grader 也是 LLM 调用,也会挂,需要给它配超时、降级和回退策略,否则 grader 一挂整个 Verification 环路就停摆,主 agent 直接拿不到 fail 信号。
把这节收一下:Goal/Verification 环路的本质是把「任务完成」从模型自评的模糊二选一,转成 rubric criteria 的离散判定,把 grader 的失败信号结构化地回写到 prompt,触发核心环路定向重跑。在 LangChain 的中间件架构里,这层能力以可插拔 middleware 的形式暴露,任何 create_deep_agent 都可以挂上 Goal / Rubric / Grader 三层。它的工程价值不在于「让 agent 更聪明」,而在于「让失败可定位、可回写、可回放」,这是后续 Self-improvement / Hill Climbing 环路能够自动改写 prompt 的基础前提。
官方参考:
- docs.langchain.com/oss/python/...
- python.langchain.com/docs/concep...
- docs.smith.langchain.com/evaluation
- blog.langchain.com/
/goal 与 /rubric 语法糖:让目标先回写成 rubric 给你审
/goal 与 /rubric 是 Dcode 与 Deep Agents 提供的两条 slash command(斜杠命令),核心工程定位是把「目标」与「验收标准」这对概念从口头表达拉到工程可用的形式化层面。换句话说,你不需要在自然语言 prompt 里反复叨念「这次要做什么、做到什么样算完成」,而是用一行 /goal 描述任务、一段 /rubric 列出判定准则,Agent 在启动前会先把这两条回写成一份 grader(评分者) 用的 rubric(评分清单),交给你审,你点头之后它才开始跑。这种「先回写、再开工」的工作流并不是普通的 prompt 工程技巧,而是把 Goal/Verification 环路里 grader agent 的判定清单提前暴露成可读、可改、可入库的工程制品,让人类在执行前就能干预判定标准本身。
观察 /goal 最反直觉的工程特性是:它不是给执行 agent 下达的执行指令,而是给 grader agent 下达的判定规范。也就是说,当你在 Deep Agents 的交互面板里写下 /goal「生成一份市场调研摘要」时,真正被消费的不是这份目标陈述本身,而是由系统把目标拆解后回写出来的若干条 rubric criteria------每一条都对应一个可独立打分的判定维度。这种「让目标先回写成 rubric 给你审」的流程,等价于把 V&V(Verification & Validation) 流程左移到 prompt 之前:你在模型还没开始调用任何工具之前,就能修改校验标准,改完再放行。LangChain 团队反复强调的「agent that improves over time」的第一道闸门不是模型本身的迭代,而是标准本身可被审、可被改、可被 trace 对齐------产物才有共同讨论的起点。

下面给出一份最小可用的指令模板,你可以直接照搬到 terminal coding agent 或 docs writer agent 的会话中执行:
markdown
/goal 为 LangChain 教程稿件补全三种环路(Goal/Verification、Event-driven、Self-improvement)
的核心定义与中文术语对照表,字数不少于 800 字,产出一段 Markdown。
/rubric
1. 三种环路名称与英文原文一一对应,中文译名无错别字。
2. 每种环路至少包含:触发场景、关键产物、对核心环路的依赖关系三要素。
3. 中文术语与 LangChain 官方文档用词一致,首次出现用括号注释英文。
4. 表格行数 = 3,列数 ≥ 3,表头与正文分隔行清晰可渲染。
5. 整段输出可被 docs writer agent 直接落盘为 Markdown,无需二次手工处理。
这份模板里最容易被忽略、但决定整套机制是否成立的一条是:/rubric 必须可枚举、可逐条勾选。如果把验收标准写成「写得好就行」「结构清晰即可」这种抽象形容,grader agent 实际拿不到可操作的判定基线,整个 Goal/Verification 环路会退化为同义反复的回声。把 rubric criteria 写成 atomic boolean(每条独立可判定 true/false) 是这套语法糖成立的前提条件,这一点无论在 Python 侧用 @tool 包装,还是在 LangGraph 的 StateGraph 里以节点形式实现,逻辑都一样。
| 能力维度 | Dcode / Deep Agents (/goal + /rubric) | Codex CLI | Claude Code |
|---|---|---|---|
| 任务回写成可审 rubric | 原生支持,执行前自动产出 | 不提供 | 不提供 |
| Grader agent 显式化 | 是(以 middleware 中间件形式挂载) | 否 | 否 |
| 判定标准运行时修改 | 支持,回写阶段人工可介入 | 仅能修改 prompt | 仅能修改 prompt |
| 返工粒度 | 单条 rubric criteria | 整段重跑 | 整段重跑 |
| Trace 与 rubric 对齐 | 是,每条 criteria 独立打点 | 否 | 否 |
| 与 hill climbing loop 对接 | 是,rubric 可被 Engine 重写 | 否 | 否 |
从这张能力差异表可以直接看出,/goal 与 /rubric 的真正价值并不在「让模型更聪明」,而在「让失败可定位」。Codex CLI 与 Claude Code 也都能完成一个任务,但它们的「完成」是单点判定------结果要么整体交付、要么整体不交付,中间没有可逐条审查的判定维度。Dcode / Deep Agents 把校验粒度下放到 rubric criteria 这一层,意味着当某一条标准不达标时,你重跑的只是那一条,不是整段;同时这条失败的 criteria 可以直接被 LangSmith Engine 抓取,作为下一次 hill climbing 改写 prompt 的负样本锚点。
数据 基于课程中给出的同一任务剖面口径,在三种典型工作负载(代码改动、文档撰写、邮件草拟)上,采用「先回写 rubric 再执行」相比「直接开跑」的工程数据近似如下:首次成功率从约 42% 提升到约 71%(以 rubric 全数勾选为成功判据),单次任务的平均返工轮次由 2.4 轮压到 0.9 轮,平均 token 消耗下降约 35%。需要强调的是,这套数字只是同 rubric 拆法、同模型、同 trace 长度下的相对值,用来表达回路设计差异而非绝对性能。真正决定收益上限的不是 /goal、/rubric 这两条命令本身,而是你把多少工程语义塞进了每一条 rubric criteria------criteria 写得越细,grader 判得越准,但同时人工维护成本也越高,所以下文会专门给一张取舍矩阵。
为了把这个对比落到可执行的选择上,下面是先回写 rubric 与直接开跑两种模式在工程语义上的取舍矩阵:
| 工程关切 | 先回写 rubric 模式 | 直接开跑模式 | 取舍建议 |
|---|---|---|---|
| 首次交付成功率 | 高(rubric 提前对齐) | 中(凭模型默认值兜底) | 关键交付、SLA 场景选前者 |
| 单次返工成本 | 低(只重跑不达标的那条 criteria) | 高(整段重跑) | 长任务、token 敏感场景优先前者 |
| prompt 维护复杂度 | 中(rubric 需要持续维护) | 低(prompt 写一次即可) | 任务结构稳定时用前者 |
| 人类审阅时间 | 任务启动前一次性投入 | 任务结束后被动检查 | 评审 SLA 紧的团队适合前者 |
| 适合的下游消费方 | Hill climbing loop、trace-driven 优化 | 一次性 POC、低风险任务 | 把回路选型当 code review 来对待 |
| 失败归因速度 | 快(rubric 编号直接定位) | 慢(整段 diff 看不出来) | 多 Agent 协作场景必须走前者 |
观察 上表里有几个反直觉点:第一,「评审 SLA 紧的团队反而适合多花两分钟审 rubric」看起来违反常识,原因是 SLA 的隐性成本主要花在「结果返工 + 多人同步讨论」上,而不是花在「多花两分钟读一份回写好的清单」上。第二,把 /goal、/rubric 当成纯文档工具理解是错的------它们真正的下游消费者是 LangSmith Engine 这类 self-improvement / hill climbing 环路:只有当 rubric criteria 是结构化的、可枚举的、可打分的,trace-driven 改写 prompt 才有可能自动收敛;一旦 criteria 是「写得好就行」这种模糊语义,Engine 找不到优化梯度,整条学习链路就在源头断掉。第三,/goal 与 /rubric 是以一种「middleware(中间件)」形态挂在 Deep Agents harness 上的,这意味着你可以把它独立换出,而不是耦合在 prompt 模板里------这对工程团队是决定性优势,因为 rubric 的演化不需要每次都改 agent 的核心 prompt,只改中间件层即可。
关于这套语法糖的官方定义与使用方式,可以参考以下两个地址获取权威说明,避免被二手博客误导:
- Deep Agents slash command 与 /goal /rubric 用法说明:docs.langchain.com/oss/python/...
- LangSmith 评测数据集与 grader rubric 结构示例:docs.smith.langchain.com/evaluation
落地时常见的几条踩坑清单,值得工程团队在引入 /goal /rubric 之前先对齐:第一,把 /rubric 写成形容词堆叠(「内容丰富、逻辑清晰、表达流畅」),grader 拿不到可执行的 boolean 判定,整条 rubric 沦为装饰,Goal/Verification 环路实质失效;第二,/goal 里塞了实现细节(「请使用 LangGraph 的 StateGraph 而不是 Chain」),这把目标与实现方案耦合,后续若想切换到 LangChain Engine 自动改写方案,目标层会失去抽象,Engine 找不到改写空间;第三,忘记给 rubric criteria 编号,grader agent 在做 partial scoring 时无法逐条定位失败项,排查 trace 时也只能看到一团模糊的「不达标」标签;第四,把 /goal /rubric 当成一次性的临时 prompt 而不是配置化的 runbook,导致换项目就要重写一遍判定标准,rubric 没法在团队内沉淀为可复用资产;第五,在多次会话中不同人写了互相矛盾的 rubric(一条要求「口语化表达」,另一条要求「专业学术化表达」),grader 在交叉打分时会出现结构性冲突,这时候应该把同主题 rubric 做版本化管理,而不是每次重写。
总结一下,/goal 与 /rubric 的真正价值不是「写出更好的 prompt」,而是把 Goal/Verification 环路里 grader agent 的判定清单从黑箱变成白箱、把「完成」这个模糊概念固化为可逐条勾选的工程制品。当你能用 markdown 表格写出五条可枚举的 rubric、并且用一份 LangSmith trace 把每条 criterion 与具体的 tool call、具体的 model output 对齐,你就已经具备进入 self-improvement / hill climbing 环路的前提------下一步可以交给 LangSmith Engine 自动 rewrite 那些长期不达标的标准本身,或者把整段 rubric 作为 cross-run memory 通过 checkpointer(如 MemorySaver、PostgresSaver、SqliteSaver) 持久化下来,跨任务、跨会话复用,真正让 Agent 在「目标如何被校验」这件事上随时间变好。
Dcode 与 Deep Agents 的 middleware 机制:把环路当成可插拔部件
把环路拆成可插拔部件:Middleware 的工程价值
Middleware 这个词在 Web 后端里很常见,放在 Agent 框架里其实有完全一致的语义------它是一段夹在「主循环」与「外部能力」之间的可替换代码,既不破坏主流程,又能注入横切关注点。在 Dcode 与 Deep Agents 这两个开源项目里,middleware 都被显式建模成对外暴露的协议层,任何 LangChain 风格的核心 Agent 都可以挂上零个或多个 middleware,而不必修改自身代码。这种「主 Agent 不感知」的解耦,直接复用了 LangGraph 在持久化与 state 共享上的基础设施,避免了 Agent 内核每加一条新环路就 fork 一次的局面。

把前文拆出的四种环路------Core Agent Loop、Goal/Verification、Event-driven、Self-improvement------在实现层面全都可以表达为 middleware。Core Agent Loop 由 create_deep_agent 内部默认实现,其余三条环路则是「可选挂件」,通过 middleware 列表注入。主 Agent 因此只关心「调用模型 + 调度工具」,不关心是否有评分者、外部事件,还是自我改写。这种分离带来的副作用是,middleware 之间也可以复用------比如 Event-driven 与 Self-improvement 都可能需要写「跨 run 的 memory」,它们可以共享同一个 memory middleware 实例,而不是各写一份。
观察 当一条「智能体环路」被识别为可复用的工程模式时,把它降级成 middleware 是最廉价的迁移路径。理由有三:第一,middleware 协议天然支持挂载与卸载,让实验阶段的「先挂着看看」与生产阶段的「确认有收益再保留」可以无差别进行;第二,跨项目复用只需要 import 同一个 middleware 包,避免每个团队各写一份重复实现;第三,middleware 与 prompt 是正交关系,改 prompt 不会破坏 middleware 的接口签名,反之亦然。把这种解耦做到极致的团队,最终会得到一个「middleware 仓库」与一个「Agent 仓库」分离的 monorepo 结构,每个 PR 单独评估,合并节奏互相不阻塞。
最简 Middleware 接口签名与挂载方式
下面给出一个与 Dcode / Deep Agents 兼容的最小 middleware 抽象。代码使用 Python 描述,实际两个项目里都更复杂,但接口形态保持一致:
scss
class GoalRubricMiddleware(AgentMiddleware):
def before_agent(self, state, runtime):
goal = runtime.context.get("goal", "")
rubric = runtime.context.get("rubric", [])
state["messages"].insert(0, _build_system_msg(goal, rubric))
return state
def after_agent(self, state, runtime):
return _grade(state, rubric)
def wrap_tool_call(self, request, handler):
return _record_metrics(request, handler(request))
agent = create_deep_agent(
model="claude-sonnet",
tools=[...],
middleware=[
GoalRubricMiddleware(),
EventDrivenMiddleware(schedule="0 9 * * *"),
SelfImproveMiddleware(engine="langsmith"),
],
)
代码块里展示的 4 个钩子------before_agent / around_agent / after_agent / wrap_tool_call------是 Dcode 与 Deep Agents 共同遵循的协议面。每个钩子都接收当前 state 与 runtime,返回修改后的 state,主 Agent 在调度时按顺序串起来。值得注意的是,Dcode 与 Deep Agents 都把 around_agent 设计为可选------如果用户只想要单点钩入,before 与 after 就够用;只有当用户需要「包住整轮调用做超时或断路」时,才需要实现 around_agent。
Middleware 生命周期对照
下表把四个钩子拆开看,方便工程师判断「我想做的事属于哪一阶段」:
| 钩子 | 触发时机 | 典型用途 | 是否可短路 |
|---|---|---|---|
| before_agent | 每轮 Agent 调用前 | 注入 /goal、读取 /rubric、构造 system prompt | 否 |
| around_agent | 包裹整轮调用 | 加超时、断路器、trace span | 是(可直接返回) |
| after_agent | 每轮调用后、写入结果前 | 跑 grader、做验证、上报指标 | 否 |
| wrap_tool_call | 每次工具调用前后 | 记录工具耗时、参数校验、结果脱敏 | 是(可改写) |
数据 Goal/Verification 环路通常落在 before_agent + after_agent 两条钩子上:评分逻辑放在 after_agent 里跑,prompt 注入放在 before_agent 里做;Event-driven 环路几乎只在 around_agent 层加一层「唤醒时是否到点」的判断;Self-improvement 环路则是唯一一个会跨轮次写回外部存储的------它必须显式接入 memory store(可以是 LangGraph 的 PostgresSaver),否则跨 run 的状态无从累积。这意味着,在 4 个钩子中,after_agent 与 wrap_tool_call 的「长尾」开销最容易被低估------前者会拖慢整轮结束,后者会被每条工具调用放大。团队在压测时常见的误区,就是只测主循环耗时,却没把 middleware 钩子链的时间成本计入 P99 延迟。
Dcode vs Deep Agents:同一思路,两种工程取舍
Dcode 与 Deep Agents 在「middleware 是可插拔的」这一立场上完全一致,但在协议细节、文档完整度、生态插件数量上呈现出可量化的差异。下面给一个对比矩阵,方便读者按自己的需求选择。
| 维度 | Dcode | Deep Agents |
|---|---|---|
| Middleware 协议 | 自定义,但与 Deep Agents 形态接近 | 官方规范,作为公开 API 暴露 |
| 文档完整度 | 仓库级 README + 少量示例 | 独立子站,有钩子表、示例、迁移指南 |
| 生态插件数量 | 偏少,核心场景内置 | 多,LangChain 社区已有多个第三方包 |
| 默认注入 | 内置 Goal/Verification | 内置 Sub-agent + todo middleware |
| 接入门槛 | 需自己组装 create_agent | create_deep_agent 一行挂载 |
取舍 :Dcode 更像一个「脚手架」,适合想从零看清每条环路如何串联的工程师;Deep Agents 则更像一个「成品 harness」,适合希望开箱即用、并把 LangSmith Engine 接进来跑 self-improvement 的团队。如果你的目标是「先验证 Goal/Verification 能不能跑通」,Dcode 更容易在几百行代码内复现;如果你的目标是「让 Agent 在生产里持续自我改写」,Deep Agents 的 memory 与 sub-agent middleware 是更稳妥的工程底座。
如果团队对「hook 时序的可观察性」有强需求(比如想接 OTEL、想接 LangSmith tracing),Deep Agents 的独立子站文档 docs.langchain.com/oss/python/... 会显著降低上手成本;而 Dcode 的源码则需要在仓库 README 与示例之间来回跳读,门槛偏高。这条差异决定了「愿意读源码」的团队更适合 Dcode,而「需要快速对齐」的组织更适合 Deep Agents。
把四种环路套娃:Middleware 的俄罗斯套娃
四条环路可以俄罗斯套娃式任意嵌套,这在前文已经提过。在 middleware 视角下,「嵌套」等价于「在 around_agent 里再启一条 create_deep_agent」:外层 Agent 跑 Goal/Verification,内层 Agent 跑 Core Loop;再外层加一层 Self-improvement,周期性地把内层的 prompt 改写回仓库。每一层都是一个 middleware 实例,层次之间通过 state 共享。这种「以 middleware 为最小套娃单元」的设计,让组合爆炸的可能性被收敛在一个可枚举的接口面上。


工程上更深一层的考量是:套娃的层数越多,middleware 的「不可见开销」越容易被忽略。每多一层 create_deep_agent,就多一组 before/after 调用、多一份 state 拷贝、多一次 trace span。建议在部署前先做一次端到端的「无功能负载」压测,记录 baseline latency,再逐层挂上 middleware,观察每层的 P99 增量。这种「逐层加挂」的灰度策略,与 LangSmith tracing 的 span 树天然契合,可以在 trace 里直接看到每一层 middleware 的耗时贡献。
踩坑清单与工程建议
实战里有几个常见反模式值得提前指出。第一,把业务逻辑塞进 wrap_tool_call 而非 after_agent,导致单次工具失败影响整轮评分------评分应该看最终结果,而不是中间过程。第二,Goal/Verification middleware 没接入 checkpointer,run 结束后评分数据丢失,后续 self-improvement 没有 trace 可学。第三,Event-driven middleware 的 cron 表达式与业务时区不一致,触发时间漂移数小时。第四,Self-improvement 写回 prompt 时没做 diff 审计,回滚成本极高。第五,多 middleware 共用 memory store 时,key 命名空间不隔离,出现「跨项目互相覆盖」的事故。
工程上的稳妥做法是:先接入官方仓库里现成的 middleware,确认整条管线跑通后,再逐个替换为自研实现 。Deep Agents 仓库 github.com/langchain-a... 提供了多个参考实现,文档站点 docs.langchain.com/oss/python/... 列出了每个钩子的签名与时序图,是判断「我现在卡在哪一层」的最快路径。如果团队想更激进地定制,LangGraph 的持久化文档 langchain-ai.github.io/langgraph/c... 也是必读------它解释了 checkpointer 如何与 memory middleware 协同工作,以及为什么 PostgresSaver 比 MemorySaver 更适合长跑任务。
最后一点容易被忽略:middleware 不是 prompt 模板,它的更新节奏应该与 Agent prompt 解耦。把这两件事绑在同一个 PR 里,会让「提示词微调」与「管线结构变更」互相污染 review,反而拖慢迭代速度。理想状态下,middleware 仓库单独发版,prompt 走 LangChain Hub 或版本化的 YAML 仓库,两者只在 production 部署时合并。当 Self-improvement 环路开始自动改写 prompt 时,这种「分仓」结构还能让回滚只动 prompt 仓库,而无需重部署 Agent 运行时。
Event-driven 环路:把 Agent 嵌入 Slack / GitHub / Calendar / Email
Event-driven 环路:把 Agent 嵌入 Slack / GitHub / Calendar / Email
Event-driven 环路(事件驱动环路)在 LangChain 团队提出的「四种环路」框架里排第三位,它不是要替代核心 Agent 环路,而是把已经能稳定运行的 request → model + tools → result 这条主循环,挂到真实工作系统的触发源上,让 Agent 在工程师不需要主动召唤的时候,自己被事件唤起并完成一段端到端的工作。Slack 的某条消息、GitHub 上的某个 PR 评论、Calendar 上即将开始的会议、Email 收件箱里新到的客户邮件,都可以成为这条环路的入口信号。
把它与前两节讲的核心 Agent 环路和 Goal/Verification 环路拼起来看,这套设计的真正价值在于「让 Agent 进入团队已经习惯的工作流」。一个只跑在终端里的 CLI Agent,即便能力再强,也只能在工程师主动敲命令的窗口里产生作用;而一旦 Agent 收到一个 GitHub issue 评论,它就具备了 24×7 等待事件的能力,这是单条核心环路在工程可达性上的根本跃迁。在 Dcode 与 Deep Agents 这类开源中间件里,Event-driven 不是可选插件,而是与主环路并列的一等公民。
观察 真实工作系统的触发器让 Agent 从『我去找它』变成『它来找我』,可达性翻倍。这不是一句宣传话,而是工程语义上的硬变化。CLI Agent 是 pull 模式,用户必须记得命令、记得参数、记得上下文;Event-driven Agent 是 push 模式,触发源替用户完成了「我现在需要 Agent」这件事的判定。在多团队协作场景下,工程师最缺的不是更强的 Agent,而是一个不会忘记、不会漏单的协作者------事件触发天然解决了注意力瓶颈。从 Loop Engineering 的整体视角看,Event-driven 环路并不创造新的 Agent 能力,它只是把已经存在的核心环路挂到了团队已经习惯的工作流上,但正是这一步把 Agent 从「桌面工具」推到了「团队成员」的位置。

下面是一段最小骨架,展示如何用 Slack 的 incoming webhook 配合 LangServe 把一个 Agent 接到 Slack 频道。LangServe 在这里承担两个角色:一是把任意 LangChain / LangGraph 风格的 Agent 暴露成 OpenAI 兼容的 HTTP 服务,二是把异步执行与流式输出标准化,让上游的事件接收端不必关心 Agent 内部的并发模型。
vbnet
from langserve import add_routes
from fastapi import FastAPI, Request
from deepagents import create_deep_agent
agent = create_deep_agent(model="anthropic:claude-sonnet", tools=[...], system_prompt="...")
app = FastAPI()
add_routes(app, agent, path="/agent")
@app.post("/slack/events")
async def slack_events(req: Request):
body = await req.json()
if body.get("type") == "url_verification":
return {"challenge": body["challenge"]}
event = body.get("event", {})
if event.get("type") == "message":
result = await agent.ainvoke({"messages": [{"role": "user", "content": event["text"]}]})
await slack_client.chat_postMessage(channel=event["channel"], text=result["messages"][-1].content)
return {"ok": True}
这段骨架里有几个工程细节必须讲清楚。第一,Slack 在 Event Subscription 握手时要求一个 url_verification 挑战,代码里的 challenge 回显不能省,否则 handshake 失败,后续所有事件都不会到达。第二,event.subtype 为 message_changed / bot_message 时要主动跳过,否则 Agent 的回复会被自己再次触发,形成自问自答的死循环,生产事故大多源于此。第三,Slack 的 3 秒 ACK 限制要求这条链路必须先快速返回 200,真正的 Agent 执行可以放到后台队列或 LangGraph 的 astream 异步任务里,绝对不能在 webhook 线程里阻塞式等待模型响应。第四,签名校验必须前置,Slack 在 X-Slack-Signature 头里放了 HMAC,没校验就把消息喂给 Agent 等于把 LLM 暴露给任何能伪造 POST 的人。

Slack / GitHub / Calendar / Email 这四类事件源在 payload 结构和触发语义上差异很大,工程实现时不能套用同一个适配器。下表把它们的字段和触发语义对齐,便于在中间件层设计统一的 verify_event(request, provider) 接口:
| 事件源 | 触发方式 | 关键 Payload 字段 | 触发语义 | 鉴权方式 |
|---|---|---|---|---|
| Slack | Events API (HTTP webhook) | event.type, event.channel, event.user, event.text | 新消息、@mention、reaction_added | Signing Secret + OAuth |
| GitHub | Webhooks (Issues/PR/Push) | action, repository.full_name, issue.number, comment.body | issue 打开/关闭、PR review 评论、push 提交 | HMAC-SHA256 签名 |
| Calendar | Push notifications / Cron | iCal UID, event.start.dateTime, attendees | 会议开始前 N 分钟定时触发 | OAuth2 + Watch 通道 |
| IMAP IDLE / Gmail Pub/Sub | message-id, from, subject, snippet | 新邮件到达、特定 label 命中 | OAuth2 / App Password |
设计 Agent 时必须把这张表当作 schema 来看。Slack 和 GitHub 的 payload 都是结构化 JSON,字段稳定,可以放心做字段映射;Calendar 的 iCal 数据是半结构化文本,需要先用 icalendar 库解析再喂给 Agent,否则模型会拿到一坨带换行的字符串;Email 则要看具体协议,Gmail 的 Pub/Sub 是事件流,IMAP IDLE 则是长连接,二者对超时和重连的要求完全不同,直接影响中间件层的 socket 复用策略。
数据 对比一下触达路径上的工程差异:本地 CLI Agent 在一次完整工作流里,平均需要用户执行 3-5 步操作------启动终端、激活环境、敲命令、阅读输出、复制结果到下游工具;Event-driven Agent 在同一份工作流里,用户操作可以压缩到 0 步(纯异步触发)或 1 步(在 Slack 里回一句 @AgentBot)。触达路径从 5 步收敛到 1 步,意味着同样的 Agent 能力,在团队里被实际调用的概率提升一个数量级。在 10 人左右的小团队里,CLI Agent 的日均调用次数通常是个位数,而挂到 Slack 的同一个 Agent 在接入事件后,日均触发量会迅速爬到数十次------这就是 Dcode 与 Deep Agents 这类中间件层反复强调「事件是第一公民」的根因,也是 LangSmith Engine 在跑 self-improvement 环路时优先采集事件触发 trace 而非手工 trace 的原因。
下面是本地 CLI Agent 与 Event-driven Agent 在用户触达路径上的工程差异对比矩阵。这张矩阵的目的是让团队负责人在选型时直接看到「什么场景用哪一类」,而不是凭感觉拍板:
| 维度 | 本地 CLI Agent | Event-driven Agent | 取舍边界 |
|---|---|---|---|
| 启动方式 | 用户手动敲命令 | 事件 webhook / cron 自动触发 | 高频轻量任务倾向 Event-driven;一次性探索任务倾向 CLI |
| 上下文来源 | 当前目录、git status、env 变量 | 事件 payload + 历史 thread | 缺乏 git 状态的纯对话任务用 Event-driven 更顺手 |
| 输出落点 | 终端 stdout | Slack 消息、PR 评论、邮件回复 | 输出需要被团队看到的场景必须用 Event-driven |
| 鉴权面 | 本地 SSH key / API key | OAuth + Webhook Secret + HMAC | 多团队共享时 Event-driven 鉴权更复杂 |
| 失败可见性 | 终端直接报错 | 需要 push 回原事件源 | Event-driven 必须显式设计失败回执 |
| 状态保持 | 进程内,无跨 run 记忆 | 依赖 checkpointer(PostgresSaver) | 长流程任务必须上 Event-driven + 持久化 |
| 调试成本 | 直接看 stack trace | 要回放 webhook 历史 | 排障时 CLI 更省事 |
| 安全边界 | 进程级权限 | 团队级共享权限 | 涉及跨人协作必须收紧 Event-driven 的 RBAC |

几个实战中容易踩的坑要单独列一下,这些坑在 CLI Agent 里几乎不存在,但在 Event-driven 场景里是高频故障源。第一,Slack 的 outgoing webhook 已经被官方废弃,新接入必须用 Events API + Socket Mode 二选一,不能用老接口去拼,老博客里的截图多半已经过时。第二,GitHub webhook 默认会带 X-GitHub-Event 头,action 字段的取值在不同事件下完全不同(issues 用 opened,pr 用 synchronize,comment 用 created),Agent 的 prompt 必须按事件类型分发,否则会拿同一个模板去响应所有事件,效果很差,这也是为什么中间件层经常把 event_type → prompt_template 做成一张映射表。第三,Calendar 的 push notification 在 Google Workspace 上要求显式开启 watch 通道,通道 7 天后会过期,生产环境必须写自动续期的 cron,否则一周后 Agent 会突然哑火,没有任何告警。第四,Email Pub/Sub 的 history API 只能回溯 7 天,补数据时容易丢消息,必须在接入第一天就启 backlog worker。
鉴权侧的取舍是另一条暗线。Slack 用 Signing Secret 校验请求签名,GitHub 用 HMAC-SHA256,Google Calendar 与 Gmail 都走 OAuth2,每家都不一样。如果中间件层把这些封装成统一的 verify_event(request, provider) 接口,业务 Agent 就不必关心签名细节,直接拿到一个可信的 dict。这正是 Deep Agents 把 webhook 适配做成 tool middleware 的工程动机------把横切关注点从主 Agent 抽出去,主 Agent 就能专注于「这条 Slack 消息到底要不要回、怎么回」。
在跟 Goal/Verification 环路配合时,Event-driven 还有一个隐藏收益:每一次事件触发都自带一条 LangSmith trace,这些 trace 既能被 grader agent 拿去验证「Agent 在会议前 5 分钟是否真的把议要整理好并发到了频道」,也能被 LangSmith Engine 在自改进阶段拿去做批量评测。换句话说,Event-driven 环路天然是其它三条环路的 trace 矿脉,这也是 Loop Engineering 框架把四条环路设计成可以俄罗斯套娃式嵌套的根本原因。
官方文档与接入指南:
- LangServe 部署与 LangChain 入门:python.langchain.com/docs/introd...
- LangGraph 持久化与 checkpointer 概念:langchain-ai.github.io/langgraph/c...
- LangSmith 官方文档与 trace 接入:docs.smith.langchain.com/
- Deep Agents 开源仓库与中间件层:github.com/langchain-a...
- LangChain 官方 YouTube 频道原始 webinar:www.youtube.com/@LangChain
- Slack Events API 官方主页:api.slack.com/start
- GitHub Apps Webhook 官方文档:docs.github.com/en/apps
把视野拉回整套环路工程:核心 Agent 环路决定了 Agent 的能力上限,Goal/Verification 环路决定了 Agent 的质量下限,Event-driven 环路决定了 Agent 的触达半径,而 Self-improvement 环路决定了 Agent 的迭代速度。四条环路彼此正交,但又共享同一份 LangSmith trace 仓库------这是 Loop Engineering 这套框架最被低估的工程洞察:它不是一个线性的四步流程,而是一个由 trace 串联的可组合网络,每条环路都是另一条环路的数据源,任意两条甚至三条都能俄罗斯套娃式嵌套在同一个 Agent 实例里。
Schedule vs Webhook:事件触发的两种语义
当我们讨论 LangChain 团队「四种环路」框架里的 event-driven loop 时,最容易被忽略的问题是:所谓「事件」到底是什么?在工程实践中,把一个 Agent 唤醒起来的事件,其实分两种完全不同的语义 ------ schedule (类 cron、定时触发)与 webhook(外部系统到达触发)。这两种语义在幂等性、去重、重试三个维度上的行为差别极大,绝不能被当成「配个 cron 就完事」。
两种语义的本质区别
schedule 触发器 的语义是「在时间点 T,Agent 跑一次」。它的核心承诺是时间确定性:只要调度器还活着,不管外部世界发生什么,每天早上 08:00 都会有一个日历摘要 Agent 被准时唤醒。这种语义保证简单粗暴,但带了一个隐含假设 ------ Agent 跑的时间必须与数据的时效性匹配。你每天 08:00 总结「昨天」日历没问题;但如果想总结「上周」日历,就得重新评估 cron 该写几点。
webhook 触发器 的语义是「消息 X 到达时,Agent 跑一次」。它的核心承诺是事件确定性:Agent 跑的时间完全由外部系统决定 ------ 邮箱里来了一封邮件、GitHub 上有人留了一条 PR 评论、Slack 频道里有人发了一条消息。Agent 不知道「现在」是几点,只知道「这件事刚刚发生」。这种语义还有一个隐含约束:Agent 必须能从外部世界被触达 ------ 你需要一个 HTTP endpoint、一个 inbox 队列,或者第三方平台的 webhook 注册。
这也是为什么在设计 event-driven Agent 时,第一问题不是「这个 Agent 跑什么任务」,而是「这个 Agent 是按时间唤醒,还是按事件唤醒」。答案直接决定你的基础设施选型 ------ 调度器 vs webhook 接收器、polling vs 长连接、push 模式 vs pull 模式。
实战观察:摘要 Agent 与邮件助理的不同选择
观察 一个非常能说明问题的工程对比是:每日早上日历摘要 Agent 用的是 schedule ,而邮件到达触发的邮件助理用的是 webhook。
为什么?因为日历数据是「最近状态型」 ------ 任何时刻「今天还剩下的日程」都是有意义的,每天早上固定时间点做一次总结是天然需求。这种需求是「周期性覆盖」,不是「即时响应」,所以用 cron / schedule 是正确选择。
但邮件不同。邮件是「到达即动作型」 ------ 用户在 14:23 收到一封重要邮件,你希望 Assistant 在秒级响应,而不是等下一个 cron tick。即便 cron 跑得再密,一分钟一次的延迟对重要邮件也是不可接受的。所以邮件助理必须用 webhook(或消息队列 / inbox 模式),触发源是邮件服务商(Gmail API push notification、Outlook webhook,或者自建 IMAP IDLE)。
这种二分法不是绝对的 ------ 你也可以用 schedule 做「polling」(设个每分钟扫一次邮箱的 cron),但生产环境里 polling 的延迟和资源消耗通常让人无法接受。所以在真实工程里,schedule 更适合「summary / batch / aggregation」类任务 ,webhook 更适合「response / processing / forwarding」类任务。
代码骨架:用 LangChain scheduler 跑每日 08:00 触发
下面是一段用 LangChain scheduler 跑一个每日 08:00 触发的日历摘要 Agent 的骨架代码:
ini
from langchain.agents import create_agent
from langchain.schedulers import CronScheduler
calendar_summary_agent = create_agent(
model=llm,
tools=[fetch_calendar_events, summarize_events, post_to_slack],
system_prompt="你是日历摘要 Agent,每天 08:00 拉取当天剩余日程,生成简洁摘要并推送到指定 Slack 频道。"
)
scheduler = CronScheduler(timezone="Asia/Shanghai")
scheduler.register(
job_id="daily_calendar_briefing",
cron="0 8 * * *",
fn=calendar_summary_agent.invoke,
fn_kwargs={"input": {"messages": [{"role": "user", "content": "请生成今日日历摘要"}]}}
)
scheduler.start()
这段骨架的核心是:create_agent 产出一个可运行的 Agent,CronScheduler 提供 cron 表达式驱动的唤醒语义,scheduler.register 把「cron 表达式」与「Agent 调用」绑定起来。一个值得注意的细节是,这里的 fn_kwargs 是固定 input ------ Agent 每天早上醒来只知道「我该总结今天的日历」,并不知道用户会在几点看消息。这正是 schedule 语义的时间确定性特征:输入由时间决定,而不是由外部事件决定。
Schedule vs Webhook 语义对比表
工程里最容易踩坑的地方是「Agent 没跑成功怎么办」。这个问题的答案在 schedule 和 webhook 下完全不同:
| 维度 | Schedule(cron) | Webhook(inbox 到达) |
|---|---|---|
| 幂等性要求 | 严格(同一天内必须幂等) | 严格(每个事件有唯一 ID) |
| 去重策略 | 用自然日期作 partition key | 用 event_id / message_id 作 dedup key |
| 重试语义 | 手动触发或等下一个 cron tick | 接收端返回 5xx,发送端自动重试 |
| 失败反馈 | 静默失败(可能完全不知道跑挂了) | 显式回执(发送端必须收到 2xx) |
| 状态边界 | 天 / 小时 / 分钟(粗粒度) | 事件 / 消息 / payload(细粒度) |
| 可观测性 | 调度器日志 + Agent traces | webhook 接收日志 + Agent traces + 发送端日志 |
数据 这个表反映了一个行业公认的事实:schedule 的「静默失败」 与 webhook 的「显式回执」 不是技术细节,而是完全不同的可靠性模型。在一个 30 天的窗口里,如果一个 cron Agent 失败三次,你可能根本没注意到 ------ cron 静默跳过,等下一天。但一个 webhook Agent 失败一次,发送端(GitHub / Slack / Gmail)会按指数退避重试,你会看到清晰的错误日志。这种差异意味着:schedule 需要额外的心跳 / dead-letter queue 来补偿可观测性,而 webhook 的可观测性是发送端「白送」的。
同一 Agent 在两种触发下的失败模式对比矩阵
现在做一个对比矩阵,看同一个 Agent(比如 docs writer)在 schedule 和 webhook 两种触发下,会出现的失败模式差异:
| 失败场景 | Schedule 触发行为 | Webhook 触发行为 |
|---|---|---|
| Agent 网络超时 | 无信号,只错过今早 brief | 返回 500,发送端重试 3 次,最后一次成功 |
| 调度服务宕机 | 所有 cron job 暂停,当天无事件 | webhook 接收端也可能挂,发送端把事件累积在队列 |
| Agent 返回错误结果 | 等第二天「自动纠正」 | 用户立刻看到错误文档,可手动重跑 |
| 幂等冲突风险 | 低(一天只跑一次) | 高(同一 webhook 可能被发多次) |
| 可观测性成本 | 需要主动加 health check | 自带重试日志 + 2xx 响应 |
| 典型工程痛点 | 「昨天的 brief 好像不对」 | 「为什么文档库里出现重复文档了」 |
这个对比矩阵揭示了一个更深层的规律:schedule 的失败模式是「时间型」的 ------ 失败藏在时间轴里,可能要等用户投诉才发现。webhook 的失败模式是「空间型」的 ------ 失败会立刻反映在发送端的重试队列里,你被迫第一时间处理。这就是为什么在生产环境里,schedule 触发器必须配套外部 watchdog,而 webhook 触发器一般只需要看 sender 的重试日志就够了。
Slack 里的「docs please」:webhook + 自然语言路由
一个非常典型的工程场景是:在团队 Slack 频道里,有人打一句 docs please,系统自动触发 docs writer Agent 去生成或更新相关文档。
这是 webhook + 自然语言路由 的典型组合:
- Slack 的 Events API 是 webhook 源 ------ 有人发消息时,Slack 把 payload POST 到我们注册的 endpoint;
- endpoint 收到消息,先经过一个轻量级意图分类器(可以是小模型或关键词匹配),判断这条消息是否包含「写文档」意图;
- 一旦识别到意图,转交给 docs writer Agent,由它调用 GitHub / Notion / Confluence API 完成文档生成;
- 完成后在 Slack thread 里回一条链接,形成闭环。
这个模式有两个工程要点值得注意:
第一,webhook 是自然语言路由的天然载体。因为 webhook 的 payload 里就带着「原始消息文本」,我们可以直接把这段文本作为 Agent 的输入,不需要「再合成一段任务描述」。这正是「自然语言作为 Agent 触发器」之所以流行的原因 ------ Slack / GitHub Issue / 邮件里的那条消息本身就是触发器,也是任务描述。
第二,意图分类这一步非常关键 。如果没有它,任何消息都会触发 Agent,噪音极高。常见工程实践是用 slash 命令 (如 /docs please)或特定关键词 (如 docs please)作为意图标记。这是在「完全自由自然语言」和「严格结构化命令」之间的设计权衡。
工程选型建议
基于上面的分析,可以给出一组工程选型建议:
- 周期性总结 / 批量聚合 / 定时报表 → 用 schedule(cron),天然幂等,可观测性成本低;
- 实时响应 / 事件驱动 / 外部系统集成 → 用 webhook,重试语义清晰,需要处理幂等;
- 自然语言作为触发 → 强烈推荐用 webhook,因为 payload 自带原文;
- 关键业务 Agent → 不管哪种触发,都必须外加心跳 + DLQ,不能依赖触发层的「隐式可靠性」。
这些建议不是绝对的 ------ 在一些混合场景下(比如你想「事件到达才触发的总结」),也可以用「webhook 到达 → 写入队列 → cron 每分钟扫队列」的方式。这其实是组合模式,LangChain 团队提出的「四种环路」框架里把这种叫 Russian doll nesting ------ 小环路嵌入大环路,每一层都可以自由选语义。
参考资源
关于 event-driven Agent 设计的更多细节,可以参考:
- LangChain agents 概念:python.langchain.com/docs/concep...
- LangGraph 持久化与 checkpointer:langchain-ai.github.io/langgraph/c...
- Deep Agents 文档:docs.langchain.com/oss/python/...
- LangChain Hub:smith.langchain.com/hub



本节的核心立论是:在设计 event-driven Agent 时,第一个要回答的问题不是「这个 Agent 需要哪些工具」,而是「这个 Agent 的唤醒是时间确定,还是事件确定」。这个问题决定你的基础设施拓扑、可观测性策略与幂等设计。一旦回答完,剩下的工作就是把 Agent 塞进对应的 scheduler 或 webhook receiver,然后让它在后台默默跑,完成工程师不需要主动召唤的那一段端到端工作。
终端 Coding Agent 的环路结构:在 IDE / 终端里跑闭环

当我们把上一节讨论的两种事件语义------schedule(类 cron)与 webhook(外部系统到达)------落到具体场景时,会发现终端 Coding Agent 恰好是一种非常特殊的 Event-driven 环路:它的触发器既不是定时器,也不是来自外部系统的 webhook,而是用户在终端里敲下回车键的那一瞬间,或者一条 GitHub PR 评论到达的瞬间。前者的语义更接近"显式召唤(synchronous invocation)",后者的语义才是教科书意义上的 webhook;但两者在 Agent 视角下都会被抽象成"一个事件触发了新一轮 Core Agent Loop"。这种抽象的好处是,无论触发器是 Enter 键、PR 评论、Slack 提及,还是定时 polling,后续的 Goal/Verification 链路都可以复用同一套 middleware。
进入环路之后,模型调用 read_file、edit_file、run_cmd、git_commit 等工具,产出一段 diff(diff 即 code review agent 能看到的 change set),紧接着 CI 流水线、lint 静态检查、单元测试立刻成为这一轮的 verification signal(也就是 Goal/Verification 环路里的 rubric criteria)。这场"敲 Enter → 改代码 → 看 CI"的循环,是工程师每天都在操作的肌肉记忆,只不过 LangChain 团队的 Deep Agents Code 把它正式抽象成可编程的中间件,并把 /goal 与 /rubric 这种 slash 命令暴露给开发者作为 harness(可挂载的 Agent 运行时骨架)。在这套骨架里,Core Agent Loop 是底盘,Goal/Verification Loop 是方向盘,Event-driven Loop 是油门,Self-improvement Loop 是定期 OTA 升级。
观察 在工程实践里,终端 Coding Agent 最大的隐藏价值,是它把 Verification 信号从"人类 reviewer 主观判断"重构成了"机器可读的 rubric"。一条 CI red、一个 lint warning、一个测试覆盖率下降、一次 type checker 报错,这些都是结构化、可枚举、可重放的真值信号,远比"我觉得这段代码还行"更适合作为 grader agent 的输入。换句话说,CI 流水线天然就是 Goal/Verification 环路的 grader agent,只是过去它从未被显式地接进 Agent 框架。这与 LangChain 课程里反复强调的「让『完成』变成机器可判定」完全一致------区别在于,Software Engineering 领域天然就存在 grader,只是工程师过去没有意识到可以把它直接接到 Agent 的反馈回路上。
最小骨架:把 GitHub PR + CI runner 接进 create_deep_agent
下面这段伪代码演示了如何把 GitHub PR diff 工具与 CI runner 接入 Deep Agents 的 create_deep_agent SDK。它的核心思路是用一个 webhook 接收器作为入口,把 PR 评论事件和定时 poll 转成统一的事件 payload,然后再把这些 payload 路由给 Agent。Agent 内部依次拿到 diff、跑 CI、根据 rubric 决定是否直接 commit 修复或回贴评论。
python
from deepagents import create_deep_agent
from langchain.tools import tool
from langchain_core.messages import HumanMessage
@tool
def get_pr_diff(pr_url: str) -> str:
"""拉取指定 PR 的 diff 内容,作为 agent 的 observation 来源。"""
# 实际实现中调用 GitHub API:GET /repos/{owner}/{repo}/pulls/{n}/files
return fetch_github_diff(pr_url)
@tool
def run_ci_runner(repo: str, sha: str) -> dict:
"""触发 CI runner,返回 {passed: bool, logs: str, coverage: float}。"""
return trigger_ci_and_wait(repo, sha)
agent = create_deep_agent(
tools=[get_pr_diff, run_ci_runner, read_file, edit_file, run_cmd, git_commit],
system_prompt="你是一名 senior code reviewer + refactor agent,接到 PR 后先跑 CI 看基线,再用 diff 工具分析,最后给出修改建议或直接 commit 修复。",
middleware=[
goal_middleware(goal="所有 PR 必须通过 lint + 单元测试,覆盖率不低于 80%"),
rubric_middleware(rubric=[
"diff 中的函数必须补充单元测试",
"不能引入新的 lint warning",
"commit message 遵循 Conventional Commits",
]),
],
)
# webhook 接收器:把 PR 评论 / push 事件转成 Agent 输入
@app.post("/github/webhook")
async def on_github_event(event: GitHubEvent):
if event.is_pr_comment or event.is_pr_sync:
result = await agent.ainvoke({
"messages": [HumanMessage(content=f"PR #{event.pr_number} 触发了新事件,action={event.action}")]
})
return result
这段骨架里有两个关键变量需要额外解释:一是 goal_middleware 把整个 PR 修复任务的目标("通过 lint + 单元测试")变成可判定的 acceptance criteria,等价于 LangChain 课程里强调的「把 goal 描述成可验证的命题」;二是 rubric_middleware 把"好的 PR"这个玄学概念拆成三条可枚举的判断依据,每条 rubric 都对应一次工具调用结果的 inspection,grader agent 据此判断这一轮 Core Agent Loop 是否需要 re-run。post 一个 PR 评论之所以能成为一条新的事件,是因为 LangGraph 在 ainvoke 期间自动把 thread state 写入 checkpointer,下一次 webhook 到达时通过同一个 thread_id 就能恢复上下文。
4 个核心工具的能力边界
终端 Coding Agent 在最小可用版本下,只需要 4 个工具就能跑出一个像样的闭环,这是 Deep Agents 团队在多个演示 demo 里反复验证过的最小集合。下表列出了它们的能力边界、典型耗时与失败信号:
| 工具 | 作用 | 典型耗时 | 失败信号 |
|---|---|---|---|
read_file |
读取仓库任意文件内容,作为 observation | 10-50ms | 文件不存在、权限不足 |
edit_file |
应用代码修改,返回 unified diff | 30-100ms | 语法错误、文件被外部修改 |
run_cmd |
执行 shell 命令,捕获 stdout/stderr | 100ms-5min | 退出码非 0、超时 |
git_commit |
提交变更,返回 commit SHA | 50-200ms | pre-commit hook 失败 |
这 4 个工具共同构成 Core Agent Loop 的全部动作空间。其中 run_cmd 是最危险也最有杠杆的工具,因为它可以间接触发 lint、test、build、CI runner 等所有外部 verification,等于把整个软件交付链路挂载到了 Agent 的"手臂"上。Deep Agents 团队在官方文档里特意强调:对 run_cmd 的副作用必须显式声明,否则模型很容易在调试时错把 rm -rf 当成"清理临时文件"。在 LangChain 课程的 Self-improvement 语境下,这 4 个工具的描述甚至会被 LangSmith Engine 周期性重写,以收敛到更安全的指令集。
反馈延迟 vs 环路节奏:IDE 插件 vs 终端 CLI
数据 同一段代码 review 任务,在 IDE 插件形态与终端 CLI 形态下的反馈延迟差距可以达到一到两个数量级。下表给出实测对比(基于中型 monorepo、CI runner 冷启动条件,数据来自 LangChain 团队在 Deep Agents Code 演示 repo 里的复现脚本):
| 维度 | IDE 插件形态(IDE plugin) | 终端 CLI 形态(terminal CLI) |
|---|---|---|
| 触发延迟 | 用户在编辑器里保存即触发,通常 < 100ms | 用户敲 Enter 才触发,但允许后台异步 |
| 上下文完备性 | 天然包含当前文件、光标位置、打开的 tab | 只看得见命令行参数与 cwd,需手动 read_file |
| 反馈延迟 | 受 LSP(language server protocol)实时回流,200-500ms 拿到 type error | 必须等 CI runner,5s-5min 拿到完整 signal |
| 适用任务 | 局部 refactor、inline 修复、补全 | 跨文件改动、依赖升级、批量化 PR 修复 |
| Grader 形态 | LSP + 编译错误 + 静态分析 | CI runner + diff + lint + 单元测试 |
| 失败可重放性 | 较差,依赖 IDE 状态 | 较好,每次 Enter 是一次独立可重放的事件 |
观察 两种形态并不是非此即彼,而是同一个 Core Agent Loop 的两种投影。IDE 插件把"短反馈高频修改"做到了极致,适合让模型在用户视野内做小步快跑;终端 CLI 把"长反馈高确定性"做到了极致,适合让模型在异步上下文里完成跨文件、跨 PR 的重型工作。LangChain 团队在 Deep Agents Code 项目里给出的范式是:让终端 CLI 负责跨文件、跨异步信号的高确定性 reasoning,让 IDE 插件负责 inline 低延迟的体感补全,两者通过统一的事件总线与 LangGraph checkpointer 共享 thread state。从环路工程的视角看,这正是同一种 Goal/Verification 环路在两种延迟预算下的实现取舍。
踩坑清单与延伸阅读
把 Coding Agent 从演示 demo 部署到真实工程团队里,通常会踩到以下三类坑:
- CI runner 配额与冷启动 :很多 CI 服务在免费层有月并发分钟数限制,Agent 高频触发会让配额迅速耗尽。建议把
run_cmd的实际指向改为本地 Docker 模拟 runner,只在关键节点 push 到真实 CI。 - diff 工具的特权边界:GitHub PAT(personal access token)如果给得过宽,模型可能误删分支或 force-push。最佳实践是给它一个只读 PAT + 单独的 bot 账号,只允许 read repo + write comment,严禁授予 admin 权限。
- rubric 漂移:rubric criteria 写得太死会让 Agent 失去灵活性,写得太松又无法形成可靠 grader。建议把 rubric 同时存进 LangSmith datasets,跑自动 eval 看覆盖率,并在 Self-improvement 阶段让 LangSmith Engine 自动迭代 rubric 文本。
具体的工程实现与官方演示案例,可以参考以下两个延伸阅读入口:
- Deep Agents Code 项目页:github.com/langchain-a...
- LangGraph 持久化与 checkpointer 文档:langchain-ai.github.io/langgraph/c...



总之,终端 Coding Agent 的本质,是把"敲 Enter"这个肌肉记忆封装成可编程的 event-driven 环路 trigger,把 CI / diff / lint 封装成天然的 grader,把 read_file / edit_file / run_cmd / git_commit 四个核心工具封装成动作空间。当这三层封装都齐备时,所谓"AI 写代码"才会从 demo 变成可被团队采纳的工程实践------而这一切,并不是因为模型变聪明了,而是因为环路结构变规整了。
Self-improvement / Hill Climbing 环路:真相在 traces 里

在 LangChain 团队关于 Loop Engineering 的整套课程里,Self-improvement / Hill Climbing 环路被摆在最高一层,讲师明确把它称为「基石思想」。其底层假设与传统软件截然相反:Agent 是一种非确定性系统,同一段 prompt 在两次运行之间可能产生截然不同的结果,因此「真相」不在人的脑子里,也不在静态的 prompt 文件里,而在每一次运行留下的 trace(追踪记录)里。这意味着我们不能再用「我觉得这个 prompt 写得不错」来做工程决策,而必须把 trace 当成唯一可信的反馈源。
观察 在传统软件里,代码的输出是确定的,所以调试靠 log、靠断点、靠推理;在 Agent 系统里,模型本身是一个不可控的黑盒,人类直觉对「prompt 好不好」的判断,误差往往是数量级的。真正能反映 prompt 实际表现的,是 trace 中那些真实发生过但被我们忽略的细节------比如模型反复在同一个 tool 上重试、或者在第 4 步突然开始使用一个从未声明的「内联工具」。这些信号只有从 trace 里反推出来,才有工程价值。
接下来这一节要解决的问题,就是「如何用 trace 倒推出 prompt / 工具描述 / memory 这些『配置』该改成什么样」。这一步看似朴素,实则是整个 Loop Engineering 框架中工程杠杆最大的环节。
一个最简单的 Hill Climbing 环路
先给出一个最朴素的实现骨架------跑主 Agent → 提取失败信号 → 改 prompt → 再跑------这是 Hill Climbing 在工程上的最小可用版本:
python
from langchain.agents import create_agent
from langsmith import Client
MAIN_AGENT = create_agent(
model="...",
tools=[...],
system_prompt=open("prompt_v0.md").read(),
)
def run_once(query: str) -> str:
return MAIN_AGENT.invoke({"messages": [{"role": "user", "content": query}]})
def extract_failure_signals(trace_id: str) -> list[dict]:
run = Client().read_run(trace_id)
# 检测工具参数错误 / tool call 缺失 / 风格漂移 / 重复循环 / 幻觉事实
return signals
def rewrite_prompt(current: str, signals: list[dict]) -> str:
return META_REWRITER.invoke({"prompt": current, "signals": signals})
current = open("prompt_v0.md").read()
for round_idx in range(20):
trace_id = run_once("回归测试集 query")
signals = extract_failure_signals(trace_id)
if not signals:
break
current = rewrite_prompt(current, signals)
MAIN_AGENT = MAIN_AGENT.with_config(system_prompt=current)
这段伪代码的核心思想是:把「调 prompt」从一次性的人类直觉行为,转成一条可以反复迭代、每一次迭代都有 trace 证据的闭环。注意 with_config(system_prompt=...) 这一行------在 LangGraph 持久化与 checkpointer 模型下,我们可以让 prompt 改动跨 run 生效而不需要重新部署。详见 LangGraph 官方文档 langchain-ai.github.io/langgraph/ 与 LangSmith 评测数据集示例 docs.smith.langchain.com/evaluation。

5 类失败信号:trace 能告诉我们什么
下表列出讲师在课程里反复强调的、可从 trace 中反推出的 5 类失败信号。每一类都对应一种具体的 prompt 或 tool description 改动策略,工程上只要把这五类信号都接上检测器,基本就覆盖了 Agent 失败案例的绝大多数。
| 信号类型 | 在 trace 里的典型表现 | 反推出的配置改动 | 检测难度 |
|---|---|---|---|
| 工具参数错误 | tool_call 的 arguments 字段类型错误或字段缺失 | 改写 tool docstring,补充参数示例 | 低 |
| tool call 缺失 | 完成某任务本应调用某个 tool,却直接给出文字答案 | 在 system_prompt 中显式声明「必须先调用 X」 | 低 |
| 风格漂移 | 同一 agent 不同 run 输出语气/格式差异很大 | 在 prompt 中钉死输出 schema,例如 JSON Schema | 中 |
| 重复循环 | trace 中出现连续 ≥3 次相似 tool call | 加入「若 tool 返回 X 则停止重试」的早停规则 | 中 |
| 幻觉事实 | 最终答案中包含无法在 trace tool 结果里溯源的事实 | 在 prompt 末尾追加「只能使用 tool 返回的内容作答」 | 高 |
数据 在该教程的演示案例里,讲师展示了一组对照实验:把同一个 Coding Agent 跑在 100 条回归测试集上,初始 prompt 的通过率大约在 60% 量级;不开 Hill Climbing 环路时,人工调 prompt 三轮,人力花费显著但通过率提升有限;开启 Hill Climbing 环路后,meta-agent 自动跑了大约 12 轮 trace-driven 改写,最终通过率稳定上升到 85%-90% 区间,且每轮都能在 trace 里看到具体的失败信号在减少。注意:这里的具体数字是该教程演示场景下的实测,不要把它外推到任意 Agent 系统------不同任务、不同模型、不同 rubric 下的曲线形状会显著不同,真正可移植的只是「trace-driven 改写」这一机制,而不是某条曲线的具体数值。
长期演化:Self-improvement 环路启用 vs 关闭
下面给出一张对比矩阵,描述长期(假设 50 轮迭代)演化下两种模式的差异。这里的关键不是「哪条曲线数值更高」,而是「成本结构与稳定性是否可承受」。对 AI 工程师和团队负责人而言,这道选择题的答案往往比看上去更微妙。
| 维度 | Self-improvement 环路关闭(纯人工) | Self-improvement 环路开启(meta-agent + traces) |
|---|---|---|
| 短期成功率(0-5 轮) | 取决于 prompt 工程师个人水平 | 由初始 prompt 决定,可能略低 |
| 长期成功率(20-50 轮) | 提升有限,易出现「越改越差」回退 | 持续上升,通常呈对数曲线 |
| 单轮迭代成本 | 高(需要资深工程师 1-2 小时) | 低(meta-agent 自动跑,只需 trace 评审) |
| 可解释性 | 高(人能解释每一处改动) | 中(需要保留每轮 prompt diff 才能审计) |
| 上限 | 受人工瓶颈限制 | 受 trace 质量与 meta-agent 能力限制 |
| 失败模式 | 工程师主观偏差、context 切换疲劳 | meta-agent 在 trace 中学到的偏差被固化 |
取舍 启用 Self-improvement 环路并不是「永远更优」的银弹。它的代价是引入了一个新的故障源------meta-agent 本身。如果 meta-agent 的改写策略有偏差,这种偏差会被外层环路持续放大,形成「在错误的方向上越走越远」的危险模式。所以工程上常见的折中做法是:人工评审每一条 prompt diff、保留 git 化的 prompt 历史、必要时一键回滚。换句话说,外层环路不是「替代」人,而是「放大」人------它把人的注意力从「逐字逐句抠 prompt」解放到「评审 meta-agent 的改写决策」,但后者的责任密度其实更高。

外层环路让内层环路越来越有效
Hill Climbing 在 LangChain 语境下的真正威力,不是「单次改 prompt」本身,而是「外层环路让内层环路越来越有效」这一嵌套结构。设想一个典型场景:Core Agent Loop(内层)负责跑一次任务,Goal/Verification 环路(中层)用 grader 给出通过或不通过的判定,Self-improvement/Hill Climbing 环路(外层)拿到 grader 输出后,改写 Core Agent 的 prompt 与 tool description。
每一轮外层迭代,Core Agent 的 prompt 都在被微调------比如把 grader 经常扣分的「tool 参数错误」修掉、把 grader 经常忽略的「幻觉事实」加一道防线。下一次内层跑回归集时,因为 prompt 已经修正了上一轮的具体失败模式,grader 的通过率就会上升;通过率上升,反过来又给外层 meta-agent 提供了「这一轮的 prompt 改动是正向」的反馈信号。
这就是 Hill Climbing 的核心动机:我们不是要做一个「能自己学」的 Agent,而是要让外层 meta-agent 把每一次跑 trace 攒下来的证据,变成下一次内层 Core Agent 更容易成功的前提。Layer 与 layer 之间的耦合不是耦合,是一种「互相喂数据」的递增结构。
落地到工程上,LangSmith Engine(见 www.langchain.com/langsmith)正...%E6%AD%A3%E6%98%AF%E8%BF%99%E5%A5%97%E6%80%9D%E6%83%B3%E7%9A%84%E7%8E%B0%E6%88%90%E8%BD%BD%E4%BD%93:%E5%AE%83%E8%B7%91%E8%BF%87%E5%8E%86%E5%8F%B2) trace 后,自动生成 prompt / skill / memory 的改写建议,并可以一键 port 回源码。配合 LangGraph 的 checkpointer(MemorySaver / PostgresSaver / SqliteSaver 三种实现见 langchain-ai.github.io/langgraph/c...,%E8%B7%A8) run 的 memory 与 prompt 版本管理都被打通了,外层环路才能真的落地为可观测、可回滚、可审计的工程流水线。
总结:Hill Climbing 环路不是「让 Agent 更聪明」的玄学,而是把 Agent 的不确定性从「不可控的随机」转成「可累积的证据」的工程机制。真相在 traces 里------这是 LangChain 团队关于 Loop Engineering 最朴素也最深刻的一句断言。当外层环路把每一条 trace 都转成下一次内层 run 的「先验知识」时,Agent 才真正开始具备「越用越好」的能力,而那正是 Loop Engineering 想要抵达的终点。
LangSmith Engine:跑过 traces 自动改 prompt / tool / skill / memory

在 LangChain 团队关于 Loop Engineering 的整套课程里,Self-improvement / Hill Climbing 环路被摆在最高一层,讲师明确把它称为「基石思想」。其底层假设与传统软件截然相反:Agent 是一种非确定性系统,同一段 prompt 在两次运行之间可能产生截然不同的结果,因此「真相」不在人的脑子里,也不在静态的 prompt 文件里,而在每一次运行留下的 trace(追踪记录)里。这意味着我们不能再用「我觉得这个 prompt 写得不错」来做工程决策,而要把决策权交给 trace。
LangSmith Engine 正是这一思路下的产物:它是 LangChain 推出的一个元 agent(meta-agent) ,专门跑过 LangSmith 已经采集到的大量 traces 做反思,然后把反思结果反向写回到源码里。和传统意义上的「自动 prompt 调优」工具不同,它不是黑盒拟合,而是把 LangChain 生态里所有的 prompts、tools、skills、memory 四类对象都视为可读写的「可改写源」,并通过 PR(代码评审)或 LangSmith Hub 之类的可信通道回流到团队仓库。它与生产主 agent 并行存在但不抢流量,把 trace 当作唯一的老师,而不是把 LLM 的偏好当成真理。
LangSmith Engine 在整体架构中的位置

把图看清楚最重要的一点是:LangSmith Engine 不是主 agent 的替代品,也不是并联运行的第二只 agent。它更像一只「外挂的旁路审视者」,订阅 LangSmith 的 trace 流,在后台离线跑分析,只在需要的时候给出改动建议。生产流量不会因为它而改道,也不会因为它而卡顿;它的存在形态更接近一只 CI(持续集成) Bot,而不是一个常驻的在线推理服务。
观察 任何把「自动改 prompt」想象成「按钮一点全网立刻生效」的人,都没有真正理解 LangSmith Engine 的工程语义。它只产出 diff(差异文件)和验证 rubric(评分标准),最终落地还是要靠工程师 / agent 双盲评审。这就把它和传统 A/B 测试平台区分开了:它不是在替人做决策,而是把决策所需的原材料(改写建议、对比 trace、回滚证据)从分散的 prompt 文件里搬出来,堆到同一张桌子上。它是一台「改造 prompt 的流水线」,而不是「决定 prompt 对错的裁判」。
最小接线示例
下面这段伪代码展示了如何让 LangSmith Engine 监听 trace 流并在累积到阈值时自动产出 prompt 改写建议。其中的关键不是 API 调用本身,而是「监听器---分析器---回写器」三段式结构,以及一个明确的 backpressure(反压)开关,避免在流量高峰期被反思任务打爆。
ini
from langsmith_engine import TraceWatcher, PromptRewriter
from langsmith_engine.policy import RewriterConfig, WriteBackPolicy
watcher = TraceWatcher(
project="prod-email-assistant",
sample_rate=0.1, # 只采样 10% 的生产 trace
only_failures=True, # 只看失败 / 投诉 / 重试的三类
window="24h", # 滚动窗口:24 小时
)
rewriter = PromptRewriter(
targets=["prompts", "tools", "skills", "memory"],
rubric="rubrics/email_assistant.yaml",
max_diff_lines=40, # 单次 diff 上限,防止一次改太多
require_pr=True, # 改写必须走 PR,禁止直写 main
writeback=WriteBackPolicy(
repo="org/agent-prompts",
reviewers=["on-call-agent-team"],
),
)
watcher.on_threshold(count=50, fn=rewriter.suggest_diff).start()
观察 上面 sample_rate=0.1、only_failures=True、max_diff_lines=40 这三个开关是工程里最容易抄漏的一行。LangSmith Engine 跑的不是「全量回放」而是「抽样 + 失败导向」的近视眼分析,目的是用 1% 的算力撬动 80% 的可解释性。如果跳过这三行,初学者很快就会在生产环境把 token 预算打穿,或者反方向被全成功 trace 误导,得出「一切看起来都很好,prompt 不需要改」的虚假结论。
LangSmith Engine 可改写的 4 类对象
| 对象类型 | 在 LangChain 生态中的位置 | 改写后回写到哪里 | 触发条件典型场景 |
|---|---|---|---|
| prompts | system prompt、user prompt template、few-shot 提示词 | LangChain Hub prompt 仓库 / git 仓库 PR | 模型输出与 rubric 评分偏差持续上升 |
| tools | tool name、tool description、tool schema、参数说明 | 工具注册源码 PR(例如 tools/email_tools.py) |
模型选错工具,或工具返回格式频繁被 model 误读 |
| skills | 复合任务脚本(例如 docs writer agent 的整段 skill 模板) | skill 仓库 PR,通常以一个 skill.md + 测试用例形式提交 | 跨多个 trace 反复犯同一类「漏写章节」错误 |
| memory | cross-run 长期记忆(MemorySaver / PostgresSaver 写入的快照) | LangSmith memory store schema 迁移 + 旧值归档 | 模型在多轮里反复忘记某个事实,或反过来记忆污染 |
数据 这张表里 4 类对象的「改写密度」并不一致。根据该教程给出的口径,在同一段时间窗口内,prompts 被改写最频繁,但回滚率也最高,因为 prompt 是最易测试错的位置,一次字符级的修改就能把分数拉下来;tools 与 skills 改写频次中等,但一旦改对收益最大,因为工具接口的修正会带来整条链路稳定度的跃升;memory 改写频次最低,然而单次改动影响最深,因为它跨多条对话持续生效。把它们放在同一张表里看,工程师就能直观判断「哪一类改写值得自动化,哪一类一定要留人介入」,而不是把所有对象塞进同一个自动改写流水线里。
人工改 prompt vs LangSmith Engine 自动改 prompt
| 维度 | 人工改 prompt | LangSmith Engine 自动改 prompt | 取舍 / 边界 |
|---|---|---|---|
| 改动频率 | 周级 / 月级,通常在事故复盘后才改 | 小时级 / 滚动窗口级,每次 trace 聚合触发 | 自动频率高 10-100 倍,但单次改动幅度更小 |
| 一致性 | 与个人风格强绑定,新人接手需要 onboarding | 团队共享同一份 rubric,跨工程师一致性更稳 | 自动一致性更高,但前提是 rubric 本身写得好 |
| 回滚成本 | 高:涉及 prompt 文件、Hub prompt、agent 配置多处同步 | 低:每条改写建议都带 trace diff、跑分对比、一键 revert PR | 自动路径天然带 audit trail(审计轨迹) |
| 决策权归属 | 人 | 人 + agent 双盲评审,改写 PR 必须人工合并 | 速度让人,质量让人 |
| 适用规模 | 几个 prompt 的小项目 | 几十上百个 prompt 的大仓 / 多租户 SaaS | 自动路径在小项目反而是负担 |
| 风险点 | 改了对不对全靠经验 | 改的可能「统计上对,但用户不喜欢」 | 都需要人在回路(human-in-the-loop) |
数据 课程里提到一个粗略数量级:在没有 LangSmith Engine 之前,一个中型 agent 团队每周大约能手工迭代 1-2 个 prompt,改写节奏完全绑死在值班工程师的空闲时间上;接入 LangSmith Engine 之后,一周能自动产出 20-50 条候选 diff,但其中真正被人工合并的只有 5-15 条,合并率约 25-30%。这个数字揭示了一个常被忽视的事实:自动改写的瓶颈不是「改得多快」,而是「人工评审能不能跟得上」。盲目追求自动 diff 数量,反而会让评审人手崩溃,合并率塌方到不足 10%,改写质量随之一落千丈。这是一条工程上非常典型的「左脚踩右脚」陷阱:越自动化越需要人,看似悖论,实质是把人从「写 diff」解放到了「审 diff」,而审 diff 的认知负担其实更高。
踩坑清单与工程配置要点
第一,rubric 必须显式存在。LangSmith Engine 不靠玄学,它依赖一份 rubric YAML(或 rubric criteria 文件)告诉它「好与不好」。凡是写过一遍内部「agent 评分标准」的团队都会发现,把 rubrics 写在文档里这一件事本身,比工具引入的收益还要大------它会强迫团队回答「我们到底在意什么」这个问题。
第二,采样策略必须刻意设计。sample_rate 过高会吞 token 预算;过低又会让 Engine 跑在「全成功」的高分集上,学不到东西。常见做法是:失败集全量采样、正常流量降采样到 1-5%、投诉工单单独开高优先级通道。
第三,writeback 通道必须分级。prompts / tools 这类改写可以直接走 PR;memory 这种跨用户数据则要走 schema 迁移 + 数据归档 + 灰度发布,绝不允许它直接 dump 进生产库。
第四,Engine 的输出必须可解释。任何一次「为什么这么改」的回答,都应当能用 trace id + rubric score + before/after diff 三件套回放。否则团队会在两周内失去对 Engine 的信任,改写 PR 大量被沉默忽略。

延伸阅读与官方入口
- LangSmith 官方文档(trace、Engine、rubric 配置):docs.smith.langchain.com/
- LangSmith 主页与产品介绍(含 Engine 介绍页):www.langchain.com/langsmith
- LangChain 博客(Self-improvement / Loop Engineering 系列文章汇总):blog.langchain.com/
- LangChain Hub(改写产物的 central registry,可直接搜 prompt / tool / skill):smith.langchain.com/hub
- Deep Agents 开源仓库,作为 Engine 下游「真正落地改写」的中间件:github.com/langchain-a...
把这几条入口对照着读,不难发现 LangSmith Engine 的定位非常克制:它不是下一只 agent,而是一层「自动化版本控制」,把 trace 里蕴含的改进信号翻译成 PR 与 diff,让团队依然握着合并权。它也因此最容易在两种团队里失效------一种是没有 trace 基础设施的团队,接进来发现没数据可喂,Engine 变成空转;另一种是完全没有 review 文化的团队,自动 diff 没人审,要么直接合并把线上改崩,要么直接废弃变成摆设。把这层边界看清楚,LangSmith Engine 才不会从「提效神器」变成「线上惊吓」。
可改进的两大类:procedural 与跨 run 持久化 memory

当 LangSmith Engine 这类 meta-agent 反推完 traces(追踪记录,即 Agent 每一次执行留下的完整步骤日志)之后,它产出的改写指令天然会落到两个完全不同的"地层"上:一类改的是 base procedural (基础流程性)资产------所有用户共享的 prompt、tool description、skill 模板;另一类改的是 跨 run 持久化 memory (跨运行持久化记忆)------只服务于特定 thread_id、特定 user_id 或特定收件人上下文的私有条目。这种二分法不是讲师拍脑袋想出来的分类标签,而是源自 LangGraph checkpointer(检查点机制,把每一步状态序列化保存的机制,见 langchain-ai.github.io/langgraph/c...%E4%B8%8E) LangSmith Hub prompt registry(提示词注册中心)从一开始就被设计成两套彼此独立的写入路径:一个面向"仓库里的源码文件",一个面向"运行时数据库里的键值对"。
一、两类的边界到底在哪里
Procedural 类改写的目标物,在工程语义上等价于"任何对 Agent 启动时静态加载的资产"的修改。典型代表包括:
- 系统 prompt(system prompt):比如"你是一位严谨的代码审查员"这种全局人设
- tool description(工具描述):LangChain 的 tool schema 里那个告诉模型"何时调用我、调用我之后会拿到什么"的 description 字段
- skill / sub-agent prompt:Deep Agents 体系下子 Agent 的提示词模板
- rubric 评分标准文档:Goal/Verification 环路里 grader agent 拿来打分的 rubric 文件
而 memory 类改写的目标物,则是 LangGraph checkpointer 写入的状态字段,或者外挂在 LangGraph Store 上的 cross-thread memory store(跨线程记忆库)。它的特征是:必须绑定一个 context key------可能是 thread_id(同一会话)、user_id(同一用户),也可能是更细粒度的语义键,例如"收件人 = 老板"。
把这两类放在一张表里,差异立刻变得直观:
| 维度 | Procedural 类(流程性) | Memory 类(跨 run 持久化) |
|---|---|---|
| 存储位置 | Git 仓库里的源码文件 / LangSmith Hub 上的 prompt commit | LangGraph checkpointer(运行时状态)或 LangGraph Store(跨线程键值库) |
| 改写权限 | 需要走 PR / code review,任何人改动都影响全员 | 仅 meta-agent 在带特定 context key 的 trace 上写,影响范围被上下文隔离 |
| 回滚粒度 | Git commit hash 级别,可整段 prompt 回退到上一版 | 单条 key/value 可独立删除或覆写,不影响其他上下文 |
| 生效时机 | 下一次 Agent cold start(冷启动,即重新加载进程后)才生效 | 写入即对当前及后续同 context 的 run 生效 |
| 审计方式 | 通过 git log / PR review | 通过 LangSmith trace 上附带的 memory write 事件 |
| 典型场景 | tool description 优化、grader rubric 改写 | 邮件助理对不同收件人的语气偏好 |
二、代码层面的最小可运行示例
下面这两段伪代码演示了 LangSmith Engine 一次反推完成后,两种改写落地时的差异。请注意它们调用的是同一个 propose_change 接口,但落地的 storage adapter 完全不一样:
ini
from langsmith_engine import propose_change, ProceduralAdapter, MemoryAdapter
# 场景 1:procedural 类改写 ------ 优化某个 tool 的 description
propose_change(
adapter=ProceduralAdapter(repo="acme/email-assistant"),
target="tools/send_email.tool.json",
diff={
"description.before": "send an email",
"description.after": "send an email with subject, body, and optional CC",
},
evidence_trace_ids=["trace-001", "trace-002"],
)
# 场景 2:memory 类改写 ------ 为特定收件人沉淀语气偏好
propose_change(
adapter=MemoryAdapter(store_url="postgres://langgraph-store"),
target=("user_pref", "tone", "recipient=ceo"),
diff={
"value.before": "neutral",
"value.after": "concise, action-first, no pleasantries",
},
evidence_trace_ids=["trace-003"],
)
第一段改完之后,会被自动 commit 到 Git 仓库、跑一遍 PR check;第二段改完之后,只会往 PostgresSaver 那张 cross-thread memory 表里 INSERT 一行,不会触发任何 CI 流程。这种"同一入口、不同落地"的设计,正是为了让 meta-agent 不用关心下层是源码还是数据库------它只负责"产出 diff + 给出 evidence trace",而具体的写入由 adapter 决定。
三、实战中的取舍矩阵
把这套机制落到真实业务里,有一个非常关键的取舍:你希望这次改写是"普惠"还是"特供"? 下面这张对比矩阵能帮你判断:
- vs 普惠所有人 :选 procedural。典型例子是 terminal coding agent(终端编码 Agent)发现自己调
run_shell这个 tool 时频繁失败,meta-agent 反推出"应该是 description 没写清楚 working directory 的语义",于是改写 description 并 PR 进仓库------所有用户下次跑这个 Agent 都会受益。 - vs 特供特定上下文:选 memory。典型例子是 email assistant(邮件助理)发现给 CEO 发邮件时,语气应该"concise, action-first, no pleasantries",但给同级同事发邮件时应该"friendly, 可以加 emoji"。这种偏好明显只对"recipient = CEO"这一个上下文成立,放进 procedural prompt 里反而会污染其他场景。
- vs 既想沉淀又想普惠 :双写。讲师在课程里举过一个 docs writer agent(文档撰写 Agent)的例子------它既需要把"调用
search_docs之前应该先看看用户给的 URL 是否已经在 context 里"这条经验写进 procedural prompt(普惠),又需要把"这个客户喜欢表格而非列表"这种私人口味写进 memory(特供)。一次反推产生两条 diff,分别走两条 adapter,这是完全合法的。
观察 从工程治理角度看,procedural 类改写天然适合走"PR + review"的人类把关流程,因为它影响面广、出错代价高;而 memory 类改写更适合"自动写 + 人工抽查"的轻治理模式,因为它的影响被 context key 天然 sandbox(沙箱隔离)住了。把这两类的 review policy 设计成同一种,要么过度限制导致 memory 写不进去,要么过度放任导致 procedural 误改全量回归,这是 Loop Engineering 落地时最常见的反模式。
数据 课程里给出的对比数据点是:一个典型的 terminal coding agent 在一周内,procedural 类改写平均只发生 1-2 次/月,而 memory 类改写可以达到数十次/天,数量级相差近 100 倍。这意味着如果两类的写入路径共用同一个慢速的 review pipeline,memory 写会被严重积压;反之,如果 procedural 走得过快,每周都会出现"误改 tool description 导致全量回归"的灾难。生产环境里通常的折中是:procedural 走 PR review + CI,人工 gate;memory 走异步 batch write,每天一次人工抽查 + 异常回滚。
四、为什么都从 LangSmith Engine 这一统一入口反推
很多工程师第一次接触这套体系时会困惑:既然两类改写落地位置差别这么大,为什么不让 meta-agent 也分裂成两个? 答案藏在 LangSmith Engine 的 contract(契约)设计里:它对外暴露的接口永远是"trace in → proposed diff out",至于 diff 落到 Git、Postgres、SQLite 还是 Redis,完全由 adapter 决定。这样的好处是:
- 统一的 evidence(证据)约束:每一个 diff 都必须挂上 evidence_trace_ids,无论它是 procedural 还是 memory。这让事后审计时,可以一站式问"这条记忆条目 / 这段 prompt 是基于哪几条 trace 反推出来的?",而不用去两套系统里分别查。
- 统一的回滚原语 :无论是
git revert还是DELETE FROM memory_store WHERE key = ...,对外都是同一个rollback_change(change_id)调用,LangSmith UI 上呈现给人类的也是同一种"撤销"按钮。 - 跨类的组合改写 :当某条 trace 同时揭示了"tool description 模糊"和"用户偏好没被记住"两类问题时,meta-agent 可以一次性产出两个 diff,而不是跑两遍反推。LangSmith Engine 的 batch propose API 正是为此而设计,详见 docs.smith.langchain.com/ 里的 tracing 与 dataset 章节。
但请注意,这套统一入口并不意味着存储位置也统一。入口可以抽象,落地必须显式------这也是为什么我们在表里单独列了"存储位置"这一行。一旦混淆这两层,就会出现"以为改了 prompt,其实写到了 memory 里"的灵异 bug,排查起来非常痛苦。
五、给团队的三条落地建议
第一条,先把 procedural 类的 PR 流程跑通 ,再考虑上 memory 自动化。前者复用的是工程师已经熟悉的 Git workflow,门槛低;后者需要额外部署 LangGraph Store(参见 github.com/langchain-a... 里的 harness 配置),并且要设计 context key 的命名规范。
第二条,给 memory 类改写加上 TTL(过期时间)与命中回放 。cross-run memory 最容易腐烂------用户的口味会变,如果一条"recipient = CEO 用 action-first 语气"的 memory 永远不被刷新,半年后反而成了负资产。LangSmith Engine 在写入 memory 时建议强制要求一个 expires_at 字段,过期由后台 job 自动清理。
第三条,两类改写必须共用同一份 evidence trace 的快照。换句话说,无论这条 diff 最终落到 Git 还是 Postgres,evidence_trace_ids 里指向的 trace 内容必须被 immutable(不可变)地保留至少 N 天。否则一旦 memory 类改写引发线上问题,你根本没有回溯"它当时是看了哪几条 trace 才决定这么写"的能力,只能靠猜。
把这两类分清楚,Self-improvement 环路才真正具备可治理性;否则它就只是一个会自己写 prompt、还会自己写记忆的"黑盒喷泉"------看起来在持续学习,实际上产出的全是噪声。
俄罗斯套娃:四种环路的任意嵌套模式
俄罗斯套娃的本质,不是要求你把所有环路都装上,而是给一个组合空间:四种环路任选、任意层叠、任意顺序。这种"组合爆炸"的背后其实有结构化的约束------Core(Agent 内核)永远是内层,Self-improvement 通常是外层,中间层可以自由拼装。换句话说,套娃模式允许你"挑你需要的那几层",而不是"套餐制必须全选"。这一点对资源受限的团队尤其重要:工程从来不是"叠 buff 越多越好",而是"每一层都要回答一个新的工程问题"。和传统单体架构相比,俄罗斯套娃的差异在于:单体把所有职责焊死在同一个函数里,改一处就要全量重测;套娃把职责切成可独立维度的中间件,每一层都可以被单测、被替换、被 A/B 测试。
观察 在真实工程里,出现频率最高的组合是 Event-driven 套 Verification 再套 Core。原因很直接:Event-driven 解决"什么时候触发",Verification 解决"做得好不好",Core 解决"具体怎么做",这三层语义正交、不打架。Self-improvement 几乎永远位于最外层,因为它要反推 traces(追踪记录,即 Agent 每一次执行留下的完整步骤日志),需要等内层跑完一个完整 run 才能产出改写指令。也就是说,Self-improvement 天然依赖下层的可观测性,而不是嵌套结构上的随意摆放。如果硬把 Self-improvement 塞到 Core 内层,逻辑上就出现"边跑边改自己 prompt"的悖论,LangSmith Engine 那一套 meta-agent 范式就失效了,因为改写指令还没来得及落盘,内层又跑出了新的 trace。

下面给一个最小骨架,展示在同一个 create_deep_agent 配置里同时挂 Event-driven middleware 与 Verification middleware 是什么样子。中间件并不是直接修改 agent 的源码,而是按调用顺序串联在请求/响应链路上,这一点对理解嵌套本质很关键------环路之间不是函数嵌套,而是"中间件责任链"上的位置先后。
ini
from deepagents import create_deep_agent
from deepagents.middleware import (
EventDrivenMiddleware, # 事件触发:Slack / GitHub / Calendar / cron
GoalMiddleware, # /goal 注入 + 自动 grader
RubricMiddleware, # /rubric 细则注入
)
agent = create_deep_agent(
model="claude-sonnet",
tools=[search_docs, send_email, create_pr],
middleware=[
EventDrivenMiddleware(
triggers=["slack_mention", "inbox_new_mail", "cron:0 9 * * 1"],
),
GoalMiddleware(goal="/goal:每周一 9 点生成周报并发到团队频道"),
RubricMiddleware(rubric="/rubric:含上周 OKR 完成度、阻塞项、下周计划"),
],
checkpointer=PostgresSaver.from_conn_string(DATABASE_URL),
)
这个骨架里,EventDrivenMiddleware 负责把外部事件翻译成 agent 的一次新 run,GoalMiddleware 负责把目标串进 system prompt 并在 run 结束时调用 grader,RubricMiddleware 负责注入评分细则。三者都通过 Deep Agents 的中间件协议挂载,协议具体定义见官方文档 docs.langchain.com/oss/python/... ;官方仓库 github.com/langchain-a... 也提供了更多 middleware 的参考实现,例如 schedule middleware、memory middleware、human-in-the-loop middleware 都可以按同一接口扩展,意味着"加一层"实际操作上就是"加一个 middleware 实例"。
把四种环路摆开,组合总数其实远不止 6,但工程上"合法"的嵌套只有以下 6 种。这里的"合法"等价于:Core 必须在最内层、Self-improvement 在最外层(若存在),其余按职责正交性排列。这样约束下,深度 1 到 3 层各覆盖一些典型场景,深度 4 的全四层嵌套单独拿出来讨论。

| 嵌套深度 | 组合形态 | 典型适用场景 | 工程复杂度 |
|---|---|---|---|
| 1 层 | Core | 一次性任务、demo、原型 | 极低 |
| 2 层 | Core + Verification | 输出可判定质量的批处理 | 低 |
| 2 层 | Core + Event-driven | 定时报告、webhook 处理器 | 中 |
| 3 层 | Core + Verification + Event-driven | 周期性高质量交付物 | 中高 |
| 2 层 | Core + Self-improvement | prompt 调优、tool description 迭代 | 中 |
| 3 层 | Core + Verification + Self-improvement | 长期稳定的生产级 agent | 高 |
数据 课程的工程经验数据是:仅 Core 时,迭代效率的天花板被 prompt 一次性写死的质量锁住;引入 Self-improvement 后,改写频率可达每日数十次,但每次改写都要经过 LangSmith Engine 反推 traces 的开销,综合时延约 5 到 15 分钟/轮;而单 Core 嵌套的人工改写周期普遍在一周以上,意味着线上问题到修复的反馈环极长。Verification 层的引入会让单次 run 的失败率显著下降,因为 grader 会在 run 末尾拦住不合格输出,据经验数据可减少 30-50% 的无效 follow-up 调用,直接降低模型推理成本。
接下来看全四层嵌套与单 Core 嵌套的取舍。
| 维度 | 全四层 (Core + Verification + Event-driven + Self-improvement) | 单 Core 嵌套 |
|---|---|---|
| 成功率上限 | 接近 95% 以上,因有 grader 拦截 + meta-agent 改写 | 取决于 prompt 一次性写入质量,通常 60-80% |
| 单次改写成本 | 数百到数千美元/月(LangSmith Engine + 模型推理) | 几乎为零 |
| 调试复杂度 | 高,需要同时读 trace、grader 输出、改写 diff | 低,直接看 prompt |
| 适用阶段 | 生产级 SLA(服务等级协议) 场景 | 早期验证、PoC(概念验证) |
| 运维门槛 | 需要 traces 基础设施 + checkpointer + grader rubric 维护 | 单个 create_deep_agent 调用即可 |
| 失败定位 | 需区分 Core 失败、Verification 误判、Event-driven 漏触发、Self-improvement 改写回退 | 单一变量:prompt 本身 |
数据 上面这张表的关键取舍点在于"成功率上限"与"工程复杂度"几乎单调正相关:你堆的层越多,理论上能逼近的输出质量越高,但需要的可观测性、回滚机制、运维人力也都水涨船高。LangChain 团队的官方建议是按"先 Core、再 Verification、再 Event-driven、最后 Self-improvement"的顺序逐层加,不要一开始就把四层全开------否则一条失败 trace 会同时穿过四个抽象层,排查时不知道该看哪一层的日志,以及 Self-improvement 改写后回滚又会覆盖掉你刚加的人工补丁。LangChain 官方文档 python.langchain.com/docs/concep... 进一步把 Agent 的可插拔组件抽象成 model / tools / prompt / middleware 四件套,使环路嵌套的本质变成"在四个槽位里选一个或多个挂上去"。
回到这套范式之所以能成立的前提:Deep Agents 与 Dcode 都是开源项目,源码在 GitHub 公开,create_deep_agent 与 create_agent 的中间件协议是同构的,你可以把任意一个 middleware 抽出来单跑、组合、替换。这是俄罗斯套娃模式得以"任意嵌套"的工程基础------如果中间件是闭源黑盒,层与层之间的契约就锁死,也就没有嵌套空间。具体持久化配置可参考 LangGraph 持久化概念页 langchain-ai.github.io/langgraph/c... ,里面的 checkpointer 抽象是跨 run memory 之所以能稳定生效的底层支撑,也是 Self-improvement 为什么要"等内层跑完一个完整 run"的物理基础。如果跳过 checkpointer,跨 run memory 就退化到单 run 内的 in-context 拼接,Self-improvement 也就无从判断"上一个版本 vs 这一个版本"。
在 Dcode 视角下,俄罗斯套娃被进一步具体化。Dcode 把 Verification 暴露成 /goal 与 /rubric 两个 slash command------即在 agent 的 system prompt 里直接写 /goal:本周生成 OKR 复盘 这样的字面字符串,GoalMiddleware 在每次 run 启动时把它解析成结构化目标,再交给 grader 去验证。Dcode 与 Deep Agents 的关系是:前者是后者的一个垂直化封装,典型场景是终端编程;后者是通用 harness,典型场景是 docs writer agent、email assistant agent。两者共享同一套 middleware 协议,所以你可以把 Dcode 里写好的 GoalMiddleware 抽出来,直接挂到另一个 Deep Agent 实例上,组合成新的环路。

总结一下:俄罗斯套娃模式不是让你把四种环路全装上,而是给一个组合空间,你可以从最小的 Core + Verification 起步,逐层加上 Event-driven 与 Self-improvement。每加一层,都意味着新一组可观测性需求与新一种失败模式;每少一层,都意味着把某些职责让渡给人工。判断"该堆几层"的方法,不是看技术能不能做到,而是看你愿不愿意为那个 +5% 的成功率上限,承担对应的工程开销。