写作

Agent Systems:从模型调用到可靠执行

从 Oracle、长任务与生产系统的演化中,整理模型调用如何被 Runtime、Context、State、Permissions 与 Verification 组织成可靠执行。

从 2025 年构建 Oracle 开始,我反复遇到同一种错位:模型已经能够给出不错的判断,产品却仍然不知道那项工作是否真正发生。

最初的问题是如何把 Response 变成仍可推进的 Task。随后,任务开始跨越一次请求,我们需要处理等待、中断、恢复与交付。再后来,媒体生成把超时、异步回调、文件和结算放进同一条生命周期;模型 fallback 又证明,一次用户请求成功,背后的基础设施仍可能正在退化。

这些问题并不是在同一天以一张完整架构图出现的。每次都只是某个局部先失去确定性:状态对不上、动作无法确认、旧 Context 干扰当前判断,或者模型宣布完成但系统拿不出证据。直到把它们放在一起,模型调用与可靠执行之间缺少的那层系统才逐渐清楚。

这篇文章因此更像一张阶段性的系统地图。它把两个对象放进同一幅图里:

  • Model Call:根据当前输入生成一次输出或一次行动建议;
  • Agent System:在时间中持续推进一个目标,并让行动受到约束、状态可以恢复、结果能够验证。

前面的文章分别记录了这些边界从哪里暴露;这里关心的是,它们最终如何组成一个可以被诊断、实现和维护的执行系统。

一次模型调用的边界

最简单的 LLM 产品可以被描述成一个近似纯函数:

output = model(instructions, user_input, context)

输入在调用前被组装好,输出在调用结束时返回。模型不必负责外部世界发生了什么,也不必在几分钟后继续同一项工作。

这种结构非常适合边界清楚的任务:改写一段文字、解释一个概念、从固定材料中提取字段,或者生成一份由人继续处理的草稿。成功条件主要存在于输出本身,人可以立即判断它好不好。

但许多真实目标不是一个等待补全的句子。

“修复这个线上问题”隐含着定位代码、理解数据流、建立假设、修改文件、运行测试和检查回归。“研究一个市场并给出可用方案”要求检索、比较、记录证据、发现空白并调整方向。模型第一次看到目标时,通常还不拥有足够的信息生成最终答案。

此时,系统的基本单位不再是 Response,而是一次仍未完成的 Task。

当输出变成下一步行动

让模型拥有工具后,它的输出可以从答案变成动作:读取文件、搜索代码、执行命令、调用 API,或者请求更多材料。工具的结果随后回到上下文,模型再决定下一步。

Goal

Model → Action → Environment
  ↑                 ↓
  └──── Observation ┘

如果把每一步写成状态转移,它大致是:

(state_t, observation_t) → decision_t
decision_t → action_t
(state_t, action_t, result_t) → state_t+1

这就是 Agent Loop 最小的形状。模型不再一次性猜出最终结果,而是通过行动逐渐获取完成目标所需的信息。

ReAct 展示了把推理轨迹与任务动作交错起来的模式:推理帮助更新计划、处理例外,动作让系统与外部环境交互。它解释了为什么“想一点、做一点、观察、再继续”比一次性回答更适合开放任务。

但 ReAct 是一种 reasoning-and-acting pattern,不是完整的运行系统。它没有自动回答工具是否被允许、状态由谁持久化、超时之后如何恢复,以及谁来证明目标已经完成。

Loop 让模型能够继续。它还没有让系统变得可靠。

朴素 Loop 会在哪里失败

最简单的 Agent 实现常常只是一个 while:只要模型继续发出 Tool Call,就执行工具并把结果追加进消息历史;当模型输出最终文本时,结束任务。

这种实现足以做 Demo,却把最危险的决定交给了同一个概率系统:做什么、做多久、哪些结果可信,以及什么时候算完成。

它通常会出现几类失败。

上下文无限增长

工具输出不断追加。一次冗长日志、一个巨大的文件或重复的搜索结果,都可能挤掉真正重要的目标与约束。历史越来越完整,模型看到的有效状态反而越来越模糊。

副作用无法回收

读取失败可以重试,发送邮件、扣减余额、创建远程资源却不能随意重放。如果一次调用超时,系统可能只知道“没有收到响应”,却不知道外部动作究竟有没有成功。再次执行有可能制造重复副作用。

状态与对话混在一起

消息历史里可能同时存在旧计划、新要求、失败尝试和已经过时的结论。若系统没有单独保存当前事实,模型只能从文本中猜测“现在到底进行到哪里”。

错误反馈被当成普通文本

工具返回 permission deniedtimeoutvalidation failed,需要完全不同的处理。若它们都只是下一条字符串,模型很容易把结构性失败误解成再试一次就能解决的问题。

