操作员审批循环 是做什么的?
与外部方对话的代理绝不应仅凭自身判断发送消息,而应在内部保持操作员知情。此技能定义了合约:每个出站草稿都作为一项义务提交,操作员决定确切文本,传递账本证明实际发送的内容。
何时使用
- 代理在共享频道、电子邮件或聊天中起草对客户、供应商、投资者或合作伙伴的回复,且必须由人类批准后才能发送。
- 你需要一条审计线索,将每条已发送消息与确切的草稿文本、批准它的操作员以及决策时间关联起来。
- 你曾遇到过过时的审批释放了重写后的草稿,或两名工作人员重复发送了同一条已批准消息的情况。
- 草稿不断向对方询问账本中已存在的事实。
工作原理
对象
| 对象 | 含义 |
|---|---|
| 义务 | 我们欠对方的一件事。状态依次为 drafted(已起草)、approved(已批准)或 rejected(已拒绝)、sent(已发送)。携带 direction(方向)、counterparty(对方)、channel(渠道)以及 updated_at(更新时间)纪元。 |
| 草稿 | 侧面行,包含确切的草稿文本、文本的 sha256 哈希、来源坐标(平台、渠道、线程、用户)和优先级(P0 到 P3)。每个义务一个,重新提交时替换。 |
| 决策 | 操作员的批准或拒绝决定,记录操作员 ID、随机数以及决策所针对的草稿纪元。 |
| 批准快照 | 已授权的决策编写者记录的不可变文本、哈希、纪元及目标。缺少快照则无法授权发送。 |
| 声明 | 带有随机令牌和状态的持久预留;每个义务最多一个活跃声明。 |
| 传递 | 证明一对(义务、决策)的一次发送或通知的账本行。 |
参考模式见 references/approval-ledger.sql。
提交草稿
- 清理输入。剥离控制字符,压缩单行字段中的空白,并强制执行长度限制(草稿、摘要、上下文、对方)。空字段或超长字段将被拒绝,而非静默截断。
- 运行基线门控(如下)。它可能拒绝提交。
- 使用 sha256 对草稿文本进行哈希处理。哈希前缀将放入摘要中,以便审批面板显示正在审批的是哪个文本。
- 更新插入。如果对于相同的(对方、渠道)已存在一个已起草的开放义务,则替换草稿侧面行并更新义务的
updated_at。此更新即为纪元旋转:任何基于旧纪元的决策将无法再释放新文本。否则,插入一个状态为drafted的新义务。 - 仅将提交回执路由到已配置、已验证的内部运维目标。如果发件源正是该内部目标,则在此处确认。永不静默意味着内部报告,而不是自动的外部回复。将草稿哈希、审批状态、操作员身份和工作流元数据保留在对方可见的渠道之外。未知或未分类的发件源保持静默;直接消息不自动视为内部。
如果验证过的内部目标不可用,则将提交结果保留在内部工具结果或操作员界面上。切勿回退到外部或未知发件源。对外部人员公开的工具结果不是内部界面。
提交草稿并不授权外部响应。任何策略允许的澄清问题或中立响应都是独立的出站决策,需遵守对方渠道纪律中已有的提及、渠道、仅限草稿、冻结和永不约束。它不得泄露内部审批元数据。
基线门控
在任何草稿提交之前,查询当前对方的基线(时间账本、合约存储或 CRM):
- 存在已签署或已交付的合约:拒绝提交,并附带证据和建议。在签署合同后向对方询问规格,正是此门控旨在阻止的确切故障。
- 操作员覆盖:
force_despite_signed_contract允许提交通过,并在草稿上下文中标记[BASELINE_OVERRIDE_SIGNED_CONTRACT]。 - 门控服务不可用:提交继续进行,并在上下文中标记
[BASELINE_CHECK_UNAVAILABLE]。面板将看到守卫已关闭。失败不会静默禁用门控。 - 当事实可用时,将最新的一些事实作为
[BASELINE FACTS: ...]摘要附加到上下文中,以便草稿基于当前真实情况。
决策
审批面板列出状态为 drafted 且方向为 we_owe_them 的义务。批准或拒绝会写入一个携带草稿纪元(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(),该函数原子地记录传递坐标,将声明标记为已传递,并将义务状态翻转为sent。相同的完成是空操作;冲突坐标会失败。回执 UNIQUE 键用于去重记录,而非先前的副作用。 - 异常、超时、begin-dispatch 后工作者死亡或回执持久化失败,会留下一个阻塞的尝试。
mark_unknown()记录不确定状态。未知声明永不过期、不重新打开、不自动重试,也不允许对该义务的另一个决策绕过它们。受信任的调用者可以使用reconcile()提供确认成功的坐标和证据;模块不验证该证据。缺少回执并非未送达的证明。
此保障是每个批准决策最多一次自动发送尝试,并非外部确切的刚好一次传递。begin-dispatch 后但传输前的崩溃可能导致零次发送和一个被持有的声明。为新的尝试释放未知结果需要隔离原始执行者并验证提供者语义;此引用故意不提供此类重试操作。
| 声明状态 | 允许的下一个状态 |
|---|---|
| claimed | dispatching 或在发送前取消 |
| 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="谢谢,我们可以保留该时段直到周五。哪个开始日期合适?",
counterparty="acme-supplier",
context="回复送货窗口问题",
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
需要测试的不变性
- 提交回执仅发送到已配置、已验证的内部运维;仅当来源正是该已验证内部目标时,来源才会收到回执。
- 未知来源保持静默。无法使用的内部目标使用内部工具结果或操作员界面,无外部回退。
- 同一(对方、渠道)提交两次会产生一个义务,两个纪元。
- 纪元过时的决策永远不会产生传递行。
- 两个并发声明者会产生一个发送权限;失败者从不尝试传输。
- 未知结果和回执持久化失败永远不会启用自动重试。
- 成功完成在一个事务中记录回执和已发送状态。
- 更改的纪元、文本、哈希或目标无法获取或开始声明。
- 活跃声明阻止重新提交;只有发送前取消才能解除该持有。
- 门控不可用时标记标记;门控有签名时,除非强制,否则拒绝。
- 自动 TTL 永远不会针对操作员随后重新提交的文本触发。