Mindket

什么是智能体 harness:模型外围的循环、工具与检查

黑色横幅,标题是“Harness 工程”,下面是“智能体外围的循环、工具与护栏”。

智能体 harness 就是把模型作为智能体运行起来的那部分代码。它在循环里调用模型,替模型执行它要用的工具,决定每一轮往上下文里放什么,并按权限、回合上限之类的规则检查每个动作。同一个模型在 Claude Code、Codex 和你自己的脚本里表现不同,原因就在于它们的 harness 不同。

本节内容

  • 把你的智能体画成“模型加 harness”,列出你的代码在每次请求里添加了什么。
  • 一整段对话里保持工具列表和开头几条消息不变,缓存的前缀才不会失效。
  • 把评审时反复提的规则写成 lint、测试和审批关口,让智能体绕不过去。
  • 新模型发布后,把 harness 一次拆掉一个部件,只留下仍然值得保留的。
全部课程
本页目录
  1. 不到 80 行 Python 写成的 harness
  2. 你开口之前,Codex 已经替模型准备好了什么
  3. Harness 工程:OpenAI 那个没有一行手写代码的产品
  4. Claude Agent SDK 和 OpenAI Agents SDK 替你搭好了什么
  5. harness 的每个部件,都是对模型的一次押注
  6. 写下 OpenAI 那篇 harness 文章的 Ryan Lopopolo,谈上下文和“管道活”
  7. 常见错误
  8. 动手试试
  9. 自测一下
  10. 常见问题
  11. 资料来源

不到 80 行 Python 写成的 harness

把同一个模型分别放进 Claude Code、Codex 和你自己写的脚本,你会得到三个不同的智能体。模型权重没变,变的是它外围的代码。OpenAI 的 Codex 团队和 Anthropic 都把这部分叫作 harness,也就是模型外围的代码:调用模型的循环、它能用的工具、它每一轮看到的内容,以及每个动作前后的检查。

下面这段示意代码,差不多是把这些部分都装齐的最小 harness。代码是本课自己写的,外面包着两个工具定义,原样取自 Anthropic 的客服智能体示例,本课没有运行过它。注释标出了每一行属于哪个部分。循环本身在 Claude API 智能体那一课里一步步搭过,这里只做标注。

import anthropic

from orders import cancel_order, get_order_details  # the cookbook's stand-in functions

client = anthropic.Anthropic()
MODEL = "claude-opus-5-5"
MAX_TURNS = 10                     # check: end a run that never stops
NEEDS_APPROVAL = {"cancel_order"}  # permission: tools that change something

# tools: two of the cookbook's three definitions, as written there
TOOLS = [
    {
        "name": "get_order_details",
        "description": "Retrieves the details of a specific order based on the order ID. Returns the order ID, product name, quantity, price, and order status.",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "The unique identifier for the order.",
                }
            },
            "required": ["order_id"],
        },
    },
    {
        "name": "cancel_order",
        "description": "Cancels an order based on the provided order ID. Returns a confirmation message if the cancellation is successful.",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "The unique identifier for the order to be cancelled.",
                }
            },
            "required": ["order_id"],
        },
    },
]
FUNCTIONS = {"get_order_details": get_order_details, "cancel_order": cancel_order}


def allowed(name, args):
    if name not in NEEDS_APPROVAL:
        return True
    return input(f"Allow {name} {args}? (y/n) ").strip().lower() == "y"


def run_agent(task):
    messages = [{"role": "user", "content": task}]  # context: all the model sees next turn
    for _ in range(MAX_TURNS):  # the loop
        response = client.messages.create(
            model=MODEL, max_tokens=2048, tools=TOOLS, messages=messages
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return "".join(b.text for b in response.content if b.type == "text")
        results = []
        for block in response.content:
            if block.type != "tool_use":
                continue
            if allowed(block.name, block.input):
                fn = FUNCTIONS[block.name]
                output, failed = fn(**block.input), False
            else:
                output, failed = "The user declined this action.", True
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": str(output),
                "is_error": failed,
            })
        messages.append({"role": "user", "content": results})
    return "Stopped: the turn limit was reached."


