Run 管理器
创建 run_id、绑定 session、维护 RunState
工具调用控制器
Skill schema 注入、参数校验、调用执行与重试
审计追踪器
所有终态前必须写 Trace / Audit
详细设计说明
把 Agent 执行从“调用链”升级为“可治理运行时”
这份文档建议按“为什么要做、系统边界是什么、核心机制怎么保证稳定、工程如何落地”的顺序阅读。现在的重点不只是说明八个步骤,而是让评审者一眼看清:Harness 是企业 Agent Platform 的执行控制面。
设计主张
Harness 不直接追求“更聪明”,而是把模型、工具、审批、审计和状态恢复统一收敛到一个执行内核里,让企业可以安全地把 Agent 接入真实业务系统。
评审关注点
- 每一次运行是否有明确 Run 状态?
- 工具调用前是否经过 Policy / HITL?
- 异常、拒绝、超时是否都能进入 Trace?
- 未来替换模型或 Skill 时,契约是否稳定?
第一章:Harness 定位与概念
一句话回答三件事——Harness 是什么、住在三层架构的哪一层、和普通 LLM 调用链有什么本质差异。本章是全文的入口,后续章节都基于这里建立的认知展开。
1.1 总体架构图
Harness 是 Tier 3 执行内核,承担"一次 Run 的确定性执行"。它不关心用户从哪个产品入口发消息(那是 Tier 1 应用层的事),也不负责把多次 Run 编织成业务流(那是 Tier 2 协同运行时)。
1.2 三句话核心哲学
整个设计可以归结为三条强约束。后续所有章节的字段定义、状态转移、契约边界都是这三条的展开。
- 状态机 + 双守门员——所有运行态显式定义;Policy(自动规则)和 HITL(人工审批)互补,不能互相替代。
- 审计强约束——不经 Trace 的执行等于没有发生。成功、被拒、失败三条路径都必须在 Trace 中留下完整记录。
- 准备与执行分离——阶段 B(②③④)是纯计算 + 只读,失败可重试且不污染外部状态;阶段 C(⑤⑥⑦)才产生副作用,由 Policy + HITL 把守入口。
顶部 D1–D5 五大工程决策是这三句哲学的细化,请回顾页面顶部 decision-strip。
第二章:八步执行管道详细设计
把一次 Agent Run 拆成创建、准备、执行、收口四个阶段,明确每一步的输入输出、失败处理和代码位置。
目标读者:后端工程师
使用场景:实现新步骤、替换某步实现、性能调优、调试超时
2.1 总体架构
2.1.5 内部组件时序图
下图把 Harness 的 6 个内部组件画成 sequence diagram,明确"哪一步由谁负责、跨组件的调用顺序是什么"。左边界是外部 Caller(T2 Coordinator),最右是审计追踪器。实线=必走,虚线=条件触发(HITL 仅在高风险才走)。点击任意 lifeline 头部色块可跳到下面对应组件的详解章节。
第三章:6 组件详解 · 沿时序图节点逐个剖析
沿上一章时序图的 6 条内部 lifeline,按顺序逐个剖析:每个组件的职责、它在时序图里收发哪些调用、输入输出契约、关键不变量、失败处理。
3.1 Run 管理器 · 主控(Run Lifecycle Owner)
Role · 主控
维护单次 Run 的状态机,所有终态必须汇回它。它是 Run 生命周期的唯一所有者,激活条贯穿整个时序图。
时序图位置
- 接收 ①
create_run(来自 Caller) - 发送 ② 装配请求(→ 上下文装配器)
- 接收 ⑯ 终态汇回(来自 计划执行器)
- 发送 ⑰ 写 Trace(→ 审计追踪器)
- 发送 ⑱ ResultSink(→ Caller)
输入输出
| 方向 | 触发 | 载荷要点 | 状态影响 |
|---|---|---|---|
| 入 | Caller create_run | user_input · session_id · caller_identity · chain_remaining(如有) | CREATED → PREPARING |
| 出 | 装配请求 | session 上下文骨架 · 租户 / 用户隔离信息 | 保持 PREPARING |
| 入 | 组件返回(终态) | SUCCEEDED 结果 / REJECTED 原因 / FAILED 错误 | EXECUTING / AWAITING_HITL → TRACING |
| 出 | 写 Trace | RunTrace 主记录 · 状态转移序列 · 关闭原因 | TRACING → CLOSED_* |
| 出 | ResultSink | 结构化 answer / artifacts / Trace 索引 · 终态码 | 终态稳定,向 Caller 返回 |
11 个 RunState 与转移
正常执行
所有终态必经
暂停或异常
只能在 TRACING 之后
关键不变量
- 终态唯一性:每个 run_id 至多一个终态(SUCCEEDED / CLOSED_REJECTED / CLOSED_FAILED / CLOSED_CANCELLED)。
- 必经 TRACING:无论成功、被拒还是失败,都必须经过 TRACING 才能进入最终关闭态——保证审计完整性。
- 异常必落显式态:超时 → CLOSED_FAILED;Policy 拒绝 → CLOSED_REJECTED;执行异常 → CLOSED_FAILED。不允许"未知中间态"。
- 恢复语义:AWAITING_HITL 是唯一可由外部唤醒的中间态,沙箱不释放。
失败处理
| 失败类型 | 触发态 | 处理 |
|---|---|---|
| 装配 / 规划阶段异常 | PREPARING / PLANNING | 可重试(无副作用);超过重试上限 → FAILED → TRACING → CLOSED_FAILED |
| Policy 拒绝 | POLICY_CHECK | REJECTED → TRACING → CLOSED_REJECTED |
| HITL 超时 | AWAITING_HITL | TIMEOUT → TRACING → CLOSED_REJECTED |
| 工具执行异常 | EXECUTING | 按 Skill 重试策略;最终失败 → FAILED → TRACING → CLOSED_FAILED |
| 僵尸 Run | 任意中间态 | 外部巡检触发强制 → TIMEOUT → TRACING → CLOSED_FAILED |
3.2 上下文装配器 · 准备(Context Assembler)
Role · 准备
把 system prompt、对话历史、用户记忆、知识库召回、租户隔离信息组装成一份token 预算可控的 ctx,交给计划执行器。
时序图位置
- 接收 ② 装配请求(来自 Run 管理器)
- 发送 ③ ctx 就绪(→ 计划执行器)
输入输出
| 方向 | 载荷要点 |
|---|---|
| 入 | session_id · user_input · caller_identity · tenant_id · 历史消息上限 / 召回 scope |
| 出 (AssembledContext) | system prompt · 对话 history · 召回 memory entries · 知识库片段 · token 预算余量 · 安全过滤标记 |
子能力(5 件事必须独立测试)
| 子能力 | 责任 | 失败处理 |
|---|---|---|
| 预检 | system + history + tools + memory token 总量预估 | 超预算 → 触发压缩 |
| 召回 | 按 tenant / user / session / team scope 召回相关 memory | 召回失败降级到无 memory,记录 trace |
| 压缩 | 保留:用户目标 / 约束 / 工具关键结果 / HITL 决策 | 压缩信息丢失关键项 → 拒绝执行(防错答) |
| 隔离 | tenant / user / session / team 四层 scope 强制 | 跨 tenant 串记忆 = 一票否决 |
| 安全过滤 | OAuth token / cookie / 临时密钥 / 短期 PII 不进 ctx | 过滤失败 → 拒绝执行 |
关键不变量
- 无副作用:装配阶段是纯读 + 纯计算,失败可任意次数重试。
- scope 不可越界:跨 tenant / user 召回是一票否决错误,不允许"经验复用"。
- 预算先于内容:永远先算 token 预算,再决定召回深度,不靠"试着塞下"。
3.3 计划执行器 · 调度中心(Plan Orchestrator)
Role · 调度中心
拿到 ctx 后驱动整条决策路径:加载 Skill schema → 调 LLM 生成 Plan → 提交 Policy 评估 → 通过后调度 Tool 调用控制器 → 收口结果。
时序图位置
- 接收 ③ ctx 就绪 / ⑤ schema 返回 / ⑦ Plan 返回 / ⑯ 工具结果回包
- 发送 ④ 加载 Skill schema / ⑥ LLM Plan 推理 / ⑧ Policy 评估请求
- 终态时把结果交回 Run 管理器
输入输出
| 方向 | 对端 | 载荷要点 |
|---|---|---|
| 入 | 上下文装配器 | AssembledContext |
| 出 | 工具调用控制器 | Skill 加载请求(按权限过滤) |
| 入 | 工具调用控制器 | Skill schema 列表(含 manifest_hash) |
| 出 | LLM API | Plan 推理请求 · prompt + tool schemas |
| 入 | LLM API | Plan 返回 · 步骤序列 / DAG / 工具调用计划 |
| 出 | HITL 控制器 | Plan + CallerIdentity,请求 Policy 评估 |
| 入 | 工具调用控制器 | Tool 执行结果汇总 |
| 出 | Run 管理器 | Run 终态(成功 / 失败 / 拒绝) |
Plan 结构
| 字段 | 含义 |
|---|---|
| steps | 顺序步骤列表,每步包含目标、输入引用、输出键 |
| tool_calls | 每个步骤要调的 Skill / LLM 调用请求 |
| dependencies | 步骤间的有向依赖(DAG) |
| requires_authorization | 是否需要 HITL 审批(由 Policy 校验后置标记) |
| budget_estimate | 预估 token / 成本 |
关键不变量
- Plan 不带副作用:生成 Plan 是纯计算;失败 → 降级 flatten(DAG 退回顺序步骤),不重试整段。
- LLM 是 Tool 之一:Plan 阶段的 LLM 调用走外部 LLM API;执行阶段的 LLM 调用走工具调用控制器(与 Skill 一同收口)。
- 步骤间通过契约通信:上一步输出 → 下一步输入只能通过结构化键,不共享内部对象。
3.4 HITL 控制器 · 双守门员(Policy + HITL)
Role · 双守门员
同时承担自动 Policy 评估(已知规则)和人工 HITL 审批(高风险)。是唯一会跨出 Harness 边界请求 Caller 的内部组件。
时序图位置
- 接收 ⑧ Policy 评估请求(来自 计划执行器)
- 自环 ⑨ Policy 自动判断
- 条件发送 ⑩ HITL 请求(→ Caller,虚线)
- 条件接收 ⑪ 批准 / 拒绝(来自 Caller,虚线)
- 发送 ⑫ 通行(→ 工具调用控制器)
Policy 分层
风险三档(决策树)
四级审批层级
| 级别 | 触发 | 审批人 | 超时动作 |
|---|---|---|---|
| L1 · 自动通过 | 低风险只读 / 个人沙箱写 | 无 | — |
| L2 · 本人确认 | 项目空间写 / 中等成本 | 用户本人 | 超时 → REJECTED |
| L3 · 审批人审批 | 生产环境 / 不可逆 / 高成本 | 指定审批人 | 超时升级 L4 或 REJECTED |
| L4 · 双人复核 | 金额操作 / 权限变更 / 数据破坏 | 2 名审批人独立批准 | 任一拒绝即终态 REJECTED |
关键不变量
- AWAITING_HITL 沙箱不释放:等待审批期间 Run 状态保留、上下文不丢、关联沙箱不回收。批准回来直接续跑。
- Break-Glass 不绕审计:紧急通过只能缩短等待时长,但身份、权限、Policy 检查、Trace 写入不变。
- Policy 与 HITL 互补:已知规则归 Policy(毫秒级),边缘高风险归 HITL(分钟级)。不能用 HITL 替代 Policy 偷懒。
3.5 工具调用控制器 · 外部 IO 收口(Tool Controller)
Role · 外部 IO 收口
所有跨出 Harness 进程的执行都由它发起:Skill 调用 / Sandbox 执行 / LLM 推理(作为 Tool 之一)。是 LLM、Skill、沙箱三者的统一调度入口。
时序图位置
- 接收 ④ Skill schema 请求(来自 计划执行器)
- 发送 ⑤ schema 返回
- 接收 ⑫ 通行(来自 HITL 控制器)
- 发送 ⑬ Tool 执行(→ Skill / Sandbox)
- 发送 ⑭ LLM 推理(→ LLM API · LLM 作为 Tool)
- 接收 ⑮ Skill / LLM 结果
- 发送 ⑯ 整合结果(→ 计划执行器)
Skill 加载契约
| 步骤 | 动作 |
|---|---|
| 权限过滤 | 按 Caller 身份与 RBAC 过滤可见 Skill 列表 |
| 版本锁定 | 记录每个 Skill 的 manifest_hash,避免运行中遇到破坏性升级 |
| schema 转换 | 转成 LLM tool schema 格式(OpenAI 风格 / Anthropic 风格自适配) |
| Trace 标记 | 把 skill_id + version + manifest_hash 写入 Run 上下文,便于 Replay |
Tool 调用执行
| 调用类型 | 路径 | 边界 |
|---|---|---|
| 本地 Skill | 同进程函数调用 | 不进沙箱(LLM / DB / 外部 API / Memory) |
| 沙箱 Skill | Sandbox Provider → 容器 | 进沙箱(claude_code / shell_exec / 起服务 / 装依赖) |
| LLM 调用 | HTTPS → LLM API | 不进沙箱(HTTPS 出站) |
AgentInvoke 调用 | Tier 2 · Coordinator → child Run → ResultSink 回填 | 跨 Run 边界(不进沙箱);T3 视角下与一次"慢 Tool 调用"等价,Run 状态保持 EXECUTING,靠 budget.deadline 兜底超时 |
沙箱边界详见 执行拓扑 · 沙箱边界 ↗。AgentInvoke 协议详见 多 Agent 协作协议 ↗——内核只认它是第 4 类 Tool,不引入 Round / Delegation / Parent 等待态等概念。
失败处理
| 失败类型 | 处理 |
|---|---|
| 网络错误 / 5xx / 429 | 指数退避重试,配额内自动恢复 |
| 4xx · auth · billing · context_length | 立即失败,不重试(避免损失放大) |
| 主模型熔断 / Skill 熔断 | 切备模型 / fallback Skill;fallback 在 manifest 声明 |
| 沙箱崩溃 | 该 Run FAILED,不影响其他 Run(per-Run 隔离) |
关键不变量
- 所有外部 IO 必经此组件:Plan 阶段的 LLM 推理由计划执行器直接调,但 Tool 执行阶段(含 LLM as Tool)必须走这里——保证 ToolTrace 完整。
- manifest_hash 锁定:Run 启动时刻确定的 Skill 版本贯穿整个 Run,避免中途被动升级。
- 沙箱按 Skill 实例化:不是"每 Run 一沙箱",而是仅需要沙箱的 Skill 才申请沙箱(端口池稀缺资源)。
3.6 审计追踪器 · 留痕(Trace Recorder)
Role · 留痕
把 Run 的所有关键事件落盘成不可篡改的 Trace。"不经 Trace 的执行等于没有发生"——成功、被拒、失败三条路径都必须有完整 Trace。
时序图位置
- 接收 ⑰ 写 Trace 请求(来自 Run 管理器)
- 无主动外发;查询接口对外提供(合规审计 / Replay 调试)
TraceSchema · 字段表
| 字段族 | 关键字段 | 说明 |
|---|---|---|
| RunTrace(主记录) | run_id · session_id · tenant / user · 终态 · 状态转移序列 · 总成本 · trace_hash | 一次 Run 一条;状态机所有跳转都被记录 |
| StepTrace | run_id · step_id · 8 步阶段编号 · 输入 hash · 输出 hash · 耗时 · 状态 | 八步管道每一步一条;输入输出按 hash 引用避免数据冗余 |
| ToolTrace | run_id · skill_id · skill_version · manifest_hash · 入参 hash · 结果 hash · 重试次数 · error_code | 每次 Tool 调用一条;含版本与重试链 |
| PolicyTrace | rule_id · 决议 · 原因 · 用时 | 每条规则的判定结果都留痕 |
| HITLTrace | 审批级别 · 审批人 · 等待时长 · 超时动作 · 是否 Break-Glass | HITL 路径专用,对接合规审计 |
| CostTrace | token / API 调用 / 沙箱时长 · 单价 · 归属 scope | 支持按 User / Workspace / Skill 聚合成本看板 |
| ErrorTrace | error_code · stack_hash · 重试历史 | 错误归一化,支持告警与同类聚合 |
不可篡改写入
| 设计 | 含义 |
|---|---|
| 仅追加 | 所有 Trace 表只允许 INSERT,不允许 UPDATE / DELETE |
| hash 链 | 每条 Trace 引用前条 trace_hash,篡改一条要重算后续所有 hash |
| 异步批写 | Redis Stream 缓冲 + 后台批量落盘 MySQL,主流程不阻塞 |
| 写入降级 | 批写失败不阻塞 Run 终态,但触发告警;最终一致性保证 |
关键不变量
- 三条路径都必须有 Trace:SUCCEEDED / CLOSED_REJECTED / CLOSED_FAILED 都要在 RunTrace 中能查到完整链路。
- Replay 是可证明的:基于 Trace + 输入 hash 可在专用 sandbox 环境复现执行路径,差异即 bug 或不确定性来源。
- 写入失败不阻塞主流程:Run 终态优先送达用户;Trace 写入异常单独告警。
第四章:外部边界依赖
说明时序图最左 / 最右 3 条虚线 lifeline(Caller / LLM API / Skill·Sandbox)与 Harness 的边界契约——Harness 不拥有它们,但所有跨边界调用都有明确的入参 / 出参约束。
4.1 Caller / T2 Coordinator · 左边界
| 方向 | 调用 | 载荷 |
|---|---|---|
| Caller → Harness | ① create_run | user_input · session · caller_identity · chain_remaining · 协作群上下文(如有) |
| Caller → Harness | ⑪ HITL 批准 / 拒绝(条件) | approver_id · 决议 · 备注 · break_glass 标记 |
| Harness → Caller | ⑩ HITL 请求(条件) | action 描述 · 风险等级 · 等待截止时间 |
| Harness → Caller | ⑱ ResultSink | answer · artifacts · trace_id · 终态码 |
Harness 不假设 Caller 是谁——Web API、Celery worker、Coordinator、外部 SDK 都是合法 Caller。Caller 只需遵守上面 4 笔契约。
4.2 LLM API · 右边界
| 方向 | 调用 | 载荷 |
|---|---|---|
| Harness → LLM | ⑥ Plan 推理(计划执行器发起) | system + history + tool schemas · 模型 ID · 温度 / top_p · 流式开关 |
| LLM → Harness | ⑦ Plan 返回 | 步骤序列 / DAG / tool_calls · 完成原因 · token 用量 |
| Harness → LLM | ⑭ Tool 阶段 LLM 推理(工具调用控制器发起) | 同上,LLM 作为 Tool 之一调用 |
| LLM → Harness | ⑮ Tool 阶段响应 | 结构化输出 + 用量统计 |
LLM 是依赖不是层:通过 Provider 抽象支持 Claude / GPT / 其他模型;切换不影响 Run 结构。
4.3 Skill / Sandbox · 右边界
| 方向 | 调用 | 载荷 |
|---|---|---|
| Harness → Skill / Sandbox | ⑬ Tool 执行(工具调用控制器发起) | skill_id · version · manifest_hash · 入参(结构化)· 沙箱配置(按需) |
| Skill / Sandbox → Harness | ⑮ 执行结果 | 结构化结果 · stdout / stderr · 工件引用(OSS path)· 用量 |
Skill 类型与是否进沙箱的判定见 执行拓扑 · 沙箱边界 ↗。沙箱 Provider 通过抽象层支持 OpenClaw / K8s Pod / Hermes Agent,运行时切换。