Appearance
Agent、Workflow 与 Harness:控制权、执行权与系统边界
前置:无需前置;从这里建立最小 Harness 心智模型
核心问题:当模型可以提出下一步动作时,谁拥有真正的控制权和执行权,Workflow、Agent、Harness、Runtime 分别负责什么?
最小控制模型
核心边界:模型可以决定“建议做什么”,但不能自己把建议变成系统事实。
1. Workflow、Agent、Harness、Runtime 先分清
- Workflow:下一步主要由程序预先写好的控制流决定。适合顺序固定、规则明确的任务。
- Agent:模型根据当前 Observation 选择下一步动作。它扩大的是决策空间,不是执行权限。
- Harness:承载 Model、Tool、Context、Policy、State、Trace 等能力的软件系统。
- Runtime:Harness 中真正推进 Run 的执行内核。它验证动作、调用 Tool、提交状态,并产生下一轮可依赖的 Observation。
最重要的边界不是“有没有 LLM”,而是:谁决定下一步,谁有权把结果提交为事实。
2. 最小运行闭环
text
User Request
↓
Harness 组装 Context
↓
Model 提出 Action
↓
Runtime validate
↓
Runtime execute Tool
↓
Tool Result
↓
Runtime normalize / commit
↓
Observation
↓
Model 决定下一步或结束在这条链路里:
Action是模型提出的意图;Tool Result是外部执行返回的原始结果;Observation是 Runtime 整理后可用于下一步决策的新事实;- “Tool 成功”“Run 完成”“允许执行”都属于 Runtime 事实,不能只由模型文本决定。
3. Run / Turn / Action / Observation
先只掌握四个词:
- Run:一次完整任务执行。
- Turn:模型得到 Context 并做一次决策的回合。
- Action:这一回合模型提出的下一步,例如调用 Tool 或给出 Final Answer。
- Observation:执行后进入下一回合的新事实。
一个 Run 可以有多个 Turn;一个 Turn 可以产生一个或多个 Action;Action 只有经过 Runtime 校验和执行,才可能产生 Observation。
4. Model proposes → Runtime commits
假设模型返回:
json
{"tool": "apply_patch", "path": "auth.py", "patch": "..."}这并不表示文件已经修改。Runtime 至少还要判断:
apply_patch是否在当前 Tool Registry;- 参数是否满足 schema;
- 当前 policy 是否允许修改该文件;
- Tool 实际执行结果是什么;
- 哪些结果可以提交为新的 Run state / Observation。
因此可以固定一句话:
Model 可以提出动作;Runtime 才拥有验证、执行与提交系统事实的权力。
5. 一个完整最小案例
用户要求“找出认证失败原因并给出修复建议”。
text
Turn 1
Model → search_code("token")
Runtime → 校验并执行
Observation → 找到 auth/token.py
Turn 2
Model → read_file("auth/token.py")
Runtime → 执行
Observation → token expiry 比较使用了错误时区
Turn 3
Model → Final Answer:指出原因与修复方式
Runtime → 结束 Run这个例子已经具备 Agent 的最小特征:下一步不是程序提前写死,而是模型根据 Observation 决定;但搜索和读文件仍然由 Runtime 真正执行。
Advanced / Canonical 深入内容
1. 为什么第一章先讲“谁拥有控制权”
学习 Agent 最容易走偏的地方,是先记框架名、Prompt 技巧或 Tool Calling API,却没有先建立一个最基本的系统模型:
模型可以决定什么?程序必须保证什么?真正的副作用由谁执行?系统如何知道一件事“真的发生了”?
如果这几个问题没有分清,后面学习 Context、Tool、Planning、State、Checkpoint、HITL、Sandbox、Tracing 时会反复混乱。
例如用户要求:
“分析这个代码仓库,修复登录失败的问题,并运行测试确认修复有效。”
模型可能提出:
- 搜索登录相关代码;
- 阅读
auth.py; - 修改 token 校验逻辑;
- 运行测试;
- 根据测试结果决定是否继续修改。
这里确实存在“模型自主选择下一步”的成分。
但下面这些事情不能只因为模型说了就成立:
- 文件是否真的被修改;
- 当前 Agent 是否有权限修改这个目录;
pytest是否真的执行成功;- Tool 是否超时;
- 修改是否已经持久化;
- 任务是否满足结束条件;
- 失败后是否应该重试;
- 一个有副作用的动作是否已经执行过。
这些都是程序需要验证和记录的系统事实(system facts)。
2. Workflow、Agent、Harness 与 Runtime 的执行链
可以把执行链表示为:
text
┌────────────────────────────┐
│ Harness │
│ │
User Goal ─────────▶│ Context / Tools / Policy │
│ State / Trace / Memory │
│ Approval / Sandbox ... │
└────────────┬───────────────┘
│
▼
┌─────────────────┐
│ Model │
│ propose next │
│ decision │
└────────┬────────┘
│
Action Intent
│
▼
┌─────────────────┐
│ Runtime │
│ validate │
│ authorize │
│ execute │
│ persist │
│ schedule │
└────────┬────────┘
│
Result
│
▼
Observation
│
└──────────▶ next Turn这张图里最重要的不是组件名称,而是两条边界:
2.1 模型拥有的是“决策建议权”
模型可以根据当前 Context 判断:
- 下一步需要搜索什么;
- 选择哪个 Tool;
- 是否还缺信息;
- 如何拆解开放任务;
- 当前结果是否值得继续探索。
但模型输出本身只是一个候选决策。
2.2 Runtime 拥有的是“执行事实的提交权”
真正改变系统状态的动作,需要经过程序:
text
Model proposes
↓
Runtime validates
↓
Policy authorizes
↓
Executor performs
↓
State/Trace records因此:
“模型认为已经完成”与“系统证明已经完成”不是一回事。
这是 Agent Engineering 最重要的边界之一。
3. Workflow:控制路径主要由程序预先决定
Anthropic 在 Building effective agents 中给出的一个实用区分是:
- Workflow:LLM 和 Tool 沿预定义的代码路径进行编排;
- Agent:模型会根据当前情况动态决定自己的过程和 Tool 使用方式。
这里的关键不是“有没有 LLM”。
一个 Workflow 完全可以包含多个 LLM 调用:
text
User Input
↓
Classify with LLM
↓
Retrieve
↓
Generate with LLM
↓
Validate
↓
Return虽然用了两个模型调用,但:
text
Classify → Retrieve → Generate → Validate这个顺序由代码确定,所以它仍然是 Workflow。
3.1 Workflow 擅长什么
Workflow 适合:
- 路径明确;
- 业务规则稳定;
- 错误成本较高;
- 每个阶段可以定义清晰输入输出;
- 希望行为高度可预测。
例如:
text
订单创建
→ 库存校验
→ 支付
→ 写账
→ 发货事件即使其中某一步使用 LLM,也不应该让模型自由重排支付和写账顺序。
3.2 Workflow 的工程优势
Workflow 的最大优势是控制流显式。
程序通常可以回答:
text
当前在哪一步?
下一步是什么?
为什么执行这一步?
失败应该跳到哪里?
哪些步骤已经完成?因此它:
- 更容易单元测试;
- 更容易做状态机;
- 更容易估算成本与 latency;
- 更容易保证 transaction / idempotency;
- 更容易审计。
3.3 Workflow 的限制
当任务本身缺少固定路径时,Workflow 会迅速膨胀。
例如:
“调查为什么这个大型代码仓库最近性能下降,并给出修复方案。”
你很难提前写出完整的:
text
if A → B
if C → D
if E → ...因为真正需要检查什么,往往取决于前一步发现的 Observation。
这就是 Agentic Control 开始有价值的地方。
4. Agent:模型根据 Observation 动态选择下一步
Agent 的关键不是“多轮对话”,也不是“能调用 Tool”。
更有用的工程定义是:
在任务的一部分控制流上,允许模型依据当前 Context 和 Observation 动态决定下一 Action。
例如修复代码:
text
Turn 1
Model:
"先搜索 login/token 相关实现"
↓
search_code
↓
Observation:
"auth/service.py 中存在 verify_token"
Turn 2
Model:
"读取 verify_token 以及它的调用方"
↓
read_file
↓
Observation:
"发现 expiry 单位转换错误"
Turn 3
Model:
"修改代码并运行相关测试"
↓
apply_patch + pytest这里的:
text
search → read → patch → test不是程序在任务开始时写死的,而是模型根据新事实逐步决定的。
这就是 Agentic Control。
4.1 Agent 不是“模型直接操作世界”
即使模型决定:
json
{
"tool": "apply_patch",
"arguments": {
"file": "auth.py",
"patch": "..."
}
}它也只是输出了一份结构化 Action Intent。
真正执行前仍需要:
text
Tool 是否存在?
参数是否合法?
路径是否允许访问?
当前 workspace 是否可信?
该动作是否需要人工审批?
是否超出 deadline?然后才真正调用执行环境。
所以 Agent 的“自主性”不应该被理解成:
模型绕过程序直接拥有系统权限。
更合理的理解是:
模型在程序允许的 capability boundary 中拥有一定的决策自由度。
5. Harness:让模型能力变成一个可运行的软件系统
Harness 并不是所有框架都使用完全相同定义的标准术语。
本文采用一个面向工程的定义:
Harness 是包围模型的整套运行环境与工程机制,它负责把模型的推理能力连接到 Context、Tools、State、Policy、Execution、Security 和 Observability。
Anthropic 2026 年介绍 Managed Agents 时,将 session、harness 和 sandbox 作为可以独立演化的接口,其中 harness 负责循环调用模型并把 Tool Call 路由到相关基础设施。这说明 Harness 更接近“模型周围的执行系统”,而不是某个 Prompt 文件。
一个成熟 Harness 可能包含:
text
Harness
├── Model Layer
│ ├── provider adapter
│ ├── structured output
│ └── streaming
│
├── Context
│ ├── instructions
│ ├── history
│ ├── retrieval
│ ├── memory
│ └── compaction
│
├── Tool System
│ ├── registry
│ ├── schema
│ ├── permission
│ └── result policy
│
├── Runtime
│ ├── run loop
│ ├── state machine
│ ├── scheduler
│ ├── retry
│ └── resume
│
├── State
│ ├── run state
│ ├── checkpoint
│ └── durable state
│
├── Security
│ ├── approval
│ ├── capability
│ ├── sandbox
│ └── credentials
│
└── Observability
├── logs
├── traces
├── metrics
└── eval5.1 Harness 的价值不是“让模型更聪明”
Harness 的目标更接近:
把概率性的模型能力包装成可测试、可恢复、可观测、受权限约束的软件能力。
同一个模型,在不同 Harness 下可能表现差异很大,因为 Harness 决定了:
- 模型能看到什么;
- 有哪些 Tool;
- Tool 如何设计;
- 失败以后怎么反馈;
- Context 怎么维护;
- 是否可以恢复长任务;
- 是否能验证工作结果。
5.2 Harness 不是越复杂越好
Anthropic 的 Agent 工程经验反复强调:先使用简单、可组合的 pattern,再根据任务需要增加复杂度。
如果单次模型调用 + Retrieval 已经能稳定完成任务,就没有必要增加:
text
Planner
→ Manager
→ Critic
→ Reviewer
→ Multi-Agent复杂度本身会产生:
- 更多 token;
- 更多 latency;
- 更多状态;
- 更多故障点;
- 更困难的 debugging。
6. Runtime:真正推进 Run 的执行内核
Runtime 同样不是所有 Agent 框架中的统一标准名词。
在 Shadow Harness 中,我们用它表示:
负责推进一次 Run 生命周期,并对状态迁移、Tool Execution、终止、错误传播、暂停和恢复拥有程序控制权的执行内核。
可以把 Harness 和 Runtime 的关系理解成:
text
Harness = 整个工程外壳
Runtime = 其中负责“跑起来”的执行核心6.1 Runtime 典型职责
Runtime 通常负责:
- 开始一个 Run;
- 构建或请求 Context;
- 调用 Model;
- 解释结构化输出;
- 找到对应 Tool;
- 参数校验;
- 权限/审批检查;
- 调度 Tool;
- timeout / cancellation;
- 写入 Observation;
- 更新 RunState;
- 判断是否继续下一 Turn;
- 判断是否成功、失败或等待;
- checkpoint;
- resume。
OpenAI Agents SDK 当前的 Runner 就体现了这种 runtime-loop 思路:Runner 调用模型,如果模型产生 Tool Calls 就执行 Tool 并把结果加入下一轮;如果产生 handoff 则切换 Agent;产生最终输出则结束;同时可以由 max_turns 等配置限制循环。它是一个具体实现,不代表行业唯一 Runtime 设计,但很适合作为参考。
6.2 为什么模型不能直接成为 Runtime
因为 Runtime 管的是程序事实。
例如模型输出:
“刚才已经成功删除文件。”
Runtime 不能因此更新:
text
file_deleted = true正确流程应当是:
text
Model proposes delete_file
↓
Runtime validates permission
↓
Tool executes
↓
OS/filesystem returns result
↓
Runtime records observation
↓
State becomes DELETE_SUCCEEDEDState 的来源必须是可验证执行结果,而不是模型自述。
7. Agent Loop:Turn、Action、Observation 到底是什么
理解 Agent Loop,需要把“模型思考”和“系统执行”拆开。
一个最小循环可以表示为:
text
┌─────────────────────────────┐
│ │
▼ │
Build Context │
↓ │
Model Inference │
↓ │
Decision │
↓ │
Final? ─── yes ──▶ Finish │
│ no │
▼ │
Action Intent │
↓ │
Runtime Validation │
↓ │
Execute Tool │
↓ │
Observation │
↓ │
Update State / Context ─────────────────┘7.1 Turn
本文把 Turn 定义为:
一次模型获得当前输入并产生输出,然后 Runtime 处理该输出所形成的一个推理周期。
Turn 的精确定义可能因框架不同而略有差异,所以内部实现必须自己固定术语。
至少应该能够记录:
python
TurnRecord(
turn_id=7,
model_request=...,
model_response=...,
started_at=...,
ended_at=...,
)7.2 Action
Action 是模型提出的下一步意图,例如:
text
调用 search_code
调用 shell
请求 handoff
读取 artifact
返回 final outputAction 不是已经执行的事实。
例如:
python
ToolCall(
name="delete_file",
arguments={"path": "..."},
)只能证明:
模型请求调用这个 Tool。
不能证明:
文件已经删除。
7.3 Observation
Observation 是执行层返回给 Agent 的新事实。
例如:
text
exit_code = 1
stderr = "3 tests failed"或者:
text
patch applied successfullyObservation 应经过 Runtime 的 normalize,再进入:
- State;
- Trace;
- 下一轮 Context。
7.4 Loop 为什么必须由程序限制
一个纯粹的:
python
while True:
response = await model(...)不是合格 Runtime。
至少需要考虑:
text
max_turns
deadline
token budget
cost budget
cancellation
approval
tool timeout
retry budget否则模型完全可能形成:
text
search
→ search
→ search
→ search
→ ...系统却没有可靠停止机制。
8. Deterministic Control 与 Agentic Control 如何分工
生产系统通常不是“Workflow 或 Agent 二选一”,而是 Hybrid。
最重要的问题是:
哪些决策值得交给模型,哪些规则必须由程序保证?
8.1 适合 Deterministic Control 的事情
这些事情通常能够形式化,并且错误成本较高:
- authentication / authorization;
- schema validation;
- transaction;
- state transition;
- DAG dependency;
- retry budget;
- timeout;
- concurrency limit;
- idempotency;
- audit;
- approval requirement;
- resource quota。
例如:
text
如果 Tool risk_level = HIGH
→ 必须进入 WAITING_APPROVAL这是规则,不需要让模型判断。
8.2 适合 Agentic Control 的事情
模型更擅长处理:
- 开放任务拆解;
- Tool 选择;
- 搜索策略;
- 判断当前证据是否足够;
- 根据 Observation 改变探索方向;
- 处理自然语言意图;
- 在多个合理方案之间做语义选择。
例如:
“为了定位性能下降,我应该先检查数据库慢查询,还是先分析应用 Trace?”
这种问题存在大量上下文判断,适合 Agentic Decision。
8.3 一个实用判断方法
可以连续问三个问题:
问题 A:这个规则能否用确定性代码准确描述?
如果可以,优先代码。
问题 B:错误执行是否会产生高风险副作用?
风险越高,越应该把最终权限保留在程序/Human Approval。
问题 C:下一步是否依赖开放世界中的新 Observation?
如果是,模型动态决策的价值更高。
9. 如何选择 Workflow、Agent 或 Hybrid
9.1 选择 Workflow
适合:
text
路径稳定
+ 强一致性
+ 可枚举异常
+ 需要高可预测性例如:
- 文档固定审批流程;
- 财务对账 pipeline;
- 明确 ETL;
- 结构化报告生成流水线。
9.2 选择 Agent
适合:
text
开放任务
+ 路径未知
+ 信息需要逐步发现
+ 多种工具组合方式都可能成立例如:
- 深度研究;
- 大型代码仓问题定位;
- 开放式数据分析;
- 多来源调查。
9.3 生产中更常见的是 Hybrid
例如 Coding Agent:
text
Agentic:
决定搜索哪些文件
决定下一步调查方向
决定修改策略
Deterministic:
路径权限
patch apply
shell timeout
test exit code
state transition
checkpoint
approval因此可以把 Hybrid 理解为:
模型负责探索空间,Runtime 负责守住系统不变量。
9.4 不要为了“更 Agent”而扩大模型控制权
一个系统不是模型自由度越高越先进。
合理目标是:
在能够获得任务收益的地方使用 Agentic Control,在必须稳定的地方保留 Deterministic Control。
10. 一个真实执行案例:修复代码并验证
把前面的概念放进一条完整执行链。
用户:
“修复仓库中登录 token 过期判断错误,并确保测试通过。”
Step 1:Harness 准备运行环境
Harness 提供:
text
instructions
workspace
read/search/patch/shell tools
permission policy
context manager
traceStep 2:Runtime 创建 Run
python
run = RunState(
status="RUNNING",
turn=0,
)这是程序事实。
Step 3:模型做 Agentic Decision
模型根据目录和用户目标输出:
json
{
"action": "search_code",
"query": "token expiry verify"
}Step 4:Runtime 校验并执行
Runtime 检查:
text
search_code 是否注册?
arguments 是否符合 schema?
workspace 是否允许?然后执行 Tool。
Step 5:产生 Observation
json
{
"matches": [
"auth/service.py:82"
]
}Runtime 将 Observation 写入 Trace,并让 ContextManager 在下一 Turn 使用。
Step 6:模型继续动态决策
模型读取文件,发现:
text
milliseconds / seconds conversion error于是提出:
text
apply_patchStep 7:Runtime 守住副作用边界
如果 apply_patch 需要 workspace write permission:
text
Policy → allow / deny / require approval真正修改成功以后,Runtime 才记录:
text
PATCH_SUCCEEDEDStep 8:模型请求测试
text
shell: pytest tests/test_auth.pyRuntime 执行,获得:
text
exit_code = 0Step 9:Runtime 判断系统事实
模型可以说:
“测试已经通过。”
但最终可验证事实来自:
text
ToolExecutionResult.exit_code == 0Step 10:结束 Run
模型输出 final response。
Runtime 检查:
- 是否存在未完成 Tool;
- 是否满足终止规则;
- 是否写入最终 State / Trace。
然后:
text
RUNNING → SUCCEEDED这条执行链就是:
text
Agentic Decision
+
Deterministic Runtime
+
Harness Capabilities组合后的真实 Agent 系统。
11. 最小 Shadow Harness 应该怎么实现
第一阶段不要做复杂 Multi-Agent。
建议最小目录:
text
shadow_harness/
├── model/
│ └── client.py
├── context/
│ └── manager.py
├── tools/
│ ├── registry.py
│ └── executor.py
├── runtime/
│ └── runner.py
├── state/
│ └── models.py
├── trace/
│ └── recorder.py
└── tests/11.1 AgentDefinition:声明“这个 Agent 是什么”
python
from dataclasses import dataclass
@dataclass(frozen=True)
class AgentDefinition:
name: str
instructions: str
tool_names: tuple[str, ...]它不应该保存:
text
current_turn
pending_tool_call
retry_count
checkpoint_id因为这些是运行事实。
11.2 RunState:保存“这次运行现在是什么状态”
python
from dataclasses import dataclass, field
@dataclass
class RunState:
run_id: str
status: str = "RUNNING"
turn: int = 0
observations: list = field(default_factory=list)11.3 Runtime:拥有循环
python
async def run_agent(agent, state, model, tools, limits):
while state.status == "RUNNING":
if state.turn >= limits.max_turns:
state.status = "FAILED"
raise RuntimeError("max turns exceeded")
request = build_context(agent, state)
response = await model.generate(request)
if response.final_output is not None:
state.status = "SUCCEEDED"
return response.final_output
for call in response.tool_calls:
tool = tools.require(call.name)
args = tool.validate(call.arguments)
result = await tool.execute(args)
state.observations.append(
normalize_tool_result(call, result)
)
state.turn += 1这个例子故意保持简单。
它还没有解决:
- 并行 Tool Calls;
- timeout;
- retry;
- approval;
- durable checkpoint;
- cancellation;
- context compaction;
- trace spans。
这些正是后续章节要逐步加入的能力。
12. Failure Modes:这一层最常见的架构错误
12.1 把“会调用 Tool”直接等同于 Agent
错误表现
系统只有:
text
User → LLM → one tool → response却默认它已经拥有完整 Agent Runtime。
问题
Tool Calling 只提供一种模型→程序的结构化交互机制,并没有自动提供:
- loop;
- state;
- retry;
- timeout;
- approval;
- resume;
- termination。
修复
明确区分:
text
Tool Calling capability
≠
Agent control loop
≠
Harness验证
用 Scripted Model 只返回一次 Tool Call,断言系统不会因为“支持 Tool Calling”就自动拥有 loop、checkpoint、approval 或 resume;这些能力必须由 Runtime/Harness 的独立 contract test 证明。
12.2 让模型决定程序事实
错误表现
模型输出:
“任务完成。”
程序立即:
python
run.status = "SUCCEEDED"问题
模型可能:
- 误判;
- hallucinate;
- 忘记某个未完成步骤;
- 忽略 Tool Failure。
修复
成功条件应尽量来自:
text
validated output
+ execution result
+ deterministic completion rule验证
让 Fake Tool 返回失败,同时让下一轮 Fake Model 声称“任务已完成”;断言 Runtime 不得进入 SUCCEEDED,并以 ToolExecutionResult 与确定性验收条件作为事实来源。
12.3 把权限、重试和状态迁移写进 Prompt
错误表现
Prompt:
“如果失败最多重试三次;没有权限时不要执行;成功以后把状态变成完成。”
问题
这些规则本应由程序强制保证。
模型理解并遵守 Prompt 不是 security/reliability guarantee。
修复
分别落到:
text
RetryPolicy
PermissionPolicy
StateMachine验证
构造 Prompt 要求越权执行或无限重试,同时配置 PermissionPolicy 拒绝、RetryBudget=2;断言 Executor 不被越权调用,attempt 数也不能突破程序策略。
12.4 Agent Loop 没有资源边界
错误表现
python
while not model_says_done:
...后果
可能产生:
- runaway loop;
- token 无限增长;
- API 成本失控;
- Tool 重复调用;
- 长时间占用资源。
修复
至少配置:
text
max_turns
deadline
token/cost budget
tool timeout
cancellation验证
让 Scripted Model 永远返回“继续搜索”,分别触发 max_turns、deadline 与 cancellation,断言 Run 在边界内停止,child Tool/Task 不继续泄漏执行。
12.5 一开始就构建复杂 Multi-Agent
错误表现
任务只有一个清晰工作流,却加入:
text
manager
planner
researcher
critic
reviewer问题
你无法判断性能提升来自哪里,却必然增加:
- coordination cost;
- context duplication;
- token;
- latency;
- failure surface。
修复
先建立:
text
single-call baseline
→ workflow baseline
→ single-agent baseline有明确 Eval 证据后再增加复杂度。
验证
在同一版本化 Eval Set 上比较 Single Agent baseline 与 Multi-Agent 方案,记录成功率、token、latency 和 coordination failure;没有可重复净收益就不保留额外拓扑。
12.6 Harness 与具体模型能力耦合过深
错误表现。 Harness 写死大量“因为当前模型做不到 X,所以必须 Y”的 workaround,例如固定分步、过频 summary、重复 critic 或僵化的 Tool 顺序。
为什么危险。 这些规则通常不是业务不变量,而是对某一代模型能力的临时假设。模型升级后,它们可能从帮助变成 dead weight:继续增加 token 和 latency,甚至限制模型已经具备的新能力。Anthropic 2026 年 Managed Agents 的经验明确提醒,Harness 会编码对模型能力的假设,因此需要持续重新验证。
修复
把 workaround 做成:
text
replaceable policy
+ capability detection
+ versioned eval不要让临时模型缺陷变成永久架构。
验证
每次模型或 provider 发生显著升级时,在同一版本化 Eval Set 上对 workaround 做 ON/OFF ablation,同时比较成功率、token、latency 和 failure rate。只有仍有稳定净收益的策略才继续保留。
13. Runtime Contract 的确定性测试
这一层主要验证 Runtime contract,而不是语义质量;大量边界可以用 deterministic testing 直接证明。
13.1 测试一:模型不能绕过 Tool Registry
Scripted Model 输出:
json
{
"tool": "delete_everything"
}如果 Registry 没有该 Tool:
text
Runtime 必须拒绝而不是“模型已经要求了,所以执行”。
13.2 测试二:达到 max_turns 必须终止
让 Fake Model 永远返回:
text
search again断言:
text
turn == max_turns
→ Run stops13.3 测试三:Tool Failure 不能变成成功事实
Fake Tool:
text
exit_code = 1即使模型下一轮说:
“执行成功。”
Runtime 也不应该把 ToolExecution 标记为成功。
13.4 测试四:Workflow 与 Agentic 节点边界明确
给模型不同的 Scripted Decision:
text
A → search_code
B → read_file验证只有声明为 Agentic 的节点控制流发生变化。
确定性的状态迁移、权限和 retry policy 不随模型输出改变。
13.5 真正需要 Eval 的部分
例如:
模型选择的 Tool 是否合理?
这是语义质量问题,可以建立 Eval Set:
text
task
expected useful tools
actual tool trajectory
task success
token
latency因此要区分:
text
Testing → 程序契约是否正确
Evaluation → Agent 决策质量是否足够好14. 常见问题(Q&A)
Q1:只要 LLM 能调用 Tool,就算 Agent 吗?
不一定。
Tool Calling 只是模型输出结构化 Action 的一种能力。
如果 Tool 调用路径仍然完全由程序预定义,例如:
text
永远先 search
→ 再 summarize
→ 再 return那它更接近带 Tool 的 Workflow。
Agent 的关键在于:模型是否拥有一部分动态控制流决策权。
Q2:Agent 和 Workflow 最核心的区别是什么?
看的是:下一步主要由谁决定,以及这种决定是否会随着新的 Observation 动态变化。
Workflow 的主要控制路径由代码预先定义。即使某一步内部调用 LLM,只要程序仍然决定 A → B → C 的顺序,它仍然属于 Workflow。
Agent 则把一部分控制流交给模型:模型读取当前 Context 和 Observation 后,可以选择不同 Tool、改变调查方向、决定是否继续探索。真实生产系统通常采用 Hybrid——开放语义决策交给模型,权限、状态机、retry、deadline 等系统不变量仍由确定性程序控制。
Q3:Harness 和 Runtime 是同一个东西吗?
这里区分为:
text
Harness = 模型周围完整工程外壳
Runtime = Harness 中负责推进 Run 的执行核心Runtime 负责 loop、Tool Execution、状态迁移、终止、恢复等。
Harness 还包含:
- Context;
- Model Adapter;
- Memory;
- Security;
- Observability;
- Sandbox 等。
需要注意,这两个术语在不同团队/框架里可能边界不同,所以项目内部必须先固定自己的 Domain Language。
Q4:为什么模型不能直接决定“任务成功”?
因为模型输出是概率性的语义判断,而任务成功通常依赖可以被外部验证的系统事实。
例如模型可能根据一段截断日志说“测试通过了”,但真正可以证明测试进程成功结束的是 exit_code = 0;模型可能说“文件已经修改”,真正的证据则是 filesystem result、diff 或 artifact record。
因此 Runtime 应把 completion criteria 尽量落到可验证条件上,再让模型负责那些无法完全形式化的语义判断。一个可靠的边界是:
text
Model judges meaning
Runtime verifies facts只有两者都满足,系统才应该把 Run 推进到 SUCCEEDED。
Q5:是不是应该把所有控制权都放到代码里?
也不是。
如果一切都由代码预定义,就失去了 Agent 对开放任务的适应能力。
合理边界是:
代码保证系统不变量
例如:
- 权限;
- transaction;
- timeout;
- state transition;
- retry limit。
模型处理开放语义决策
例如:
- 搜索什么;
- 使用哪个 Tool;
- 信息是否足够;
- 下一步调查方向。
Q6:什么时候没必要使用 Agent?
当:
- 路径明确;
- 输入输出结构稳定;
- 错误成本较高;
- Workflow 已经能高质量解决;
就没有必要为了“Agent 化”增加动态循环。
先使用最简单能满足目标的架构,通常更容易维护。
Q7:为什么说 Harness 会随着模型升级而变化?
Harness 经常承担“补偿当前模型不足”的职责。
例如某代模型可能需要:
- 更强的显式分步;
- 特定 context reset;
- 更多 Tool 描述;
- 特定纠错 Prompt。
模型升级后,这些策略可能不再必要。
因此 Harness 应尽量:
text
稳定接口
+
可替换 Policy
+
版本化 Eval而不是把临时 workaround 写死成永久架构。
Q8:Shadow Harness 第一阶段为什么不直接做 Multi-Agent?
因为首先要证明单 Agent Runtime 的基本机制正确:
- Context;
- Tool;
- Loop;
- State;
- timeout;
- retry;
- Trace;
- Test。
如果这些底层机制都没有稳定,多 Agent 只会把同样的问题复制多份。
正确顺序是:
text
single agent
→ reliable runtime
→ measurable baseline
→ only then multi-agent when justified15. 执行边界总图
text
┌──────── Harness ────────┐
│ │
User Goal ─────────▶│ Context / Capability │
│ Policy / State / Trace │
│ │
│ ┌───────────┐ │
│ │ Model │ │
│ └─────┬─────┘ │
│ │ │
│ Action Intent │
│ │ │
│ ┌─────▼─────┐ │
│ │ Runtime │ │
│ │ validate │ │
│ │ execute │ │
│ │ persist │ │
│ └─────┬─────┘ │
│ │ │
│ Observation │
│ │ │
└────────────┼────────────┘
│
next Turn这张图对应六个稳定边界:
- Workflow:程序预定义主要控制路径;
- Agent:模型拥有部分动态控制流决策权;
- Harness:把模型、上下文、能力、状态、安全和可观测性组成完整运行环境;
- Runtime:真正执行并提交系统事实的内核;
- Agent Loop:模型决策与真实 Observation 之间的反馈循环;
- Hybrid Design:模型负责开放决策,程序保证系统不变量。
Agent 可以决定下一步“想做什么”;Runtime 决定这件事“能不能做、怎么做,以及做完之后哪些事实真正成立”。
16. Sources
Anthropic — Building effective agents
https://www.anthropic.com/engineering/building-effective-agentsAnthropic — Scaling Managed Agents: Decoupling the brain from the hands
https://www.anthropic.com/engineering/managed-agentsAnthropic — Building agents with the Claude Agent SDK
https://www.anthropic.com/engineering/building-agents-with-the-claude-agent-sdkOpenAI Agents SDK — Agents
https://openai.github.io/openai-agents-python/agents/OpenAI Agents SDK — Running agents
https://openai.github.io/openai-agents-python/running_agents/OpenAI Agents SDK — Testing
https://openai.github.io/openai-agents-python/testing/
可选源码深挖:Codex Turn Lifecycle / Granularity
Codex 源码对照:Turn Lifecycle / Granularity
要回答的问题
Codex 里的一个 Turn 到底对应多大的 Runtime 生命周期?为什么不能把 Codex Turn 直接等同于本知识库 canonical Turn?
精确定位
Tag: rust-v0.150.1
File:
Symbols:
run_turn(...)run_pre_sampling_compact(...)run_sampling_request(...)SamplingRequestResult
最小调用链
text
run_turn(input)
→ run_pre_sampling_compact(...)
→ build / record context
→ loop
→ clone_history().for_prompt(...)
→ run_sampling_request(...)
→ model ResponseItem(s)
→ tool execution when requested
→ tool result enters history
→ post-sampling token status
→ optional mid-turn compact
→ continue when follow-up is required
→ final assistant message / stop condition
→ return last_agent_messageSource Fact
run_turn() 的源码注释明确写出:每次 sampling 后,模型可能返回 function call 或 assistant message;如果返回 function call,Runtime 执行工具并把结果放进 下一次 sampling request;当只得到 assistant message 且没有 follow-up 时,Turn 才结束。
因此 Codex 的一个 run_turn() 可以包含多次:
text
model sampling
→ tool execution
→ tool result
→ next sampling这与本知识库 canonical Turn 的粒度不同。本知识库 canonical Turn 是一次逻辑上的:
text
Model Decision
→ Action / Observation一个 Codex Turn 因而可能覆盖多个 canonical logical Turn;同时也不能用 sampling count 强行做 1:1 映射,因为一次 model response 可以包含多个 output item。
Design Inference
不按类型名直接建立概念映射,应比较 lifecycle、ownership 和 state transition。
对 Harness 来说,更稳定的 invariant 是:一次 user-facing operation 内部可以包含多个 model decision / tool continuation cycle;Runtime 必须拥有循环推进、终止条件、tool result 回灌和 budget 检查,而不是让模型文本决定“Turn 是否已经完成”。
Codex-specific Trade-off
Codex rust-v0.150.1 把这段多-sampling 生命周期命名为 Turn,并在同一个 run_turn() 中处理 pending input、hooks、compaction 和 continuation。这是 Codex 当前的产品/runtime 组织方式,不代表所有 Harness 都应把同样的粒度叫 Turn。
Shadow Harness Delta
- 保留 canonical
Turn = one logical Model Decision → Action/Observation,不要为迎合 Codex 类型名改 Domain Language。 - 在 Run/Task 层增加一个能容纳多个 canonical Turn 的 outer loop owner。
- trace 中分别记录
run_id / canonical_turn_id / model_sample_id / tool_call_id,避免把一次 user request 内多次 sampling 压成一个不可分析事件。 - 增加测试:
sample → tool → observation → sample → final时,只能在最终 completion 条件成立后关闭 Run。
不要过度推导
这段源码不能证明“Codex Turn 就是行业标准 Turn”,也不能证明每次 sampling 必然只产生一个 tool call。源码注释反而明确提示一次 sampling 可能返回多个 item。
完整映射见 OpenAI Codex Source Map。
可选源码深挖:Codex Public SDK Boundary
Codex 源码对照:Public SDK Boundary
要回答的问题
内部 Session / Turn / Tool / Compaction / Sandbox 很复杂时,为什么外部 SDK 不应该把这些内部对象全部暴露出去?
精确定位
Tag: rust-v0.150.1
File:
sdk/typescript/src/codex.tssdk/typescript/src/thread.tssdk/typescript/src/threadOptions.tssdk/typescript/src/exec.ts
Symbols:
Codex.startThread(...)Codex.resumeThread(...)Thread.run(...)Thread.runStreamed(...)TurnThreadOptionsCodexExec.run(...)
最小调用链
text
Codex.startThread(options)
or
Codex.resumeThread(id, options)
→ Thread
→ run(input, turnOptions)
→ runStreamedInternal(...)
→ CodexExec.run(... threadId / sandboxMode / approvalPolicy / ...)
→ thread / item / turn events
→ Turn { items, finalResponse, usage }Source Fact
TypeScript SDK 的 public surface 主要围绕 Codex → Thread → run/runStreamed → Turn result。resumeThread(id, ...) 通过 thread id 恢复;ThreadOptions 暴露 sandboxMode、approvalPolicy、working directory、network access 等配置。CodexExec.run() 再把这些稳定参数翻译成 CLI argument / config(例如 --sandbox、resume <threadId>、approval_policy=...)并启动 Codex executable,而不会要求 SDK 调用方直接操纵内部 SessionState、TurnState、ToolRegistry、compaction loop 或 process manager。
Design Inference
Harness 内部复杂度与 public API 复杂度应解耦。 外部 API 应围绕稳定业务语义暴露少量对象,把易变的 Runtime plumbing 隐藏在 abstraction boundary 后面。
Codex-specific Trade-off
Thread / Turn 的命名、TypeScript API 形状、sandboxMode 的三个枚举值,以及通过本地 Codex executable 驱动 SDK,都是 Codex 当前产品选择,不是通用 Harness 标准。
Shadow Harness Delta
- 为 Shadow Harness 增加独立 public facade,不直接向调用方暴露内部 Scheduler / Store / ToolExecutor 对象。
- 对外优先保留稳定操作:
start/resume/run/stream/cancel+ 明确 configuration。 - 内部 domain schema 升级时,增加 adapter test,保证 public API 不因内部状态对象重构而大面积变化。
不要过度推导
SDK 暴露 Thread 和 Turn 不能反向证明内部 Runtime 必须使用同名对象,也不能证明 sandboxMode 足以表达 Codex 内部所有 permission / approval / sandbox 状态。
完整映射见 OpenAI Codex Source Map。
自测
- 模型输出“
apply_patch已成功”时,为什么这句话还不能成为系统事实?从 Action Intent 到 Observation,中间至少需要哪些 Runtime 步骤? - 同一个代码修复需求,什么条件下适合写成固定 Workflow,什么条件下才需要让 Agent 根据 Observation 决定下一步?
- 在
User → Model → Action → Runtime → Observation中,哪一层拥有真实副作用的提交权?如果把这项权力交给模型文本,会破坏什么边界?