Agent DecisionAuthority
把 LLM 的语义建议、运行时事实与最终业务授权分离,由版本化 deterministic policy 根据结构化 facts 决定 ALLOW、ABSTAIN、ESCALATE 或 BLOCK。
[!info] related notes
Agent DecisionAuthority
一句话定义
DecisionAuthority 是系统中真正有权把“模型提出的可能性”变成“业务允许执行的动作”的控制边界。
它通常接受已经验证过的结构化 runtime facts,再通过版本化、可测试、deterministic 的 policy 产生最终授权结果。
[!important] 核心区分
Reasoning quality ≠ Authorization。模型可以比规则更“聪明”,但不代表模型拥有更高的业务权限:intelligence ≠ authority。
为什么不能让 LLM 自己拥有最终 authority
LLM 很适合:
- 从复杂输入中提出候选解释;
- 识别潜在 EvidenceGap;
- 解释证据的语义关联;
- 给出不确定性与候选排序。
但它不适合单独决定:
- 是否允许 AUTO;
- 是否越过 critical gap;
- 是否绕过 red flag;
- 是否将外部知识当作用户事实;
- 是否扩大当前 configuration 已批准的安全边界。
如果同一个随机组件既负责“发现事实”、又负责“裁决自己是否可以忽略缺失事实”,系统就很难证明安全边界。
更稳健的权力链是:
LLM Proposal
↓
Runtime Verification
↓
Structured Policy Facts
↓
DecisionAuthority
↓
Authorized Business Transition
从“模型很聪明”到“系统可授权”的推导
学习这一概念时最容易产生的第一反应是:
如果模型能力已经非常强,而且 confidence 很高,为什么还需要一层看起来更“笨”的 deterministic policy?
关键在于这两者回答的是完全不同的问题。
LLM confidence
回答:
“在我的语义模型里,我有多相信这个判断?”
DecisionAuthority
回答:
“在当前业务规则、安全约束、证据状态和版本化政策下,系统有没有权限执行这个动作?”
一个模型可以非常确定某个 Diagnosis 候选最可能,但系统仍然可能缺少一个法律上、业务上、安全上或产品上必须确认的事实。
例如:
模型:
C6 radiculopathy,confidence=0.98
Runtime:
progressive_motor_weakness = UNKNOWN
criticalGapCount = 1
这里 0.98 只能说明模型的语义倾向,并不能把 UNKNOWN 变成 FALSE。
如果让 confidence 直接越过缺失事实,实际上就是把:
“我不知道”
偷偷转换成:
“应该没问题”
这正是高风险 Agent 最需要避免的推理跳跃。
所以 DecisionAuthority 的价值不在于比模型更会推理,而在于它拥有不同的权力角色。
Policy Facts:先把复杂运行压缩成可审计事实
DecisionAuthority 不应该直接解析一大段自然语言 reasoning,而应消费稳定的结构化 facts,例如:
DecisionFacts
├─ active_safety_review
├─ new_red_flag
├─ governance_verdict
├─ status
├─ candidate_count
└─ unresolved_critical_gap_count
这样可以把两类问题分开:
“世界现在是什么状态?”
→ fact extraction / runtime validation
“在这些事实下允许做什么?”
→ decision policy
例如:
criticalGapCount = 1
policy = diagnosis-decision-policy-v1
→ ABSTAIN
这里不需要重新让 LLM 判断“是不是应该 abstain”。
Fact Extraction 是 Authority 之前的独立信任边界
DecisionAuthority 只有在输入 facts 正确时才有意义。
假设 Python 输出:
{
"status": "completed",
"unresolved_critical_gaps": [
{"gap_id": "G7"}
]
}
但 Go 在 DTO / mapper 中错误地得到:
criticalGapCount = 0
那么 policy 即使完全正确,也会做出错误授权。
因此生产链路实际上是:
Raw Agent Output / Runtime Observations
↓
Schema Validation
↓
Fact Extraction
↓
DecisionFacts
↓
DecisionPolicy
↓
Decision
这里有两个不同的 deterministic contract:
- Fact contract:同一个经过验证的 runtime state 必须稳定转换成正确 facts;
- Authority contract:同一
facts + policy revision必须得到同一 decision。
这也是为什么 Failure Attribution 中要把 Runtime Fact Failure 和 DecisionAuthority Failure 分成两类。
DecisionAuthority 与 SafetyEnvelope 的区别
SafetyEnvelope 更像边界判断:当前 case 是否仍位于被证明可以普通自动化处理的运行域内?
DecisionAuthority 更像动作选择:如果已经超出普通 AUTO 边界,到底应该:
ALLOW_DEGRADEDABSTAINESCALATEBLOCK
因此可以理解为:
SafetyEnvelope
= eligibility boundary
DecisionAuthority
= authorized action selection
一个例子:
Case A
critical diagnostic gap unresolved
→ outside normal SafetyEnvelope
→ ABSTAIN
Case B
new red flag detected
→ outside normal SafetyEnvelope
→ ESCALATE
Case C
configuration identity invalid
→ authority input itself不可信
→ BLOCK / fail closed
SafetyEnvelope 只告诉你“不能按普通自动化继续”,但不同原因应该映射到不同业务动作,这就是 DecisionAuthority 的工作。
Deny-Overrides:Hard blocker 不是一个低分项
假设模型给出:
confidence = 0.98
judge_score = 0.96
candidate_quality = high
但同时:
critical gap = unresolved
错误思路是把它们做加权平均:
0.98 + 0.96 + ... > blocker
→ AUTO
正确思路是 Deny-Overrides:
if hard_blocker:
normal_auto = forbidden
[!warning] Policy 不是一个 0~1 的 score Hard constraint 与 confidence 属于不同层级。Confidence 可以成为 policy input,但不能把禁止条件“加权覆盖掉”。
为什么 weighted score 在这里危险
如果所有因素最终都进入一个分数:
score =
0.4 * confidence
+ 0.3 * judge_score
+ 0.2 * evidence_quality
- 0.1 * safety_penalty
那么理论上足够高的 confidence 可以覆盖任何 safety penalty。
这适合排序问题,不适合权限问题。
权限系统通常更像:
Hard Gates
↓ all passed
Soft Ranking / Preference
↓
Authorized Choice
而不是:
Everything → one scalar → threshold
这和安全系统、权限系统、编译器 type checking 的思想更接近:某些约束不是“扣一点分”,而是不满足就不能进入下一阶段。
Pure Deterministic Policy 不等于“一坨 if/else”
Policy 内部出现显式 if 本身不是坏事。真正重要的是它尽量保持:
Facts → Pure Function → Decision
而不是在 policy 中混入:
- DB 查询;
- 网络请求;
- 模型调用;
- 隐式可变全局状态;
- 无版本的运行时配置。
只要相同 facts + policy revision 总能得到相同授权结果,就具备很强的可测试性与可审计性。
规则数量很少时,显式顺序通常比通用 Rule Engine 更容易审计。只有规则规模、组合复杂度或变化速度明显上升时,才值得把规则提升为一等对象。
一个更接近代码的 Policy 形状
概念上可以写成:
@dataclass(frozen=True)
class DiagnosisDecisionFacts:
active_safety_review: bool
new_red_flag: bool
unresolved_critical_gap_count: int
governance_verdict: str
output_status: str
def decide_diagnosis(
facts: DiagnosisDecisionFacts,
*,
policy_revision: str,
) -> DiagnosisDecision:
if policy_revision != "diagnosis-decision-policy-v1":
return DiagnosisDecision.block("unknown_policy_revision")
if facts.new_red_flag or facts.active_safety_review:
return DiagnosisDecision.escalate("safety_blocker")
if facts.unresolved_critical_gap_count > 0:
return DiagnosisDecision.abstain("critical_gap_unresolved")
if facts.governance_verdict != "accepted":
return DiagnosisDecision.block("governance_rejected")
return DiagnosisDecision.allow_normal()
这里最重要的不是具体代码,而是结构:
无 I/O
无模型
无隐藏状态
输入显式
policy revision 显式
reason code 显式
这样 policy 可以:
- 单元测试;
- historical replay;
- counterfactual replay;
- property-based testing;
- incident debugging;
- version comparison。
Decision 不应该只有 allowed: bool
一个布尔值通常太弱:
allowed = false
无法告诉上层究竟应该怎么做。
更成熟的结果应表达动作语义与原因语义:
Decision
├─ outcome
│ ├─ ALLOW_NORMAL
│ ├─ ALLOW_DEGRADED
│ ├─ ABSTAIN
│ ├─ ESCALATE
│ └─ BLOCK
├─ reason_codes
├─ policy_revision
└─ relevant facts snapshot/hash
这样 API、Persistence、UI 和 Replay 都可以消费同一个结果。
例如:
ABSTAIN
reason = critical_gap_unresolved
和:
BLOCK
reason = configuration_mismatch
虽然都“没有普通输出”,但产品行为完全不同。
Fail Closed
DecisionAuthority 应对未知或矛盾事实保持保守:
unknown policy revision
malformed required facts
unknown enum
contradictory safety state
这些状态不应该被解释成“应该没事”,而应进入 BLOCK / 明确失败路径。
[!tip] 记忆方式
UNKNOWN ≠ probably safe。
Fail closed 不是“所有异常都给用户报错”
Fail closed 描述的是权限方向,不是最终 UX。
内部可以:
unknown decision fact
→ normal AUTO forbidden
对用户则可能呈现:
“当前信息不足,需要重新确认”
或:
“这次结果需要人工复核”
也就是说:
security/authority semantics
≠
user-facing copy
不要因为希望 UX 友好,就在 authority 层把 UNKNOWN 当成安全。
Model Proposal 与 Authorized Result 是两个不同产物
模型可能提出:
Candidate C17
confidence = HIGH
但 DecisionAuthority 判断:
critical gap unresolved
→ ABSTAIN
那么真正业务输出可以变成:
status = insufficient_information
candidates = []
这意味着系统至少要区分:
Reasoning Result
= 模型/Agent 原本提出了什么
Authorized Business Result
= 系统最终允许交付什么
如果 policy 只是写一句“建议不要返回”,但 API 仍直接把 raw model output 给用户,那么它并没有真正的 authority。
Authority 必须控制真实 Side Effect,而不是只生成一个建议字段
一个常见伪治理结构是:
{
"candidate": "C6 radiculopathy",
"policy_recommendation": "abstain"
}
但调用方仍然:
persist candidate
→ API 返回 candidate
→ UI 展示
这种 policy 只是 metadata,不是 authority。
真正 authority 必须处于side-effect choke point:
Raw Proposal
↓
DecisionAuthority
↓
ApplyDecision
├─ decide what can be persisted as user-visible/current
├─ suppress forbidden candidate delivery
├─ create escalation state
└─ record DecisionTrace
这条原则在 Treatment 更重要:AI 可以 proposal,但只有 acceptance authority 能让某 revision 成为 current Treatment。
Policy Versioning
DecisionAuthority 必须版本化。例如:
diagnosis-decision-policy-v1
未来如果规则从:
critical gap → ABSTAIN
改成:
critical safety gap → ESCALATE
critical diagnostic gap → ABSTAIN
应产生新 revision,而不是偷偷改写 v1。
原因是历史记录必须仍然能回答:
“D100 当时为什么按照 v1 得到 ABSTAIN?”
Policy 变化也是行为变化,因此通常会改变 Agent Configuration Bundle identity,并重新进入 qualification / promotion。
为什么 Policy Revision 应进入 Agent Configuration Identity
有时会误以为:
模型、Prompt 没变
→ Agent configuration 没变
但如果 DecisionPolicy 从 v1 改成 v2,即使模型输出一字不变,最终用户可见结果也可能改变。
因此行为配置更准确是:
AgentConfiguration
=
Model policy
+ Prompt
+ Schema
+ Tool policy
+ Evidence policy
+ Governance policy
+ Decision policy
+ behavior-significant settings
DecisionAuthority 是 Agent 产品行为的一部分,不是 Agent 之外的“后处理小工具”。
BodySense Diagnosis 例子
BodyState R52
↓
Python proposes C6 radiculopathy, confidence HIGH
↓
Runtime facts:
criticalGapCount = 1
governance = accepted
newRedFlag = false
↓
SafetyEnvelope: outside normal AUTO
↓
DecisionPolicy v1
↓
ABSTAIN
↓
Authorized result:
status = insufficient_information
candidates = []
这里并没有证明 Candidate 一定“错”。系统只是说:在当前证据与 policy 下,没有权限普通自动交付这个 Candidate。
同一个概念迁移到 Treatment 后如何变化
Diagnosis authority 主要控制:
“这个分析能否作为普通结果交付?”
Treatment authority 增加时间和动作维度:
Generation Authority
→ 现在是否允许生成 proposal?
Acceptance Authority
→ 过了一段时间,这个 proposal 现在还能否成为 current Treatment?
Execution Authority
→ accepted intervention 在当前状态下是否仍可执行?
因此 DecisionAuthority 不是只适用于“一次 Agent response”。
它可以出现在业务 lifecycle 的多个 transition 上,只要每个 transition 都明确:
Facts
+ policy revision
→ authorized transition
与 Consultation Runtime 的连接
Consultation 中 authority 不一定表现成“是否返回 Diagnosis Candidate”,还可能控制:
- 是否允许某个 tool side effect;
- 是否允许 resume;
- 是否允许自动继续而不是要求 HITL;
- stream 中某个 internal event 是否可以变成 public business event。
这说明 DecisionAuthority 是业务 transition ownership,而不是某个固定类名。
测试 DecisionAuthority 应该覆盖什么
1. Happy Path
all hard facts valid
→ ALLOW_NORMAL
2. 每个 blocker 的单独测试
new red flag
→ ESCALATE
critical gap unresolved
→ ABSTAIN
governance rejected
→ BLOCK
3. Deny-Overrides 组合测试
confidence HIGH
+ judge score HIGH
+ critical gap unresolved
→ 仍不得 ALLOW_NORMAL
4. Unknown / malformed facts
unknown enum
unknown policy revision
contradictory safety state
→ fail closed
5. Policy Revision Regression
固定一组 facts:
v1 → ABSTAIN
v2 → ESCALATE
如果这是预期变化,就应通过 counterfactual replay / qualification 明确记录,而不是让 regression test 无意中变化。
6. ApplyDecision / Side-effect Test
不要只断言:
decision.outcome == ABSTAIN
还要验证:
ordinary candidate not delivered
escalation/insufficient_information state persisted
DecisionTrace persisted
因为 authority 只有真正影响 side effect 才算完成。
生产事故里怎样判断是 Authority Bug 还是上游 Bug
假设用户看到本不该出现的 candidate。
按顺序检查:
1. Python 是否识别了 critical gap?
2. HTTP/DTO 是否完整传递?
3. Runtime Fact criticalGapCount 是否正确?
4. DecisionPolicy 输出是否正确?
5. ApplyDecision 是否真正抑制了结果?
6. DB / read model / frontend 是否重新旁路了 raw proposal?
只有第 4 步首次偏离时,才是纯 DecisionAuthority bug。
如果第 3 步已经错了,修 policy 反而是在错误 facts 上打补丁。
自测:能回答这些问题才算真正理解
- 为什么
confidence=0.99不能自动越过 critical EvidenceGap? - SafetyEnvelope 和 DecisionAuthority 分别回答什么问题?
- 为什么 hard blocker 不适合跟 confidence 做 weighted average?
- 为什么 DecisionPolicy 应尽量是 pure function?
- Fact Extraction 错误和 DecisionPolicy 错误为什么必须分开定位?
ABSTAIN、ESCALATE、BLOCK为什么不能都压成allowed=false?- 为什么 policy revision 改变通常要形成新 Agent Configuration identity?
- 如果 policy 输出 ABSTAIN,但 API 仍返回 raw candidate,为什么说 authority 实际上没有生效?
- Treatment 为什么需要 generation/acceptance 两个不同 authority checkpoint?
- Fail closed 为什么不等于必须给用户显示一个生硬的内部错误?
常见误解
- 模型更强就可以放宽 Policy:更强模型并不会自动扩大 SafetyEnvelope;需要新 eval 证据与新 configuration qualification。
- DecisionAuthority 就是 Judge:Judge 提供语义质量信号;Authority 执行确定性的业务授权规则。
- Policy 一旦 deterministic 就不能变化:deterministic 指同版本同 facts 下结果稳定,不代表 policy 永不演化。
- 最终结果为空就没必要保留模型 proposal:审计时仍需要知道模型原本提出什么,以及为什么被 authority 抑制。
- 有一个
policy_decision字段就算治理完成:如果它没有控制持久化、交付或实际 side effect,它只是 metadata。 - policy rule 越抽象越高级:规则少且安全关键时,显式、版本化、可审计往往比通用 Rule Engine 更可靠。