完成只是模型的一句话

模型能够输出“已经修复”,不代表代码通过了测试;能够生成文件路径,不代表文件存在;能够写出结论,不代表证据覆盖了目标。生成终止语句和证明任务完成,是两项不同的能力。

权限等于工具是否出现在 Prompt

隐藏某个工具不是安全边界。即使模型遵守指令,外部内容仍可能包含 Prompt Injection,工具参数也可能越过预期范围。可执行能力必须受到确定性策略约束,而不是依赖模型始终做出正确判断。

这些失败有一个共同点:它们不是“模型还不够聪明”,而是系统没有为行动建立明确的责任边界。

我所说的 Agent Harness

Harness 在行业里还没有完全统一的定义。它有时指 Coding Agent 外围的执行框架,有时只是某种测试或编排设施。

我在这里使用一个更明确的定义:

Agent Harness 是围绕模型建立的执行系统。它向模型提供经过选择的 Context、Tools、State 与 Feedback,同时约束行动、保存事实,并把“继续生成”变成可控制、可恢复、可验证的工作。

Harness 不是一个与 Runtime、Context 或 Evaluation 并列的模块。它是容纳这些责任的系统边界。

Agent Harness
├── Runtime & lifecycle
├── Context construction
├── Task state
├── Memory
├── Tools & permissions
├── Verification
└── Observability & evaluation

这个定义也刻意不把 Reasoning 放成 Harness 的普通模块。推理能力主要来自模型与它采用的决策策略;Harness 能够提供计划表示、约束、反馈和计算预算,却不能假装自己拥有另一套独立智能。

Harness 需要承担什么

Runtime:控制一次任务的生命

Runtime 驱动 Loop,但它不只是重复调用模型。它需要维护任务生命周期,例如:

created → running → waiting → running → verifying → completed
                    ↘ failed / cancelled / blocked

每次行动都应该有身份、开始时间、输入摘要、执行结果和明确状态。超时不应直接等于失败,因为外部动作可能已经发生;重试也不应只是再次发送同样请求,而要结合幂等键、执行记录或补偿机制判断。

Runtime 还拥有模型不应该独自拥有的控制权:最大步骤数、成本预算、取消信号、并发限制、可重试错误,以及必须交还给人的审批点。

模型可以建议继续。Runtime 决定系统是否仍有资格继续。

Context:构造当前工作集

Context Engineering 不是把所有已知信息塞进 Context Window,而是为下一次判断构造最小但充分的 working set。

一次调用的有效 Context 可以抽象为:

C_t = instructions
    + goal_and_constraints
    + current_task_state
    + relevant_evidence
    + selected_memory
    + recent_interaction

这里没有“全部历史”。搜索、裁剪、去重、压缩和渐进披露都在回答同一个问题:哪一部分信息会改变下一步判断?

这也意味着 Context 必须与 State 分开。Context 是当前送进模型的视图,State 是无论模型是否看到都必须保持一致的事实。前者可以被压缩,后者不能因为压缩而丢失。

State:保存已经发生的事实

Task State 至少需要表达目标版本、当前阶段、已完成动作、未解决问题、产物引用和外部副作用。它不应该只存在于一段自然语言总结里。

例如,payment_requestedpayment_succeededpayment_status_unknown 是三种不同状态。把它们压成“已经尝试支付”会让后续决策失去可靠基础。

好的 State 不要求每个判断都变成数据库字段,但所有影响恢复、幂等与用户结果的事实,都必须拥有比对话更稳定的 authority。

Memory:携带跨任务仍有价值的知识

Memory 解决的不是“保存更多聊天”,而是哪些知识在未来仍然值得复用。项目约定、稳定偏好、已验证的调试结论和长期方法可以进入 Memory;某次任务的临时猜测、过期计划和巨大工具输出通常不应该。

因此:

  • Context 是这一步需要看到什么;
  • State 是这项任务现在真实发生了什么;
  • Memory 是跨越当前步骤或任务后仍值得保留什么。

把三者混成一份 Transcript,会让系统拥有很多 Token,却没有清晰事实。

Tools 与 Permissions:把能力变成受约束的 Capability

一个 Tool 不应该只有名称、描述和 JSON Schema。系统还需要知道它的副作用类型、允许范围、超时策略、重试语义、输出上限和审批要求。

Tool capability
├── input contract
├── execution boundary
├── side-effect class
├── permission policy
├── timeout / cancellation
├── retry / idempotency
└── typed result

这样,read_filedelete_resource 才不会只因为同样采用 Tool Call 协议而被当作同一风险等级。

权限判断也必须发生在执行层。模型可以解释为什么需要越权,用户或确定性策略决定是否授权,而工具执行器只接受已经验证的 Capability。Prompt 负责引导,Policy 负责约束。

