yoi/tickets/pod-factory.md

17 KiB
Raw Blame History

Pod Factory: 設定カスケードとプロンプト資産による Pod 自動生成

レビュー状態

初回レビュー実施済み。pod-factory.review.md を参照。 要件全項目達成、アーキテクチャ・テスト被覆とも良好で無条件で受け入れ可。指摘は 6 件(すべて任意修正または nit レベル)。

背景

現状、Pod を起動するには test_pod.local.toml のような完全な PodManifest TOML を手書きする必要がある。1 人のユーザーが1 つのエージェントを試験運用するには十分だが、Insomnia が狙う「複数のエージェントが独立プロセスとして spawn されて自律的に動く」世界観では、Pod のライフサイクル全体が自動化可能でなければならない。そのためには、Pod の作成自体も自動化可能である必要がある。

手書きマニフェストには以下の問題がある:

  • 1 Pod = 1 ファイルで、Pod を動的に増やす用途にスケールしない
  • 設定項目が多く(worker.* / provider.* / scope.* / compaction.* / tool_output.* 等)、毎回コピペしてわずかな差分だけ書き換える苦行になる
  • system_prompt を TOML 文字列に埋め込む形はプロンプト資産の再利用性が低い
  • Pod の起動条件の共通部分(プロバイダ・モデル・デフォルトツール設定など)は本来一度書けば良いのに、毎回書かされる

ゴール

Pod 作成を「最終的に PodManifest を1 つ構築する問題」として定式化し、その PodManifestカスケード + 差分上書きで組み立てる基盤を提供する。手書きが必要な TOML は「ユーザー / プロジェクト単位のデフォルト上書き」だけに縮退させ、個別の Pod 起動ごとに人間が TOML を触らない状態を目指す。

プロンプトは手書きマニフェストに文字列を埋め込む方式をやめ、テンプレート資産ライブラリとして参照可能にする。

方針

同じ型で、層で上書きする

  • 解決後の型は現行の manifest::PodManifest のまま(必須フィールド持ち)。
  • カスケードの各層は PodManifestConfigPodManifest と同じ構造だが全フィールドを Option / 部分形で保持する新規型)で表現する。部分形同士を順番にマージし、最後に TryFrom<PodManifestConfig> for PodManifest で必須チェックを行いつつ確定させる。
  • 各層は部分形を持てる(全フィールドを埋める必要はない)。存在するフィールドだけが下層を上書きする。
  • 人間が書くときは PodManifest と同じ TOML スキーマで書く(サブセット可)。ファイル名はすべて manifest.toml。「設定」という曖昧な名前を避け、「Pod manifest」という語彙に揃える。

カスケードの層

優先順位が低い方から高い方へ:

  1. ビルトインのデフォルト: コードに焼き込んだ基本値(現在 PodManifest 各フィールドの #[serde(default)]Default 実装に散っているものを集約)
  2. ユーザー manifest: $XDG_CONFIG_HOME/insomnia/manifest.tomlXDG_CONFIG_HOME 未設定時は ~/.config/insomnia/manifest.toml)。ユーザー個人のプロバイダ指定・デフォルトモデル・常用ツール設定等を書く
  3. プロジェクト manifest: プロジェクトルート下の .insomnia/manifest.toml。プロジェクト固有の scope・compaction 設定・system_prompt のベース等を書く
  4. プログラマティック overlay: Pod 生成を呼ぶコードGUI / CLI / 別 Pod からの spawn 等)が渡す部分形。ここで pod.namepod.pwd のようなその Pod に固有の値を与える

プロジェクトルートの判定

起動ディレクトリから上方向に .insomnia/ ディレクトリを探索し、最も近いものをプロジェクトルートとする。見つからなければ「プロジェクト無し」として扱い、プロジェクト層をスキップする。

マージのセマンティクス

  • スカラー (String, u32, bool 等): 上層が存在すれば丸ごと置換
  • Option 型: 上層が Some なら置換、None なら据え置き
  • マップ (例: tool_output.per_tool): キー単位でマージ、同一キーは上層優先
  • scope.allow / scope.deny: union各層から全部足す。すべての層が追加のみ可能。最終的な effective scope は既存の Scope::from_config ロジックに委ね、allow_union - deny_union として解決される。上位層は deny を追加することで下位層の allow を必ず削れるので、「上に行くほど強制力を持てる」構造が自然に出る
  • その他リスト: 当面は scope.* 以外にリスト型は存在しないが、新規にリスト型フィールドを追加するときは原則 unionscope と同じ)で扱う

エラー戦略

  • 未知フィールド: TOML に書かれた未定義のキーは tracing::warn! のみ出して無視する。#[serde(deny_unknown_fields)]使わない(将来バージョンアップで読めない旧設定が出るとユーザー体験が悪いため)
  • 型ミスマッチ: max_tokens = "100" のような型エラーは hard error として resolve 失敗させる。ファイルパスと位置情報をエラーメッセージに含める
  • パスフィールド: pod.pwd / provider.api_key_file / scope.*.target 等のパスは絶対パスのみ受け付ける。相対パスが書かれていたら resolve 時に hard error とする。相対パス解決を層ごとの manifest_dir に依存させるとカスケードの意味論が濁るため、各ファイルを書く人間の責任で絶対パスを指定させる