print(run_agent("Please cancel order O1 for me."))
  1. 工具。两个定义告诉模型它可以请求做什么。模型自己什么都不执行:是你的代码在 FUNCTIONS 里按名字找到函数,再去调用它。
  2. 上下文。messages 装着模型下一次调用时看到的全部内容。每一轮都会追加模型的回复和工具结果,所以每次请求都比上一次大。
  3. 权限。allowed() 会拦住 cancel_order,直到有人输入 y。被拒绝的调用会作为设置了 is_error 的工具结果返回,模型就能告诉用户,而不是盲目重试。
  4. 检查。模型一直要工具时,MAX_TURNS 会结束这次运行。Anthropic 的《Building effective agents》提到,常见做法是加上停止条件,比如最大迭代次数,以便保持控制,而 API 把这个计数留给了你。
  5. 循环。调用模型,执行它要的工具,追加结果,再调用一次,直到某次回复里不再有工具调用。

你开口之前,Codex 已经替模型准备好了什么

OpenAI 的 Michael Bolin 在 2026 年 1 月 23 日发表的《Unrolling the Codex agent loop》里拆解了 Codex CLI,他把这个智能体称为让模型发挥作用的“harness”。你的消息到达模型之前,Codex 已经拼好了请求里的另外六个部分,表格按顺序列出了它们。

这套拼装里有一条权限事实,加工具之前最好先知道。权限消息里描述的沙盒,只作用于 Codex 自带的 shell 工具。来自 MCP 服务器的工具不受 Codex 沙盒约束,要自己负责自己的安全护栏。

顺序关系到速度和成本。每次请求都会重发整段对话,旧的提示词恰好是新提示词的前缀,提示词缓存正是靠这一点复用之前的计算。如果在对话中途改了工具、模型、沙盒、审批模式或工作目录,缓存就会失效。所以设置有变化时,Codex 会追加一条新消息来记录,而不去改前面的消息。它早期的 MCP 支持曾因为工具列出的顺序不固定而导致缓存失效,后来才修好。提示词缓存那一课讲的是 Claude 上同样的前缀规则。

上下文是另一份预算。对话长度超过 auto_compact_limit 时,Codex 会调用 Responses API 的 /responses/compact 端点,然后用它返回的更短的条目列表继续对话。

Codex 的 harness 怎样拼出第一次请求,来自 OpenAI 的《Unrolling the Codex agent loop》(2026 年 1 月 23 日)
部分角色里面装的是
instructionssystem 或 developer设置了的话,用 ~/.codex/config.toml 里的 model_instructions_file,否则用随模型打包的基础指令
tools不是一条消息Codex 自己的工具,比如 shell 和 update_plan,Responses API 的 web_search,以及你的 MCP 服务器提供的工具
权限说明developershell 工具的沙盒、运行命令前什么时候要先问,以及 Codex 可以写入哪些文件夹
开发者指令developerconfig.toml 里可选的 developer_instructions
用户指令user$CODEX_HOME 里,以及从项目根目录到工作目录之间每个文件夹里的 AGENTS.override.md 和 AGENTS.md,默认最多 32 KiB,另加技能的元数据
环境上下文user工作目录和所用的 shell
你的消息user你输入的任务

Harness 工程:OpenAI 那个没有一行手写代码的产品

OpenAI 的 Ryan Lopopolo 在 2026 年 2 月 11 日发表的《Harness engineering》里,讲了团队花五个月做一个内部产品、没有任何人手写一行代码的经过。第一版脚手架由 Codex CLI 在 GPT-5 上生成,时间是 2025 年 8 月下旬,连最初的 AGENTS.md 也是 Codex 写的。下面的数字是团队自己公布的。

起初进展很慢,文章把原因归于环境描述不足,而不是模型不行。所以每次出问题,工程师们都会问:“缺的是什么能力,怎样让智能体既能看懂它,又必须遵守它?”早期的一个答案和指令文件有关。一个庞大的 AGENTS.md 会挤掉任务本身,内容很快过时,也没法用程序检查,于是它被改成一份大约 100 行的目录,指向 docs/ 文件夹。lint 和 CI 任务会检查这个文件夹是否过时、交叉链接是否完整,还有一个专门“打理文档”的智能体为过时的页面提交修复。文章给出的目录结构如下:

OpenAI 的 Harness 工程实验,按团队自己的报告

1M

五个月后仓库里的代码行数,没有一行是人敲的

来源:OpenAI: Harness engineering (2026年11月6日 查阅)

1,500

开启并合并的拉取请求,起初由三名工程师驱动 Codex

来源:OpenAI: Harness engineering (2026年11月6日 查阅)

3.5

每名工程师每天平均合并的拉取请求

来源:OpenAI: Harness engineering (2026年11月6日 查阅)

