目录

AI Agent 架构模式:Model + Harness + Memory

Building effective agents 把 agentic system 分成两类:workflow 走预写好的代码路径;agent 由模型自己决定下一步调什么工具。能上线的实现,几乎都还是这个二分,外加一层 harness 和可检索的 memory。步骤能写死,选图;写不死,选 loop。默认不上 multi-agent。

选 / 不选

模式 不选
Tool-calling loop 步数事先不知道,环境反馈(测试、文件、命令输出)是 ground truth 固定流水线,延迟和成本必须可预测
Workflow 图 任务能干净拆成固定子步骤,或需要路由 / 并行 / 评审 子任务形状随输入变化,硬编码路径立刻过时
Multi-agent 子任务互相独立,各自需要独立 context 为了「看起来像系统」拆角色;通信成本高于收益

Anthropic 的原文写得很直:先找最简单的方案,单次 LLM 调用加检索往往就够。Agentic system 用延迟和成本换任务表现,这笔账要先算。

脊柱仍是 Model + Harness + Memory

Claude Code 的官方说法:Claude Code 是 harness,Claude 是里面的模型。Harness 提供文件访问、shell、权限门、memory 加载,以及把动作串起来的 loop。这不是产品口号,是 2026 年 coding agent 的默认拆法。

另外两套公开 harness 走同一条脊柱,不编它们的内部 API:

  • Codex:本地 coding agent,读、改、跑当前目录里的代码。
  • Goose:本机通用 agent,桌面 / CLI / API,工具经 MCP 扩展接入。

Model 负责推理和选工具。这里不排模型名次。选模型看的是:能不能稳定产出符合 schema 的 tool call、上下文是否撑得住这一轮 loop、单价乘以平均轮数是否可接受。官方 tool use 示例里的模型 ID 会变,以当时文档为准。

Harness 负责执行环境。工具定义、调用、把结果写回 messages、token 预算、重试、权限、沙箱,都在这一层。模型换版本,loop 不该重写。

Memory 负责跨 turn、跨 session 还能找回的事实。不是把历史全塞进 window。Claude Code 用 CLAUDE.md 加 auto memory;Messages API 有 client-side 的 memory tool;Goose 有内置 Memory 扩展。共同形状:磁盘或外部存储上的文件,模型按需 view,而不是预加载。

工具插座是 MCP(Model Context Protocol)。它规定 host / client / server 怎么交换 tools、resources、prompts,不规定 loop 怎么写。Harness 已经带 bash / 读文件的,不要为了「接入 MCP」再包一层同名工具。

模式 1:Tool-calling loop

Anthropic 对 agent 的定义几乎就是一句话:模型根据环境反馈,在 loop 里用工具。实现上对应 tool usestop_reasontool_use 时执行 tool_use 块,把 tool_result 作为下一条 user message 送回去,直到模型停或触及上限。

选:开放式任务,步数无法预写。修一个失败测试、在陌生仓库里定位回归、按错误信息改到绿,都属于这一类。

不选:每一步的输入输出已经能写成函数。翻译完再发邮件、先分类再走固定模板,用下面的 workflow 更省。

下面按官方 Messages API 的 tool-use 循环改写,加了 max_turns。示意,未在本机跑过。

# 示意:官方 tool-use 循环 + 轮次上限。不要 while True。
def run_agent(client, model, tools, messages, handlers, max_turns=10):
    for _ in range(max_turns):
        response = client.messages.create(
            model=model,
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return response

        results = []
        for block in response.content:
            if block.type != "tool_use":
                continue
            handler = handlers.get(block.name)
            if handler is None:
                content, is_error = f"Error: Tool '{block.name}' not found", True
            else:
                try:
                    content, is_error = handler(block.input), False
                except Exception as err:
                    content, is_error = f"Error: {err}", True
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": str(content),
                "is_error": is_error,
            })
        messages.append({"role": "user", "content": results})
    raise RuntimeError("max_turns exhausted")

常见失败:某个 tool_use 块没有对应的 tool_result。官方 computer use 文档写明,漏回任何一块会得到 invalid_request_error。看见它的办法:对 response.content 里每个 tool_use.id 打日志,请求发出前断言一一对应。另一个失败:没有 max_turns,模型反复调同一个坏工具。看见它的办法:按 tool name 计数,同一调用连续失败就停。幻觉出来的工具名不要让进程崩溃,把错误字符串回给模型,让下一轮自己改。

工具描述就是 ACI。Anthropic 的附录写过:花在 agent-computer interface 上的力气,至少要和花在 HCI 上的相当。参数名、边界、和相邻工具的差别写进 description;相对路径这类会在工作目录变化后踩坑的输入,改成强制绝对路径。Loop 很短,真正决定稳不稳的是工具好不好用。

Computer use 是同一种 loop,工具换成截图和键鼠。Computer use 把「应用执行、结果回传、模型再决定」称为 agent loop,并给出带 max_iterationssampling_loop。官方安全要求也写死了:专用虚拟机或容器、不要把登录凭证交给模型、外网走 allowlist、有真实后果的步骤要人确认。网页和图片里的指令可以盖过 system prompt,这不是边角,是默认风险。

选 computer use:目标系统没有 API,只能点界面。

不选:bash、文件、浏览器 DOM 已经够用。Coding agent 的默认工具集不是桌面键鼠。

官方文档里的 sampling_loop 就是这个形状。下面从 computer use 页摘了骨架,注释是这里加的。

