---
type: reference
topic: runtime
audience: [human, agent]
---

# Control IR

Control IR は LLM が artifact と並行して出力できる副作用 op のリストです。OS は各 op をディスパッチし、LLM（または次の Phase）が消費するために結果を返します。

## Op の種類

| 種類 | 目的 | 必要な Permission |
|------|---------|---------------------|
| `file` | ファイルの読み取り、書き込み、glob、grep、編集、削除 | `file.<op>` |
| `ask_user` | Phase を一時停止してユーザーに質問する | なし（常に許可） |
| `run_skill` | 別の Skill をサブワークフローとして実行する | なし（Skill レベルの決定） |
| `lint` | Skill ディレクトリに DSL リンターを実行する | なし |
| `shell` | シェルコマンドを実行する（**非推奨** — `sandboxed_exec` を使用） | `shell`（デフォルトオフ；`--allow-shell` が必要） |
| `sandboxed_exec` | `SandboxPolicy` と `SandboxBackend` を介して argv を実行する | バックエンドが強制（`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` |
| `embed` | LiteLLM embedding モデルでテキストや artifact チャンクをベクトル化する | なし（embedding API コスト） |
| `index_write` | ベクトル付きチャンクをインデックスバックエンド（SQLite）に書き込む | なし |
| `index_query` | インデックス済みソース 1 件に対してセマンティック検索を行う | なし |
| `recall` | マクロ: embed → 各ソースに index_query → トップ K をマージ | なし |
| `index_drop` | インデックス済みソースを完全削除する（破壊的） | Skill frontmatter の `permissions.index_drop: ask` |
| `judge_output` | rubric + threshold + `on_fail` ポリシーを持つ LLM スコアラー | なし（LLM コスト） |
| `skill_resolve` | Skill 名をオンディスクパスに解決する（読み取り専用） | なし |

## 共通エンベロープ

すべての op は `kind` ディスクリミネーターを持つ JSON オブジェクトです:

```json
{
  "kind": "file",
  "op": "read",
  "path": "src/foo.py"
}
```

OS は op をその kind のスキーマに対して検証し、実行し、呼び出し元 Phase に結果を返します。

## `file`

サブ操作: `read`、`write`、`edit`、`delete`、`glob`、`grep`、`regenerate_index`。

```json
{"kind": "file", "op": "read", "path": "src/foo.py"}

{"kind": "file", "op": "write", "path": "out.txt", "content": "..."}

{"kind": "file", "op": "edit", "path": "src/foo.py",
 "old_string": "...", "new_string": "..."}

{"kind": "file", "op": "delete", "path": "tmp.txt"}

{"kind": "file", "op": "glob", "pattern": "**/*.py"}

{"kind": "file", "op": "grep", "path": "src", "pattern": "def \\w+",
 "glob": "**/*.py", "output_mode": "content"}

{"kind": "file", "op": "regenerate_index",
 "path": ".reyn/memory",
 "output_path": ".reyn/memory/MEMORY.md",
 "entry_template": "- [{name}]({slug}.md) — {description}",
 "header": "# Memory Index\n\n"}
```

**`regenerate_index`** は `path` 配下のすべての `*.md` ファイルの YAML frontmatter から Markdown インデックスを再生成します。フィールド:

- `path`（必須）— スキャン対象のソースディレクトリ。
- `output_path`（必須）— 生成するインデックスファイルのパス（スキャン対象から除外）。
- `entry_template`（必須）— 各ファイルの frontmatter キーと `{slug}`（`.md` なしのファイル名）をプレースホルダーとして持つフォーマット文字列。
- `header`（省略可）— エントリの前に付加するプレアンブル。

`reyn memory`（変更後の同期）や、インデックスファイルを管理するメモリスキルで使用されます。

Permission スコープは op の種類ごとに設定されます。`reference/config/permissions.md` を参照してください。

## `ask_user`

Phase を一時停止してユーザーに質問します。OS は質問を表示し、stdin を読み取り、回答を `user_message` artifact として入力にマージした上で**同じ Phase** を再実行します。訪問カウントは増加しません。

```json
{
  "kind": "ask_user",
  "question": "どのモデルをターゲットにしますか？",
  "suggestions": ["light", "standard", "strong"]
}
```

## `run_skill`

別の Skill をサブワークフローとして実行します。結果は呼び出し元 Phase が使用するための構造化 artifact として返されます。

```json
{
  "kind": "run_skill",
  "skill": "recall_memory",
  "input": {"type": "user_message", "data": {"text": "what did I tell you about my preferences?"}}
}
```

