#企业智能体工程体系v1.1| 企业智能体工程卷 · 第1期
技能即契约——把能力做成可检查的声明
作者:技术治理研究组
系列:企业智能体工程卷(发布版 v1.1)
主案例:CASE-CR-0042(信用提额申请)
本集对象:SkillContract · AuditEvent
核心协议:P2 契约 vs 权限
适合读者:架构师、技术负责人、AI 产品经理、企业级 Agent 开发者
📌 本文档声明
- 性质:本文为企业智能体工程化设计参考框架的第 1 期,聚焦 Agent 能力边界的契约化设计,提供架构思路与教学级示意代码,不构成生产级实现方案或法律合规意见。
- 证据锚定:文中案例(CASE-CR-0042)为教学示意,不对应任何真实客户系统。
- 系列定位:本篇在第 0 期(企业公民 · 身份与审计)基础上,引入 SkillContract 对象与 P2 协议,后续各期将进一步叠加 DecisionRecord、ToolSpec、MemoryItem 等对象。
摘要
在第 0 期中,我们建立了 Agent 的身份(Identity) 与审计(AuditEvent) 基础——明确了“谁在行动”和“留下了什么痕迹”。但仅有身份和审计还远远不够:我们仍然不知道 Agent“凭什么”能做某件事。
本期回答一个核心问题:
如何让 Agent 的能力边界从“提示词里的口头约束”变成“调用前可检查的硬契约”?
CASE-CR-0042 中,客服“口头禁止”改额度、数据 Agent“理论上”不该缓存证件号——这些边界靠自觉,不靠系统。本期引入 SkillContract(技能契约) 对象,将每个 Agent 的能力、副作用、前置授权、信任等级声明为可编程检查的结构化契约,并通过 P2 协议 解决契约与权限系统的冲突裁决问题。
一句话核心:契约不是写在提示词里的“客气话”,是挂在调用路径上的“硬约束”。
1. 问题:为什么“口头禁止”永远拦不住
1.1 CASE-CR-0042 的真实困境
在 CASE-CR-0042 的链路中,三个角色有不同的能力边界:
| 角色 | “应该”能做的事 | “不应该”能做的事 |
|---|---|---|
| 客服(support.intake) | 建单、澄清信息、转交工单 | 不能改额度 |
| 数据(data.credit) | 只读查询信用快照 | 不能裁决额度,不能写业务系统 |
| 财务(finance.limit) | 提额裁决 | 不能绕过四轴直接通过 |
但在传统的 Agent 实现中,这些边界靠什么保障?
| 保障方式 | 可靠性 | 问题 |
|---|---|---|
| 提示词约束 | ❌ 极低 | “你是一个客服,不能改额度”——越狱/注入可绕过 |
| 代码注释 | ❌ 极低 | 没人读注释,维护者可能不知道 |
| 口头约定 | ❌ 极低 | 新人不知道,赶进度时“顺手”就破了 |
| API 权限(仅靠 IAM) | ⚠️ 中等 | 只能管“能不能调”,管不了“调了干什么” |
结论:无契约技能 = 边界靠自觉。企业公民不靠自觉。
1.2 生产事故的常见前奏
“顺手”模式:
客服看到额度字段 → “顺手”改了一下 → 测试没发现 → 上线后出了事故
事后复盘:
“我以为只有财务才能调用……不对啊,为什么客服的 Agent 有这个权限?”
P2 协议要解决的核心问题:契约与权限两张皮,是生产事故的标配前奏。
2. SkillContract:能力即对象
2.1 什么是 SkillContract
SkillContract
├── skill_id # 唯一标识,如 "limit.decide"
├── role # 允许执行的角色
├── capabilities # 具体能做什么(只读/可写/可裁决)
├── preconditions # 调用前需要哪些授权(grants)
├── side_effects # 允许的副作用集合(如 limit.write)
├── trust_required # 信任等级:0 low / 1 mid / 2 high
└── evidence_ref # 设计/测试依据的追溯编号
核心理念:技能不是松散的自然语言描述,而是调用前可 assert 的结构化对象。
2.2 为什么这比提示词可靠
| 维度 | 提示词约束 | SkillContract |
|---|---|---|
| 可检查 | ❌ 运行时无法自动验证 | ✅ 调用前断言检查 |
| 可审计 | ❌ 自然语言无结构化痕迹 | ✅ 每次检查都有审计事件 |
| 可测试 | ❌ 无法单元测试 | ✅ 契约即测试规格 |
| 可追溯 | ❌ 改提示词不留痕 | ✅ Git 变更可追溯 |
| 可组合 | ❌ 难以叠加 | ✅ 可组合多个契约 |
3. CASE-CR-0042 的三项契约
以下将三个角色的能力声明为结构化的 SkillContract:
| skill_id | role | capability | side_effects | trust | preconditions |
|---|---|---|---|---|---|
case.accept | support.intake | 建单、澄清、转交 | 无写额度 | 1 | case:write |
credit.snapshot | data.credit | 读信用快照 | 无写 | 1 | credit:read |
limit.decide | finance.limit | 提额裁决 | limit.write | 2 | limit:write |
关键观察:
- 客服没有
limit.decide契约——不是“提示词里不许”,而是“对象里没有” - 财务的
limit.decide显式声明了副作用limit.write——调用前系统会检查 - 信任等级 2(高信任)对应了财务裁决的高风险操作
4. 协议 P2:契约 vs 权限——以更严者为准
4.1 两张皮的问题
运行时同时存在两套控制体系:
| 控制体系 | 来源 | 管什么 |
|---|---|---|
| 契约(SkillContract) | 设计阶段声明 | 能力边界、副作用、信任等级 |
| 权限(Grants) | 身份系统授予 | 具体资源/操作的访问权限 |
问题:两者可能不一致。
| 不一致场景 | 后果 |
|---|---|
| 契约说只读,权限给了写 | Agent 可能“越权”执行未声明操作 |
| 权限不足,契约允许 | Agent 无法完成本职工作 |
| 两者冲突且都有效 | 系统陷入不确定性 |
4.2 P2 裁决规则
P2 裁决(契约 vs 权限):
1. 以更严者为准:契约和权限的交集决定实际能力
2. 若契约说只读、权限却给了写 → 拒绝调用,记缺陷「权限过宽」
3. 若权限不足、契约允许 → 拒绝调用,记「授权不足」
4. 禁止“先执行、后补授权/补契约”
5. 上线前须证明契约与权限矩阵一致(联动第8期 Assurance)
4.3 P2 在 CASE-CR-0042 中的应用
| 场景 | 契约 | 权限 | P2 裁决 |
|---|---|---|---|
| 客服受理工单 | case.accept 允许 | 有 case:write | ✅ 允许 |
| 客服“顺手”改额度 | 无 limit.decide | 有 limit:write | ❌ 拒绝(契约不存在) |
| 数据拉信报 | credit.snapshot 只读 | 有 limit:write(过宽) | ❌ 拒绝(P2:权限宽于契约) |
| 财务裁决提额 | limit.decide 需 limit:write | 有 limit:write | ✅ 允许(需配合四轴,见第2期) |
5. 契约生命周期
定义
→ 授信(assign trust)
→ 注册(register to skill registry)
→ 调用前检查(P2)
→ 审计(AuditEvent 记录)
→ 有证据演化(基于反馈更新)
6. 最小代码:三项契约 + P2 实现
以下为教学级示意代码,展示 SkillContract 的定义与 P2 裁决逻辑:
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class SkillContract:
"""技能契约:能力边界的结构化声明。"""
skill_id: str
role: str
capabilities: frozenset[str]
side_effects: frozenset[str]
trust_required: int
preconditions: frozenset[str]
@dataclass
class CallContext:
"""调用上下文:包含调用者的身份信息与权限。"""
role: str
grants: set[str]
trust_level: int
requested_effects: set[str]
class ContractViolation(Exception):
"""契约违反异常。"""
pass
# ===== 注册三项契约 =====
SKILLS = {
"case.accept": SkillContract(
skill_id="case.accept",
role="support.intake",
capabilities=frozenset({"accept", "handoff"}),
side_effects=frozenset(), # 无写额度
trust_required=1,
preconditions=frozenset({"case:write"}),
),
"credit.snapshot": SkillContract(
skill_id="credit.snapshot",
role="data.credit",
capabilities=frozenset({"read_snapshot"}),
side_effects=frozenset(), # 只读
trust_required=1,
preconditions=frozenset({"credit:read"}),
),
"limit.decide": SkillContract(
skill_id="limit.decide",
role="finance.limit",
capabilities=frozenset({"decide_limit"}),
side_effects=frozenset({"limit.write"}),
trust_required=2,
preconditions=frozenset({"limit:write"}),
),
}
# ===== P2 裁决引擎 =====
def assert_p2(skill: SkillContract, ctx: CallContext) -> None:
"""P2: 契约与权限取更严者;不一致则拒绝。"""
# 1. 角色匹配
if ctx.role != skill.role:
raise ContractViolation(
f"角色不匹配: ctx={ctx.role} skill={skill.role}"
)
# 2. 前置授权检查(权限是否满足契约要求)
missing = skill.preconditions - ctx.grants
if missing:
raise ContractViolation(
f"授权不足: {sorted(missing)}"
)
# 3. 权限不得宽于契约副作用(P2 核心)
# 如果权限中有写额度,但契约未声明 limit.write → 拒绝
if "limit:write" in ctx.grants and "limit.write" not in skill.side_effects:
raise ContractViolation(
"P2: 权限含 limit:write 但契约未声明 limit.write"
)
# 4. 信任等级检查
if ctx.trust_level < skill.trust_required:
raise ContractViolation(
f"信任不足: 需要 {skill.trust_required}, 当前 {ctx.trust_level}"
)
# 5. 请求的副作用必须在契约声明范围内
illegal = ctx.requested_effects - skill.side_effects
if illegal:
raise ContractViolation(
f"未声明副作用: {sorted(illegal)}"
)
def invoke(skill_id: str, ctx: CallContext, action: str) -> str:
"""调用一个技能(带 P2 检查)。"""
skill = SKILLS[skill_id]
assert_p2(skill, ctx)
return f"OK {skill_id} @ CASE-CR-0042 :: {action}"
# ===== 测试用例:CASE-CR-0042 =====
if __name__ == "__main__":
print("=== 场景1:客服正常受理 ===")
ctx_ok = CallContext(
role="support.intake",
grants={"case:write"},
trust_level=1,
requested_effects=set(),
)
print(invoke("case.accept", ctx_ok, "受理 T-CR-0042"))
print("\n=== 场景2:客服试图走提额技能(角色不匹配) ===")
try:
invoke(
"limit.decide",
CallContext(
role="support.intake",
grants={"limit:write"},
trust_level=2,
requested_effects={"limit.write"},
),
"改额度",
)
except ContractViolation as e:
print("拦截:", e)
print("\n=== 场景3:数据角色权限过宽(P2 拦截) ===")
try:
invoke(
"credit.snapshot",
CallContext(
role="data.credit",
grants={"credit:read", "limit:write"}, # 多了一个不该有的权限
trust_level=1,
requested_effects=set(),
),
"拉信报",
)
except ContractViolation as e:
print("P2 拦截:", e)
print("\n=== 场景4:财务正常裁决(需四轴,见第2期) ===")
ctx_finance = CallContext(
role="finance.limit",
grants={"limit:write"},
trust_level=2,
requested_effects={"limit.write"},
)
print(invoke("limit.decide", ctx_finance, "提额至 120000"))
运行输出:
=== 场景1:客服正常受理 ===
OK case.accept @ CASE-CR-0042 :: 受理 T-CR-0042
=== 场景2:客服试图走提额技能(角色不匹配) ===
拦截: 角色不匹配: ctx=support.intake skill=finance.limit
=== 场景3:数据角色权限过宽(P2 拦截) ===
P2 拦截: P2: 权限含 limit:write 但契约未声明 limit.write
=== 场景4:财务正常裁决(需四轴,见第2期) ===
OK limit.decide @ CASE-CR-0042 :: 提额至 120000
代码要点:
| 场景 | 契约 | 权限 | 结果 |
|---|---|---|---|
| 客服受理 | ✅ | ✅ | 通过 |
| 客服改额度 | ❌(无此契约) | ✅ | 拒绝:角色不匹配 |
| 数据拉信报 | ✅(只读) | ⚠️(过宽) | 拒绝:P2 拦截 |
| 财务裁决 | ✅ | ✅ | 通过(需叠加四轴) |
7. 三个教训
基于 CASE-CR-0042 的设计经验:
| 教训 | 含义 | 证据 |
|---|---|---|
| 契约必须挂在调用路径上 | 不能只在文档/注释里写,必须由调用前检查强制执行 | 第 6 节代码中的 assert_p2() |
| 改契约 = 改边界 | 变更契约需要证据与回归测试,不能“改个提示词就上线” | 联动第 8 期 Assurance |
| P2 两张皮是事故前奏 | 契约与权限不一致时,系统进入不确定状态,必须拒绝 | 场景 3 展示 |
核心推论:如果 CASE-CR-0042 上线前只做了权限配置(IAM),没做契约声明(SkillContract),那么“客服顺手改额度”的边界是不存在的——权限系统只知道“客服能调用写 API”,不知道“客服不该调用提额 API”。
8. 思考题
以下问题供团队内部讨论,帮助将 SkillContract 概念落地到具体场景:
-
契约盘点:在 CASE-CR-0042 上,还有哪些“口头禁止”或“隐含边界”尚未写成 SkillContract?例如:数据 Agent 能否缓存证件号?客服能否查看其他客户的工单?
-
特批流程:若业务临时要求客服“特批改额度”(紧急场景),按 P2 协议应走什么流程?提示:不应是“改提示词”,而应是“申请临时契约 + 双人复核 + 8h 回滚”(参见总览 P4 Hotfix-8h)。
-
权限过宽排查:场景 3 中,数据角色被授予了
limit:write权限,但契约只读。这种“权限过宽”在生产中可能通过什么途径产生?如何预防?
9. 下期预告
第 2 期:决策四轴(Decision Axes)
财务在 limit.decide 内用四轴(目标轴、约束轴、价值轴、后果轴)做提额裁决,引入 P1 协议(任一轴失败默认拒绝)。本期将 SkillContract + 四轴 + P1 组合为完整的财务提额决策链路。
10. 延伸阅读
| 资源 | 说明 |
|---|---|
| Contractual Skills: A GovernSpec Design Framework for Enterprise AI Agents(arXiv:2605.22634) | 技能契约设计的学术框架 |
| 本卷第 0 期:企业公民——Identity + AuditEvent | 身份与审计基础 |
| 本卷总览:冲突与例外协议 P1–P5 | P2 在五条协议中的位置 |
| 本卷第 2 期:决策四轴(预告) | P1 + 四轴裁决 |
| 本卷第 8 期:落地验收(预告) | AssuranceReport + 上线前契约与权限一致性证明 |
本文是「企业智能体工程卷」十期专栏的第 1 期。技能即契约——把能力做成可检查的声明,让 Agent 的边界从“口头约束”变成“系统强制”。欢迎转载,请注明出处与原文标题。
1276

被折叠的 条评论
为什么被折叠?