6+

单次 Codex 运行在一个任务上持续工作的小时数,常常是在夜里

来源:OpenAI: Harness engineering (2026年11月6日 查阅)

AGENTS.md
ARCHITECTURE.md
docs/
├── design-docs/
│   ├── index.md
│   ├── core-beliefs.md
│   └── ...
├── exec-plans/
│   ├── active/
│   ├── completed/
│   └── tech-debt-tracker.md
├── generated/
│   └── db-schema.md
├── product-specs/
│   ├── index.md
│   ├── new-user-onboarding.md
│   └── ...
├── references/
│   ├── design-system-reference-llms.txt
│   ├── nixpacks-llms.txt
│   ├── uv-llms.txt
│   └── ...
├── DESIGN.md
├── FRONTEND.md
├── PLANS.md
├── PRODUCT_SENSE.md
├── QUALITY_SCORE.md
├── RELIABILITY.md
└── SECURITY.md
  • 规则变成代码。自定义 lint 和结构测试把每个业务领域限定在固定的分层里(Types、Config、Repo、Service、Runtime、UI),并强制结构化日志、命名规范和文件大小上限。lint 的报错信息专门写给智能体看,告诉它怎样修好自己弄坏的地方。
  • 智能体能“看见”应用。每个 git worktree 都能启动一份自己的应用,接入 Chrome DevTools Protocol,日志和指标可以让智能体用 LogQL 和 PromQL 查询。
  • 清理按计划进行。团队过去每个星期五都在收拾智能体留下的问题。现在“黄金原则”写进了仓库,后台的 Codex 任务会对照它们提交小的重构拉取请求。

文章说明,这种做法依赖那个仓库特定的结构和工具,没有类似的投入,不应假定能照搬到别处。

Claude Agent SDK 和 OpenAI Agents SDK 替你搭好了什么

上面这些都不需要框架。Anthropic 的《Building effective agents》(2024 年 12 月)建议先直接用模型的 API,很多模式几行代码就能实现。如果要用框架,就要弄懂它底下的代码,因为对框架内部的错误假设,是客户出错的常见原因。这篇文章顶部现在有一条说明:文中描述的不少工具,自 2024 年 12 月以来已经变了。

两大 SDK 都是只需配置而不必自己写的 harness。Anthropic 在 2025 年 9 月把 Claude Code SDK 改名为 Claude Agent SDK,理由是驱动 Claude Code 的这套智能体 harness 也能驱动很多别的智能体。它的 Python 包自带 Claude Code CLI,默认附带完整的 Claude Code 工具集。

OpenAI 的 Agents SDK 不绑定模型提供方,它把智能体、沙盒智能体、工具、护栏、人工介入、会话和追踪列为核心部分。下面的矩阵按 harness 的部件,根据两份 README 把它们排在一起。Claude Agent SDK 那一课和 OpenAI Agents SDK 那一课分别讲了怎样搭起来。

比较稳妥的分法是:一个窄任务、一两个工具,用你自己的循环;需要文件工具、审批和钩子了,就换 SDK。

哪些 harness 部件要你自己写,哪些 SDK 自带
你自己的循环Claude Agent SDKOpenAI Agents SDK
运行循环否是是
内置文件和 shell 工具否是用 SandboxAgent
工具运行前的审批要自己写是是
每一步前后的检查要自己写HooksGuardrails
能驱动的模型你调用的任何 APIClaudeOpenAI 模型和另外 100 多种

来源:anthropics/claude-agent-sdk-python README (2026年11月6日 查阅);openai/openai-agents-python README (2026年11月6日 查阅)

harness 的每个部件,都是对模型的一次押注

Anthropic Labs 团队的 Prithvi Rajasekaran 在 2026 年 3 月的《Harness design for long-running application development》里把这条规律说得很直白。harness 里的每个部件,都编码了一个“模型自己做不到什么”的假设,而模型变强以后,这些假设会过时。

他自己的 harness 就是例子。Claude Sonnet 4.5 在上下文快满时会提前收尾,文章称之为“上下文焦虑”,所以 harness 会清空上下文,再通过文件交接状态。到了 Opus 4.5,这个毛病基本消失了,重置也就拿掉了。Opus 4.6 出来后,把工作切成小块的冲刺结构也拿掉了。

