Agent DecisionTrace

用结构化、面向决策语义的记录保存一次 Agent 为什么能 AUTO、为什么被阻断、取过哪些证据以及最终采用哪条 policy 路径。

#type / concept #status / growing #tech / ai #tech / architecture #tech / ops #resource / agent

[!info] related notes

Agent DecisionTrace

一句话定义

DecisionTrace 是一次 Agent 决策的结构化业务因果记录:它不只记录发生了哪些调用,还记录哪些输入、证据缺口、真实取证、runtime facts、policy 规则和 blocking condition 共同导致了最终授权动作。

为什么普通日志不够

普通应用日志很容易长成:

09:01 request started
09:01 model completed
09:01 search_knowledge called
09:01 policy returned abstain
09:01 response 200

这些信息对排查“服务有没有报错”有帮助,但如果半年后问:

为什么当时这个用户没有收到普通 Diagnosis candidate?

日志仍缺少一条稳定业务因果链:

哪一个 BodyState revision?
哪一个 Configuration?
缺的是哪条 critical Evidence?
是否真的尝试获取?
为什么停止?
哪些 Evidence 被拒绝?
Policy 用哪一版?
哪个 blocker 命中?
Raw proposal 是什么?
Authorized result 为什么被压成空?

DecisionTrace 的核心价值,就是把这些散落于模型输出、tool trace、policy、DB 和 API 的事实,组织成可稳定查询的业务决策对象

与普通 Trace 的边界

Observability Trace 主要回答:

运行时发生了什么?

例如:

request started

LLM call 824ms

tool call search_knowledge

HTTP 200

DecisionTrace 回答的是:

为什么最终允许 AUTO,或者为什么必须 ABSTAIN / ESCALATE / BLOCK?

例如:

BodyState R42

Configuration C9

G8 critical + unresolved

EvidenceBudget exhausted

SafetyEnvelope outside

DecisionPolicy v1

ABSTAIN

两者可以共享 run_id / trace identity,但业务代码不应该在事故发生半年后重新解析供应商 span 才能猜出当时为什么阻断。

Observability Trace 与 DecisionTrace 是两个视角,不是二选一

可以把一次运行看成两张图。

技术执行图

HTTP span
→ Python span
→ LLM span
→ tool span
→ PostgreSQL span

回答:

哪里慢?
哪里 500?
重试几次?

业务决策图

Pinned Input
→ Gap
→ Attempt
→ Evidence State
→ Decision Facts
→ Policy
→ Authorized Transition

回答:

为什么允许?
为什么拒绝?
为什么转人工?

最好的系统通常把两者通过:

run_id / trace_id / analysis_id

关联起来,而不是强行让一张图承担另一张图的职责。

DecisionTrace 的推荐分层结构

一个更完整的结构可以是:

DecisionTrace

├─ input
│   ├─ user / subject identity
│   └─ pinned BodyState revision

├─ configuration
│   ├─ configuration_id
│   └─ policy revisions

├─ reasoning proposal
│   ├─ candidate proposals
│   └─ semantic gaps

├─ evidence state
│   ├─ accepted gaps
│   ├─ resolved / unresolved status
│   └─ criticality

├─ acquisition
│   ├─ EvidenceAttempt A1
│   ├─ EvidenceAttempt A2
│   └─ returned / rejected evidence identities

├─ runtime facts
│   ├─ new_red_flag
│   ├─ candidate_count
│   ├─ critical_gap_count
│   └─ governance_verdict

├─ policy
│   ├─ decision_policy_revision
│   ├─ matched blocker / rule
│   └─ envelope state

├─ authority
│   ├─ outcome
│   └─ reason codes

└─ authorized result
    ├─ final status
    └─ delivered candidate cardinality

这比只保存:

result = ABSTAIN

强得多。

Trace 应该保存“事实”和“引用”,而不是复制所有原始数据

DecisionTrace 不需要成为一个巨大的 JSON dump。

更合理是保存:

