会推理不等于能上线:Agent 的判断与行动,如何变成不会丢失的软件事实?
页面一刷新,审批就消失;进程一重启,退款就重来。判断没错、授权没错,系统还是丢了现场。vivo 在一篇 KDC 工程补篇中,把 Agent 生产级运行的隐藏缺口拆到了最底层——判断与行动,必须先变成可恢复的软件事实。
领域现实 / 业务判断 / 软件运行
业务因果链 × 运行事实链
runId → feedbackId
读法:这三个数字对应原文拆解的三层工程责任——事实分层、链路连接、身份追溯,在正文各节展开。
页面一刷新,审批就消失;进程一重启,退款就重来。判断没错、授权没错,系统还是丢了现场。vivo 在一篇 KDC 工程补篇中,把 Agent 生产级运行的隐藏缺口拆到了最底层——判断与行动,必须先变成可恢复的软件事实。
读法:这三个数字对应原文拆解的三层工程责任——事实分层、链路连接、身份追溯,在正文各节展开。
先从退款场景开始。
用户问售后 Agent:"帮我看看这笔订单能不能退。如果可以,就直接帮我处理。" Agent 查询订单、引用当前退款政策、判断订单符合条件,准备调用退款能力。由于退款会改变订单和资金状态,系统要求用户确认。页面随即显示了一条审批提示。
用户还没点击确认——页面突然刷新。
刷新之后,审批提示消失。聊天记录里还能看到 Agent 说过"准备退款",但前端不知道审批是否仍然有效,后端不知道用户是否已经做过决定,能力运行时也不知道应该继续、暂停还是取消。
更危险的情况还在后面。
这一次,问题不在知识是否可靠,也不在 Agent 是否理解了用户目标。判断和授权规则可能都是正确的,系统仍然可能因为缺少稳定运行事实而丢失现场、重复执行或无法追责。
从"Agent 做出了正确判断"到"系统可靠地完成了行动"之间,有一层不能省略的工程结构。
在 Agent 系统中,至少存在三种需要分别管理的事实。它们相互关联,却有着完全不同的生命周期与可信等级。
应用软件的最终参照物。软件只能通过接口、事件、人工确认和其他表示间接观察它。
系统基于什么目标、知识和证据形成结论,又为什么建议某项行动。KDC 用推理对象表达这类责任。
某次运行中已经发生了什么:Run 是否开始,审批是否挂起,Tool 是否执行,Checkpoint 位于哪里。
三者不能互相替代。下面三组"不等号"是最容易踩的坑:
tool.call.completed
≠
退款已经到账
pendingApproval = null
≠
用户已经同意退款
页面显示"已完成"
≠
业务目标已经实现
读法:Runtime 可以权威地声明"这次 Tool Call 已经完成",却不能仅凭这个运行事实证明用户已经收到资金——前者是日志,后者是现实。
生产级 Agent 需要两条可以连接的链。一条回答系统为什么行动,一条回答执行到了哪里。
业务因果链 —— 为什么行动
运行事实链 —— 执行到哪里
读法:上链由业务目标驱动,下链由事件驱动。User Control(批准 / 停止 / 重试)产生新事件,State 由此归约,View 从 State 派生。
两条链不能各自独立。只保留业务因果链,系统可能解释得清楚却无法恢复;只保留运行事实链,系统可以恢复调用,却不知道行动是否具备业务资格。
连接两条链的关键不是复制更多文本,而是稳定身份:
runId
turnId
reasoningObjectId
skillId
capabilityId
policyDecisionId
approvalId
toolCallId
artifactId
feedbackId
这些身份让系统知道,一条审批属于哪次判断,一次 Tool Call 来自哪个能力,一份 Artifact 支持哪个目标,一条反馈又应该修正哪次行动。
"让 Agent 记住"在工程讨论里经常同时指五件不同的事。它们都与历史有关,却不能共享同一个生命周期和可信等级。
| 对象 | 角色 | 持久化边界 |
|---|---|---|
| Session | 当前交互线程的可恢复历史来源 | 进程外持久化 |
| Context | 一次模型调用对历史的选择性投影 | 可裁剪 / 压缩 / 重建 |
| State | 可恢复的运行事实,由 Runtime 写入 | 持久化 + 版本化 |
| Memory | 跨 Session 可检索的经验 | 跨线程检索 |
| Knowledge | 经过来源确认与时效治理的知识 | 治理后入库 |
判断标准:如果某个状态影响恢复、审批、继续执行、跨端一致、审计或结果检查,它就不应该只存在于聊天文本、Prompt、组件变量或临时缓存中。
最危险的误用是让 Context 承担 State 的职责。Context 可以被裁剪、压缩、重排或重新生成——一次上下文压缩如果遗漏了"用户已经批准",不该让审批重新变成待处理;一次新的模型调用如果没有看到全部 Trace,也不该改变已经发生的外部事实。
一个常见失败方式,是让前端从消息文本中推断运行状态。例如模型输出"需要用户确认后才能发起退款",UI 就本地生成一个审批按钮——这个实现短期内能工作,却回答不了:这条审批对应哪个 Run 和 Tool Call?刷新后如何恢复?用户点击同意时,决定应该发送给谁?
因此需要区分三类责任:
可恢复的运行事实,由 Runtime 或明确的业务边界产生并持久化。
activeRun / currentTurn
pendingApprovals / toolCalls
checkpointId / runtimeStatus
Runtime 写入
从事实派生的展示。pendingApproval 存在所以展示审批条,activeRun 在运行所以允许 Stop。
isBusy / canStop
approvalBanner / toolBadges
subagentProgress
UI 读取
命令,不是状态变更。表示"请求系统做什么",不表示事情已经发生。
resume(approvalId, decision)
stop(runId) / retry(toolCallId)
reload(threadId)
UI 提交
一个关键边界是 result_unknown。Tool 调用发出后,支付渠道可能已经受理但响应丢失,State 不应回退成"尚未调用",而应保留同一个 toolCallId 和幂等键:
{
"toolCallId": "tool-call-031",
"capabilityId": "refund-order-v2",
"status": "result_unknown",
"idempotencyKey": "refund:order-001:intent-031",
"externalRequestId": "payment-request-8841",
"reconciliationStatus": "pending",
"lastError": "response_timeout"
}
result_unknown 不是失败的另一种写法,而是一个恢复边界——Runtime 必须先使用外部请求身份查询或对账,再决定完成、补偿还是转人工。
这不是象牙塔理论。vivo 是手机厂商,有真实用户、真实售后、真实支付流程——退款场景就是他们系统里每天都在发生的事。
"Agent Harness"目前更像一组正在收敛的工程实践。不同项目从不同故障出发,各自强调了生产级 Agent 的一部分责任:
在此基础上,vivo 提出了三个可被实现、检查和测试的工程契约,用于把判断与行动变为可交接的软件事实:
恢复契约:一次运行中断后,从哪个 Checkpoint、以什么前提继续,哪些外部结果需要先对账。
进度契约:任务当前进行到哪一步,下一步允许做什么,哪些条件变化会导致进度失效。
评估契约:用什么反馈证据、什么标准判定业务目标实现或失败,而非只看 Tool 是否返回 200。
读法:三个契约对应运行恢复、进度交接、结果验证三段生命周期,是双链结构在工程接口层面的具象化。
模型参数可以卷,但"状态不丢、执行不重、追责有人"这三件事,卷不出奇迹——它们只能一行一行地写进工程里。
Agent 的推理能力可以靠模型迭代快速追平,但运行事实层的工程深度没有捷径。它正在成为国产 Agent 从 Demo 走向生产的分水岭。
KDC 系列共六篇。前五篇完成了从 Reality 到 Feedback 的理论闭环,这篇补充了最具体的一环:让判断与行动成为可恢复、可控制、可审计的软件事实。
KDC 系列 · 工程补篇 06/06 —— 理论闭环的最后一块拼图