LLM 駆動ではなく Phase の preprocessor から決定論的に呼び出す場合は、`run_skill` preprocessor ステップを使用してください。`reference/dsl/preprocessor.md` を参照してください。

## `lint`

Skill ディレクトリに DSL リンターを実行します。Skill を構築する Skill（`skill_builder`、`skill_improver`）が出力を検証するために使用します。

```json
{
  "kind": "lint",
  "skill_path": "reyn/local/my_skill"
}
```

## `shell`

シェルコマンドを実行します。**デフォルトオフ。** ランタイムを `--allow-shell` で起動しなければならず、かつプロジェクトが `reyn.yaml` で `shell` を許可している（またはプロンプト経由でランごとに付与している）必要があります。

```json
{
  "kind": "shell",
  "cmd": "reyn run my_skill 'hello'",
  "timeout": 120
}
```

シェルが拒否された場合、OS は `shell_not_allowed` を発行し、Phase を失敗させるのではなく拒否結果を返します。

**非推奨。** 1.0 で削除予定。代わりに `sandboxed_exec`（下記）を使用してください — 宣言された `SandboxPolicy` を強制する `SandboxBackend` を経由します。スキル初回呼び出し時に `DeprecationWarning` が発行されます。

## `sandboxed_exec`

宣言された `SandboxPolicy` と OS が選択した `SandboxBackend` を介して `argv` を実行します。分離強制が必要な（または将来必要になる）ケースで `shell` を置き換えます。

```json
{
  "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` でプロジェクト全体をブロックできます。

```json
{
  "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` で明示的に事前承認できます。