stable identity
+ decision-relevant snapshot
+ references to durable artifacts

例如:

body_state_revision = R52
configuration_id = C12
analysis_id = D210
critical_gap_ids = [G8]
evidence_attempt_ids = [A7]
policy_revision = v1
decision = ABSTAIN

必要时再链接到:

  • exact BodyState snapshot;
  • Evidence snapshot;
  • execution trace;
  • raw proposal artifact。

这样既避免 duplicated truth,又保持历史解释能力。

为什么要同时保留 Reasoning Proposal 与 Authorized Result

模型可能原本提出:

Candidate C17
confidence = HIGH

但 Go / Domain Policy 得出:

criticalGapCount = 1
→ ABSTAIN
→ candidates = []

如果 DecisionTrace 只保存最终的空列表,就无法回答:

“模型本来想交付什么,系统为什么把它压掉了?”

因此:

Model Proposal

Authorized Business Result

两个层次都值得追踪,但真正对用户/业务生效的是 authorized result。

Trace 中保留 Raw Proposal 不代表允许它重新旁路 Authority

审计层可以保存:

raw proposal = Candidate C17

但 public read model 不能以后因为“数据库里还能找到 C17”就把它展示给用户。

要明确区分:

Audit visibility

Business visibility

Raw proposal 是历史审计材料;Authorized Result 才是正常业务交付源。

这条边界能防止一个常见 bug:policy 明明 ABSTAIN,但 UI 从“原始模型结果表”重新读出了被抑制 candidate。

Evidence 被拒绝也要留在 Trace

假设 A2 实际获取了 E8,但它违反当前 configuration 的 source/version policy:

E8 retrieved = true
E8 admissible = false
G3 remains unresolved

这里不能把 E8 从历史里删除。

DecisionTrace 应记录:

Attempt A2
├─ returned E8@v4
├─ admissible = false
└─ reason = source_version_not_approved

因为“搜到了但不能用于决策”本身就是重要的 Execution Truth。详见 agent-evidence-admissibility

为什么 Rejected Evidence 对改进系统特别重要

如果生产里大量出现:

source_version_not_approved

说明可能不是模型能力差,而是:

  • evidence policy 太旧;
  • corpus lifecycle 与 configuration 不同步;
  • retriever 经常召回未发布源。

如果大量出现:

semantic_irrelevance

则更像 retrieval/query quality 问题。

所以 rejected evidence 不只是“审计垃圾”,还可以形成:

production observation
→ failure slice
→ eval dataset
→ retrieval/policy improvement

Runtime Facts 是 Trace 中的关键桥梁

模型输出和最终 DecisionAuthority 之间,最好有一层稳定 facts:

Semantic / Execution State

Fact Extraction

Policy Facts

DecisionAuthority

例如:

critical_gap_count = 1
new_red_flag = false
candidate_count = 1

这样事故调查可以明确区分:

  • 模型没发现 gap;
  • gap 发现了,但 fact extraction 丢了;
  • facts 正确,但 policy 错了;
  • policy 正确,但 delivery 绕过了 authorized result。

这正是 Failure Attribution 的基础。

Trace 中的 Facts 最好来自 Authority 真正消费的同一对象

一个反模式:

Policy 实际消费 Facts-A

Trace 在 policy 完成后
又从 raw response 重新计算 Facts-B

如果 A/B 的 mapper 稍有差异,可能出现:

Policy 当时看见 criticalGapCount=1
Trace 却记录 criticalGapCount=0

历史解释就失真。

更安全的是:

facts = BuildDecisionFacts(...)
decision = Decide(facts)
trace = BuildTrace(facts, decision, ...)

也就是:

Trace 记录真正参与决策的事实,不要事后重建一个“看起来像”的版本。

DecisionTrace 不等于 Chain of Thought

DecisionTrace 不需要保存模型完整私有推理过程,也不要求逐 token 复现。

应该保存的是系统可审计的:

输入 identity
configuration identity
真实 tool/evidence observations
Gap states
policy facts
policy revision
rule/blocker
final authority

