05 Agent Runtime / Harness Engineering
学习目标:能设计 Agent Runtime,让模型在受控环境中观察、执行、验证、失败恢复、等待人工审批,并受 step/token/cost 预算约束。 重点度:必会(15 分)—— 全路线最高分之一,真正护城河 前置要求:Ch2-4 全部章节
概述#
Agent 不是 prompt,而是模型外的一整套受控运行环境。模型能力之外的脚手架,才是产品的核心竞争力。
很多团队以为"Agent = 大模型 + prompt + 几个工具调用"。但真正上线一个稳定 Agent 产品,模型只占 30%,剩下 70% 是 Runtime / Harness——决定 Agent 在以下场景表现:
- 跑长任务(几十步)不爆上下文、不跑偏
- 工具调用失败时能恢复,不死循环
- 高风险操作能等人工审批,不擅自动手
- 出 bug 能定位到具体哪一步、哪个工具、哪个调用
- 成本能控制住,不会一次任务烧几十万 token
Claude Code、OpenAI Codex、Cursor、WorkBuddy、OpenClaw 这些产品的差异化竞争力,绝大部分来自这一层,而不是底座模型。
Agent Loop#
基本结构#
所有 Agent Runtime 的核心都是一个 Loop:
while not task_done and not budget_exceeded:
1. Observe: 读取当前上下文(用户消息/工具结果/状态)
2. Think: 模型推理下一步该做什么
3. Act: 执行(调用工具/修改文件/发请求)
4. Verify: 校验结果是否符合预期
5. Reflect: 更新状态/记录/上下文与 ReAct 的区别#
ReAct 是 Reason+Act 的简单循环。Agent Runtime 在 ReAct 之上加了:
- Policy:什么能做、什么不能做(Context/Tool/Execution Policy)
- Permission:要不要人工审批
- Verification:结果对不对
- Failure Attribution:错了怪谁、怎么办
- Budget:步数/Token/成本上限
裸 ReAct 跑 demo 够用,跑生产就翻车——这是 Runtime 存在的意义。
Context Policy / Tool Policy / Execution Policy#
Context Policy(上下文策略)#
决定"什么进上下文、什么不进":
- 哪些历史步骤保留原文、哪些 summarize
- RAG 结果用完即丢还是保留
- 是否注入 workspace 文件内容
- 何时触发 Compaction(见 Ch3)
Tool Policy(工具策略)#
决定"哪些工具可用、什么顺序、什么参数范围":
tool_policy = {
"allowed_tools": ["search_web", "query_db", "send_email"],
"denied_tools": ["delete_user", "deploy_prod"], # 当前会话禁用
"tool_order": "model_decision", # 模型决策 vs 固定顺序
"max_consecutive_same_tool": 3, # 防止循环调用同一工具
"param_constraints": {
"query_db": {"sql_must_have_limit": True, "max_rows": 1000}
}
}Execution Policy(执行策略)#
决定"工具怎么执行":
- 同步 vs 异步
- 超时设置
- 重试策略(次数、退避)
- 沙箱执行 / 隔离环境
- 资源限制(CPU/内存/网络)
Permission Mode(权限模式)#
权限模式分级#
Claude Code 的设计已成为事实标准:
| Mode | 行为 | 适用 |
|---|---|---|
| bypassPermissions | 全自动,不需审批 | 沙箱、可信任务、低风险 |
| acceptEdits | 文件编辑自动通过,其他仍审批 | 编码 Agent 改代码 |
| default | 每个工具调用都问 | 高风险场景、初次跑 |
| plan | 只规划不执行,等用户批准计划 | 复杂任务前置规划 |
高风险操作必审批#
HIGH_RISK_TOOLS = [
"delete_file", "delete_user", "deploy_prod",
"send_email", "transfer_money", "execute_shell_rm"
]
def call_tool(tool_name, args):
if tool_name in HIGH_RISK_TOOLS:
approval = request_human_approval(
tool=tool_name, args=args,
reason="高风险操作, 需人工确认"
)
if not approval.approved:
return ToolResult(error="用户拒绝")
return execute(tool_name, args)审批 UX 要点#
- 可读:给用户看的是"即将删除 user_id=123",不是 raw tool call
- 可比较:显示历史同类操作,让用户判断是否异常
- 可拒绝:拒绝后让 Agent 知道为什么被拒,调整策略
- 可批量:低风险重复操作支持批量审批(如清理 100 个文件)
Hooks#
Hooks 是什么#
Hooks 是 PreToolUse / PostToolUse 等关键节点的拦截器——某些规则不能靠模型自觉,必须用代码强制。
PreToolUse hook:
→ 在工具执行前拦截
→ 可改参数、可阻止、可注入额外校验
PostToolUse hook:
→ 在工具执行后拦截
→ 可改结果、可记录、可触发后续动作实战例子#
# PreToolUse hook: 阻止删除生产数据库
@hook("PreToolUse", tool="execute_sql")
def prevent_prod_delete(args):
if args["db"] == "prod" and "DROP" in args["sql"].upper():
return HookResult(block=True, reason="禁止 DROP 生产库")
# PostToolUse hook: 工具结果自动脱敏
@hook("PostToolUse", tool="query_db")
def mask_pii(result):
result.text = mask_phone_numbers(result.text)
return result
# PreToolUse hook: 自动注入审计
@hook("PreToolUse")
def audit_log(args, tool_name):
audit_logger.log(who=current_user, tool=tool_name, args=args)Hook 自身的风险#
- 死循环:Hook 调用 Agent → Agent 调用 Hook → 死循环
- 性能:每个工具调用都过 Hook,慢
- 可调试性:Hook 静默拦截后,模型不知道为什么操作被阻止
→ Hook 要可观测、可禁用、有日志。
Sandbox(沙箱)#
为什么要 Sandbox#
Agent 能执行代码、写文件、调网络——一旦跑飞了能毁机器。Sandbox 把 Agent 关在笼子里:
| 沙箱类型 | 隔离级别 | 代表 |
|---|---|---|
| 进程级 | 低 | subprocess + seccomp |
| 容器级 | 中 | Docker / gVisor(用户态内核,强隔离容器) |
| VM 级 | 高 | Firecracker microVM / Kata / 真虚拟机 |
| WebAssembly | 中 | Wasm runtime |
Codex / Claude Code 的沙箱#
- Codex Cloud:每个任务一个云端容器,文件/网络/进程全隔离
- Claude Code:本地沙箱 + 权限模式组合
- OpenClaw:内置浏览器 sandbox + 设备权限分级
沙箱策略#
- 文件系统: 只能写 /workspace, 不能写 /etc /home
- 网络: 白名单域名, 禁止内网
- 进程: 不能 fork bomb, 不能 kill 其他进程
- 资源: CPU 5%, 内存 1G, 时间 10 分钟
- 凭证: 不挂载宿主机凭证Workspace / Worktree / Session Storage#
Workspace(工作区)#
Agent 的"工作台"——所有中间文件、交付物、状态都放这里:
/workspace/
├── task.md ← 当前任务说明
├── plan.md ← 任务计划
├── progress.md ← 进度记录
├── scratch/ ← 草稿、临时文件
├── output/ ← 最终交付物
└── .state/ ← 内部状态(checkpoint等)把状态放文件而不放上下文,是节省 token 的关键(见 Ch7)。
Worktree(Git 工作树)#
Coding Agent 的关键设计:
主仓库 (main branch)
├── worktree-agent-task-1/ ← Agent 在这改代码
└── worktree-agent-task-2/ ← 另一个 Agent 任务
→ Agent 改代码不影响主仓库
→ 多 Agent 并行不冲突
→ 改完 diff → review → mergeSession Storage#
每次会话的状态要能持久化、能恢复:
- 会话 ID / 用户 ID / 任务 ID
- 完整 messages 历史(或压缩后的)
- 当前 step / budget 状态
- 已生成文件列表
- 可恢复点 checkpoint
Verification Loop / Test Loop / Review Gate#
Verification Loop#
每次工具执行后,验证结果:
def execute_with_verify(tool_name, args):
result = call_tool(tool_name, args)
# 验证 1: 工具是否成功
if not result.success:
return handle_failure(result)
# 验证 2: 结果是否符合 schema
if not validate_schema(result):
return retry_with_hint("结果格式不对")
# 验证 3: 业务校验
if not business_check(result):
return handle_business_error(result)
return resultTest Loop(Coding Agent 关键)#
Agent 改代码
↓
跑测试
↓
测试失败?
↓ Yes
分析失败原因 → 改代码 → 再跑测试 (循环)
↓ No
进入 Review没有 Test Loop 的 Coding Agent 会"自信地写 bug"——模型不知道自己写得对不对,必须有客观测试来校准。
Review Gate#
Agent 完成 PR
↓
自动 Review (lint / security scan / dependency check)
↓
人工 Review (PR review)
↓
通过?
↓ Yes
merge
↓ No
Agent 修改 → 再 review (循环)Failure Attribution / Human Intervention#
失败归因#
Agent 失败时要能定位到具体哪一步错了:
失败 trace:
Step 7: 调用 search_web("Q3 财报")
└─ Tool 返回 timeout (network error)
└─ Agent 重试 1: 还是 timeout
└─ Agent 重试 2: 还是 timeout
└─ Agent 放弃, 任务失败
→ 失败归因: 网络问题, 不是模型问题, 不是工具实现问题
→ 处置: 重试任务, 加上网络重试策略失败分类#
| 类型 | 现象 | 处置 |
|---|---|---|
| 模型决策错 | 选错工具/填错参/方向跑偏 | 改 prompt / 加 router / eval |
| 工具实现错 | 工具 bug / 返回脏数据 | 改工具代码 |
| 临时故障 | 网络/限流/超时 | 重试 / 降级 |
| 资源耗尽 | 上下文爆 / 预算超 | Compaction / 提示用户 |
| 业务规则错 | 权限/约束不允许 | 告知用户、走审批 |
Human Intervention#
某些情况必须人工介入:
- Agent 卡住 N 步没进展
- 工具持续失败重试无效
- 任务需要人类判断(创意/合规/伦理)
- 预算即将耗尽需要决策
设计:Agent 不能"假装完成"——卡住要显式报告并请求人工,不能自欺欺人地输出"任务完成"。
★ Agent 经济学 / 预算治理(token = 钱)#
为什么预算是硬约束#
一次 autonomous 任务可能烧几十万 token:
长任务 50 步:
每步 input ~10K + output ~2K = 12K token
50 步 = 600K token
按 GPT-4o 价格:
input $2.5/M, output $10/M
600K * 0.8/0.2 split (480K input + 120K output) ≈ $2.4/任务
→ 一个用户一天跑 100 个任务 ≈ $240/天 ≈ $7.2K/月不设预算的 Agent 系统能把公司烧破产。
三种预算#
Step Budget(步数上限)#
MAX_STEPS = 50
for step in range(MAX_STEPS):
result = agent.step()
if result.task_done:
break
else:
notify_user("达到步数上限, 任务停止")作用:防止失控循环——Agent 反复尝试同一件事,无脑烧 token。
Token Budget#
TOKEN_BUDGET = 500_000
used = 0
while used < TOKEN_BUDGET:
response = call_llm(messages)
used += response.usage.total_tokens作用:直接限制 token 消耗,比 step budget 更精确。
Cost Budget#
COST_BUDGET_USD = 5.0
spent = 0
while spent < COST_BUDGET_USD:
response = call_llm(messages)
spent += response.usage.input_tokens * INPUT_PRICE
spent += response.usage.output_tokens * OUTPUT_PRICE作用:直接以美元为单位,跨模型可比。
预算治理策略#
分层预算#
单步预算: max 20K token / step (防单步爆炸)
任务预算: max 200K token / task (防任务失控)
用户预算: max 1M token / user / day (防滥用)
系统预算: max 100M token / month (成本控制)预算预警#
- 70% 预算 → 提醒 Agent "请优化策略, 减少冗余调用"
- 90% 预算 → 提示用户"预算将耗尽, 是否继续?"
- 100% 预算 → 强制停止
路由降级#
预算紧张时,自动降级:
预算充足: 用 GPT-5 (贵但强)
预算紧张: 切到 Claude Haiku (便宜快)
预算临界: 切到本地小模型 (零成本)产品映射#
Claude Code#
- hooks(PreToolUse/PostToolUse 拦截)
- skills(技能扩展)
- subagents(context 隔离)
- MCP(工具调用)
- permissions(4 种权限模式)
- sessions(会话持久化 + 恢复)
Codex(CLI / IDE / Cloud 三形态)#
- worktrees(Git 工作树隔离)
- cloud environments(云端容器)
- Skills(技能系统)
- Automations(自动化)
- parallel agents(并行任务)
OpenClaw#
- local runtime(本地运行)
- channel adapter(消息渠道适配)
- skills(技能市场 ClawHub)
- device permissions(设备权限分级)
- 内置浏览器(CDP 控制)
腾讯 WorkBuddy#
- multi-agent 办公工作流
- 并行 agent(多任务并发)
- 交付物生成(报告/表格/图像)
阶段产出#
完成本章后应该能:
- 设计 Agent Loop(Observe → Think → Act → Verify → Reflect)
- 实现 Context/Tool/Execution Policy
- 实现 4 级 Permission Mode(bypass/acceptEdits/default/plan)
- 写 PreToolUse/PostToolUse Hooks(含 Hook 风险防范)
- 设计 Sandbox 策略(文件/网络/进程/资源/凭证)
- 实现 Workspace + Worktree + Session Storage
- 实现 Verification Loop + Test Loop + Review Gate
- 实现 Failure Attribution(5 类失败分类)
- 设计 Human Intervention 机制(卡住自动请求人工)
- 实现 Step/Token/Cost 三层预算 + 预警 + 路由降级
下一章 Ch6 进入 Agent 工作流编排——复杂任务的拆解、分支、循环、人工确认和失败恢复。