Clipping 微信公众号

如何构建你自己的 Agent Harness?超越 Cursor - Codex - Claude Code,告别一次性 SDK 选型,15 项职责独立各自可替换升级

by 邵猛 原文 ↗
Created: 2026-06-01

公众号名称:AI 启蒙小伙伴

作者名称:邵猛

发布时间:2026-06-01 07:50

如何构建你自己的 Agent Harness

先看几个问题:

  • 生产级 Harness 是“选一个框架”就能搞定的吗?
  • 生产级 Harness 必须承担的 15 项真实职责是什么?
  • 每项职责如何做成可安装、可版本化、可换语言的 worker?
  • 单次 turn 如何跑通?
  • 策略、审批、预算、trace 在生产级 Harness 里为什么重要?

How to Build Your Own Agent Harness

原文链接https://iii.dev/blog/how-to-build-your-own-agent-harness/
作者:Mike Piccolo(iii 创始人 & CEO)


这篇文章在讲什么

这篇文章不不单单是一篇「如何写代码搭建 Agent」的教程,更是一篇Agent 架构立场文章

作者的核心主张可以概括为:

Agent harness 不应该是「整包导入的框架」,而应该是一组可独立替换、通过统一总线协作的 worker 组合。

文章用 iii 项目的实际生产栈作为例证,说明:当你把 harness 拆成 10 余个职责明确的 worker 后,「自建 harness」就不再等于 fork 某个框架,而是替换其中几个 worker

这与当前主流做法形成对照——大多数团队会直接采用 LangChain、LangGraph、OpenAI Agents SDK、Anthropic SDK、CrewAI、AutoGen 等现成方案,把 loop、tools、memory、orchestration 作为一次性的框架选型决策。​作者认为,这种「单体框架」形态会在规模化后迫使团队重写 harness,因为框架内嵌的 policy、approval、credential、budget 等模块往往无法按业务需求单独演进。​


问题的根源:bundling,而非缺功能

文章先不急着推销 iii,而是先做了职责拆解。它认为,一个生产级 agent harness 至少要承担 15 项工作:

#职责简要说明
1接收并持久化 turn 请求客户端发起一次对话轮次
2解析模型 provider 凭证安全地获取 API key / token
3查询模型能力vision、tools、streaming、context window 等
4驱动 per-turn 状态机provision → stream → tool → steer → teardown
5加载 skill 文档描述每个 function 的请求格式、错误码、用法
6组装 system promptmode、identity、working directory、默认 skills
7向客户端流式返回 token实时 UI 体验
8工具调用策略检查policy engine,执行前拦截
9人工审批需要人决策的 tool call 暂停并恢复
10LLM 预算追踪workspace / agent 级别 spend cap
11hook 机制tool call 前后 logging、redaction、副作用
12分支式 session 存储支持 fork、resume
13context compaction上下文窗口满时压缩历史
14事件流UI 订阅 agent 状态变化
15分布式 tracingOpenTelemetry,跨步骤串联调试

作者指出:严肃 harness 几乎都会覆盖这些职责。差别在于:

  • 廉价方案:先砍 corner,上线后再补
  • 昂贵方案:一开始全做
  • 框架方案:把上述能力打包成 monolith 的一个版本

真正昂贵的是最后一种——当你发现框架自带的 policy engine 不符合需求时,替换它往往意味着替换整个 harness。

这是全文最有价值的诊断:问题不是「缺一个 agent 框架」,而是「缺少可组合的 harness 原语」。


iii 的解法:worker + trigger 总线

iii 的 bet 是:

  1. 上述每项职责对应一个独立 worker
  2. worker 通过 WebSocket 连到共享 engine
  3. worker 注册 functiontrigger,彼此用 iii.trigger() 调用
  4. 任意 worker 可独立版本化、独立替换、任意语言实现(有 SDK 即可)

这与「再接入一个 service 并和所有其他 service 集成」的模式不同。iii 强调:所有 worker 被同等对待,集成逻辑下沉到 engine 的路由层,而不是散落在各 worker 之间。

生产栈一览(11 个 worker)

Worker职责
iii-directorySkill 与 prompt 注册表,按需 directory::skills::get
harness元 worker:加载 iii-permissions.yaml,暴露 policy 与 UI 事件面
turn-orchestrator11 状态 FSM,驱动单次 turn 全生命周期
approval-gate人工审批入口,approval::resolve 路由回对应 turn
session分支式 session tree + per-session inbox
llm-budget14 个 budget 相关 function,含 check / record / forecast
hook-fanout通用 publish-collect,所有 hook 的底层模式
auth-credentials文件-backed 凭证 vault
models-catalog静态模型能力目录
provider-*Anthropic / OpenAI / Kimi / LM Studio 等 SSE 流式 provider
context-compactiontoken 超阈值时压缩 session history

