如何构建你自己的 Agent Harness?超越 Cursor - Codex - Claude Code,告别一次性 SDK 选型,15 项职责独立各自可替换升级
公众号名称: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 prompt | mode、identity、working directory、默认 skills |
| 7 | 向客户端流式返回 token | 实时 UI 体验 |
| 8 | 工具调用策略检查 | policy engine,执行前拦截 |
| 9 | 人工审批 | 需要人决策的 tool call 暂停并恢复 |
| 10 | LLM 预算追踪 | workspace / agent 级别 spend cap |
| 11 | hook 机制 | tool call 前后 logging、redaction、副作用 |
| 12 | 分支式 session 存储 | 支持 fork、resume |
| 13 | context compaction | 上下文窗口满时压缩历史 |
| 14 | 事件流 | UI 订阅 agent 状态变化 |
| 15 | 分布式 tracing | OpenTelemetry,跨步骤串联调试 |
作者指出:严肃 harness 几乎都会覆盖这些职责。差别在于:
- 廉价方案:先砍 corner,上线后再补
- 昂贵方案:一开始全做
- 框架方案:把上述能力打包成 monolith 的一个版本
真正昂贵的是最后一种——当你发现框架自带的 policy engine 不符合需求时,替换它往往意味着替换整个 harness。
这是全文最有价值的诊断:问题不是「缺一个 agent 框架」,而是「缺少可组合的 harness 原语」。
iii 的解法:worker + trigger 总线
iii 的 bet 是:
- 上述每项职责对应一个独立 worker
- worker 通过 WebSocket 连到共享 engine
- worker 注册 function 和 trigger,彼此用
iii.trigger()调用 - 任意 worker 可独立版本化、独立替换、任意语言实现(有 SDK 即可)
这与「再接入一个 service 并和所有其他 service 集成」的模式不同。iii 强调:所有 worker 被同等对待,集成逻辑下沉到 engine 的路由层,而不是散落在各 worker 之间。
生产栈一览(11 个 worker)
| Worker | 职责 |
|---|---|
iii-directory | Skill 与 prompt 注册表,按需 directory::skills::get |
harness | 元 worker:加载 iii-permissions.yaml,暴露 policy 与 UI 事件面 |
turn-orchestrator | 11 状态 FSM,驱动单次 turn 全生命周期 |
approval-gate | 人工审批入口,approval::resolve 路由回对应 turn |
session | 分支式 session tree + per-session inbox |
llm-budget | 14 个 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-compaction | token 超阈值时压缩 session history |
关键设计属性:表格里每一行都是一个可替换边界。不喜欢静态 model catalog?换一个注册同样 function id 的 worker。不喜欢 file-backed credentials?换成读 secrets manager 的实现。想改 turn FSM?替换 turn-orchestrator,其余 worker 仍通过 run::start 和 turn_state 交互,无需改动。
一次 turn 如何跑通(机制细节)
文章用「从客户端 POST 到 turn 结束」的顺序,把抽象架构落到了具体数据流。理解这段,是读懂全文的关键。
4.1 入口与 tracing
Browser/CLI → harness::trigger → run::start → turn-orchestrator
harness::trigger 这一跳的存在,是为了在 OpenTelemetry span 中注入 session_id 和 message_id baggage,使后续所有嵌套 iii.trigger 共享同一 trace 树。这是第 15 项职责(全链路可观测)的工程落地。
4.2 异步 durable FSM
run::start 立即返回;真正工作由 durable per-state machine 在 turn-step FIFO 上逐步推进。终端状态只有两个:
stopped:正常结束failed:未预期异常,ack 队列停止重试,向 UI 发送message_complete{stop_reason:'error'}
teardown 不是单独入队的步骤,而是内联的 finishSession() port——减少一次 durable queue hop。
4.3 provisioning 阶段三件事
- 按需启动 iii-sandbox microVM(隔离执行环境)
- 调用
directory::skills::download预缓存默认 skills - 组装三层 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::events 发 message_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_unavailabledenial - hook fanout publish 失败 → 视为 deny
4.6 审批恢复:反应式,而非 per-call resume
旧设计需要为每个 pending call 注册 turn::approval_resume::;新设计改为:
- orchestrator 在 scope
approvals上注册唯一turn::on_approvaltrigger - console 调用
approval::resolve→ approval-gate 写入 state - state write 触发 trigger → 唤醒对应 session
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 hopcontext-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。
替换某层的操作步骤:
- 选定要替换的层
- 写一个新 worker,注册相同的 function id
iii worker add安装- 停掉旧 worker
其余 stack 自动路由到新 worker。
五个具体替换示例
1. 动态 model catalog
注册 models::list / models::get / models::supports,从 provider API 定时拉取并缓存。turn-orchestrator 仍调用 iii.trigger('models::list'),无感知。
2. 新增 provider
参照 provider-kimi、provider-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 harness | turn-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 文章说得对的地方
-
职责 bundling 是真实痛点
规模化团队普遍遇到:policy 不够灵活、审批 UI 绑死、凭证管理接不进企业 secrets、预算与 trace 割裂。这些问题往往跨模块,单体框架里改一处牵动全局。 -
可替换边界是正确抽象方向
把 harness 视为「一组 job」而非「一个 package」,与 microservices、plugin architecture 的演进逻辑一致。function id 作为 stable contract 是可行的集成方式。 -
Fail-closed + 单一 chokepoint 是安全 harness 的 baseline
policy 超时即 deny、hook publish 失败即 deny、tool dispatch 集中过consultBefore——这是生产 agent 应有的默认姿态,文章给出了清晰实现路径。 -
Observability 是一等公民
从 trigger 入口注入 baggage、Proxy 自动 wrap span、UI 支持 group by session/message/function——把 tracing 当作 harness 第 15 项职责,而非事后补丁。
7.2 需要冷静看待的地方
-
复杂度转移,而非消失
worker 组合模型把框架内耦合换成了「function id 契约维护 + 多进程运维 + registry 版本管理」。对小团队或 MVP,单体框架的学习曲线可能更低。 -
「13 个 worker」本身是一种 opinionated 分解
职责清单合理,但边界划分并非唯一真理。例如 prompt 组装放在 turn-orchestrator 还是独立 worker,policy 嵌在 harness meta-worker 还是完全外置——都是设计选择,不是物理定律。 -
iii 生态成熟度
文章以 iii 生产栈为例,读者需独立评估:registry 生态、SDK 覆盖语言、社区规模、与现有 LangGraph/Cursor/Claude Code 等工具的互操作成本。 -
Durable FSM + FIFO 的运维成本
文章强调 durable turn loop 与 queue 语义,这对长任务可靠性重要,但也引入 queue 积压、 poison message、状态迁移调试等分布式系统问题——框架通常替你封装了这部分。 -
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;若团队仍在探索阶段,单体框架可能足够。
若你暂不迁移,仍可借鉴的设计原则
- 把 harness 职责写成 checklist,避免「框架有就用、没有就忍」
- 为 policy / approval / credentials 定义 stable interface,即使暂时 monolith 实现,也为将来抽 worker 留缝
- 从 turn 入口注入 trace context,不要等出事故才补 OTel
- tool dispatch 走单一 chokepoint,集中做 permission 与 hook
- 审批恢复用 reactive trigger,避免 per-call callback 注册导致的状态泄漏
- 把 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_collecttopic(体验 extend)
相关资源:
- 文档:https://iii.dev/docs
- Engine:https://github.com/iii-hq/iii
- Worker registry:https://workers.iii.dev
- Harness bundle:https://github.com/iii-hq/workers/harness
核心结论
| 维度 | 传统框架模型 | iii worker 模型 |
|---|---|---|
| Harness 是什么 | 一个 import 的 package | 一组必须完成的 job |
| 自建含义 | Fork 或绕过框架 | 替换/增删 worker |
| 集成原语 | 框架 API + adapter | iii.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::list、policy::check_permissions |
| Trigger | 状态变化或事件驱动的唤醒机制,如 turn::on_approval |
| Turn | 一次完整的 agent 交互轮次(可能含多轮 model ↔ tool 循环) |
| FSM | turn-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

原创 邵猛 AI 启蒙小伙伴
作者提示: 内容由AI生成
内容效果不满意?点此反馈