也就是说:

可解释决策依据
+ 可验证 policy path
+ 可关联 execution trace

而不是依赖自由文本思维过程。

“可解释”不等于要求模型解释自己所有内部思考

系统真正需要回答:

Which facts were considered?
Which evidence was observed?
Which rule matched?
Which transition was authorized?

这些完全可以通过结构化业务事实回答。

所以 DecisionTrace 既能提供可解释性,又不需要依赖不可验证的模型自述。

Trace 应该在 Decision 时生成,而不是事后拼装

最可靠的时机:

DecisionFacts ready
→ Policy evaluates
→ Decision produced
→ DecisionTrace atomically materialized/persisted with business transition

尤其 Treatment acceptance 这种关键 transition,最好:

acceptance DecisionTrace
+ revision accepted
+ current pointer update

在同一事务语义中完成。

否则可能发生:

业务 transition 成功
trace 写失败

留下一个无法解释的历史动作。

为什么它是 Replay 的基础

Historical Replay 需要知道:

当时 pinned input 是什么?
配置是什么?
哪些 evidence 真正参与了运行?
Gap 当时是什么状态?
Policy 用哪一版?
最终为什么作出该决定?

Counterfactual Replay 又需要拿同一个历史 case 对比新 configuration。

没有结构化 DecisionTrace,Replay 很容易退化成:

“把旧 prompt 再发一次看看。”

这无法真正解释历史差异。

Replay 时哪些部分可以 deterministic 重算

例如历史 trace 保存:

DecisionFacts F
PolicyRevision v1

可以直接做:

Historical Authority Replay:
Decide_v1(F)
→ expected original decision

这个过程甚至不需要模型调用。

这非常有价值,因为它能独立验证:

“历史 decision 是否符合当时 policy?”

而不被当前模型/provider 的非确定性干扰。

Counterfactual Policy Replay

同一历史 facts:

F

可以比较:

v1(F) → ABSTAIN
v2(F) → ESCALATE

这能清晰回答:

仅仅改变 DecisionPolicy,会改变哪些历史 case?

如果同时换模型、Evidence、Prompt,则要明确这是更大的 Configuration counterfactual,而不是单一 policy replay。

为什么它也是 Fault Localization 的基础

生产事故:

“明明有 critical gap,为什么 UI 还是显示 Candidate?”

如果 Trace 能一路确认:

BodyState correct ✅
Gap recognized ✅
criticalGapCount=1 ✅
Decision=ABSTAIN ✅
ApplyDecision candidates=[] ✅
DB candidates=[] ✅
API/UI candidate exists ❌

根因就被收敛到 read model / API / frontend delivery path,而不是模型。

DecisionTrace 也应该有 Schema Version

Trace 本身会演化。

例如 v1 只有:

facts + decision

后来 v2 增加:

evidence rejection reasons
execution provenance reference

如果没有 trace schema version,历史 parser 很容易无法判断字段语义。

可以显式记录:

decision_trace_schema = diagnosis-decision-trace-v2

注意:Trace schema version 不一定等于 DecisionPolicy version。一个是记录结构,一个是业务决策规则。

Trace 的隐私与数据最小化

“为了审计”不代表把所有原始用户文本、Prompt、敏感字段全部复制到 Trace。

应尽量:

保存 identity / revision / reason code /必要 snapshot
而不是无限复制敏感 payload

例如 BodyState 可引用 revision ID;真正需要历史重放的字段通过受控 snapshot 保存。

这既减少数据泄露面,也避免 Trace 变成第二个无治理的数据仓库。

用途

  • production incident diagnosis;
  • safety audit;
  • behavioral contract verification;
  • eval failure analysis;
  • regression case extraction;
  • historical replay;
  • counterfactual policy/config comparison;
  • rollout observation;
  • 人工审核界面的可解释摘要来源。

BodySense Diagnosis 示例

DecisionTrace DTrace-210

