实践部分 3. 项目宪法:首次规则公投
状态:建议。 将 immutable_principles 与 mutable_rules 分离,并明确设置 ttl、max_scope、rollback_condition —— 这是一种稳健的实践。具有确定性平局决胜规则(tie-breaker)的代理自动公投属于前沿方案:其具体形式取决于特定团队和模型。
对于教学通关,手动填写 constitution.md 并检查可变规则是否具备 ttl、max_scope 和 rollback_condition 即已足够。自动公投和外部仲裁属于完整生产路径。
此处的「项目宪法」是指一个版本化的不变量集合、可变规则及修正案通过程序。该集合在执行危险操作前进行校验。
同一个词,两个不同的文件
在第一卷中,项目宪法分布在三个文件中:mission.md(我们为何做这件事)、tech-stack.md(我们用什么技术编写)、roadmap.md(按什么顺序做什么)。该契约描述的是产品。
在第二卷中,同一个词出现了第四个文件 —— constitution.md。它描述的不是产品,而是安全自动化的边界:哪些代理行为被禁止,哪些在限制条件下被允许,谁以及如何修改规则。这是上层补充,而非替代。第一卷的文件保留原位,constitution.md 添加在旁边,并在任何危险操作前被读取。
如果你需要一句话的区分:mission/tech-stack/roadmap 回答「我们在构建什么」,constitution.md 回答「即使代理非常想做,也不能对系统做什么」。
在教学最小化场景中,一个包含两条不可变禁令和一条可变规则的小型 constitution.md 即已足够。代理公投、外部仲裁和剧本自动检查仅在完整路径中需要。本章依赖第一卷第6部分(mission.md、tech-stack.md、roadmap.md 的来源)和第18部分(SDD 流程安全概念的来源)。
阅读前
- 第一卷的支撑:第6部分引入
mission.md、tech-stack.md、roadmap.md;第18部分解释 SDD 安全性。
- 本地教学案例:
node_not_ready,因为它能清晰展示允许的温和操作与危险操作禁令之间的区别。 capstone/的足迹:两个immutable_principles,一个mutable_rule,以及针对high_memory_usage的简短governance_protocol。- 首次通过的主要术语:
immutable_principles和mutable_rules。第三层governance_protocol为参考性质,仅在团队引入投票规则时才需要。 - 可推迟的内容:代理公投、外部仲裁和剧本自动检查。
单独设置生产层的原因很简单:node_not_ready 或 autoscale_200pct 等场景不给你聊天讨论的时间。危险操作必须通过正式网关,或被阻止。该网关的安全性我们将在第18部分. SDD 安全中处理。与现有运行代码的交互关联见第13部分. 现有项目支持。
本章路线图
本章较长,无需线性阅读。行进地图:
- 最小教学场景。 手动填写
constitution.md:两个不可变禁令,一条带六个字段的可变规则。这就是通关所需的全部内容。 - 核心思想。 解释为什么最小场景是这样的设计。
- 通过 Qwen Code 访谈。 加速填写
constitution.md的一种方法。首次通过时可以跳过 —— 手动填写同样有效。 - 完整路径。
governance_protocol、change_log、decision_hash、公投。首次通过时无需阅读:当团队拥有自动网关时,这些内容才会变得必要。
如果你是首次阅读,在「核心思想」部分后停止,去做练习。等用最小片段完成 capstone/constitution.md 后,再回来阅读完整路径。
目标
到本节结束时,你将为自动修复设计一个可用的 constitution.md。不变量和可变规范将变得清晰,通过代理公投演进,并在修改谱系中完全可追溯。
这在实践中的意义。代理不再依据零散的提示行事。它将每个决策与正式的项目契约进行核对。这种契约有助于加速对重复事件的响应,但不会将速度变成自动绕过备份、审计、影响半径(blast radius)和安全回滚条件的权利。
最小教学场景
教学案例
对于 node_not_ready,需要在 triage 阶段允许代理的温和重启,但禁止对生产数据库、备份和审计进行危险操作。目标是获得一个可以在任何生产操作前用肉眼检查的小型 constitution.md。
准备工作
book2/examples/templates/proposal.md—— 修正案表格。- 一份不能自动执行的危险操作清单。
- 一个可成为可变规则的重复性未知模式。
首次通过时可以使用以下起始集:
dangerous_actions:
- delete_namespace
- restart_database
- bypass_audit_trace
candidate_mutable_rule:
incident_type: node_not_ready
pipeline_phase: triage
permitted_actions: ["soft_restart_agent"]
max_scope: "single_node"
ttl: "30d"
rollback_condition: "repeat_incidents_same_node>=2 or Safety veto"
如果你将本章适配到自己的项目,请替换操作名称,但不要移除 max_scope、ttl 和 rollback_condition。
步骤
- 将至少两条
immutable_principles记录为禁令,而非建议。 - 添加一条带
incident_type、pipeline_phase、permitted_actions、max_scope、ttl、rollback_condition的mutable_rule。 - 描述
governance_protocol:角色、法定人数、Safety 否决权和平局决胜规则。 - 填写简短的
proposal.md:规则为何出现、谁投了票、何时激活以及如何回滚。 - 用这个问题检查文件:「即使能降低 MTTR,代理也无法执行什么操作?」*预期:答案应在
immutable_principles中,而非聊天中。* - 与 [
examples/spec-ci/](examples/spec-ci/README.md) 中的可运行类比进行比较:理念相同 —— 操作前必须有可检查的网关。
控制事实
每条可变规则都有 ttl、max_scope 和 rollback_condition。如果缺少任一字段,该规则不得进入宪法,仍作为提案草案。
可用 capstone/ 中的同一个可运行类比检查文件:
cd book2/examples/constitution
python3 scripts/check.py \
--constitution specs/constitution.yaml \
--proposal proposals/valid_proposal.md
获得 verdict: PASS。然后用 proposals/missing_evidence.md 和 proposals/conflict_with_immutable.md 重复 —— 获得 verdict: BLOCK 及原因。这就是危险操作前的教学网关。
迁移到 high_memory_usage
本章的本地案例是 node_not_ready,但计分案例是 high_memory_usage。迁移通过一条原则性表述完成,而非复制规则。
从 node_not_ready 借鉴什么 | 为 high_memory_usage 写入 capstone/constitution.md 的内容 |
|---|---|
| 不可变原则「无连续两次 OK 不得关闭事件」 | 不可变原则「无确认 RSS 连续两次低于 80% 不得关闭 high_memory_usage」 |
可变规则 soft_restart_agent,ttl: 30d,max_scope: single_node | 可变规则 restart_pod,ttl: 14d,max_scope: single_pod,rollback_condition: 5xx_increase OR memory_percent>=90% after 2 windows |
| 扩大影响半径时的 Safety 否决 | 试图将 restart_pod 扩展到命名空间时的 Safety 否决 |
如果这条表述无法形成,说明本地案例尚未产生可迁移的原则 —— 在转入 capstone/ 前请回到该案例。
如何进入 capstone/
将两个 immutable_principles、一个 mutable_rule 和简短的 governance_protocol 移入 capstone/constitution.md。如果你做了 proposal.md,在 capstone/README.md 中留下一行规则出现的原因。如果未运行代理公投,不要移入完整的公投内容。
最小片段:
immutable_principles:
- "不保留 audit_trace 不得执行 auto-remediation。"
- "无确认备份不得触碰 stateful workload。"
mutable_rules:
- incident_type: high_memory_usage
pipeline_phase: recovery
permitted_actions: ["restart_pod"]
max_scope: "single_pod"
ttl: "14d"
rollback_condition: "memory_percent>=90% after 2 windows or 5xx increases"
governance_protocol: "任何 max_scope 的扩展都需要单独的 proposal。"
可审查足迹
在教学包中,将 constitution.md 与 proposal.md 或 change_log 记录一起保存。无出处的修正案不算作 production SDD 的一部分。
核心思想
constitution.md 的第一层是 immutable_principles。immutable_principles(不可变原则)是指永远不能被自动关闭的规则。这包括安全级别的禁令和义务:
- 无当前备份不得重启生产数据库;
- 不得为释放空间而删除备份;
- 无确认稳定不得将事件转入
resolved; - 不得在关键安全命名空间中执行绕过(bypass)。
将这些规范表述为不变量,而非建议。正是它们在压力时刻限制代理,当降低 MTTR 的最短路径可能引发更大级联故障时。
第二层是 mutable_rules。这是具有精确作用半径的可变规范(与 immutable_principles 不同,此类规则可通过正式程序取消或重写)。为每条规则指定至少六个字段。它们的含义:
incident_type—— 规则适用的事件类别;pipeline_phase—— 响应阶段(triage、recovery等);permitted_actions—— 允许的操作列表;
max_scope—— 影响半径限制(例如single_node);ttl(生存时间,time to live)—— 规则有效期,之后需复审或续期;rollback_condition—— 规则自动回滚的条件。
缺少这些字段,修正案将过于宽泛,开始与不变量竞争。比较两个版本。
差:
- id: node_not_ready_soft_restart
incident_type: "NodeNotReady"
permitted_actions: ["soft_restart_agent"]
问题:无 ttl,无 max_scope,无 rollback_condition。规则无限期且无影响边界。半年后无人会复审它。
好:
- id: node_not_ready_soft_restart
incident_type: "NodeNotReady"
pipeline_phase: "triage"
permitted_actions: ["soft_restart_agent"]
max_scope: "single_node"
ttl: "30d"
rollback_condition: "repeat_incidents_same_node>=2 or Safety veto"
例如,允许在 triage 阶段对 incident:NodeNotReady 进行温和重启(soft restart),但禁止在 recovery 阶段对 incident:DBWriteLag 执行。相同操作在不同上下文中对数据和可用性的风险不同。
首次通过的停顿点
如果你首次阅读至此,你已拥有本章通关所需的一切:两个不可变禁令、一条带六个字段的可变规则、理解其与第一卷 mission/tech-stack/roadmap 的区别。接下来是完整路径 —— governance_protocol、change_log、decision_hash、公投。当你的团队拥有自动网关和三四个人类角色时,这些机制才变得必要。对于计分的 capstone/constitution.md,它们不是必需的。
如果你的项目中还没有任何自动操作,直接跳到「产出物与就绪标准」部分,完整路径可在第8章后阅读(届时角色将在文件仲裁中再次出现)。
完整路径:governance_protocol
让演化触发器可观测:同一 pattern_id 发生三次及以上重复不可预测事件将启动强制修正案公投。该阈值将单一异常与规则中的稳定空白区分开,防止每次嘈杂告警后都修改宪法。
固定聚合窗口,例如48小时。在候选修正案(即提交公投的 proposal.md)中包含:
- 输入事件;
- 当前
unknown分类; - 假设风险;
- 预期效果;
- 取消条件。
为什么这里需要多个角色,而第一卷中审查只需一人。在第一卷第16部分中,一个审查者足够,因为审查对象是教学 AgentClinic 中的功能代码。错误代价是重做功能。在 production 场景中,审查对象不同:对实时服务的自动危险操作。错误代价是数据丢失、影响半径扩大、P0 被悄悄关闭。一个审查者无法应对不是因为不够聪明,而是因为人类难以同时兼顾不变量正确性、剧本适用性和 production 风险。因此审查被拆分到不同角色,每个角色有自己狭窄的问题。最小形式下,这仍可以是同一代理的三个模型依次读取同一文件 —— 但它们的问题各不相同。
在 governance_protocol 中描述投票程序。内容包括代理角色、投票权重、法定人数、召集时限和确定性平局解决。最小配置 —— 三个角色:
Verifier(验证器)查找不变量违反;Implementor(实现者)评估剧本适用性;Safety(安全)检查影响半径、隐私和回滚条件。
通过条件设为两票赞成,规则 2 approve + no Safety-veto,触发后15分钟内召集,平局决胜规则 safety-first。在此规则下,Safety 的临界风险总是导致否决。
第四角色协调员(Coordinator)在 governance_protocol 中不投票。它在下一层级 —— 文件仲裁(第8部分)中出现,负责维护 judgment.md 和轮次秩序。在宪法层面,协调员仅将投票结果记录到 change_log。
将每次更新固定为不可变足迹,而非无出处的修改。记录包含:
version和parent_version—— 当前和上一版宪法;reason—— 修正案原因;votes—— 各角色投票结果;
decision_hash—— 决策的加密哈希,可供后续复算验证;incident_context—— 触发事件;activation_time—— 修正案生效时刻;- 仓库内差异(diff)链接。
此类日志将修改历史转化为证据链。如果日后出现「代理为何获得执行某操作的权限」的问题,答案将不在团队聊天中,而在可复现的决策谱系中。
示例与应用
constitution.md 的基本结构可以这样:
immutable_principles:
- id: prod_db_no_restart_without_backup
rule: "Production database restart is forbidden unless a verified backup exists."
enforcement: "always_on"
- id: backups_are_protected_assets
rule: "Backup deletion, overwrite, or quarantine bypass cannot be executed automatically."
enforcement: "always_on"
mutable_rules:
- id: node_not_ready_soft_restart_triage
incident_type: "NodeNotReady"
pipeline_phase: "triage"
permitted_actions:
- "cordon_node"
- "soft_restart_agent"
max_scope: "single_node"
ttl: "30d"
rollback_condition: "increase in repeat incidents or Safety veto"
governance_protocol:
roles:
verifier:
vote_weight: 1
implementor:
vote_weight: 1
safety:
vote_weight: 1
veto: "critical_risk"
quorum: 2
pass_rule: "at_least_2_approve_and_no_safety_veto"
convene_after: "3_unknown_incidents_same_pattern"
convene_within: "15m"
tie_breaker: "safety_first_then_latest_matching_precedent"
change_log:
- version: "1.2.0"
parent_version: "1.1.0"
reason: "3 unknown NodeNotReady incidents in 48h"
votes:
verifier: "approve"
implementor: "approve"
safety: "abstain"
decision_hash: "sha256:..."
activation_time: "2026-05-17T12:00:00Z"
将宪法连接到执行剧本前的网关检查,而非之后。步骤顺序:
- 本地脚本检查
immutable_principles和mutable_rules; - Qwen Code 在规划模式(Plan Mode)下解释风险和空白;
- 执行者获得操作权限。
命令 /constitution 和 /tasks 不是 Qwen Code 的内置命令。如需使用,请将其设为项目自定义命令(project custom commands)。
通过 Qwen Code 访谈
在手动编写 constitution.md 之前,先进行与第一卷第6部分相同的访谈。生产层为经典三元组 mission/tech-stack/roadmap 增加了两个边界,进入这些边界不能没有人类的明确决策:代理有权在无确认情况下执行哪些操作、谁获得否决权、triage 阶段允许的最大影响半径是多少。
请求:
/clear
我们正在扩展 AgentClinic-production 的宪法。
查看 @specs/mission.md、@specs/tech-stack.md、@specs/roadmap.md
和 @specs/playbooks/node-not-ready.md。
创建 @specs/constitution.md 草案,包含三个部分:
- immutable_principles
- mutable_rules
- governance_protocol
在写入磁盘前,向我提出恰好三个分组问题:
1. 代理在 production 中哪些操作永远被禁止(备份、审计、密钥)。
2. 哪些操作可在 triage 阶段委托,附带影响半径限制和生存期(ttl)。
3. 谁投票、谁有否决权、公投按什么阈值召集。
暂时不要写文件。在我回答后展示修改计划,等待批准。
如果 Qwen Code 未经提问直接开始写 YAML,请制止它:
停下。访谈结束前不要写入文件。
先提出三个分组问题,如 QWEN.md 中所述。
人类对访谈的良好回答包含决策,而非愿望:
不可变原则:
禁止任何无验证备份的生产数据库自动重启。
禁止任何自动删除备份、审计和密钥的操作 —— 即使面临释放空间的压力。
事件无连续两次 OK 不得转入 resolved。
可变规则:
在 triage 阶段,NodeNotReady 允许 soft_restart_agent,max_scope=single_node,ttl=30d。
规则回滚条件 —— 同一节点重复两次事件或 Safety 否决。
禁止未经公投扩大 max_scope。
治理:
Verifier、Implementor、Safety 投票。每票权重为 1。
法定人数 —— 2 票赞成且无 Safety 否决。
48 小时内同一 pattern_id 发生三次 unknown 事件后召集公投。
平局决胜规则 —— safety_first。
然后让 Qwen 检查自己的草案:
检查 @specs/constitution.md。
找出表述为建议的不可变规则。
找出缺少 ttl、max_scope 或 rollback_condition 的可变规则。
找出缺少平局决胜规则(tie-breaker)的 governance_protocol。
不要修改文件,返回不符项列表。
此步骤的典型错误:
- 不可变规则写成「尽量避免」—— 这是建议,不是禁令;
- 可变规则缺少
rollback_condition,事件发生时规则变成无限期; governance_protocol未描述平局决胜规则 —— 平局时系统挂起;change_log缺失,宪法的首次变更即失去出处。
仅当通过此清单后,才将草案固定到项目中。在教学 book2/ 中,此步骤不需要 git 命令:重要的是可读的 constitution.md 及规则出处。
> 宪法网关:可运行类比与项目接口。 可运行检查位于 [examples/constitution/](examples/constitution/README.md) —— 它读取宪法 YAML 和带 YAML-frontmatter 的 Markdown(修正案提案),返回 verdict: PASS|BLOCK 和阻止项列表。计分时使用它即已足够。在实际项目中,此网关还补充两步:Qwen Code 规划模式查询,以及剧本 max_scope 对规则 max_scope 的检查。三个步骤作为一个整体执行。
# [runnable] —— 提案对宪法的检查
python3 book2/examples/constitution/scripts/check.py \
--constitution book2/examples/constitution/specs/constitution.yaml \
--proposal book2/examples/constitution/proposals/valid_proposal.md
# [project script] —— 在你的宪法和剧本上执行相同检查
python3 scripts/constitution/check.py \
--constitution specs/constitution.yaml \
--proposal proposals/<your-proposal>.md
# [project script] —— Qwen 规划模式,单独网关
qwen -p "检查 @specs/constitution.yaml 和 @specs/playbooks/node-not-ready.md。
模式:仅规划。说明修复计划是否违反 immutable_principles
以及需要哪些 mutable_rules 才能通过。" \
--approval-mode plan \
--output-format json \
> out/node-not-ready-constitution-review.json
flowchart TD A[事件] --> B[规划模式与脚本检查不可变性] --> C[可变规则检查] --> D[通过 / 阻止] --> E[执行的操作] C --> F[未知事件] --> G[达到未知阈值] --> H[形成提案] --> I[公投] --> J[提交] --> K[新宪法]
公投适合用重放场景训练。三个相同的 unknown 事件创建 proposal.md,代理投票,通过的修正案以决策哈希进入 change_log。
示例:临时规则 hotfix_ticketing_flood 可允许12小时内简化工单路由。条件是存在恢复点(checkpoint)并保留审计不可丢失的不变禁令。通过后检查修正案是否未变成无限期。确认 ttl、rollback_condition、decision_hash 和 activation_time 与变更记录一起可见于仓库。
总结
constitution.md 为自动修复设定了边界:代理可以自适应什么,以及无权为局部优化而关闭什么。
各层在此边界中的角色:
- 不变量保护备份、审计、数据和关键区域;
- 可变层允许针对重复事件细化剧本;
- 代理公投使规则变更可检查;
- 修改谱系保存原因、投票、决策哈希和激活时刻。
接下来,这些规范将在 LLM 对决中接受检验:验证器对阵实现者。
产出物与就绪标准
| 产出物 | 就绪条件 |
|---|---|
specs/constitution.md | 不可变规则表述为禁令而非建议;从文件中能看出即使能改善 MTTR,代理也无法执行什么操作 |
一条 mutable_rule | incident_type、pipeline_phase、permitted_actions、max_scope、ttl、rollback_condition 六个字段全部填写 |
proposal.md 或规则出处记录 | 修正案有出现原因,否则不属于 SDD 包的一部分 |
完整路径添加若干不同影响半径的修正案、带 parent_version、投票和 decision_hash 的 change_log 记录,以及在执行剧本前检查宪法的项目网关。其就绪标准是:网关确实能在执行前阻止违反不可变层的行为,公投有法定人数、Safety 否决权和确定性平局决胜规则,通过的修正案可从事件上下文追溯到差异(diff)。
实践
- 打开
book2/examples/templates/proposal.md,为一个危险领域(部署、迁移、事件、密钥)填写:至少两条immutable_principles(作为禁令)和一条带incident_type、pipeline_phase、permitted_actions、max_scope、ttl、rollback_condition的mutable_rule。预期:每条可变规则包含全部六个字段;教学场景的控制事实得到满足。 - 记录
governance_protocol:角色、法定人数、Safety 否决权和平局决胜规则;提出问题「即使能降低 MTTR,代理也无法执行什么操作?」*预期:答案在immutable_principles中,而非聊天中。*
- 将两个
immutable_principles、一条mutable_rule和简短governance_protocol移入capstone/constitution.md(格式见本章教学场景片段);如果运行过proposal.md,在capstone/README.md中添加一行规则出现的原因。预期:无出处的修正案不进入 SDD 包。
自测题
- 项目规则中哪些应是不可变的,哪些不应是?
- 为什么可变规则需要
ttl,没有它一年后会发生什么? - Safety 否决时应发生什么,为什么不能用两个
approve绕过它? - 48 小时内三次相同的
NodeNotReady触发了公投,但此时正有活跃事件进行。你会接受、推迟还是回滚该修正案?