CTRLRun Operator について
CTRLRun Operator は、CTRLRun が保留したエージェント操作を確認するための MCP サービスです。人による審査を行うチーム向けに、保留項目の一覧表示、詳細確認、許可または拒否を提供します。
主な機能
- 01保留中の承認の一覧表示
- 02操作リクエストの確認
- 03リクエストの許可または拒否
活用シーン
- 01人の承認待ち操作の確認
- 02許可前のリクエスト内容の審査
- 03実行すべきでない操作の拒否
MCP 対応
MCP クライアントに CTRLRun の承認操作を公開します。保留項目の一覧表示、リクエストの確認、許可または拒否が可能です。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
ctrlrun は、AI エージェントがワークフロー内で誤った、制限された、または悪意のある操作を行うことを防ぎます。
すべての操作は、実行される前にあなたのルールに照らしてチェックされます。許可された操作はそのまま通ります。
機密性の高い操作は人の判断を待ちます。禁止された操作はブロックされます。
AI エージェントのための実行安全性。行動を決断することと、実際に行動する呼び出しの間に位置する Python ライブラリです。
重要な影響を持つ操作は、最大でも一度だけ、承認されたとおりに正確に実行され、記録を残します。
結果が不明な場合、ctrlrun は推測せずにその旨を伝えます。
単一ファイル上でも、ホストをまたいで Postgres 上でも本番環境で動作します。Apache-2.0。
pip install ctrlrun && ctrlrun demo
できること
モデルは推測します。ctrlrun は推測しません。 チケットには 500 ユーロの返金と書かれています。エージェントは 5,000 ユーロ、ゼロが 1 つ多い金額を要求します。ツールはその一覧にあり、引数は正しい形式で、モデルは 完全に自信を持っています。呼び出しより上の層では何も矛盾しません。呼び出しより上の層は何も チェックしていないからです。ツールが呼び出し可能であることは、その引数で呼び出してよいという許可ではありません。
| ctrlrun なし | ctrlrun あり |
|---|---|
| 金額をチェックするものは何もない。呼び出しは実行される。4,500 ユーロの過払い。 | あなたのルールが金額をチェックする。呼び出しは外に出ない。誤払い 0 ユーロ。 |
ctrlrun はそのチェックです。プロセスから出ていこうとしている引数を読み取り、それらに何が起こり得るかを答えます。実行させる、人間に確認する、あるいは完全に止める。4 つのルールがその役割を担い、それぞれはここに書かれた文章になる前に、このリポジトリ内のテストです。
| Exact は exact を意味する | 変更された引数には新しい承認が必要です。 |
| Once は once のまま | 同じエフェクトキー、共有ストア、繰り返しなし。 |
| Unknown は待つを意味する | 再試行の前に結果を確認してください。 |
| すべての答えは保持される | リクエスト、決定、結果、拒否も含めて。 |
3 つ目は人が忘れがちな半分です。正しい 500 ユーロの返金がプロバイダ側で確定し、その応答が戻る途中で失われると、エージェントは再試行します。再試行ライブラリ、エージェントフレームワーク、ツールループはこれは失敗したを何が起こったかわからないに collapse してしまいます。ctrlrun はそれらを区別します。失われた応答は AMBIGUOUS であり、決して FAILED ではなく、AMBIGUOUS なエフェクトに対する再試行は、人間または reconcile フックが何が起こったかを告げるまで拒否されます。
ctrlrun demo が示すもの:5 つの失敗と 5 つの拒否、バイト単位で
$ ctrlrun demo
ctrlrun demo — five ways an agent action goes wrong, and what stops it.
Policy: refunds up to €1,000 are autonomous, up to €10,000 need a human, above that are denied.
1. Duplicate effect after a lost response
refund €500 → remote commits → response lost → effect: AMBIGUOUS
agent retries the same refund
✗ BLOCKED — effect may already have committed; blind retry refused
remote refund calls: 1
only a human moves it on: ctrlrun resolve refund:txn_1 --committed|--failed
2. Approval mutation
agent proposes refund €2,000 → human approves apr_0aa78e0380ba55d77a601dc782f57095 (bound to the action hash)
agent executes refund €5,000 →
✗ BLOCKED — approved action ≠ requested action (mismatch)
3. Concurrent agents, same effect
Agent A reserve refund:txn_123 → ACQUIRED → executes
Agent B reserve refund:txn_123 →
✗ BLOCKED — already reserved (in_progress)
4. Approval replay
approval apr_dbc8bc6f06690cdf2e2c55a4e591ef3b used once → consumed
same approval presented again →
✗ BLOCKED — single-use approval already consumed
5. Authority escalation
human €100,000 delegable → finance agent €25,000 → support agent €2,000
support agent's grant: dlg_5f8d41938a3f29972d5489d676cd9edb
support agent requests €50,000 →
✗ BLOCKED — outside the delegated grant (authority_constraint)
remote refund calls: 0
finance agent tries to delegate €50,000 under its own €25,000 → refused (containment: constraints)
support agent requests €1,500 → authority permits it, and the policy asks a human (apr_f86eca24dd80206ab5189ccb1b62aa55)
two axes, and an action needs both: the stricter of the pair wins
Receipts (8): .ctrlrun/demo/receipts.jsonl
Events: .ctrlrun/demo/events.jsonl
Read them: CTRLRUN_STATE=.ctrlrun/demo/state.db ctrlrun receipts
承認と委任の id は実行ごとに生成されます。それ以外はすべてデモが出力する内容そのままであり、両者が乖離するとテストが失敗します。ネットワークなし、外部サービスなし、1 秒未満。pip install ctrlrun && ctrlrun demo を実行すると、ほぼ同じ時間でローカルに実行できます。
どこで止まるか。 プロンプトインジェクションを検出するわけではありません。原因を読むのではなく、結果を封じ込めるのです。制御できないリモートに対して exactly-once を約束することはできず、意図的に 2 回実行することを拒否し、何もロールバックしません。レシートはチェーン化されているため、改ざんは検出されます。末尾の切り詰めと偽造された追記は検出されません。それらを捕捉するはずのヘッドが同じデータベース内の行であり、それを解決するのが ctrlrun anchor の役割だからです。署名はされていません。改ざんは authorship ではないからです。上のバッジは
宣言された保証が、実行されたセットアップでパスすることを意味し、安全、セキュア、コンプライアント、認証済み、または監査済みを意味するものではありません:
バッジの意味
· OWASP-AGENTIC-TOP10.md
は、これが対処しない 4 つの項目を挙げています。
エージェントが読んで答えるだけなら、ctrlrun は必要ありません。エージェントが送信、支払い、返金、削除、デプロイ、付与、取り消し、承認、提出、購入、またはキャンセルを行えるようになった瞬間、必要になります。
3 つのステップで使う
上のアニメーションはこのセクションを実際のライブラリに対して録画したものです。ポリシーファイル 1 つ、短いプログラム 2 つ、コマンド 4 つ、演出は一切なしです。
1. インストールします。
pip install ctrlrun
2. エージェントが何をしてよいかを書き下します。 ファイルは 1 つ、ctrlrun.yaml です。金額は整数の最小通貨単位なので、50000 は €500 です。すべての範囲は上限と下限の両方が必須です。上限だけでは負の金額が通ってしまい、負の金額の返金は請求になるからです。記載されていないものはすべて拒否されます。デフォルト許可はありません。
schema: ctrlrun.policy/v2
actions:
stripe.refund:
effect: "refund:{payment_id}"
rules:
- when: { amount_gte: 0, amount_lte: 50000 }
decision: allow # up to €500: the agent acts alone
- when: { amount_gte: 0, amount_lte: 1000000 }
decision: approve # up to €10,000: a human decides
- decision: deny # above that: never
3. 結果を伴う呼び出しをラップします。 デコレーターはアクションを指定し、エフェクトキーはそれが現実世界で持つ結果を指定し、コンテキストは誰が行為しているかを指定します。ここでの stripe は、呼び出しを行う代わりに記録するスタンドインです。
import sys
import ctrlrun
class FakeStripe:
"""Stands in for the provider: it records calls instead of making them."""
def __init__(self) -> None:
self.calls: list[tuple[str, int]] = []
def refund(self, payment_id: str, amount: int) -> dict:
self.calls.append((payment_id, amount))
return {"id": f"re_{payment_id}", "amount": amount, "status": "succeeded"}
stripe = FakeStripe()
@ctrlrun.protect("stripe.refund", effect="refund:{payment_id}")
def refund(payment_id: str, amount: int) -> dict:
return stripe.refund(payment_id, amount)
if __name__ == "__main__":
with ctrlrun.context(agent="support-agent"):
print("€500 ->", refund(payment_id="txn_1", amount=50_000)["status"])
try:
refund(payment_id="txn_2", amount=500_000)
except ctrlrun.ApprovalRequired as pending:
print("€5,000 -> a human decides:", pending.request_id)
with open("request_id.txt", "w") as handle:
handle.write(pending.request_id)
else:
sys.exit("the €5,000 refund ran without a human; the policy is not in force")
print("calls that reached the provider:", len(stripe.calls))
€500 の返金はそのまま実行されます。€5,000 の返金は停止し、人間が答えるべきリクエストとして提示します:
€500 -> succeeded
€5,000 -> a human decides: apr_63e80076f2cccfee52b17491a4b2e125
calls that reached the provider: 1
人間がシェルから答えます。 グラントは、人間が読んだ正確なアクションのハッシュと、それが失効する時期を指定します。ID、ハッシュ、日付は実行ごとに生成されるため、あなたのものは異なります。
ctrlrun approve "$(cat request_id.txt)"
granted apr_63e80076f2cccfee52b17491a4b2e125 for sha256:22ec1c398e4b93d080b6cba61e5e11b0e21879552ac5dbf63c192d2b2e6af752
expires 2026-09-13T20:10:11.367Z
エージェントはそれを提示し、その後それを別の何かに使おうとします。 最初の呼び出しは人間が承認したものと正確に一致し、実行されます。2 番目は同じ承認の数字を 1 桁変えたもので、何にも一致しません:
import sys
import ctrlrun
from agent import refund, stripe
with open("request_id.txt") as handle:
request_id = handle.read().strip()
with ctrlrun.context(agent="support-agent"), ctrlrun.with_approval(request_id):
# Exactly what the human read: €5,000 on txn_2.
print("€5,000 with the approval ->", refund(payment_id="txn_2", amount=500_000)["status"])
# The same approval, one digit changed.
try:
refund(payment_id="txn_2", amount=900_000)
except ctrlrun.ApprovalMismatch:
print("€9,000 on that same approval -> refused")
else:
sys.exit("a mutated action ran on a human's approval; that is the bug this exists to stop")
print("calls that reached the provider:", len(stripe.calls), "(the €9,000 never left)")
€5,000 with the approval -> succeeded
€9,000 on that same approval -> refused
calls that reached the provider: 1 (the €9,000 never left)
拒否を含むすべての試行はレシートを残し、ctrlrun receipts がそれらを一覧表示します。統合はこれだけです: ポリシーファイル、デコレーター、コンテキスト、そしてグラントを提示する with_approval。お金は例であり、対象範囲ではありません。条件は <argument>_<op> なので、同じポリシー言語は amount_lte と同じくらい簡単に role_in: [reader, viewer] や replicas_lte: 10 も読み取れます。また、下記の9 つのドメインで同じ形にはそれぞれ 1 つのポリシーがあります。
最初のアクションを保護するでは、すべての出力を説明しながら同じ道筋をたどります ·
ポリシー YAML リファレンス ·
クックブック: 返金、デプロイ、IAM、削除、メール、MCP。
3 つの使用方法
おそらくアダプターは必要ありません。 @protect はこのプロセス内で動作するあらゆるものをカバーします。生のモデル呼び出し、LangChain ツール、自作のループ、cron ジョブなどです。ゲートウェイは、あらゆる言語で MCP 経由でそのツールに到達するあらゆるものをカバーします。
| お持ちのもの | 使用するもの | 必要なもの |
|---|---|---|
| このプロセス内の Python | 上記で示した @protect デコレーター | pip install ctrlrun 以外は何も不要 |
| あらゆる言語の MCP サーバー背後にあるツール | ゲートウェイ: pip install "ctrlrun[gateway]" | コマンド 1 つ、エージェントやサーバーのコード変更なし |
| 独自の承認割り込みを持つフレームワーク | アダプター | フレームワークに human-in-the-loop プリミティブがあること |
変更できるエージェントでも、できないエージェントでも動作します。 WhatsApp、Slack、Teams のボット、ChatGPT、Cursor、Codex、OpenAI Agents: お持ちのあらゆる AI エージェントです。ctrlrun はエージェントではなくアクションをチェックするため、エージェントがツールサーバーまたはあなたが実行する API を通じて行動する場合、そのアクションがチェックされ、エージェントは再構築、再デプロイ、または通知されることはありません。 変更できないエージェントでは、それぞれの種類について境界がどこにあるかを説明しています。
アダプターが存在する理由は 1 つです。approve の決定をフレームワーク自身の割り込みを通じてルーティングし、人間がすでに回答している場所で回答できるようにするためです。yes と言う場所が 2 つになることは決してありません。ctrlrun-langgraph は防止を提供します。再開時に引数が引き継がれ、コアがそれらをハッシュに対して再チェックするからです。
ctrlrun-openai-agents は帰属を提供します。その SDK は呼び出しが承認されたことを記録し、その引数が何であったかは記録しないからです。3 つのうちいずれもエージェント専用ではありません。ワーカー、Webhook ハンドラー、スケジュールされたジョブも、初回の試行と再試行を区別できません。
これは公式の LangChain ミドルウェア統合です。
ctrlrun-langchain は LangChain のミドルウェア統合に掲載されています。その CTRLRunMiddleware は create_agent(middleware=[...]) に組み込み、あなたが書いていないツールも含め、すべてのツール呼び出しを LangChain 自身の wrap_tool_call を通じてゲートします。拒否された呼び出しはツールに到達せず、モデルにはどのルールがそれを拒否したかが伝えられます。
LangChain ミドルウェアを使用するにセットアップ方法があります。
仕組み
保護されたすべての呼び出しは、どの経路で到着しても、同じ 7 つのステップを通ります。その後になって初めて、あなたのシステムに到達します。
normalize → decide → approve → reserve → execute → resolve → record
- 正規化: 1 つのアクション、1 つの ID。 呼び出しは
Actionになります。名前、正規化された引数 (ソートされたキー、浮動小数点数なし)、リソース、プリンシパルです。その SHA-256 がアクションハッシュになります。 - 判定: 許可、確認、またはブロック。 まず権限です (そもそも このプリンシパル がこれを提案できるのか、
できるならどの範囲内で?)。次にポリシーです (このアクション にどれだけの自律性が与えられるのか?)。未知の
アクション、ポリシーの欠落、プリンシパルの欠落は
denyです。沈黙は決して許可ではありません。 - 承認: このアクションに紐付けられます。 人間はアクションハッシュに対して回答します。承認は
単回使用で、期限切れになり、その正確なアクション以外とは一致しないため、回答後に引数が変更されると
無効になり、人が再度回答します。
preconditions=プロバイダーを指定すると、承認はさらに、それが 付与された時点のリソース状態にも紐付けられ、予約の直前に厳密に再チェックされます。これにより、回答から 実行までの間の窓が、数分の熟考からミリ秒へと狭まります。ただし、完全には閉じません。再チェックは ネットワーク呼び出しであり、アトミックな書き込みの内部では実行できないからです。 - 予約: 一度だけ要求されます。 エフェクトキー、
refund:txn_1またはnamespace:prod-eu:checkoutが、 1 回のアトミックな書き込みで取得されます。別のプロセスや別のホストにいる 2 番目の呼び出し元は拒否されます。 - 実行: あなたのコードが実行されます。 あなたが発生させる
NotExecutedのみがFAILEDを意味します。 それ以外のすべての例外とすべてのタイムアウトはAMBIGUOUSを意味します。どちらを見ているのかを判断するのが 難しい部分なので、ctrlrun.transportがそれを代行します。標準ライブラリのurllibとhttp.clientのurlopen、HTTPConnection、HTTPSConnectionは、それらが開いた接続にリクエストバイトが 1 バイトも 渡されていない場合にのみNotExecutedを発生させます。1 バイトの後は、すべての失敗はそれが持っていた例外のままとなり、 結果はAMBIGUOUSになります。それを広げる設定はありません。 - 解決: 不明は失敗ではありません。
AMBIGUOUSなエフェクトはキーを保持し、ctrlrun resolve、または リモートに問い合わせたreconcileフックが何が起こったかを示すまで、再試行を拒否します。 推測で何かが二度実行されることはありません。 - 記録: どちらの場合もレシート。 ポータブルな JSON レシート: 誰が、何を、判定、承認、 エフェクトキー、結果、そしてそれを決定したポリシーのハッシュが、その前のレシートにチェーンされます。 拒否にもレシートが付きます。
誰が要求でき、どれだけ要求できるのか。 ポリシーはアクションを決定し、誰が要求しているかを見ることはできません。
そもそも誰が要求できるかは第 2 の軸である権限です。すべてのプリンシパルには付与が必要で、委任は付与を
広げることはできず、アクションには両方の軸が必要で、より厳しい方が適用されます。0.9 以降、付与は
バジェット、すなわちローリングウィンドウにわたる上限付きのメトリックを保持することもでき、同じ書き込み内で
予約時に消費されるため、それぞれが amount_lte を通過する千件の返金が、付与が許す以上に積み上がることは
ありません。AMBIGUOUS なエフェクトは解決されるまでバジェットを保持します。そうでなければ、曖昧さを
作り出せるエージェントが権限を作り出せてしまうからです。バジェットは次の予約ができることを制限しますが、
すでに飛行中のアクションを呼び戻すことはできません。
完全なモデルは Authority にあります。
状態はデフォルトでは SQLite に保存されます。サーバーも運用も不要なファイルであり、予約は単に
スレッド間だけでなくプロセス間でも保持されます。複数のホストが書き込む場合は Postgres を指定します。
pip install "ctrlrun[postgres]"、1 つの URL、同じスイートで格付けされた同じ保証です。ctrlrun verify で
自分のセットアップにおいて証明できます。これはカーネル自身の障害シナリオを、スクラッチストア内であなたの
ポリシーに対して実行します。ネットワークには到達しません。開くソケットは、指定したストアと、自身がバインドした
ループバックリスナーのみであり、これがトランスポート分類器を格付けする方法です。
| 保証 | @protect | Gateway | Adapter |
|---|---|---|---|
| 承認のバインド — 承認は正確なアクションにバインドされ、改変または再生されたものは拒否されます。 | はい | はい | アダプターごとに防止または帰属 |
| 1 つの効果を 1 回だけ — 1 つの論理的な効果は、スレッド、プロセス、ホストをまたいでも最大 1 回だけ発生します。 | はい | はい | はい |
| 不明は失敗ではない — 不明な結果は AMBIGUOUS であり、決して FAILED ではなく、盲目的な再試行をブロックします。 | はい | はい | はい |
| クローズドに失敗 — 不明なアクション、欠落したポリシー、または欠落したプリンシパルは拒否されます。 | はい | はい | はい |
| 権限と委任 — すべてのプリンシパルには付与が必要であり、委任によってそれを広げることはできず、付与は総量を制限します。 | はい | はい | はい |
| レシート — 実行されたすべてのアクションは、誰が、何を、どのような結果だったかのポータブルな JSON レシートを残します。 | はい | はい | はい |
9 つのドメインで同じ形
ctrlrun は返金が何であるかを知りません。アクションとは 名前、正規化された引数、 効果キー、リソースであり、それに対して問われる 3 つの質問は、どのドメインから来た ものでも同じです。このアクションにどれだけの自律性が与えられるか、人間がまさにこの アクションを承認したか、そしてこの効果がすでに発生したかどうかです。あなたのドメインを 担うものは 2 つあり、その両方をあなたが書きます。
- 効果キーは、システム内で唯一のドメイン知識です。 これは 2 つの呼び出しが同じ現実世界
の結果であることを示す文字列です:
refund:{payment_id}、namespace:{cluster}:{name}、grant:{user_id}:{role}、prescription:{patient_id}:{drug}。 適切に命名すれば、再試行が 2 回作用することはありません。省略すれば、最大 1 回が 対象とするものが何もなくなります。 - 条件は引数であり、金額ではありません。 言語は
<argument>_<op>なので、同じ演算子がamount_lteと同じくらい簡単にreplicas_lte: 10、role_in: [reader, viewer]、to_domain_eq: acme.comとして読めます。バンドは、一度も請求書を発行したことのない ドメインでも利用できます。
| ドメイン | 自律的 | 人間が決定 | 決してしない |
|---|---|---|---|
| DevOps | k8s.scale_deployment を 10 レプリカまで | terraform.apply | k8s.delete_namespace |
| セキュリティ運用 | firewall.add_deny_rule | firewall.add_allow_rule | edr.disable_protection |
| ヘルスケア | appointment.reschedule | patient.export_record | prescription.change_dose |
| 法務 | document.draft_internal | document.file_with_court | contract.execute |
| 人事 | バンド内での pto.approve | payroll.run | employee.delete_record |
| 保険 | claim.request_documents | バンドを超える claim.approve_payout | policyholder.delete |
| E コマース | バンド内での inventory.adjust | price.update | customer.delete |
| 公共サービス | eligibility.precheck | benefit.terminate | record.delete |
| 決済 | €500 未満の stripe.refund | それを超える stripe.refund | stripe.delete_customer |
どの行も左から右へ読めば、異なる名詞をまとった 1 つのルールです。セキュリティの行は 2 度読むべき行です。ファイアウォールに deny ルールを追加するのは自律的ですが、 allow ルールを追加するのはそうではありません。これはどのような金額のしきい値でも 教えてくれなかったでしょう。ポリシーは、あなたのドメインに関する判断が書き留められる 場所であり、ctrlrun はそれを維持させるものです。
ドキュメント
docs.ctrlrun.dev がドキュメントです。概念、ガイド、クックブック、 完全なリファレンスを掲載しています。
| まずはここから | Why · Protect your first action |
| アイデアと、それを用いた実践 | Concepts · Guides · Cookbook |
| エージェントと MCP | Agents you can't modify · MCP overview · The gateway in five minutes · Approve from your assistant |
| 本番環境での運用 | Production · Postgres · Recovery · Operations |
| すべてのキー、フラグ、エラー | Reference · FAQ |
| 比較対象 | Idempotency keys · Framework human-in-the-loop · Guardrail libraries · Durable workflows · Governance toolkits |
| 保証されること、されないこと | Threat model · What verify proves · CLAIMS.md、すべての文がテストに対応付けられています · How this is built |
コントリビューション
Issue とプルリクエストを歓迎します。
CONTRIBUTING.md と
CODE_OF_CONDUCT.md に
作業上の合意事項が記載されており、
SECURITY.md に脆弱性の
報告方法が記載されています。このファイル内のすべての主張にはテストが伴っているため、文章の変更は
通常、テストスイートの変更を意味します。
CHANGELOG.md と
https://docs.ctrlrun.dev/ROADMAP には
今後の方向性が記載されています。リリースには GitHub Actions による PyPI の来歴証明が付随します。
ライセンス
Apache-2.0。執行カーネルは現在も、そして今後も完全なオープンソースであり続けます。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。