关键设计属性:表格里每一行都是一个可替换边界。不喜欢静态 model catalog?换一个注册同样 function id 的 worker。不喜欢 file-backed credentials?换成读 secrets manager 的实现。想改 turn FSM?替换 turn-orchestrator,其余 worker 仍通过 run::startturn_state 交互,无需改动。


一次 turn 如何跑通(机制细节)

文章用「从客户端 POST 到 turn 结束」的顺序,把抽象架构落到了具体数据流。理解这段,是读懂全文的关键。

4.1 入口与 tracing

Browser/CLI → harness::trigger → run::start → turn-orchestrator

harness::trigger 这一跳的存在,是为了在 OpenTelemetry span 中注入 session_idmessage_id baggage,使后续所有嵌套 iii.trigger 共享同一 trace 树。这是第 15 项职责(全链路可观测)的工程落地。

4.2 异步 durable FSM

run::start 立即返回;真正工作由 durable per-state machineturn-step FIFO 上逐步推进。终端状态只有两个:

  • stopped:正常结束
  • failed:未预期异常,ack 队列停止重试,向 UI 发送 message_complete{stop_reason:'error'}

teardown 不是单独入队的步骤,而是内联的 finishSession() port——减少一次 durable queue hop。

4.3 provisioning 阶段三件事

  1. 按需启动 iii-sandbox microVM(隔离执行环境)
  2. 调用 directory::skills::download 预缓存默认 skills
  3. 组装三层 system prompt:
  • mode 段落(plan / ask / agent
  • iii identity preamble(教模型 agent_trigger 约定与按需 skill 发现)
  • 默认 skills index appendix

调用方可在 run::start 传入 system_prompt 完全覆盖;否则由 orchestrator 构建。function schemas 来自 engine 的 live catalog。

4.4 流式推理

assistant_streaming 调用 provider::::stream。provider worker 通过 auth::get_token 取凭证,将上游 SSE 写入 iii channel,orchestrator 读取 channel 并向 agent::eventsmessage_update。streaming 逻辑封装在 pull-based MessagePump 后,FSM 只关注状态转换。

4.5 工具调用:单一 chokepoint

所有 tool call 经过 dispatchWithHook,其中 consultBefore 调用 policy::check_permissions(5 秒超时)。policy worker 读 iii-permissions.yaml,按 function_id 匹配规则,返回三种结果:

决策行为
allow正常 dispatch
deny短路,写入 DenialEnvelope
needs_approval该 call 进入 awaiting_approval,其余 batch 继续;全部 pending 时 turn 进入 function_awaiting_approval

Fail-closed 设计

  • policy worker 不可达或超时 → gate_unavailable denial
  • hook fanout publish 失败 → 视为 deny

4.6 审批恢复:反应式,而非 per-call resume

旧设计需要为每个 pending call 注册 turn::approval_resume::;新设计改为:

  1. orchestrator 在 scope approvals 上注册唯一turn::on_approval trigger
  2. console 调用 approval::resolve → approval-gate 写入 state
  3. state write 触发 trigger → 唤醒对应 session
  4. function_awaiting_approval 只读刚落地的 decision,逐条 dispatch

好处:无需 per-call resume function、无需启动时 re-scan pending approvals。一个 trigger 覆盖所有 session。

这也是文章强调的「composition property」例证——turn-orchestrator 内部从 11 状态 refactor 到 7 状态,approval 机制大改,但 approval::resolve wire shape 不变,其余 worker 零改动。

4.7 性能优化(从架构中自然长出)

  • after-function-call hook:无 durable subscriber 时 short-circuit publish_collect,省约 500ms/call
  • tearing_down 内联,省一次 queue hop
  • context-compaction 订阅 agent::turn_end,per-turn 唤醒而非 per-event
  • session-create fanout 用 in-process scope match,去掉 RPC

4.8 steering 与结束

batch 完成后 steering_check 决定 continue / stop / max_turns。continue 则回到 assistant_streaming;否则 inline finishSession():发 agent_end、释放 sandbox、进入 stopped

全程各 worker 自动 emit OTel span(iii.session.id / iii.message.id / iii.function.id),engine 的 engine::traces::group_by 支持按 session / message / function 分组——instrumentation 通过 registerFunction Proxy 自动完成,worker 作者无需手动加 span。


「Build your own」到底意味着什么

文章刻意消解「自建 harness」的神秘感:

写 harness worker = 写任意 business worker。

替换某层的操作步骤:

  1. 选定要替换的层
  2. 写一个新 worker,注册相同的 function id
  3. iii worker add 安装
  4. 停掉旧 worker

其余 stack 自动路由到新 worker。

五个具体替换示例

1. 动态 model catalog
注册 models::list / models::get / models::supports,从 provider API 定时拉取并缓存。turn-orchestrator 仍调用 iii.trigger('models::list'),无感知。

2. 新增 provider
参照 provider-kimiprovider-lmstudio:一个 folder + iii.worker.yaml + register.ts,注册 provider::::stream::complete,SSE → iii channel,usage 写 budget::record

3. 私有 skill 存储
替换 directory::skills::get / list,后端接 S3 或内部文档系统。agent 的「首次调用前 fetch skill」模式不变。

4. 完全自定义 system prompt
run::start.system_prompt 字段跳过 orchestrator 的三层组装。bootstrap 仍下载 skills,保留按需 discovery。

5. Slack 审批 UI
不改 approval-gate。新写 Slack worker 监听 slash command,调用同样的 approval::resolve payload。orchestrator 不知道 UI 来自 console 还是 Slack。

6. 自定义 policy engine(OPA / Cedar / 自研 DSL)
注册 policy::check_permissions,返回 { decision, rule_id?, matched_constraint? }。5 秒超时与 fail-closed 语义保持不变。

这些例子的共性:每层通过一两个 function id 暴露边界;替换 = 注册相同 id 的新 worker。


Thin vs Thick:从 fork 到 slider

传统 harness 辩论常被框定为「thin loop vs thick DAG」(如 Anthropic 式简单 loop vs LangGraph 显式图)。这隐含一个假设:你必须选边站队

iii 的 reframing:

形态组成适用场景
Thin harnessturn-orchestrator + provider + auth + minimal harness内部实验、自主研究 agent、信任模型
Thick harness全部 13+ worker + 自定义 policy + Slack 审批 + budget客户工作流、可审计、财务 rollup

两者之间的「架构距离」不是 rewrite,而是 config.yaml 里增减 worker。同一 wire protocol、同一 trace shape、同一 observability story。

框架模型的局限在于:框架替你选了 slider 上的位置并锁死。worker 模型把 slider 留在你手里。


和现有框架生态的关系(客观对照)

文章带有明显的 product positioning,但其诊断与行业经验 largely 吻合:

7.1 文章说得对的地方

  1. 职责 bundling 是真实痛点
    规模化团队普遍遇到:policy 不够灵活、审批 UI 绑死、凭证管理接不进企业 secrets、预算与 trace 割裂。这些问题往往跨模块,单体框架里改一处牵动全局。

  2. 可替换边界是正确抽象方向
    把 harness 视为「一组 job」而非「一个 package」,与 microservices、plugin architecture 的演进逻辑一致。function id 作为 stable contract 是可行的集成方式。

  3. Fail-closed + 单一 chokepoint 是安全 harness 的 baseline
    policy 超时即 deny、hook publish 失败即 deny、tool dispatch 集中过 consultBefore——这是生产 agent 应有的默认姿态,文章给出了清晰实现路径。

  4. Observability 是一等公民
    从 trigger 入口注入 baggage、Proxy 自动 wrap span、UI 支持 group by session/message/function——把 tracing 当作 harness 第 15 项职责,而非事后补丁。

7.2 需要冷静看待的地方

  1. 复杂度转移,而非消失
    worker 组合模型把框架内耦合换成了「function id 契约维护 + 多进程运维 + registry 版本管理」。对小团队或 MVP,单体框架的学习曲线可能更低。

  2. 「13 个 worker」本身是一种 opinionated 分解
    职责清单合理,但边界划分并非唯一真理。例如 prompt 组装放在 turn-orchestrator 还是独立 worker,policy 嵌在 harness meta-worker 还是完全外置——都是设计选择,不是物理定律。

  3. iii 生态成熟度
    文章以 iii 生产栈为例,读者需独立评估:registry 生态、SDK 覆盖语言、社区规模、与现有 LangGraph/Cursor/Claude Code 等工具的互操作成本。

  4. Durable FSM + FIFO 的运维成本
    文章强调 durable turn loop 与 queue 语义,这对长任务可靠性重要,但也引入 queue 积压、 poison message、状态迁移调试等分布式系统问题——框架通常替你封装了这部分。

  5. Slider 隐喻的边界
    thin → thick 确可通过加 worker 实现,但 worker 之间的隐式假设 仍可能存在(例如 turn-orchestrator 对 policy 返回 shape 的依赖)。替换单层看似简单,跨层集成测试仍是必要成本。


与 Anthropic harness 研究的对照

Anthropic 近期公开的 agent harness 研究(generator / evaluator 分离、context reset、sprint contract 等)侧重单任务质量与长程 coherence

本文(iii)侧重基础设施 composability 与生产治理(policy、approval、budget、tracing、session fork)。

两者解决不同维度的问题,并不互斥:

Anthropic 方向         iii 方向
────────────────       ────────────────
任务怎么拆、怎么评        harness 怎么拆、怎么换
模型行为与 context      系统边界与 enterprise 需求
quality loop           governance + observability loop

一个完整生产系统可能需要同时考虑:上层 task harness(planner/generator/evaluator)底层 runtime harness(iii 式 worker stack)


实践建议:读完后可以做什么

若你在评估是否迁移

先对照第 15 项职责清单,诚实标记团队当前框架的红区(已痛)与黄区(可预见会痛):

  • Policy 能否接 OPA / 自研规则?
  • 审批能否脱离框架自带 chat UI?
  • 凭证能否接 Vault / AWS Secrets Manager?
  • 预算能否 rollup 到 finance dashboard 且与 trace 关联?
  • Session 是否支持 fork / resume?

若红区 ≥ 3,文章提出的 decomposed harness 值得 POC;若团队仍在探索阶段,单体框架可能足够。

若你暂不迁移,仍可借鉴的设计原则

  1. 把 harness 职责写成 checklist,避免「框架有就用、没有就忍」
  2. 为 policy / approval / credentials 定义 stable interface,即使暂时 monolith 实现,也为将来抽 worker 留缝
  3. 从 turn 入口注入 trace context,不要等出事故才补 OTel
  4. tool dispatch 走单一 chokepoint,集中做 permission 与 hook
  5. 审批恢复用 reactive trigger,避免 per-call callback 注册导致的状态泄漏
  6. 把 thin/thick 当作配置维度,而非架构重写

若想亲手验证 iii 论点

文章建议路径:

git clone https://github.com/iii-hq/workers
pnpm install && pnpm build
# 运行 composite entry point,获得完整 14-worker harness

然后尝试:

  • 从 boot list 移除某个 worker(体验 thin harness)
  • 写一个替换 models::list 的 worker(体验 swap)
  • 订阅 hook-fanout::publish_collect topic(体验 extend)

相关资源:


核心结论

维度传统框架模型iii worker 模型
Harness 是什么一个 import 的 package一组必须完成的 job
自建含义Fork 或绕过框架替换/增删 worker
集成原语框架 API + adapteriii.trigger(function_id)
Thin/Thick选型时决定运行时 slider
可观测性常作附加从 trigger 入口贯穿
主要代价扩展性锁死多 worker 运维与契约维护

用一句话收束原文 bet:

Harness 不是你要安装的东西,而是你的系统为了让 agent 持久、安全、可观测地运行所必须完成的工作集合。iii 赌的是:这些工作都可以被同一个足够小的原语(worker + function + trigger)分别吸收,且组合结果比任何单体框架更贴近你的系统形状。

这是否成立,取决于你的团队规模、治理需求、与 iii 生态的匹配度。但文章对「framework bundling 问题」的诊断,以及「职责清单 + 可替换边界 + fail-closed chokepoint + 全链路 trace」这套工程语言,值得任何正在 build production agent 的团队认真读一遍——即便最终不选 iii,也应把 harness 当作可组合层来设计,而非一次性框架选型。


附录:关键术语

术语含义
Harness包裹 LLM 的运行时基础设施:loop、tools、policy、session、observability 等
Worker连到 iii engine 的独立进程,注册 functions 与 triggers
Function id总线上的稳定调用名,如 models::listpolicy::check_permissions
Trigger状态变化或事件驱动的唤醒机制,如 turn::on_approval
Turn一次完整的 agent 交互轮次(可能含多轮 model ↔ tool 循环)
FSMturn-orchestrator 内的有限状态机,驱动 provisioning → streaming → execute → …
Skill描述 function 用法、schema、错误码的文档,按需 fetch
Fail-closed安全组件不可用时默认拒绝,而非放行
Durable loop基于 queue 的持久化状态推进,进程重启后可恢复

相关资源推荐

Agent Harness Engineering 三重前沿实践:Codex、Claude Code、Cursor 如何让人类从编码者升为架构师

LLM 只是引擎,Harness 才是底盘:Cursor 官方首次系统披露 AI Coding Agents 工程方法论

Claude Code 架构深度解读:Agent 系统的真正护城河不在模型,而在 Harness


cover_image

原创 邵猛 AI 启蒙小伙伴

作者提示: 内容由AI生成


内容效果不满意?点此反馈

输入关键词开始搜索