コンテンツにスキップ

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 を使用、FP-0017) shell(デフォルトオフ;--allow-shell が必要)
sandboxed_exec SandboxPolicySandboxBackend を介して argv を実行する(FP-0017) バックエンドが強制(SandboxPolicy
web_search DuckDuckGo で公開ウェブを検索する Tier 1 — デフォルト許可;reyn.yamlweb.search: deny でブロック
web_fetch 単一 URL を取得してテキストを抽出する Tier 1 — デフォルト許可;reyn.yamlweb.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 オブジェクトです:

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

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

file

サブ操作: readwriteeditdeleteglobgrepregenerate_index

{"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_indexpath 配下のすべての *.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 を再実行します。訪問カウントは増加しません。

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

run_skill

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

{
  "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_builderskill_improver)が出力を検証するために使用します。

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

shell

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

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

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

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

sandboxed_exec

宣言された SandboxPolicy と OS が選択した SandboxBackend を介して argv を実行します(FP-0017)。分離強制が必要な(または将来必要になる)ケースで 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.yamlsandbox.backendauto | seatbelt | landlock | noop)および sandbox.on_unsupportedwarn | error | ignore)で上書き可能。

結果フィールド: returncodestdoutstderrtruncatedbackend

発行イベント: sandboxed_exec_startedsandboxed_exec_completed(P6 監査証跡)。

DuckDuckGo を使って公開ウェブを検索し、構造化された結果を返します。Tier 1 — デフォルト許可;Permission 宣言不要(FP-0022)。reyn.yamlweb.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 完全一致
  • -termterm を含む結果を除外

ユーザーの意図が特定サイトや phrase に限定される場合に operator を使用し、それ以外は通常のキーワードで問題ありません。結果は results フィールドの {title, url, snippet} オブジェクトのリストとして返されます。

web_fetch

単一 URL を取得し、テキスト抽出したコンテンツを返します。Tier 1 — デフォルト許可;Permission 宣言不要(FP-0022)。通常は web_search の後、特定の結果ページを詳しく読むために使用します。reyn.yamlweb.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.yamlmcp.servers: に server が宣言されており、かつ Skill の permissions.mcp frontmatter ブロックに列挙されている必要があります。

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

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

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

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

mcp_install

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

{
  "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. RegistryClientserver.json を取得 2. ランタイムコマンドの利用可能性確認(npx / uvx / docker / dnx) 3. PermissionResolver.require_mcp_install でゲート(ADR-0029) 4. intervention_bus 経由で isSecret=true 環境変数を収集;secrets.store で保存 5. 対象スコープの設定ファイルに mcp.servers.<name> を書き込む 6. mcp_server_installed イベントを発行(P6)— キー名のみ。値は含まない

embed

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

Form A — インライン(小さなペイロード。recall クエリのベクトル化など):

{
  "kind": "embed",
  "texts": ["フランスの首都はどこですか?"],
  "model": "standard"
}

Form B — artifact 参照(大きなペイロード。多数のチャンクのインデックス化など):

{
  "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 のみ、バッチごと — embeddedskipped の累積カウント)。

index_write

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

Form A — インライン:

{
  "kind": "index_write",
  "source": "project_docs",
  "chunks": [
    {"text": "...", "vector": [0.1, 0.2, ...], "metadata": {"path": "README.md"}}
  ],
  "mode": "append"
}

Form B — artifact 参照:

{
  "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 件に対してセマンティック類似検索を行います(ADR-0033)。

{
  "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_vectornull のときの列挙フォールバックのトークン上限。

戻り値: {"kind": "index_query", "source": str, "results": [{"text": str, "score": float, "metadata": dict}]}.

recall

マクロ op: クエリを embed → 各ソースに index_query → グローバルにトップ K をマージして結果を返します(ADR-0033)。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)が必要で、デフォルトでユーザー承認ゲートが発動します(ADR-0029)。

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

フィールド:

  • source(str、必須)— 削除する論理ソース名。

戻り値: {"kind": "index_drop", "source": str, "chunks_dropped": int}.

イベント: index_droppedsourcechunks_dropped)。

judge_output

Phase 内の評価ループで使用する LLM ベースの出力スコアラー(FP-0007 Component D)。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_executedop=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/)を使用し、パスメタデータを返します。ファイル内容の読み取りは行いません。

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

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

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

イベント: skill_resolve_completednameresolvedsource)— 呼び出しごとに発行(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 を参照してください。

LLM に op が提示される場所

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

関連情報