Verification:把完成变成证据

Agent 的停止条件不能只是 model_says_done == true

每类任务都应该把成功条件尽量映射成可观察证据。例如代码任务可以要求相关测试通过、类型检查没有新增错误、Diff 与 Scope 一致;数据任务可以要求查询结果满足不变量;涉及外部写入时,可以要求从系统 of record 独立回读最终状态。

Verification 发生在一次任务的执行闭环里,回答“这次是否真的完成”。Evaluation 更偏向系统层,回答“这种策略在一批任务上是否持续可靠”。两者相关但不可互换:离线 Eval 很好,不代表这一次支付已成功;这一次测试通过,也不代表系统整体具有稳定可靠性。

Observability:让失败可以被解释

如果系统只保存最终对话,就很难区分模型选错了工具、工具本身失败、权限被拒绝、Context 漏掉关键约束,还是 Verifier 给出了错误信号。

可观察性至少应该把一次任务拆成可关联的事件:Model Decision、Tool Attempt、Policy Decision、State Transition、Verification Result 和 User Intervention。

观测的目的不是记录更多内部思维,而是建立行动责任链。系统不需要保存模型的隐式推理,仍然可以保存“基于哪些可见证据选择了什么动作,以及动作产生了什么结果”。

一张更完整的系统图

把这些责任放在一起,Agent System 更接近下面的形状:

                         ┌────────────────────┐
                         │  Goal / User input │
                         └─────────┬──────────┘

┌────────────────────────────────────────────────────────┐
│                     Agent Harness                      │
│                                                        │
│  Policy & Permissions ─────┐                            │
│                            ↓                            │
│  State → Context Builder → Model → Proposed Action     │
│    ↑                           │            │           │
│    │                           └── stop? ───┤           │
│    │                                        ↓           │
│  Event Log ← Runtime ← Typed Result ← Tool Executor     │
│    ↑                                        │           │
│    └──────── Verification ← Evidence ───────┘           │
│                                                        │
│  Memory supplies selected knowledge; observability     │
│  records decisions, attempts, transitions and results. │
└───────────────────────────┬────────────────────────────┘

                Filesystem / APIs / Humans / World

这里其实存在三个速度不同的 Loop:

  1. Action Loop:模型选择动作,工具返回观察;
  2. Task Loop:系统检查进度、重规划、等待、恢复并验证目标;
  3. Evaluation Loop:团队从一批真实任务中发现失败模式,调整模型、Context、Policy、Tool 或 Runtime。

只优化最内层的 Action Loop,往往会得到一个调用工具更流畅、却仍然无法稳定交付结果的系统。

Harness 也不能解决什么

工程系统可以减少不确定性,却不能消灭不确定性。

如果目标本身矛盾,Harness 只能暴露冲突或请求澄清;如果环境没有提供可观察信号,Verifier 无法凭空知道结果;如果模型无法理解问题,增加更多状态机不会自动产生正确判断;如果一项任务需要数月才能知道结果好坏,系统也不能用一个廉价即时指标替代真正的 Evaluation。

Harness 最重要的价值不是“让 Agent 永不失败”,而是让失败具有边界:

  • 不确定时不会伪装成确定;
  • 副作用不会因为重试被随意复制;
  • 状态能够在中断后恢复;
  • 权限不会因为 Prompt 被绕过;
  • 完成需要证据,而不是一句自信的话。

这也是我判断一个 Agent System 是否成熟的方式:不是看它能连续调用多少次工具,而是看它是否知道哪些事情已经发生、哪些还没有被证明,以及何时应该把决定交还给人。

用这张图诊断系统

当一项真实任务失败时,这张地图首先提供的不是新术语,而是一组定位问题:

  • 模型是否基于错误证据作出了判断?
  • Context Builder 是否遗漏了会改变决定的约束?
  • Runtime 是否重放了一个不能重复的动作?
  • State authority 是否与对话、前端或外部系统产生冲突?
  • Policy 是否允许了超出 Scope 或预算的副作用?
  • Verifier 是否把一句完成声明当成了结果?
  • Evaluation 是否只统计活动,而没有测量任务是否接近目标?

更强的模型可能改善第一项,却不会自动修复后面的责任缺口。Harness 这个词是否流行并不重要;重要的是,每一项必须由系统承担的责任都有明确位置,也有失败后可以检查的证据。

这篇文章到这里完成的是地图。它落到一次真实代码任务里会如何运转,我在《Coding Agent 的执行系统》中沿着一个跨越前端、API 与持久化的 Bug 继续拆解。

文章数据

次阅读条评论

讨论

评论区

关于文章本身,也欢迎留下不同意见或补充。

留下评论

评论会立即公开,并可在当前浏览器中删除。

发布时会进行一次隐私友好的人机验证。