生产级Agent(22):策略即代码与Policy Simulation
文章摘要
前二十一篇已经把生产级 Agent 的运行、权限、审计、回放、SLO 和多租户隔离逐层搭起来。
到了这一阶段,平台已经有越来越多“规则”:
哪些模型能用
哪些Tool能调
哪些租户能访问哪些数据
什么动作需要审批
什么情况下要Fail Closed
哪个Artifact允许外发
哪个Sandbox可以联网
Error Budget烧到多少要冻结发布
如果这些规则散落在:
System Prompt
Java if/else
数据库配置
管理员后台
Kubernetes YAML
审批流程
真正出问题时,团队很难回答:
当时为什么允许?
哪条规则生效?
如果改规则,会影响多少Run?
这次Policy变更会不会把某个租户直接打挂?
所以 Agent Control Plane 下一步必须把“规则”从业务代码里抽出来,变成:
Policy-as-Code
再进一步,在真正生效前运行:
Policy Simulation
也就是拿历史真实 Run、当前配置和候选规则做离线重放,先看“如果昨天就用了新规则,会发生什么”。
这一篇直接把策略模型、版本、决策日志、影子评估、Impact Analysis、Historical Replay、Canary、Break Glass 和发布门禁串成一套。
一、为什么Agent平台特别容易Policy爆炸
一个普通 API 权限可能只是:
Role → Permission
Agent 决策通常同时看:
User
Tenant
Agent
Purpose
Capability
Resource
Data Class
Risk
Approval
Model
Region
Budget
Time
例如:
legal-agent
想调用:
document.export
真正决策可能是:
用户属于Matter吗?
Artifact是不是Privileged?
目标是不是外部邮箱?
当前租户允许外发吗?
是否有审批?
审批绑定的Action Hash一致吗?
如果全部写在一个 Service 里:
if (...) {
if (...) {
if (...) {
}
}
}
半年后没人敢改。
二、Policy首先必须独立版本化
定义:
public record PolicyBundle(
String policyId,
String version,
String contentHash,
PolicyStatus status,
Instant createdAt,
String createdBy) {
}
状态:
public enum PolicyStatus {
DRAFT,
VALIDATED,
SHADOW,
CANARY,
ACTIVE,
RETIRED
}
不要允许:
编辑ACTIVE Policy并原地保存
每次修改都生成新版本。
三、Policy输入必须结构化
不要把整个 Prompt 丢给另一个 LLM:
“你觉得这次允许吗?”
真正 Policy 输入应该是确定性对象。
public record PolicyInput(
String tenantId,
String subjectId,
String agentId,
String purpose,
String capability,
String resourceId,
DataClass dataClass,
RiskLevel risk,
String modelId,
String region,
String approvalId,
BigDecimal estimatedCost) {
}
模型可以帮助提取 Purpose。
最终 Policy 不直接依赖自然语言。
四、Decision至少不是boolean
public enum PolicyDecision {
ALLOW,
DENY,
REQUIRE_APPROVAL,
REQUIRE_STRONG_AUTH,
REQUIRE_HUMAN_REVIEW
}
生产系统里:
允许 / 禁止
通常太粗。
例如:
email.send
可以允许,但需要:
Action-bound Approval
这就不是 DENY。
五、Decision还必须给出Reason Code
public record PolicyResult(
PolicyDecision decision,
List reasonCodes,
String policyId,
String policyVersion,
String inputHash) {
}
例如:
{
"decision": "DENY",
"reasonCodes": [
"CROSS_TENANT_RESOURCE",
"EXTERNAL_EGRESS_BLOCKED"
],
"policyVersion": "v42"
}
以后排障不需要重新猜。
六、Policy定义最好声明输入和输出
例如 YAML:
policy: external-send
version: v42
match:
capability:
- email.send
- messages.send
rules:
- when:
data_class: RESTRICTED
destination: EXTERNAL
decision: DENY
reason: restricted_external_egress
- when:
risk: HIGH
decision: REQUIRE_APPROVAL
- otherwise:
decision: ALLOW
真实实现可以使用:
OPA/Rego
Cedar
自研DSL
关键不是语言。
关键是:
Policy是数据和代码资产
而不是散落逻辑。
七、Policy-as-Code必须进Git
目录:
policies/
├── model/
├── capability/
├── data-egress/
├── tenant/
├── sandbox/
└── release/
例如:
policies/data-egress/external-send-v42.yaml
每次变更:
PR
→ Review
→ CI
→ Simulation
→ Canary
→ Active
和代码发布一样。
八、Policy CI第一层:Schema Validation
最简单但必须有。
例如:
Decision拼错
Reason缺失
Risk Enum非法
直接在 CI 拒绝。
policy_ci:
schema_validation: required
duplicate_rule_check: required
unreachable_rule_check: required
九、第二层:Static Conflict Detection
两个规则:
Rule A:
HIGH → DENY
Rule B:
HIGH → ALLOW
如果优先级没有明确:
Policy本身就不确定
CI 要发现:
Overlapping Rules
Conflicting Outcome
十、第三层:Golden Policy Tests
每条高风险 Policy 都要有固定测试。
例如:
cases:
- name: restricted_external_send
input:
data_class: RESTRICTED
destination: EXTERNAL
expect: DENY
- name: high_risk_send
input:
data_class: INTERNAL
risk: HIGH
expect: REQUIRE_APPROVAL
Policy 修改后自动回归。
十一、但Golden Case远远不够
真正危险的是:
规则本身看起来对
但对真实流量影响巨大
例如:
新的Region Policy
可能误伤 30% 客户。
所以需要:
Policy Simulation
十二、Simulation到底模拟什么
候选:
policy-v43
不要立刻 Active。
拿过去 7 天真实 Decision Input:
100万条
重新评估:
Current Policy v42
vs
Candidate v43
然后比较:
Decision Diff
十三、Simulation不需要重跑LLM
Policy 输入已经结构化。
所以只重放:
Decision Input
而不是整个 Agent Run。
成本非常低。
数据模型:
public record PolicySimulationCase(
String decisionId,
PolicyInput input,
PolicyResult baseline,
PolicyResult candidate) {
}
十四、最核心指标:Decision Flip
ALLOW → DENY
DENY → ALLOW
ALLOW → REQUIRE_APPROVAL
REQUIRE_APPROVAL → ALLOW
这四种变化风险完全不同。
十五、我会给Flip分类
public enum DecisionFlipType {
MORE_RESTRICTIVE,
LESS_RESTRICTIVE,
APPROVAL_ADDED,
APPROVAL_REMOVED,
NO_CHANGE
}
尤其:
DENY → ALLOW
必须重点 Review。
因为它在扩大权限面。
十六、Simulation报告至少输出
{
"baseline": "v42",
"candidate": "v43",
"total_cases": 1000000,
"changed": 18291,
"more_restrictive": 17200,
"less_restrictive": 1091,
"approval_added": 8200,
"approval_removed": 91
}
单看:
changed=1.8%
不够。
必须知道变化方向。
十七、还要按Slice看
总体只变化 1%。
但:
Legal Tenant:
变化 38%
就很危险。
Slice 至少:
Tenant Tier
Risk
Capability
Data Class
Agent
Region
十八、不要全组合Slice
否则样本爆炸。
只看业务关键:
HIGH_RISK
RESTRICTED_DATA
EXTERNAL_SEND
FINANCE
LEGAL
这些 Slice 单独 Gate。
十九、Simulation要计算影响业务对象
不只是 Decision 数。
还要:
涉及多少用户?
多少Agent?
多少Tenant?
多少Scheduled Task?
多少关键Workflow?
例如:
{
"tenants_impacted": 39,
"agents_impacted": 112,
"critical_workflows_impacted": 4
}
这才方便 Release Review。
二十、Policy Input必须长期可回放
每次生产 Decision 都保存:
input_hash
policy_version
decision
reason
完整 Input 可以加密保存。
例如:
public record PolicyDecisionRecord(
String decisionId,
String runId,
String tenantId,
String inputRef,
String inputHash,
String policyVersion,
PolicyDecision decision,
List reasons,
Instant occurredAt) {
}
没有历史输入,就无法真正 Simulation。
二十一、Privacy要注意
Policy Input 可能包含:
Resource ID
Subject
Tenant
Data Class
不一定要保存原始业务内容。
策略评估应该尽量依赖:
Metadata
而不是完整 Prompt。
这也会让 Simulation 更安全。
二十二、Shadow Policy
Simulation看的是历史。
下一步:
Shadow
Candidate v43 在真实线上请求中同时计算:
但不执行
Active v42:
真正决定
Shadow v43:
只记录结果
二十三、Shadow可以发现历史数据没覆盖的新模式
比如上线后出现:
新Capability
新Tenant类型
新Region
Simulation 数据里根本没有。
Shadow 能看到。
二十四、Shadow结果不能影响用户
即使 Candidate:
DENY
只记录。
不要在 Shadow 阶段偷偷改变:
Latency
Approval
Tool
否则不是真 Shadow。
二十五、Shadow Metrics
policy_shadow_total
policy_shadow_flip_total
policy_shadow_less_restrictive_total
policy_shadow_error_total
再看:
Evaluation Latency
Policy 本身也不能把 Agent Tool Call 拖慢几百毫秒。
二十六、Policy性能也有SLO
例如:
P95 Decision
Tenant Restriction
>
Enterprise Base
>
Exception
但 Exception 是否能覆盖 Tenant Restriction,要明确。
我一般建议:
Exception只能放宽可放宽的Policy
不能覆盖:
Zero Tolerance
Cross Tenant
Legal Hold
四十八、Policy层级不要动态由模型决定
模型不能说:
“这个情况特殊,我认为Exception优先。”
优先级是确定性系统规则。
四十九、Policy Simulation还可以做“反事实事故分析”
发生事故:
某个Agent错误外发
你准备新 Policy:
v44
可以拿事故 Run:
Replay Input
问:
如果当时是v44,
会不会挡住?
这就是非常直接的 Regression Evidence。
五十、每个事故最终应该沉淀Policy Regression Case
例如:
incident: INC-2026-081
input:
data_class: CONFIDENTIAL
destination: EXTERNAL
capability: email.send
expected:
decision: DENY
以后任何 Policy Candidate 都跑。
五十一、Policy Dataset不要只来自事故
还要有:
Normal Cases
Edge Cases
Synthetic Cases
Abuse Cases
否则 Policy 会过拟合历史事故。
五十二、Property-based Policy Test
例如不变量:
任何CrossTenant Resource
永远不能ALLOW
随机生成:
Tenant A
Tenant B
Resource
Capability
断言:
assertNotEquals(
PolicyDecision.ALLOW,
evaluate(crossTenantInput));
这比只写几个固定样例强。
五十三、Policy Coverage也要统计
有多少生产 Decision:
命中了显式规则
多少落到:
default
如果:
default hit = 60%
说明 Policy 设计过于粗糙。
五十四、我会看Default Decision Rate
default_decision_total
/
all_policy_decisions
目标应该逐步下降。
特别是高风险 Capability:
最好0%
每种情况都有显式规则。
五十五、Unknown Input不能默认Allow
新 Data Class:
TOP_SECRET
旧 Policy 不认识。
最危险:
else → ALLOW
更合理:
Unknown
→ DENY / REVIEW
Policy Schema 演进要采用:
Secure Default
五十六、Policy版本和Run必须绑定
Run 中途 Policy 升级怎么办?
一般有两种:
Snapshot Policy
Run 创建时固定:
v42
整个 Run 使用 v42。
适合:
低风险长任务
Dynamic Policy
每个高风险 Action:
读取当前Active
适合:
安全写操作
两者不能混得不清楚。
五十七、高风险Action我建议Dynamic Re-evaluate
即使 Run 开始时:
允许email.send
半小时后 Security 禁掉。
真正执行前应该重新评估:
current policy
不要因为 Run Snapshot 还允许就继续写。
五十八、但Replay必须知道当时版本
Audit 保存:
Run Policy Snapshot
Action Policy Version
事故重放时才能还原。
五十九、Policy Rollback也要一键
Candidate v43 出现问题:
切回v42
不能重新编辑一份“像v42”的文件。
Active Pointer:
policy_id → version
回滚只切 Pointer。
六十、Rollback前也要注意新产生的数据
如果 v43 已经允许了一批新操作:
回滚Policy
不会自动撤销历史副作用。
所以 Release Report 需要:
affected_runs
side_effects
方便补救。
六十一、Policy Observability
指标:
policy_decision_total{
policy,
version,
decision
}
policy_flip_total{
type
}
policy_eval_latency_ms
policy_default_total
policy_exception_total
policy_break_glass_total
高基数 Tenant 不一定放 Metric。
细节进 Trace / DB。
六十二、Policy SLO
P95 Decision slices,
List criticalCases) {
}
六十九、Release Gate接口
public interface PolicyReleaseGate {
ReleaseDecision evaluate(
PolicySimulationReport simulation,
ShadowReport shadow,
PolicyChangeRisk risk);
}
Policy 本身也进入 Control Plane。
七十、本篇上线检查清单
□ Policy独立于Prompt和业务代码
□ 每次修改生成新Version
□ Policy输入结构化
□ Decision不是只有Allow/Deny
□ 每次Decision记录Reason Code
□ Policy进入Git和PR
□ CI做Schema和Conflict检查
□ 高风险Policy有Golden Cases
□ Candidate上线前做历史Simulation
□ Simulation区分权限扩大和收紧
□ 关键Tenant/Risk做Slice分析
□ Candidate支持Shadow
□ Shadow不影响真实决策
□ Policy有P95性能SLO
□ 高风险变更走Canary
□ Exception有Owner/Reason/Expiry
□ Break Glass独立治理
□ Policy失败模式提前定义
□ Emergency Deny能绕过普通Cache
□ Decision Trace可解释
□ Incident沉淀Regression Case
□ Unknown Input默认安全
□ 高风险Action执行前重新评估
□ Active Version可一键Rollback
总结
Agent 平台发展到一定规模以后,真正难维护的往往不再是 Prompt。
而是:
“到底哪些事情允许发生。”
如果这些规则散落在几十个 Service、管理员页面和配置文件里,系统越自动化,风险越难预测。
Policy-as-Code 解决的是:
规则可版本、可Review、可审计
Policy Simulation 解决的是:
改规则之前,
先知道它会改变什么
Shadow 和 Canary 再解决:
历史上没见过的新流量怎么办
做到这一层以后,Agent 权限和风险治理才从:
“配置管理”
真正升级成:
可测试的软件工程。
下一篇继续推进:
生产级Agent(23):Agent配置供应链——模型、Prompt、Skill、Tool与Policy如何做签名发布和可信加载。
更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界:
https://www.zyentor.com/