# 摘自 Anthropic computer use 文档的 sampling_loop 骨架。示意。
def sampling_loop(model, messages, max_iterations=10):
    for _ in range(max_iterations):
        response = client.messages.create(
            model=model,
            max_tokens=4096,
            messages=messages,
            tools=TOOLS,
        )
        messages.append({"role": "assistant", "content": response.content})
        tool_results = process_tool_calls(response)
        if not tool_results:
            return messages  # 不再要工具,任务结束
        messages.append({"role": "user", "content": tool_results})
    return messages  # 触及上限,避免无限 loop 把 token 烧完

上限不是优化项。Computer use 文档把 max_iterations 写成防止意外 API 费用的保险。普通 tool-calling loop 同样适用。

模式 2:Workflow 图

Workflow 是代码编排模型,不是模型编排代码。Anthropic 列出的几种仍然够用,不必先上框架:

  • Prompt chaining:上一步的输出是下一步的输入,中间可加程序化 gate。适合能干净切开的固定子任务。
  • Routing:先分类,再交给不同 prompt / 模型 / 工具。适合类别清楚、一类的优化会伤另一类。Anthropic 自己的例子是常见问题走更便宜的模型,少见问题走更强的模型——这是路由,不是榜单。
  • Parallelization:独立子任务并行(sectioning),或同一任务多跑几次再汇总(voting)。
  • Orchestrator-workers:中心 LLM 现场拆任务、派给 worker、再合成。看起来像 multi-agent,但仍是一条预写好的「拆 / 派 / 合」代码路径。
  • Evaluator-optimizer:一个生成,一个按明确标准评审,循环改到过线。

选:分解稳定,要可预测的延迟、成本和失败点。客服分流、先出大纲再成文、独立文件的并行审查,属于这一类。

不选:子步骤的数量和种类取决于这一次输入。那种任务硬编码路径会立刻过时,回到 loop。

官方建议从 LLM API 直接写这几种图。框架能省掉解析和串联,也会把 prompt 和响应藏起来,调试时先确认自己看得懂底层调用。

示意:prompt chaining 加一个程序化 gate,未在本机跑过。

# 示意:固定三步。gate 失败就停,不把控制权交给模型。
def prompt_chain(client, model, source_text):
    outline = complete(client, model, f"Write an outline:\n{source_text}")
    if "TODO" in outline or len(outline.strip()) < 40:
        raise ValueError("outline failed gate")
    draft = complete(client, model, f"Write the document from this outline:\n{outline}")
    return complete(client, model, f"Translate to English:\n{draft}")

模式 3:Multi-agent

先分清两件事。Orchestrator-workers 仍是 workflow:一条代码路径在派活。真正的 multi-agent 是多个带独立 context 的 loop 同时跑,再通过消息或共享产物汇合。Claude Code 文档里的 subagent 在同一 session 内委派、只向父级交摘要;agent teams 是实验特性,默认关闭。Goose 可以 spawn 独立 subagent 做并行审查或研究。

选:探索会污染主 context,或几段工作可以并行且边界清楚(例如改代码的 loop 和只读调研的 loop)。

不选:任务还撑不起第二个 context window。每多一个 agent,就多一份 compounding error、多一份工具权限面、多一次「谁说了算」。Anthropic 的原则仍是:设计保持简单,规划步骤要看得见,工具接口当 ACI 来打磨,而不是先上角色扮演。

通信不要一上来设计投票。默认 hierarchical:父 loop 调用子 loop,拿回摘要。共享消息队列和共识机制只在已经能画出失败模式时再加。

MCP 只解决「工具怎么接」

MCP 把自己比作 AI 应用的 USB-C。Host(Claude Code、Goose、IDE)为每个 server 建一个 client。Server 暴露三种原语:tools(可执行动作)、resources(上下文数据)、prompts(可复用模板)。传输是本地 stdio 或远程 Streamable HTTP。协议不管模型怎么选工具,那是 harness 的 loop。

选:同一套工具要给多个 host 复用,或工具活在别的进程 / 机器上。

不选:harness 已经内置同等能力(读文件、shell、编辑器)。也不要把高频、要事务的路径放到额外的 server 上——多一跳就多一种超时和权限问题。工具 schema 全量塞进 system prompt 会挤占任务上下文;Claude Code 的做法是启动时只加载工具名,用到再取完整 schema。自己写 host 时按这个形状,不要把「接了 30 个 server」当成能力。

Memory:能找回,而不是预塞

Context window 是工作记忆,不是档案库。Claude Code 在窗口将满时先清旧的 tool output,再摘要对话;项目根目录的 CLAUDE.md 和 auto memory 会从磁盘重新加载,只存在对话里的指令可能丢掉。持久规则写进文件,不要写进某一轮 chat。

Messages API 的 memory tool 是同一思路的显式版本:模型对 /memoriesview / create / str_replace,应用在自己控制的存储上执行。跨 session 能续上,是因为 handler 指向同一份目录,不是因为 API 代为持久化。路径必须锁在 memory 根下,../ 按攻击处理。

选分层记忆:当前 turn 的 messages、可检索的外部文件、定期摘要。不选:无上限地把仓库、日志、旧对话追加进下一次请求。失败时的症状很具体——一次请求的图片或 tool result 数量触达上限、compaction 反复摘要仍立刻填满、模型开始执行上下文中间那段从未要求的指令。看见它:在发请求前打印 messages 体积和 tool_result 条数,超阈值先剪旧截图和旧输出,而不是加窗口。

长任务跨多个 session 时,Effective harnesses for long-running agents 的做法是:第一个 session 写进度文件和清单,后续 session 先读这些文件再动手,结束前更新进度。记忆是恢复机制,不是聊天记录的别名。

复杂度只在能测到收益时加。从一次带工具的调用开始,加上限的 loop,再是能画出的图,最后才是第二个 agent。