为什么采用 Agent 框架后团队仍要重写 Harness
大多数团队在搭建生产级 AI Agent 时,都会直接引入 LangChain、LangGraph、CrewAI 或官方 SDK,把 loop、tools、memory 和 orchestration 一次性打包。这不是技术懒惰,而是生态在当时只提供了这种“全家桶”选项。我起初也认为这很合理——快速验证、统一接口、社区示例丰富。可当系统进入长期运行、多租户、需要严格预算控制和人工审批的阶段后,问题
大多数团队在搭建生产级 AI Agent 时,都会直接引入 LangChain、LangGraph、CrewAI 或官方 SDK,把 loop、tools、memory 和 orchestration 一次性打包。这不是技术懒惰,而是生态在当时只提供了这种“全家桶”选项。
我起初也认为这很合理——快速验证、统一接口、社区示例丰富。可当系统进入长期运行、多租户、需要严格预算控制和人工审批的阶段后,问题就开始浮现:某个策略引擎不满足合规要求,想换却发现必须 fork 整个框架;审批 UI 死死绑在聊天界面上,想接 Slack 就得改核心代码;预算追踪和 trace 无法打通,只能事后补监控。
这些痛点不是个例,而是框架把 10 多个本应独立演进的职责强行绑定在一起的必然结果。
Harness 真正要做的 15 件事
把生产级 Agent Harness 剥开来看,它必须承担以下职责:
- 接收客户端的 turn 请求并持久化
- 为模型提供商解析并注入凭证
- 查询模型能力(vision、tools、streaming、上下文窗口)
- 驱动每轮状态机:provision → 流式生成 → 执行工具 → steering → 结束
- 加载并提供技能定义(请求结构、错误码、使用说明)
- 组装 system prompt(模式段落 + 身份前言 + 默认技能索引)
- 将模型生成的 token 实时流回客户端
- 工具调用前通过策略检查
- 需要人工决策的调用暂停并路由回正确会话
- 按 workspace 或 agent 维度追踪 LLM 花费
- 工具调用前后执行 hook(日志、脱敏、自定义副作用)
- 将会话持久化为可分支的树结构,支持 fork 与 resume
- 上下文窗口即将溢出时进行压缩
- 向 UI 推送事件流
- 横跨所有步骤的统一 OpenTelemetry trace(很多框架在此缺失)
这些职责本身没有问题。问题出在:现有框架把它们做成了一个不可分割的发布单元。版本升级、策略替换、UI 定制、预算后端切换,任何一处改动都可能牵动全局。久而久之,团队要么忍受妥协,要么推倒重来。
iii 的不同选择:把 Harness 做成一组可替换的 Worker
iii 的核心判断是:Harness 不是一个东西,而是一组必须完成的工作。既然生态没有提供把这些工作优雅组合的 primitive,那就自己造一个。
他们的做法是:把上述职责拆成独立 Worker,所有 Worker 通过同一个引擎总线通信,只依赖一个原语——iii.trigger()。每个 Worker 可以独立发布、独立版本、独立替换,甚至用不同语言编写。
实际生产栈目前包含 11 个 Worker(加上 engine 本身):
- harness meta(入口与 trace 包装)
- turn-orchestrator(核心状态机与 prompt 组装)
- provisioning(sandbox 启动、技能下载、默认 prompt 构建)
- assistant_streaming(provider 流式调用与 channel 管理)
- 各 provider worker(如 provider-anthropic、provider-kimi)
- auth-credentials(凭证解析)
- policy worker(权限检查)
- approval-gate(人工审批持久化与唤醒)
- llm-budget(花费记录)
- hook-fanout(前后 hook 广播)
- context-compaction(上下文压缩)
- models-catalog(模型能力查询)
它们都注册到同一套 function id 和 trigger scope 上。引擎负责路由,Worker 只关心自己注册的那几个函数。
这带来了一个关键属性:替换任意一层不再需要改动其他层。
单次 Turn 的真实执行路径
一个完整 turn 的协作顺序大致如下:
- 客户端通过
harness::trigger发起请求,harness meta 负责注入 OpenTelemetry baggage(session_id、message_id),保证整条链路可追踪。 run::start落到 turn-orchestrator,持久化请求、初始化 TurnStateRecord 并返回。- provisioning 启动 sandbox、下载默认技能、组装 system prompt(模式 + iii 身份前言 + 技能索引)。
- assistant_streaming 调用对应 provider worker,后者通过 auth-credentials 取凭证,流式把模型响应写入 channel。
- orchestrator 消费 channel,边产生边向 UI 推送
message_update。 - 模型返回 tool call 后,进入 function_execute。所有调用必须经过
dispatchWithHook→consultBefore。 - policy worker 在 5 秒超时内返回 allow / deny / needs_approval。
- needs_approval 的调用被挂起,等待 approval-gate worker 写入决策后,通过
turn::on_approval单一 trigger 唤醒。 - 批次完成后,steering_check 决定是否继续下一轮,或调用
finishSession()清理 sandbox 并结束。
整个过程中,任何 Worker 都可以被替换,而 orchestrator 感知不到区别——它只调用约定的 function id。
// 自定义 Policy Worker 示例(逻辑重构后)
import { iii } from '@iii/engine-sdk';
iii.registerFunction('policy::check_permissions', async (call) => {
// 从 iii state 或外部 OPA/Cedar 加载规则
const result = await evaluateCustomPolicy(call.function_id, call.args);
return {
decision: result.decision, // allow | deny | needs_approval
rule_id: result.ruleId,
matched_constraint: result.constraint
};
});
// 部署后执行:iii worker add your-org/custom-policy
// 引擎会自动把该 function id 路由到最新注册的 Worker
Thin 与 Thick 不再是架构二选一
传统框架把你锁定在某个位置:要么用 Anthropic 的 thin loop,要么用 LangGraph 的厚 DAG。
在 iii 模型里,thin 和 thick 只是你安装了多少 Worker 的区别。
- Thin:只保留 turn-orchestrator + 一个 provider worker + 基础 auth + 最小 meta。适合内部研究型 Agent。
- Thick:加上完整 policy、approval-gate、budget、hook-fanout、context-compaction,还可以再接 Slack 审批 Worker。适合面向客户的审计强场景。
切换只需要改 config.yaml 里的 worker 列表,合约(function id + wire schema)保持稳定。甚至 turn-orchestrator 内部把 FSM 从 11 个状态压缩到 7 个,其他 Worker 完全无感知。
这才是真正的“slider”——不是框架给你选好的固定挡位,而是你随时可以加减的连续调节。
从“用框架”到“拥有 primitive”的范式迁移
我后来意识到,框架时代的问题根源在于:它把本应是基础设施层的能力,做成了应用层的一次性选择。
当 substrate(iii engine + worker 总线)把 credential、policy、approval、budget、trace 这些横切关注点都变成可独立替换的 Worker 时,“构建自己的 Harness”就从“fork 一个框架”变成了“在总线上多注册几个函数”。
这不是微优化,而是 Agent 基础设施的操作系统级重构。它让长期项目不再被迫在“忍受框架限制”和“全量重写”之间二选一。
如果你正在维护一个已经运行半年以上的 Agent 系统,问问自己:现在想换一个更严格的审批策略,或者把预算控制接进公司 FinOps 平台,代价到底有多大?
把 github.com/iii-hq/workers 克隆下来,关掉一个 Worker 再加一个自定义的,感受一下“替换”真正应该是什么样子。也许你会发现,真正的技术债,从来不是代码量,而是被打包在一起、无法单独演进的那些职责。
我是紫微AI,在做一个「人格操作系统(ZPF)」。后面会持续分享AI Agent和系统实验。感兴趣可以关注,我们下期见。
更多推荐


所有评论(0)