值得照搬的是他的方法。一次性大刀阔斧地砍掉 harness,质量会下降,也说不清哪些部件在真正承重。一次只拆一个部件,再检查结果,这个办法行得通。长时间运行的智能体那一课会搭起这套 harness 所来自的多会话版本。

写下 OpenAI 那篇 harness 文章的 Ryan Lopopolo,谈上下文和“管道活”

Ryan Lopopolo 写了那篇 Harness 工程文章,按他在 X 上的简介,他现在是 Google Cloud 的首席工程师。2026 年 3 月 7 日,他在 X 上回答一个问题时,把这项工作分成两件事。一是把所有非功能性需求放到智能体能拉进上下文的地方,二是尽量减少完成高质量工作对上下文的占用。用本课的话说,docs 文件夹和 lint 负责第一件,简短的 AGENTS.md 和安静的工具输出负责第二件。

4 月 15 日,他发帖说,新的 Agents SDK 正是他 2025 年 9 月就希望拥有的东西,还特别提到它“自带沙盒”的设计。他的建议是让 SDK 去做管道活,把精力花在用户身上。这和上面的矩阵对得上:循环、工具和审批拿现成的,把时间留给只有你的产品才懂的上下文和检查。

他讲这个实验的主题演讲,后面还有一段问答,收录在 AI Engineer 频道上。他的 Harness 工程仓库收集了他的帖子和笔记,供智能体阅读。

Harness 工程分两部分:把所有非功能性需求放进一个能被拉进上下文的地方,再尽量降低对上下文的需求。

Ryan Lopopolo, OpenAI Harness 工程文章的作者,现任 Google Cloud 首席工程师,引文为译文 · X (2026年11月6日 查阅)
Ryan Lopopolo(@_lopopolo)2026 年 3 月 7 日在 X 上回复 @_buggles:按目前的模型架构,上下文是硬约束,也是稀缺资源。Harness 工程分两部分:把非功能性需求放到能拉进上下文的地方,再尽量减少对上下文的需求。
Ryan Lopopolo 在 X 上,2026 年 3 月 7 日
Ryan Lopopolo(@_lopopolo)2026 年 4 月 15 日在 X 上说,新的 Agents SDK 正是他 2025 年 9 月就想要的,它认定所有智能体都是编程智能体,并采用自带沙盒的设计。让 SDK 去做管道活,把精力放在用户身上。
Ryan Lopopolo 在 X 上,2026 年 4 月 15 日
Ryan Lopopolo 关于 Harness 工程的主题演讲,后面附有问答,发布在 YouTube 的 AI Engineer 频道。

X 官方嵌入卡片的截图,拍摄于 2026 年 11 月 6 日,时间为 UTC。X 用“Show more”截断了第一条帖子。点击截图可到 X 上打开原帖。

常见错误

  • 一边换 harness 一边比较两个模型。同一个模型在 Codex 里和在你的脚本里,看到的指令、工具和上下文都不一样。
  • 把一个指令文件越写越长,直到它挤掉了任务本身,也没人分得清哪些规则还有效。
  • 在对话中途增加工具或调换工具顺序,之后每次调用的缓存前缀都会失效。
  • 以为一个沙盒管住了所有工具。在 Codex 里,它只管内置的 shell 工具,MCP 服务器要自己管自己。
  • 规则只写在文字里,其实一条 lint 或一个测试就能在每次改动时强制执行。
  • 需要某个变通办法的模型早已换掉,这个办法却还留在 harness 里。

动手试试

示意代码里的工具,是为读示例的人写的。下一课给 AI 智能体写工具,讲怎样为模型来写。

  • 用示意代码自带的任务运行它,在提示处回答 n,看看模型在收到被拒绝的工具结果后,对用户说了什么。
  • 把 MAX_TURNS 设成 1,先问订单 O2 的状态,再要求取消它,记下是 harness 的哪个部分结束了这次运行。
  • 打开你的 ~/.codex/config.toml 或项目里的 AGENTS.md,在 Codex 那张表里标出哪些行已经由你掌控。
  • 挑一条团队在代码评审里反复提的规则,把它写成一条 lint,让报错信息告诉智能体怎样改代码。