Input
  BodyStateRevision = R52

Configuration
  ConfigurationID = C12
  DecisionPolicy = diagnosis-decision-policy-v1

Proposal
  Candidate = C6 radiculopathy / HIGH

Gap
  G8 = progressive motor weakness?
  kind = user_information
  critical = true

Acquisition
  no generic RAG permitted
  user information unavailable
  stop = required_user_fact_unavailable

Facts
  criticalGapCount = 1
  newRedFlag = false
  governance = accepted

Decision
  ABSTAIN
  reason = critical_gap_unresolved

Authorized Result
  status = insufficient_information
  candidates = []

以后即使用户今天已经补充了“没有进行性无力”,这个 Trace 也不能被改写,因为它记录的是 R52 当时的决策世界。

Treatment 中 Trace 为什么更重要

Treatment 有至少两个 authority checkpoint:

Generation DecisionTrace
→ 为什么当时允许产生 proposal?

Acceptance DecisionTrace
→ 为什么后来允许这个 proposal 成为 current Treatment?

如果两者相隔几小时,BodyState 可能已经变化。

所以 acceptance trace 必须记录当时重新读取的 current facts,而不是简单复用 generation trace。

这体现:

same proposal
+ different time/world
→ different decision facts

Consultation 中 Trace 的形态

长运行 Consultation 可以把一次 run/turn 的关键 authority 事件记录为:

selected configuration
runtime identity handshake
interrupt required
resume authorized
cancel authorized
terminal outcome

不必把每个 token delta 都复制进 DecisionTrace;token/event 流属于 runtime event log。DecisionTrace 只保存决策相关 control facts。

测试 DecisionTrace 应覆盖什么

1. Trace 与 Decision 同源

policy consumed facts == trace recorded facts

2. Raw vs Authorized

raw proposal exists
ABSTAIN
→ trace records proposal
→ authorized result suppresses it

3. Rejected Evidence

retrieved but inadmissible evidence
→ trace retains evidence identity + rejection reason

4. Historical Immutability

未来 BodyState 更新后:

old DecisionTrace unchanged

5. Schema/Policy Version

trace schema revision explicit
policy revision explicit

6. Transactional Persistence

关键 domain transition 成功时:

corresponding decision trace must persist

7. Replay

stored facts + stored policy version
→ deterministic authority replay matches original decision

自测题

  1. OTel Trace 和 DecisionTrace 分别回答什么问题?
  2. 为什么不能只保存 Decision=ABSTAIN
  3. Raw proposal 为什么值得保存,但不能被 public read model 重新展示?
  4. 为什么 rejected Evidence 仍是 Execution Truth?
  5. 为什么 Trace 中的 DecisionFacts 应来自 policy 实际消费的同一对象?
  6. DecisionTrace 为什么不等于 Chain of Thought?
  7. 为什么关键 authority trace 最好和业务 transition 一起持久化?
  8. Historical authority replay 为什么可能完全不需要调用模型?
  9. Trace schema version 和 policy version 有什么区别?
  10. Treatment 为什么需要 generation 与 acceptance 两份不同 DecisionTrace?

常见误解

  • DecisionTrace = 日志:普通日志是离散事件;DecisionTrace 有稳定业务决策 schema。
  • DecisionTrace = Chain of Thought:它记录可审计依据,不依赖模型私有思维文本。
  • 有 OTel Trace 就不需要 DecisionTrace:基础设施 trace 回答技术执行;业务 authority 值得拥有一等结构。
  • 只存最终 Decision 就够了:没有中间 facts / evidence / blocker,就无法解释为什么。
  • 被拒绝 Evidence 不重要:它仍是实际发生过的 execution fact,必须可追溯。
  • Trace 越全越好:应保存 decision-relevant facts,避免复制无关敏感数据和整个框架内部状态。
  • 事后从日志重建 Trace 效果一样:重建容易与真正 decision facts 漂移;最好在决策时生成。
创建于 2026/8/19 更新于 2026/8/23