#企业智能体工程体系v1.1| 企业智能体工程卷 · 第1期· 技能即契约——把能力做成可检查的声明

#企业智能体工程体系v1.1| 企业智能体工程卷 · 第1期

技能即契约——把能力做成可检查的声明

作者:技术治理研究组
系列:企业智能体工程卷(发布版 v1.1)
主案例:CASE-CR-0042(信用提额申请)
本集对象:SkillContract · AuditEvent
核心协议:P2 契约 vs 权限
适合读者:架构师、技术负责人、AI 产品经理、企业级 Agent 开发者

📌 本文档声明

  1. 性质:本文为企业智能体工程化设计参考框架的第 1 期,聚焦 Agent 能力边界的契约化设计,提供架构思路与教学级示意代码,不构成生产级实现方案或法律合规意见。
  2. 证据锚定:文中案例(CASE-CR-0042)为教学示意,不对应任何真实客户系统。
  3. 系列定位:本篇在第 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_idrolecapabilityside_effectstrustpreconditions
case.acceptsupport.intake建单、澄清、转交无写额度1case:write
credit.snapshotdata.credit读信用快照无写1credit:read
limit.decidefinance.limit提额裁决limit.write2limit: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.decidelimit:write❌ 拒绝(契约不存在)
数据拉信报credit.snapshot 只读limit:write(过宽)❌ 拒绝(P2:权限宽于契约)
财务裁决提额limit.decidelimit:writelimit: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 概念落地到具体场景:

  1. 契约盘点:在 CASE-CR-0042 上,还有哪些“口头禁止”或“隐含边界”尚未写成 SkillContract?例如:数据 Agent 能否缓存证件号?客服能否查看其他客户的工单?

  2. 特批流程:若业务临时要求客服“特批改额度”(紧急场景),按 P2 协议应走什么流程?提示:不应是“改提示词”,而应是“申请临时契约 + 双人复核 + 8h 回滚”(参见总览 P4 Hotfix-8h)。

  3. 权限过宽排查:场景 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–P5P2 在五条协议中的位置
本卷第 2 期:决策四轴(预告)P1 + 四轴裁决
本卷第 8 期:落地验收(预告)AssuranceReport + 上线前契约与权限一致性证明

本文是「企业智能体工程卷」十期专栏的第 1 期。技能即契约——把能力做成可检查的声明,让 Agent 的边界从“口头约束”变成“系统强制”。欢迎转载,请注明出处与原文标题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TunerT_TQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值