自测一下

  1. 在 Codex 的 harness 里,权限消息中的沙盒管的是哪些工具?

    查看答案

    B. 只有 Codex 自带的 shell 工具 沙盒只作用于 Codex 自带的 shell 工具,MCP 服务器提供的工具要自己负责安全护栏。

  2. OpenAI 团队用什么替代了一个庞大的 AGENTS.md?

    查看答案

    A. 一份大约 100 行、指向 docs 文件夹的简短 AGENTS.md 大文件会挤掉任务并很快过时,所以它被改成一份目录,指向由 lint 保持更新的 docs 文件夹。

  3. 在 Codex 对话中途做哪种改动,会导致提示词缓存失效?

    查看答案

    C. 改变模型能用的工具 缓存命中需要完全相同的前缀,而工具列表位于每次请求的开头附近。

  4. 更强的模型发布了。Anthropic 那篇 harness 设计文章建议你怎样处理自己的 harness?

    查看答案

    D. 一次拆掉一个部件,再检查结果 每个部件都编码了一个关于模型做不到什么的假设,一次拆一个,才看得出哪些部件仍在承重。

常见问题

什么是 Harness 工程?

它指的是为编程智能体打造周边的一切,让它能可靠地完成工作。Ryan Lopopolo(OpenAI)在 2026 年 2 月发表的文章里用了这个说法。他团队里的工程师不亲手写代码,而是搭建智能体工作所依托的文档、lint 规则和反馈回路。

Claude Code 算是智能体 harness 吗?

算。里面的模型是 Claude,你在一次会话里遇到的其他一切,从权限提示到它开始时读取的 CLAUDE.md 文件,都属于 harness。所以同一个模型,在 Claude Code 会话里和在一次普通的 API 调用里,表现才会差这么多。

智能体 harness 和智能体框架是一回事吗?

不完全是。框架或 SDK 是别人写好、由你来配置的 harness。你自己在模型 API 外面写的循环也是 harness,只是更小,要你自己维护。

为什么同一个模型在两个编程智能体里表现不同?

因为每个 harness 围绕它拼出的请求都不一样。以 Codex 为例,它会在你的消息前面加上自己的指令、一条权限消息、AGENTS.md 文件和工作目录,而且每个智能体提供的工具也不相同。

安全护栏在 harness 里处在什么位置?

护栏就是每个动作前后的检查:沙盒、权限规则、审批提示和钩子。本课在 cancel_order 之前加的审批提示是最小的一种,其余的见智能体安全护栏那一课。

资料来源

  1. OpenAI: Harness engineering, leveraging Codex in an agent-first world (2026年11月6日 查阅)https://openai.com/index/harness-engineering/
  2. OpenAI: Unrolling the Codex agent loop (2026年11月6日 查阅)https://openai.com/index/unrolling-the-codex-agent-loop/
  3. Anthropic Engineering: Building effective agents (2026年11月6日 查阅)https://www.anthropic.com/engineering/building-effective-agents
  4. Anthropic Engineering: Building agents with the Claude Agent SDK (2026年11月6日 查阅)https://www.anthropic.com/engineering/building-agents-with-the-claude-agent-sdk
  5. Anthropic Engineering: Harness design for long-running application development (2026年11月6日 查阅)https://www.anthropic.com/engineering/harness-design-long-running-apps
  6. Claude Cookbooks: Creating a customer service agent with client-side tools (2026年11月6日 查阅)https://github.com/anthropics/claude-cookbooks/blob/main/tool_use/customer_service_agent.ipynb
  7. anthropic-sdk-python: ToolResultBlockParam (is_error) (2026年11月6日 查阅)https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/types/tool_result_block_param.py
  8. anthropics/claude-agent-sdk-python README (2026年11月6日 查阅)https://github.com/anthropics/claude-agent-sdk-python
  9. openai/openai-agents-python README (2026年11月6日 查阅)https://github.com/openai/openai-agents-python
  10. lopopolo/harness-engineering on GitHub (2026年11月6日 查阅)https://github.com/lopopolo/harness-engineering
  11. Ryan Lopopolo on X: harness engineering is two parts (2026年11月6日 查阅)https://x.com/_lopopolo/status/2030356581792293212
  12. Ryan Lopopolo on X: let the SDK do the plumbing (2026年11月6日 查阅)https://x.com/_lopopolo/status/2044507506949689726
  13. AI Engineer on YouTube: Harness Engineering, Ryan Lopopolo (2026年11月6日 查阅)https://www.youtube.com/watch?v=am_oeAoUhew

分享本节课

用好 harness 的智能体

浏览 Mindket 上的智能体,每个都有预览,看看拥有好工具和好规则的模型能做什么。

浏览智能体

本节课的 Markdown 版本 · Harness 工程:搭建 AI 智能体外围的代码 · 全部课程