```json
{
  "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 ブロックに列挙されている必要があります。

```json
{
  "kind": "mcp",
  "server": "filesystem",
  "tool": "read_text_file",
  "args": {"path": "README.md"}
}
```

フィールド: `server`（必須 — `reyn.yaml` の `mcp.servers:` のキーと一致する必要がある）、`tool`（必須 — server の `tools/list` レスポンスで公開されているツール名）、`args`（省略可、デフォルト `{}`）。

OS は server のトランスポート（`stdio`、`http`、`sse`）を解決し、`MCPClient` 経由でディスパッチして、ツール結果を返します。呼び出しごとに `mcp_called`、`mcp_completed`、（失敗時）`mcp_failed` イベントが発行されます。

server の設定、トランスポートの選択、セキュリティモデルについては [concepts/mcp.md](../../concepts/tools-integrations/mcp.md) を参照してください。

## `mcp_install`

`registry.modelcontextprotocol.io` から MCP server をプロジェクト設定にインストールします。**Phase 専用**（ルーターからは使用不可）。Skill frontmatter に `permissions.mcp_install: true` が必要で、ユーザー承認も必要です。

```json
{
  "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）— キー名のみ。値は含まない

## `embed`

LiteLLM embedding モデルを使ってテキスト（または artifact JSONL）をベクトル化します。入力形式は 2 種類:

**Form A — インライン**（小さなペイロード。recall クエリのベクトル化など）:
```json
{
  "kind": "embed",
  "texts": ["フランスの首都はどこですか？"],
  "model": "standard"
}
```

**Form B — artifact 参照**（大きなペイロード。多数のチャンクのインデックス化など）:
```json
{
  "kind": "embed",
  "input_artifact": "chunks.jsonl",
  "text_field": "text",
  "output_artifact": "embedded_chunks.jsonl",
  "model": "standard"
}
```

`texts` / `input_artifact` のいずれか一方のみ指定します。フィールド:

- `texts`（list[str]、Form A）— インラインでベクトル化するテキスト。
- `input_artifact`（str、Form B）— ワークスペース相対 JSONL パス。各行に `text_field` キーが必要。
- `text_field`（str、デフォルト `"text"`）— Form B でベクトル化するフィールド名。
- `output_artifact`（str、Form B）— 出力ベクトルを書き込む JSONL パス。`content_hash` 済みの行はスキップ（冪等）。
- `model`（str、デフォルト `"standard"`）— `reyn.yaml embedding.classes` で解決するモデルクラス、または LiteLLM 文字列。

戻り値: Form A → `{"kind": "embed", "vectors": [[float, ...]]}`. Form B → `{"kind": "embed", "status": "ok", "embedded": int, "skipped": int}`.

イベント: `embed_progress`（Form B のみ、バッチごと — `embedded`、`skipped` の累積カウント）。

## `index_write`

埋め込み済みチャンクを名前付き SQLite インデックスバックエンドに書き込みます。入力形式は 2 種類:

**Form A — インライン**:
```json
{
  "kind": "index_write",
  "source": "project_docs",
  "chunks": [
    {"text": "...", "vector": [0.1, 0.2, ...], "metadata": {"path": "README.md"}}
  ],
  "mode": "append"
}
```

**Form B — artifact 参照**:
```json
{
  "kind": "index_write",
  "source": "project_docs",
  "input_artifact": "embedded_chunks.jsonl",
  "mode": "replace",
  "description": "プロジェクトドキュメントのインデックス",
  "path": "docs/**/*.md"
}
```

フィールド:

- `source`（str、必須）— 論理ソース名。`.reyn/index/<source>/index.db` にマップされます。
- `chunks`（list[dict]、Form A）— `{text, vector, metadata}` オブジェクトのインラインリスト。
- `input_artifact`（str、Form B）— ワークスペース相対 JSONL パス。
- `mode`（`"append" | "replace"`、デフォルト `"append"`）— `"replace"` はソースを先にドロップ。
- `embedding_model`（str、省略可）— チャンクメタデータに記録する埋め込みモデルのオーバーライド。
- `description`（str、省略可）— ソースマニフェストに保存される説明（ルーターシステムプロンプトに表示）。
- `path`（str、省略可）— ソースマニフェストに保存される元の glob またはパス。

戻り値: `{"kind": "index_write", "source": str, "chunks_written": int, "chunks_skipped": int}`.

## `index_query`

インデックス済みソース 1 件に対してセマンティック類似検索を行います。

```json
{
  "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 です。

```json
{
  "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`）が必要で、デフォルトでユーザー承認ゲートが発動します。

```json
{
  "kind": "index_drop",
  "source": "project_docs"
}
```

フィールド:

- `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）と合格/不合格フラグを返します。

```json
{
  "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 に渡すだけです。

## `skill_resolve`

Skill 名をオンディスクの `skill.md` パスに解決します。標準解決チェーン（`reyn/local/` → `reyn/project/` → `stdlib/`）を使用し、パスメタデータを返します。ファイル内容の読み取りは行いません。

```json
{
  "kind": "skill_resolve",
  "name": "skill_improver"
}
```

フィールド:
- `name`（str、必須）: 短い Skill 名（スラッシュや `.md` 拡張子は不要）。

戻り値:
- `name: str` — 入力のエコー
- `resolved: bool` — いずれかの解決レイヤーに `skill.md` が存在する場合 `true`
- `skill_md_path: str | null` — `skill.md` への絶対パス。未解決の場合 `null`
- `source: "local" | "project" | "stdlib" | null` — マッチした解決レイヤー
- `skill_dir: str | null` — `skill.md` の親ディレクトリ。未解決の場合 `null`

**イベント**: `skill_resolve_completed`（`name`、`resolved`、`source`）— 呼び出しごとに発行（P6）。

**パーミッション**: 不要。本 op は読み取り専用（信頼済み解決チェーン内のパス存在確認のみ）であり、ファイル内容は読み取りません。

**OpPurity**: `world`（ファイルシステムメタデータの読み取り。Skill が追加／削除されると結果が変わる可能性あり）。

**ユースケース**: Skill の絶対パスを必要とする stdlib python ステップは、このフィルシステムウォーク処理を本 op に委ねることで `mode: safe` を宣言できます。R-PURE-MODE Class D リファクタの主要利用元は `skill_improver/copy_to_work_resolver` および `eval_builder/analyze_skill_resolver` です。

---

**コントリビューター向けメモ:** `src/reyn/schemas/models.py` および `src/reyn/op_runtime/registry.py` に新しい Control IR op kind を追加する際は、**同じ PR でここにセクションを追加してください**。reference と registry は同期を保つ必要があります。ルールの詳細は [CLAUDE.md](https://github.com/tya5/reyn/blob/main/CLAUDE.md) を参照してください。

## LLM に op が提示される場所

OS は利用可能な op をすべてのコンテキストフレームに `available_control_ops` として注入します。各エントリーは `kind`、一行の説明、動作例を含みます。LLM は意図を説明にマッピングして op を選択します。Phase の Markdown は op の構文を説明してはなりません（P8）。

## 関連情報

- [run.md](../cli/run.md) — `--allow-shell`、`--allow-unsafe-python`
- [events.md](events.md) — op の種類ごとに発行されるイベント
- [コンセプト: principles P8](../../concepts/architecture/principles.md#p8-phase-instructions-contain-only-domain-logic)
