Agent DecisionTrace
用结构化、面向决策语义的记录保存一次 Agent 为什么能 AUTO、为什么被阻断、取过哪些证据以及最终采用哪条 policy 路径。
[!info] related notes
- 所属 MOC: agent-evals-moc、agent-runtime-moc
- 前置概念: agent-configuration-bundle、decision-relevant-evidence-gap、evidence-acquisition-policy、deny-overrides-policy-composition
- 并列概念: agent-configuration-and-execution-provenance
- 易混淆概念: trace
- 关系笔记: agent-decision-authority、agent-evidence-admissibility、agent-failure-attribution、bodysense-diagnosis-agent-architecture
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
自测题
- OTel Trace 和 DecisionTrace 分别回答什么问题?
- 为什么不能只保存
Decision=ABSTAIN? - Raw proposal 为什么值得保存,但不能被 public read model 重新展示?
- 为什么 rejected Evidence 仍是 Execution Truth?
- 为什么 Trace 中的 DecisionFacts 应来自 policy 实际消费的同一对象?
- DecisionTrace 为什么不等于 Chain of Thought?
- 为什么关键 authority trace 最好和业务 transition 一起持久化?
- Historical authority replay 为什么可能完全不需要调用模型?
- Trace schema version 和 policy version 有什么区别?
- 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 漂移;最好在决策时生成。