操作员审批循环 是做什么的?
与外部各方通信的代理绝不能仅凭自身判断发送消息,并且必须在内部持续告知操作员。此技能定义了合约:每个对外草稿均作为义务提交,由操作员决定确切文本,投递账本证明已发送内容。
何时使用
- 代理在共享频道、电子邮件或聊天中起草对客户、供应商、投资者或合作伙伴的回复,且必须获得人工审批后才能发送。
- 你需要一条审计追踪,将每条已发送消息与确切的草稿文本、批准该消息的操作员以及决策时间关联起来。
- 你遇到过已失效的审批放行了一篇被重写的草稿,或者两个工作者投递了同一份已批准的草稿两次。
- 草稿反复向对方询问账本已持有的信息。
工作原理
对象
| 对象 | 含义 |
|---|---|
| 义务 (Obligation) | 我们欠对方的一件事。状态流转:草稿中,然后 已批准 或 已拒绝,然后 已发送。携带 方向、对方、渠道 和一个 updated_at 时间戳。 |
| 草稿 (Draft) | 存储确切草稿文本、该文本的 sha256 哈希、来源坐标(平台、渠道、线程、用户)和优先级(P0 到 P3)的侧边行。每个义务一个,重新提交时替换。 |
| 决策 (Decision) | 操作员的批准或拒绝,记录操作员 ID、随机数 (nonce) 以及决策所针对的草稿时间戳。 |
| 批准快照 (Approval snapshot) | 由已授权的决策写入者记录的不可变文本、哈希、时间戳和目标。缺少快照无法授权投递。 |
| 声明 (Claim) | 持有随机令牌和状态的持久化预留;每个义务最多一个活跃声明。 |
| 投递 (Delivery) | 证明一次发送或通知对应一个(义务,决策)对的账本行。 |
参考模式见 references/approval-ledger.sql。
提交草稿
- 清理输入。去除控制字符,折叠单行字段中的空白,并强制执行长度限制(草稿文本、摘要、上下文、对方)。拒绝空字段或超限字段,而非静默截断。
- 运行基线门控(见下文)。它可能拒绝提交。
- 使用 sha256 哈希草稿文本。哈希前缀放入摘要,以便审批面板显示正在批准哪个文本。
- 更新插入 (Upsert)。如果同一 (对方, 渠道) 组合已存在一个开放的草稿中义务,则替换草稿侧边行并推进该义务的
updated_at。该推进即时间戳轮换:任何指向旧时间戳的决策都无法再释放新文本。否则,插入一个状态为草稿中的新义务。 - 仅将提交回执路由到配置的、已验证的内部运维目标。如果来源正是该内部目标,则在该处确认。永不静默指内部报告,而非自动的外部回复。保持草稿哈希、审批状态、操作员身份和工作流元数据远离对方可见的渠道。未知或未分类的来源保持静默;直接消息并非自动视为内部消息。
如果已验证的内部目标不可用,则在内部工具结果或操作员界面中保留提交结果。绝不能回退到外部或未知来源。暴露给外部的工具结果不属于内部界面。
提交草稿并不授权外部响应。任何策略允许的澄清问题或中性回应都属于独立的对外决策,需遵守对方-渠道-纪律中已有的提及、渠道、仅草稿、冻结和绝不约束。它不得透露内部审批元数据。
基线门控
在任何草稿提交前,查询该对方的当前基线(时间序列账本、合约存储或 CRM):
- 已签署或已投递的合约记录在案:拒绝提交,附上证据和建议。在签约后询问对方规格正是此门控旨在阻止的确切失败场景。
- 操作员覆盖:
force_despite_signed_contract允许提交通行,并在草稿上下文中打上[BASELINE_OVERRIDE_SIGNED_CONTRACT]标记。 - 门控服务不可达:提交继续进行,并在上下文中打上
[BASELINE_CHECK_UNAVAILABLE]标记。面板将看到此防护已关闭。故障永远不会静默禁用门控。 - 当事实可用时,将最新的几条作为
[BASELINE FACTS: ...]摘要附加到上下文中,以便草稿在提交时掌握当前真相。
决策
审批面板列出状态为 草稿中 且方向为 我方欠对方 的义务。批准或拒绝会写入一个携带草稿时间戳 (draft_updated_ts) 的决策行,并在同一事务中翻转义务状态。时间戳不匹配当前 updated_at 的决策为过期决策,不得释放任何内容。
对于已经授权的批准决策,同一事务会插入一个不可变的 obligation_approval_snapshots 行:包含决策和义务 ID、当前草稿时间戳、确切的文本和 SHA-256、平台/渠道/线程以及类型 draft_sent。决策写入者必须事先建立授权;参考实现从不认证操作员或制造决策。自动审批策略不由参考实现启用或扩展。没有快照的历史决策需要显式协调或重新审批;绝不能从当前可变草稿回溯填充权限。
投递
SQLite 参考实现位于 references/approval_claims.py。它授予投递权限但从不调用传输层。使用从 SQL 固定数据初始化的现有本地参考数据库;该模块不应用模式或生产迁移。只有受信任的决策写入者才能填充审批记录。所有写入者必须启用外键和递归触发器并遵守模式防护;管理性数据库篡改不在本模型范围内。
- 发现已绑定的已批准草稿。发现不等于权限。
claim()打开自己的BEGIN IMMEDIATE事务,验证当前已批准的时间戳、确切文本、计算出的 SHA-256 以及针对快照的完整目标,并在返回其令牌之前插入唯一声明。冲突在传输前阻止工作者。已完成的回执不能再次声明。 begin_dispatch()重新验证绑定,并使用令牌原子地将claimed更改为dispatching。只有获胜的调用者在提交后才能收到确切的draft_text和目标。绝不重新生成文本、为传输重新读取可变侧边行或为另一次尝试重用载荷。嵌套的调用者事务将被拒绝;权限不能依赖后续调用者的提交。数据库事务在传输期间不得保持打开。- 确认的成功结果传递给
complete(),它原子地记录投递坐标,将声明标记为已投递并将义务翻转为已发送。相同的完成是无操作的;冲突的坐标会失败。回执 UNIQUE 键用于去重记录,而非先前的副作用。 - 异常、超时、begin_dispatch 之后工作者死亡或回执持久化失败会留下一个被阻塞的尝试。
mark_unknown()记录不确定性。未知声明永不过期、重新打开、自动重试或允许对该义务的另一个决策绕过它。受信任的调用者可以使用reconcile()和确认成功的坐标及证据;该模块不验证该证据。缺席的回执并非未投递的证据。
保证是每个已批准决策一次自动投递尝试,而非两次外部投递。begin_dispatch 之后、传输之前的崩溃可能导致零次发送和持有声明。为了一次新尝试而释放未知结果需要隔离原始执行者并验证提供者语义;此参考实现明确不提供此类重试操作。
| 声明状态 | 允许的下一状态 |
|---|---|
| claimed | dispatching 或 pre-dispatch 的 cancelled |
| dispatching | delivered 或 unknown |
| unknown | 仅通过受信任协调的 delivered |
| delivered, cancelled | 终态;决策键无法重用 |
声明激活期间,数据库防护冻结义务、草稿和决策的写入,包括替换。快照和声明无法被擦除。重新提交前请使用其令牌取消已声明的操作;随后过期令牌不授予任何权限。投递开始后,为协调保留新编辑或撤销。这将更改序列化而非假装召回进行中的操作。
被拒绝的决策以及没有草稿侧边行/快照的历史行永远不会进入此外部草稿发送路径。在内部操作员界面上报告它们以供手动处理。内部回执页脚保持内部:approved by <operator> · receipt <decision_id> · draft sha256 <prefix>。绝不修改已批准的对外文本以附加工作流元数据。
聚焦的本地验证使用临时数据库、独立连接和模拟尝试计数器,而非提供者或真实消息:
python3 -m unittest discover -s tests/skills -p 'test_approval_delivery_claims.py'。
测试需要 Python 3.11+ 和支持序列化的 SQLite;参考实现仅使用标准库。现有的桌面模式合约检查仍然作为独立的兼容性检查。
定时自动审批(可选)
草稿可能携带 auto_send_after(纪元秒)。一个扫描程序批准截止日期已过且无决策的草稿,记录操作员为 auto-ttl,然后投递通过正常路径进行。操作员操作始终获胜:决策在扫描程序看到之前翻转状态,重新提交轮换时间戳并移动或清除截止日期。扫描程序在写事务内重新检查状态和时间戳,因此竞态条件解析为无操作。没有截止日期的草稿永远保持硬性门控。
信号链接
草稿可以命名其回应的入站义务(signal_obligation_id)。这是延迟测量的唯一真实链接(入站信号到起草响应),并允许 SLA 扫描将该入站项标记为已答复。如果引用的行不存在,则拒绝提交。
示例
提交草稿
file_request(
draft="Thanks, we can hold the slot until Friday. Which start date works?",
counterparty="acme-supplier",
context="reply to delivery window question",
origin_platform="slack", origin_channel="#acme-shared",
origin_thread="1712345678.000100", priority="P1",
signal_obligation_id=412)
-> {obligation_id: 431, draft_sha256: "9f2c...", refiled: false}
配置的、已验证的内部运维目标将看到:
Draft filed for approval (P1, sha 9f2c8a1b). Waiting on operator.
对方可见的来源渠道不会收到任何提交通知。如果没有可用的已验证内部目标,回执将保留在内部工具结果或操作员界面中,不进行外部回退。
引导后重新提交
操作员要求更短的草稿。为相同的 (对方, 渠道) 再次提交返回 refiled: true,侧边行文本和哈希更改,并且 updated_at 前进。点击旧面板行上的批准按钮会携带旧时间戳并被忽略。
门控拒绝
DeskApprovalError: baseline gate refused this draft: the ledger shows a
signed contract for 'acme-supplier'. Evidence: master agreement executed
2026-08-14. Recommendation: do not ask. Re-file with
force_despite_signed_contract=true if this is genuinely a new thread.
内部渠道中的投递页脚
Confirmed for Friday, start date 2026-09-08.
approved by operator-a · receipt 118 · draft sha256 9f2c8a1b2d3e4f50
要测试的不变性
- 提交通知仅发送到配置的、已验证的内部运维;仅当来源是已验证的内部目标时,来源才会收到通知。
- 未知来源保持静默。不可用的内部目标使用内部工具结果或操作员界面,不进行外部回退。
- 相同 (对方, 渠道) 提交两次会产生一个义务和两个时间戳。
- 带有过期时间戳的决策永远不会产生投递行。
- 两个并发声明者会产生一个投递权限;输家永远不会尝试传输。
- 未知结果和回执持久化失败绝不会启用自动重试。
- 成功完成在一次事务中记录回执和已发送状态。
- 被更改的时间戳、文本、哈希或目标无法获取或开始声明。
- 活跃声明阻止重新提交;只有预投递取消才能释放该持有。
- 门控不可用时打上标记;门控签署时在无强制的情况下拒绝。
- Auto-ttl 绝不会针对操作员此后已重新提交的文本触发。