プロンプト資産ライブラリ

  • プロンプトは TOML 文字列ではなくファイルとして管理する。
  • 検索パスは以下の3層上層優先で解決:
    1. ビルトイン: レポジトリ直下の resources/prompts/ 以下を include_dir! マクロでバイナリに同梱
    2. ユーザー: $XDG_CONFIG_HOME/insomnia/prompts/
    3. プロジェクト: <project>/.insomnia/prompts/
  • ファイル名の stem がそのままプロンプト名。サブディレクトリも許容し、resources/prompts/common/tool-usage.md{% include "common/tool-usage" %} で参照できる
  • ファイル形式は .mdfrontmatter は無し。前方互換として --- 区切りの TOML フロントマターを将来持てる余地だけ認識しておく)
  • 既存の SystemPromptTemplateminijinja ベース)の Environment にカスタムローダを仕込み、テンプレート内から他のプロンプトを {% include "name" %} / {% import "name" as p %} で参照できるようにする
  • プロンプト資産自体もテンプレートとして評価され、現行の SystemPromptContextnow / cwd / scope / tools / files 等)と同じ変数が見える
  • include 時の変数伝搬は minijinja のデフォルト挙動(親スコープの変数が自動で見える)に任せる

設定値のテンプレート参照は扱わない

worker.max_tokens = "{{ env.INSOMNIA_MAX_TOKENS }}" のような設定値の中でテンプレートを展開する機能は本チケットの範囲外とする。テンプレートエンジンはプロンプト本文の組み立てだけに限定する。設定値の動的化が必要になった時点で別チケットで検討する。

プログラマティック Pod 作成 API

factory は PodManifest を組み上げる builder として設計する。イメージ:

let manifest: PodManifest = PodFactory::new()
    .with_user_manifest_auto()?      // XDG から自動読み込み、不在 OK
    .with_project_manifest_auto()?   // cwd から上方向に .insomnia/ を探索、不在 OK
    .with_overlay_toml(overlay)?     // programmatic な最上層 overlayTOML 文字列)
    .resolve()?;                     // -> PodManifest
Pod::from_manifest(manifest, store).await?;

Pod::from_manifest は現在 manifest_dir を取るが、本チケットで以下の二段に書き換える:

  • Pod::from_manifest(manifest: PodManifest, store) — 一次 API。factory の出力がそのまま入る
  • Pod::from_manifest_toml(toml: &str, store) — 便利関数。単層 manifest を TOML 文字列で直接投げる用途(テスト・デバッグ・動作確認)

manifest_dir 引数は廃止する。パスは絶対化済み前提のため、Pod 側で相対パス解決ロジックを持たない。ファイル読み込みは呼び出し側factory か callerの責務。

CLI

pod バイナリの CLI surface を factory に合わせて更新する。新 umbrella コマンド(insomnia spawn 等)は作らない。ユーザーが直接 CLI を叩くのは debug/automation の文脈で、GUI/TUI は Pod を subprocess として spawn する運用のため、pod バイナリ1本の fla だけで十分。

pod [--user-manifest <path>] [--project <path>] [--overlay <toml>] [--pwd <path>]
  • --user-manifest: 省略時は XDG から自動解決
  • --project: 省略時は cwd から上方向に .insomnia/ を探索
  • --overlay: 最上層の overlay を inline TOML 文字列で渡す(例: --overlay 'pod.name = "dbg"'
  • --pwd: 便利ショートカットoverlay に pod.pwd = ... を書く代わり)
  • --manifest <path> フラグは廃止。単層 manifest を直接読み込みたい場合は --user-manifest <path> で代替する

要件

カスケード基盤

  • ユーザー manifest・プロジェクト manifest・プログラマティック overlay を順に重ねた結果が PodManifest として取れる。
  • 各層が部分形を許容する(pod.pwd だけ書いてあっても良い等)。
  • マージセマンティクスがフィールドごとに定義され、単体テストで担保される(スカラー / Option / マップ / scope union
  • 未知フィールドは warn のみ、型ミスマッチは hard error。
  • 層が全て空で resolve 失敗する場合は、必要なフィールド(pod.pwd / provider / scope.allow 等)を明示するエラーを返す。

プロンプト資産ライブラリ

  • 3層の検索パスでプロンプトファイルを解決できる。
  • 同名プロンプトは上層優先で解決される。
  • SystemPromptTemplate の minijinja Environment にカスタムローダを仕込み、{% include "name" %} / {% import "name" as x %} で資産を参照できる。
  • 親テンプレートの変数が include 先にも見えるminijinja デフォルト挙動)。
  • ビルトインプロンプトは resources/prompts/ から include_dir! マクロで同梱され、追加・編集するときは該当ディレクトリにファイルを置く・編集するだけで済む。
  • プロンプト資産自体もテンプレートとして評価され、SystemPromptContext と同じ変数が見える。

