Control IR¶
Control IR は LLM が artifact と並行して出力できる副作用 op のリストです。OS は各 op をディスパッチし、LLM(または次の Phase)が消費するために結果を返します。
Op の種類¶
| 種類 | 目的 | 必要な Permission |
|---|---|---|
read_file |
ファイルを読み取る(行範囲指定も可) | file.read |
write_file |
ファイルを書き込む(作成 / 上書き) | file.write |
edit_file |
ファイル内の文字列を置換する | file.write |
delete_file |
ファイルを削除する | file.write |
glob_files |
glob パターンに一致するファイルを列挙する | file.read |
grep_files |
正規表現でファイル内容を検索する | file.read |
ask_user |
Phase を一時停止してユーザーに質問する | なし(常に許可) |
sandboxed_exec |
SandboxPolicy と SandboxBackend を介して argv を実行する(削除済みの shell op を置き換え) |
バックエンドが強制(SandboxPolicy) |
web_search |
DuckDuckGo で公開ウェブを検索する | Tier 1 — デフォルト許可;reyn.yaml の web.search: deny でブロック |
web_fetch |
単一 URL を取得してテキストを抽出する | Tier 1 — デフォルト許可;reyn.yaml の web.fetch: deny でブロック |
mcp |
設定済み MCP server のツールを呼び出す | Skill frontmatter の permissions.mcp: [server_name] |
mcp_install |
レジストリから MCP server をプロジェクト設定にインストールする | Skill frontmatter の permissions.mcp_install: true |
index_query |
インデックス済みソース 1 件に対してセマンティック検索を行う | なし |
recall |
マクロ: embed → 各ソースに index_query → トップ K をマージ | なし |
index_drop |
インデックス済みソースを完全削除する(破壊的) | Skill frontmatter の permissions.index_drop: ask |
judge_output |
rubric + threshold + on_fail ポリシーを持つ LLM スコアラー |
なし(LLM コスト) |
共通エンベロープ¶
すべての op は kind ディスクリミネーターを持つ JSON オブジェクトです:
OS は op をその kind のスキーマに対して検証し、実行し、呼び出し元 Phase に結果を返します。
ファイル op(細粒度)¶
LLM が発行できるファイル操作は 6 つの細粒度 kind です — chat router がツールとして公開しているのと同じサブセットです(concepts/architecture/llm-invocation-surfaces.md を参照)。それぞれ独自のスキーマを持つ独立した op kind であり、op サブフィールドはありません。
{"kind": "read_file", "path": "src/foo.py"}
{"kind": "read_file", "path": "src/foo.py", "offset": 100, "limit": 40}
{"kind": "write_file", "path": "out.txt", "content": "..."}
{"kind": "edit_file", "path": "src/foo.py",
"old_string": "...", "new_string": "...", "replace_all": false}
{"kind": "delete_file", "path": "tmp.txt"}
{"kind": "glob_files", "path": ".", "pattern": "**/*.py", "max_results": 50}
{"kind": "grep_files", "path": "src", "pattern": "def \\w+",
"glob": "**/*.py", "case_sensitive": false, "max_results": 50}
| 種類 | Permission | 備考 |
|---|---|---|
read_file |
file.read |
offset / limit(行範囲)は省略可。 |
write_file |
file.write |
作成または上書き;親ディレクトリは必要に応じて作成。 |
edit_file |
file.write |
replace_all: true でない限り old_string は一意でなければならない。 |
delete_file |
file.write |
|
glob_files |
file.read |
path のデフォルトは .。 |
grep_files |
file.read |
glob で検索対象ファイルを絞り込む。 |
Permission スコープは op の種類ごとに設定されます。reference/config/permissions.md を参照してください。
粗粒度 file 実行バックエンド(Phase からは発行不可)¶
上記の細粒度 kind が、Phase が LLM に提示し(また LLM から受け付ける)唯一のファイル op です。これらは統一 ToolRegistry を通じてディスパッチされ、内部で粗粒度の FileIROp({kind: "file", op: ...})を構築して共有バックエンド op_runtime/file.py にルーティングします。その粗粒度 file kind は — OP_KIND_MODEL_MAP から削除済み — LLM が発行できる Control IR kind ではありません。次の用途でのみ存続します:
- 細粒度ハンドラが委譲する共有実行バックエンド、および
- OS 決定論的な preprocessor
run_opステップ({kind: file, op: ...})、chat ホストのファイルメソッド、reyn memoryCLI のディスパッチ先。
これらの非 Phase 呼び出し元は、細粒度 kind が公開しない拡張サブ操作 — mkdir、move、stat、regenerate_index(reyn memory やインデックスを管理するスキルが preprocessor / CLI 経由で使用、Phase Control IR としては決して使われない)— にも到達します。
ask_user¶
Phase を一時停止してユーザーに質問します。OS は質問を表示し、stdin を読み取り、回答を user_message artifact として入力にマージした上で同じ Phase を再実行します。訪問カウントは増加しません。
{
"kind": "ask_user",
"question": "どのモデルをターゲットにしますか?",
"suggestions": ["light", "standard", "strong"]
}
sandboxed_exec¶
宣言された SandboxPolicy と OS が選択した SandboxBackend を介して argv を実行します。分離強制が必要な(または将来必要になる)ケースで shell を置き換えます。
{
"kind": "sandboxed_exec",
"argv": ["echo", "hello"],
"network": false,
"read_paths": ["{{workspace}}"],
"write_paths": ["{{workspace}}/output"],
"allow_subprocess": false,
"env_passthrough": ["PATH"],
"timeout_seconds": 60
}
フィールド:
- argv(必須)— コマンドと引数。argv[0] が実行可能ファイル。
- network(省略可、デフォルト false)— アウトバウンドネットワークを許可。
- read_paths(省略可)— プロセスが読み取り可能なファイルシステムパス(glob パターン可)。
- write_paths(省略可)— プロセスが書き込み可能なファイルシステムパス。
- allow_subprocess(省略可、デフォルト false)— 子プロセス生成の許可。
- env_passthrough(省略可)— 引き渡す環境変数名(それ以外は除去)。
- timeout_seconds(省略可、デフォルト 60)— ウォールクロック上限。
バックエンド選択: get_default_backend() がプラットフォームに応じて選択します。macOS < 26 では SeatbeltBackend(sandbox-exec SBPL)。Linux ≥ 5.13 かつ sandbox-linux extra インストール済みの場合は LandlockBackend(+ オプションの seccomp-BPF スタック)。その他のプラットフォームまたは選択バックエンドが利用不可の場合は NoopBackend(監査のみ、強制なし)にフォールバック — 初回使用時に一行 WARN を出力。reyn.yaml の sandbox.backend(auto | seatbelt | landlock | noop)および sandbox.on_unsupported(warn | error | ignore)で上書き可能。
結果フィールド: returncode、stdout、stderr、truncated、backend。
発行イベント: sandboxed_exec_started、sandboxed_exec_completed(P6 監査証跡)。
web_search¶
DuckDuckGo を使って公開ウェブを検索し、構造化された結果を返します。Tier 1 — デフォルト許可;Permission 宣言不要。reyn.yaml の web.search: deny でプロジェクト全体をブロックできます。
{
"kind": "web_search",
"query": "reyn agent OS site:github.com",
"max_results": 10,
"backend": "duckduckgo"
}
フィールド: query(必須)、max_results(省略可、デフォルト 10)、backend(省略可、デフォルト "duckduckgo";現在唯一サポートされる値)。
query では標準の DuckDuckGo 検索 operator が使用できます:
site:<domain>— 特定ドメインに絞り込む(例:site:news.ycombinator.com)"phrase"— phrase 完全一致-term—termを含む結果を除外
ユーザーの意図が特定サイトや phrase に限定される場合に operator を使用し、それ以外は通常のキーワードで問題ありません。結果は results フィールドの {title, url, snippet} オブジェクトのリストとして返されます。
web_fetch¶
単一 URL を取得し、テキスト抽出したコンテンツを返します。Tier 1 — デフォルト許可;Permission 宣言不要。通常は web_search の後、特定の結果ページを詳しく読むために使用します。reyn.yaml の web.fetch: deny でブロック、web.fetch: allow で明示的に事前承認できます。
{
"kind": "web_fetch",
"url": "https://example.com/article",
"prompt": "主要な知見を抽出する",
"max_length": 50000
}
フィールド: url(必須)、prompt(省略可 — 何を抽出するかの LLM 向けヒント。OS は実行しない)、timeout(省略可、デフォルト 30 秒)、max_length(省略可、デフォルト 50000 文字)。
HTML レスポンスはテキスト抽出されます(script、style、非コンテンツタグは除去)。コンテンツが max_length を超える場合は切り詰められ、結果に truncated: true が付きます。非 HTML レスポンスはそのまま返されます。
mcp¶
設定済み MCP server のツールを呼び出します。reyn.yaml の mcp.servers: に server が宣言されており、かつ Skill の permissions.mcp frontmatter ブロックに列挙されている必要があります。
フィールド: server(必須 — reyn.yaml の mcp.servers: のキーと一致する必要がある)、tool(必須 — server の tools/list レスポンスで公開されているツール名)、args(省略可、デフォルト {})。
提示名。 Phase はこの op を chat-tool 名
call_mcp_toolとして LLM に提示し、OS がパース境界でmcpkind にエイリアスし直します。mcpはOP_KIND_MODEL_MAP上およびディスパッチされる op 上の正規 kind のままです。
OS は server のトランスポート(stdio、http、sse)を解決し、MCPClient 経由でディスパッチして、ツール結果を返します。呼び出しごとに mcp_called、mcp_completed、(失敗時)mcp_failed イベントが発行されます。
server の設定、トランスポートの選択、セキュリティモデルについては concepts/tools-integrations/mcp.md を参照してください。
mcp_install¶
registry.modelcontextprotocol.io から MCP server をプロジェクト設定にインストールします。Phase 専用(ルーターからは使用不可)。Skill frontmatter に permissions.mcp_install: true が必要で、ユーザー承認も必要です。
{
"kind": "mcp_install",
"server_id": "io.github.modelcontextprotocol/server-filesystem",
"scope": "local",
"env_overrides": {"GITHUB_TOKEN": "ghp_..."}
}
フィールド:
- server_id(必須)— レジストリ識別子(例: "io.github.foo/bar-mcp")。
- scope(省略可、デフォルト "local")— 書き込む設定層:
- "local" → <project>/.reyn/config.yaml
- "project" → <project>/reyn.yaml
- "user" → ~/.reyn/config.yaml
- env_overrides(省略可)— シークレット環境変数の事前提供値。ここに指定したキーは対話型プロンプトをスキップ。
ハンドラーのライフサイクル:
1. RegistryClient で server.json を取得
2. ランタイムコマンドの利用可能性確認(npx / uvx / docker / dnx)
3. PermissionResolver.require_file_write(= .reyn/mcp.yaml)+ require_http_get(= registry host)でゲート。 旧 require_mcp_install bool-axis gate は廃止済み
4. intervention_bus 経由で isSecret=true 環境変数を収集;各 save_secret は PermissionResolver.require_secret_write を経由(= Phase 6 で wildcard "*" が runtime-determined key set を許可)
5. 対象スコープの設定ファイルに mcp.servers.<name> を書き込む
6. mcp_server_installed イベントを発行(P6)— キー名のみ。値は含まない
削除された op。
embedとindex_writeの control-IR op は削除されました。 embedding とインデックス書き込みは現在 provider-direct でreyn.api.safe.embed_index.embed_and_index()の内部で行われます(safe-mode のpythonstep が自分で chunk を作ってそこに流し込みます — それをラップしていた stdlib skill ごと、バンドルされていたindex_docs/index_eventsの chunker も削除されました)。クエリ側の embedding はrecallop の内部で 行われます。EmbeddingProviderとSqliteIndexBackendのプリミティブは 変わっていません — 削除されたのは run-op のラッパーとバンドル済み chunker だけです。kind: embed/kind: index_writeを発行するものはもうありません。
index_query¶
インデックス済みソース 1 件に対してセマンティック類似検索を行います。
{
"kind": "index_query",
"source": "project_docs",
"query_vector": [0.1, 0.2, ...],
"top_k": 5,
"filters": {"path": "docs/concepts"}
}
フィールド:
source(str、必須)— 論理ソース名。query_vector(list[float]、省略可)— 事前計算済み埋め込み。nullの場合はカタログ列挙にフォールバック(fallback_size_capトークン上限)。top_k(int、デフォルト5)— 返す結果数。filters(dict[str, str]、省略可)— ランキング前に適用するメタデータキー/値フィルター。fallback_size_cap(int、デフォルト4096)—query_vectorがnullのときの列挙フォールバックのトークン上限。
戻り値: {"kind": "index_query", "source": str, "results": [{"text": str, "score": float, "metadata": dict}]}.
recall¶
マクロ op: クエリを embed → 各ソースに index_query → グローバルにトップ K をマージして結果を返します。RAG 取得において推奨される高レベル op です。
{
"kind": "recall",
"query": "クラッシュリカバリはどのように動作しますか?",
"sources": ["project_docs", "api_reference"],
"top_k": 5,
"embedding_model": "standard"
}
フィールド:
query(str、必須)— embed して検索する自然言語クエリ。sources(list[str]、必須)— 検索する論理ソース名。空にはできません。top_k(int、デフォルト5)— グローバルマージ後に返す結果数。filters(dict[str, str]、省略可)— 各index_queryサブ op に転送。embedding_model(str、デフォルト"standard")—embedサブ op に転送するモデルクラス。
戻り値: {"kind": "recall", "results": [{"text": str, "score": float, "source": str, "metadata": dict}]}.
イベント: embed サブ op が失敗した場合に recall_embed_failed(query、error)。
index_drop¶
インデックス済みソースを完全に削除します。SQLite バックエンドとマニフェストエントリを削除します。破壊的かつ不可逆です。 Skill frontmatter に permissions.index_drop: ask(または明示的な allow)が必要で、デフォルトでユーザー承認ゲートが発動します。
フィールド:
source(str、必須)— 削除する論理ソース名。
戻り値: {"kind": "index_drop", "source": str, "chunks_dropped": int}.
イベント: index_dropped(source、chunks_dropped)。
judge_output¶
Phase 内の評価ループで使用する LLM ベースの出力スコアラー。target の dot-path で値を解決し、呼び出し元が供給する rubric と共に LLM を呼び出し、スコア(0.0〜1.0)と合格/不合格フラグを返します。
{
"kind": "judge_output",
"target": "artifact.data.summary",
"rubric": "0.0〜1.0 でスコアリング: サマリーは簡潔で正確かつ完全ですか?",
"threshold": 0.8,
"on_fail": "transition"
}
フィールド:
- target(str、必須): スコアリング対象の値への dot-path(例: "artifact.data.summary")。現在のワークスペース artifact に対して解決されます。
- rubric(str、必須): LLM prompt 本文。評価基準は Skill author が記述します。OS はこの内容を解釈しません(P7)。
- threshold(float、省略可、デフォルト 0.8): 合格スコア([0.0, 1.0])。
- on_fail("transition" | "abort" | "continue"、省略可、デフォルト "transition"):
- "transition": LLM が次の Phase を選択(既存の decision フロー)。
- "abort": Skill 実行を中止。
- "continue": スコアを記録するのみ。フロー変更なし。
- model(str | null、省略可): モデルクラスのオーバーライド(例: "strong")。省略時は Skill の現在のモデルを使用。
戻り値: {"kind": "judge_output", "score": float, "passed": bool, "reason": str, "threshold": float, "on_fail": str}
Audit イベント: tool_executed(op=judge_output, target, score, passed, threshold, reason)(P6)。
P7 注記: Reyn は rubric に依存しません。rubric の内容は Skill author の authored prompt の一部であり、OS は解釈せずそのまま LLM に渡すだけです。
コントリビューター向けメモ: src/reyn/schemas/models.py および src/reyn/core/op_runtime/registry.py に新しい Control IR op kind を追加する際は、同じ PR でここにセクションを追加してください。reference と registry は同期を保つ必要があります。ルールの詳細は CLAUDE.md を参照してください。
LLM に op が提示される場所¶
OS は利用可能な op をすべてのコンテキストフレームに available_control_ops として注入します。各エントリーは kind、一行の説明、動作例を含みます。LLM は意図を説明にマッピングして op を選択します。Phase の Markdown は op の構文を説明してはなりません(P8)。
関連情報¶
- events.md — op の種類ごとに発行されるイベント
- コンセプト: principles P8 (principles doc removed)