オペレーター承認ループ は何をしますか?
外部の相手先とやり取りするエージェントは、自身の判断でメッセージを送信してはならず、常にオペレーターに内部で通知する必要があります。このスキルは、すべての送信下書きが義務として記録され、オペレーターが正確なテキストを決定し、配信台帳が何が送信されたかを証明する、という契約を定義します。
使用するタイミング
- エージェントが共有チャネル、メール、チャットなどで顧客、サプライヤー、投資家、パートナーへの返信を作成し、送信前に人間の承認が必要な場合。
- 送信された各メッセージを、正確な下書きテキスト、承認したオペレーター、決定時刻にリンクする監査証跡が必要な場合。
- 古い承認が書き換えられた下書きをリリースしたり、2人のワーカーが同じ承認済みメッセージを2回配信したりする問題が発生した場合。
- 下書きが、台帳がすでに保持している事実を相手先に繰り返し尋ねている場合。
仕組み
オブジェクト
| オブジェクト | 意味 |
|---|---|
| 義務 (Obligation) | 相手先に対して負う1つの責務。ステータスは drafted、approved / rejected、sent と遷移します。direction、counterparty、channel、updated_at (エポック) を持ちます。 |
| 下書き (Draft) | 正確な下書きテキスト、そのsha256、発信元情報 (プラットフォーム、チャネル、スレッド、ユーザー)、優先度 (P0-P3) を持つサイドカーレコード。義務ごとに1つ存在し、再提出時に置き換えられます。 |
| 決定 (Decision) | オペレーターによる承認または拒否。オペレーターID、nonce、および決定が行われた時点の下書きエポックと共に記録されます。 |
| 承認スナップショット (Approval snapshot) | すでに認可された決定書き込み者によって記録された、不変のテキスト、ハッシュ、エポック、および宛先。スナップショットがない場合、ディスパッチを許可できません。 |
| 請求 (Claim) | ランダムなトークンと状態を持つ永続的な予約。義務ごとに最大1つのアクティブな請求が存在できます。 |
| 配信 (Delivery) | 1つの(義務、決定)ペアに対する1回の送信または通知を証明する台帳行。 |
参照スキーマは references/approval-ledger.sql にあります。
下書きの提出
- 入力のクリーニング。制御文字を取り除き、単一行フィールド内の空白を圧縮し、長さ制限(下書き、概要、コンテキスト、相手先)を適用します。空またはサイズ超過のフィールドは、静かに切り詰めるのではなく拒否されます。
- ベースラインゲート(後述)を実行します。提出を拒否する可能性があります。
- 下書きテキストをsha256でハッシュ化します。ハッシュのプレフィックスが概要に含まれるため、承認パネルはどのテキストを承認しているかを確認できます。
- Upsertを実行。同じ(相手先、チャネル)に対してオープンな drafted 状態の義務がすでに存在する場合、下書きサイドカーを置き換え、義務の
updated_atを進めます。この更新がエポックローテーションです。古いエポックをキーとする決定は、新しいテキストをリリースできなくなります。そうでなければ、ステータスdraftedの新しい義務を挿入します。 - 提出領収書は、設定済みで検証済みの内部運用宛先にのみルーティングします。発信元がその内部宛先である場合、そこで確認応答します。決して沈黙しないとは、内部報告を意味し、自動的な外部返信を意味しません。下書きのハッシュ、承認ステータス、オペレーターID、ワークフローメタデータを、相手先から見えるチャネルに保持しないでください。不明または分類不能な発信元は沈黙を保ちます。ダイレクトメッセージは自動的に内部と見なされません。
検証済みの内部宛先が利用できない場合は、内部ツール結果またはオペレーター画面に提出結果を保持します。外部または不明な発信元にフォールバックしてはいけません。部外者に公開されるツール結果は内部画面ではありません。
下書きを提出しても、外部への応答は許可されません。ポリシーで許可されている明確化の質問や中立的な応答は、別個の送信判断であり、counterparty-channel-discipline の既存の言及、チャネル、下書きのみ、凍結、および禁止の制約に従います。内部の承認メタデータを開示してはなりません。
ベースラインゲート
下書きが提出される前に、相手先の現在のベースライン(時間台帳、契約ストア、または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と義務ID、現在の下書きエポック、正確なテキストとSHA-256、プラットフォーム/チャネル/スレッド、種類 draft_sent)が挿入されます。決定書き込み者は、書き込み前に認可を確立する必要があります。リファレンスはオペレーターを認証したり、決定を生成したりすることはありません。自動承認ポリシーは、リファレンスによって有効化または拡張されません。スナップショットのないレガシー決定は、明示的な調整または新しい承認が必要です。現在の可変な下書きから権限をバックフィルしてはいけません。
配信
SQLiteリファレンスは references/approval_claims.py です。ディスパッチ権限を付与しますが、トランスポートを呼び出すことはありません。SQLフィクスチャから初期化された既存のローカルリファレンスデータベースを使用します。このモジュールはスキーマや本番マイグレーションを適用しません。信頼された決定書き込み者のみが承認レコードを入力できます。すべての書き込み者は外部キーと再帰トリガーを有効にし、スキーマガードを尊重する必要があります。管理上のデータベース改ざんはこのモデルの範囲外です。
- バインドされた承認済み下書きを発見します。発見は許可ではありません。
claim()は自身のBEGIN IMMEDIATEトランザクションを開始し、現在の承認済みエポック、正確なテキスト、計算されたSHA-256、および完全な宛先をスナップショットに対して検証し、一意の請求を挿入してからそのトークンを返します。競合が発生した場合、トランスポート前にワーカーは停止します。完了した領収書は再度請求できません。 begin_dispatch()はバインディングを再検証し、トークンを使用してclaimedをdispatchingにアトミックに変更します。勝者はコミット後にのみ正確なdraft_textと宛先を受け取ります。テキストを再生成したり、可変なサイドカーをトランスポートのために再読み取りしたり、ペイロードを別の試行で再利用したりしないでください。ネストされた呼び出し元のトランザクションは拒否されます。許可は後続の呼び出し元のコミットに依存してはいけません。トランスポート全体にわたってデータベーストランザクションを開いたままにしないでください。- 成功が確認された結果は
complete()に送られ、配信座標をアトミックに記録し、請求を配信済みとしてマークし、義務をsentに変更します。同一の完了はno-opです。競合する座標は失敗します。領収書のUNIQUEキーはレコードを重複排除しますが、以前の外部効果は重複排除しません。 - 例外、タイムアウト、begin-dispatch後のワーカーの終了、または領収書の永続化失敗により、ブロックされた試行が残ります。
mark_unknown()は不確実性を記録します。不明な請求は期限切れにならず、再オープンせず、自動再試行せず、その義務に対する別の決定がそれらをバイパスすることを許可しません。信頼された呼び出し元は、成功が確認された座標と証拠を使用してreconcile()を使用できます。モジュールはその証拠を検証しません。領収書がないことは、未配信の証明にはなりません。
保証されるのは、承認済み決定ごとに1回の自動ディスパッチ試行であり、厳密な1回限りの外部配信ではありません。begin-dispatch後、トランスポート前のクラッシュは、0回の送信と保持された請求を残す可能性があります。新しい試行のために不明な結果を解放するには、元の実行者をフェンシングし、プロバイダーのセマンティクスを検証する必要があります。このリファレンスは意図的にそのような再試行操作を提供しません。
| 請求状態 | 許可される次の状態 |
|---|---|
| claimed | dispatching、またはディスパッチ前の 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 が記録され、その後、通常のパスを通じて配信が進みます。オペレーターのアクションが常に優先されます。決定がスイープがそれを確認する前にステータスを変更し、再提出がエポックをローテーションして期限を移動またはクリアします。スイープは書き込みトランザクション内でステータスとエポックを再確認するため、競合はno-opとして解決されます。期限のない下書きは永久にハードゲートされたままになります。
シグナルリンケージ
下書きは、応答する受信義務 (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
テストすべき不変条件
- 提出領収書は、設定済みで検証済みの内部運用のみに送信されます。発信元がその検証済み内部宛先である場合にのみ、発信元は1つを受信します。
- 不明な発信元は沈黙を保ちます。利用できない内部宛先は、内部ツール結果またはオペレーター画面を使用し、外部フォールバックはありません。
- 同じ(相手先, チャネル)で2回提出すると、1つの義務、2つのエポックが生成されます。
- 古いエポックを持つ決定は、決して配信行を生成しません。
- 2人の同時請求者は1つのディスパッチ権限を生成します。敗者はトランスポートを試みません。
- 不明な結果と失敗した領収書の永続化は、自動再試行を決して有効にしません。
- 成功した完了は、1つのトランザクションで領収書と送信ステータスを記録します。
- 変更されたエポック、テキスト、ハッシュ、または宛先は、請求を取得または開始できません。
- アクティブな請求は再提出をブロックします。ディスパッチ前のキャンセルのみがその保留を解放できます。
- ゲートが利用できない場合はマーカーがスタンプされ、ゲート署名済みの場合はforceなしでは拒否されます。
- Auto-ttlは、オペレーターがその後再提出したテキストに対して決して発動しません。