プログラマティック Pod 作成

  • Pod::from_manifest(manifest: PodManifest, store)Pod::from_manifest_toml(toml: &str, store) の二段構成。path 受け API は廃止。
  • PodFactory builder が上記に繋がる。
  • TUI / GUI / daemon 等の上位クライアントが、TOML ファイルパスではなく overlay + 設定ディレクトリパスを渡すだけで Pod を起動できる。

CLI

  • pod バイナリが --user-manifest / --project / --overlay / --pwd を受け付ける。
  • 引数無しで cwd + XDG を自動解決して起動できる最小構成が動く。

ドキュメント

  • カスケード層の優先順位・マージ規則を docs/ にまとめる。
  • ユーザー manifest / プロジェクト manifest の最小例と全オプション例を残す。
  • resources/prompts/ のディレクトリ構造とビルトインプロンプト一覧を docs/ に記載。

リソース構成

insomnia/
├── crates/
│   ├── pod/
│   │   └── src/
│   │       ├── factory.rs        # 新規。PodManifestConfig カスケード → PodManifest
│   │       ├── prompt_loader.rs  # 新規。minijinja loader + 3層検索
│   │       └── ...
│   └── ...
├── resources/
│   └── prompts/
│       ├── coder.md
│       ├── reviewer.md
│       ├── planner.md
│       └── common/
│           └── tool-usage.md
  • factory とプロンプト loader は新規 crate を作らず pod crate 内に配置する。既に podmanifest に依存しているため追加の依存関係整理が不要
  • include_dir!("$CARGO_MANIFEST_DIR/../../resources/prompts") で全ビルトインプロンプトをバイナリに取り込む
  • ファイルの追加・編集は通常の file 操作だけで済む。既存ファイルの編集は cargo の変更追跡で自動再ビルドされる。新規追加・削除が反映されない場合は cargo clean -p pod を案内する(必要が増えたら build.rsrerun-if-changed を足す)
  • ビルトインプロンプトの初期ラインナップ(coder / reviewer / planner / common/tool-usage 等)は実装時に選定し、ドキュメントに列挙する

完了条件

  • PodFactory がカスケード層のマージで PodManifest を構築でき、マージセマンティクスの単体テストが通るスカラー・Option・マップ・scope union・未知フィールド warn・型ミスマッチ hard error
  • ユーザー manifest・プロジェクト manifest・プログラマティック overlay のすべての層を使う end-to-end テストで、Pod が path を一切渡さずに起動できる。
  • プロンプト資産ライブラリを経由して system_prompt が組み立てられ、{% include "ビルトイン名" %}resources/prompts/ 以下の資産を参照できることをテストで確認できる。
  • Pod::from_manifest(manifest)Pod::from_manifest_toml(toml) の二段 API が動き、path 受け API は削除されている。
  • pod バイナリが --user-manifest / --project / --overlay / --pwd を受け、引数無しでも cwd + XDG 自動解決で起動できる。
  • カスケード層の優先順位・マージ規則 / ユーザー manifest 例 / プロジェクト manifest 例 / ビルトインプロンプト一覧のドキュメントが docs/ に存在する。

他チケットとの関係

  • tickets/native-gui-mvp.md: 現状「manifest ファイルを選ぶ UI」を含むが、本チケット完了後はその UI が「overlay 指定 + Pod 起動」に置き換わる想定。native-gui-mvp 実装時に本チケットの API を使うか、先に path 渡しで済ませて後から差し替えるかは別途判断
  • tickets/tui-pod-spawn-ui.md: 同上。Pod spawn UI は本チケットが提供する API の上に構築される
  • tickets/protocol-design.md: Pod ↔ Client protocol 自体は変わらない。spawn 要求を protocol に載せるかどうかは protocol-design 側で検討
  • docs/system-prompt-template.md / crates/pod/src/system_prompt.rs: プロンプト資産ライブラリはこの minijinja 基盤の拡張として実装される

範囲外

  • preset の概念: 名前付き overlay セット(insomnia spawn coder 等)は本チケットでは導入しない。将来別チケットで検討
  • 設定値の中のテンプレート展開max_tokens = "{{ env.X }}" のような動的値)。プロンプト本文のテンプレート展開のみを扱う
  • GUI 内での manifest 編集 UI。編集は人間がエディタで TOML を書くだけあくまで「Pod 生成時に手書きしない」ことを目指す)
  • チーム共有・同期。ユーザー manifest とプロジェクト manifest は各自・各リポジトリ単位で管理される
  • 秘密情報管理API キー等)。既存の api_key_file 方式を維持する
  • 設定値の型バリデーション強化JSON Schema など)。現行の serde ベースで十分な範囲に留める
  • プロンプトフロントマターdescription / required_vars 等のメタデータ)。将来一覧 UI が必要になった時点で検討。フォーマット上の前方互換性だけは意識する
  • umbrella CLI コマンドinsomnia spawn 等)。pod バイナリ単体で完結させる