yoi/tickets/pod-prompt-catalog.md

9.0 KiB
Raw Blame History

Pod 内部プロンプトのカタログ化

背景

Pod は Worker を拡張する機構を持つ: コンテキスト圧縮 (Compact)、非同期通知 (Notify)、中断と再開 (Interrupt)、system prompt の trailing section、AGENTS.md 取込時の注記。これらの機構がランタイムで Worker に注入する/Worker 向けに構成するプロンプトは、現状各モジュール内の const &str / format! に分散している。

場所 役割
crates/pod/src/pod.rs:50 SUMMARY_SYSTEM_PROMPT Compact worker の system prompt
crates/pod/src/notification_buffer.rs:73 format_notification Method::Notify を Worker history に system_message として注入するラッパー
crates/pod/src/interrupt_and_run.rs:18-20 中断時の synthetic tool_result と system_message
crates/pod/src/system_prompt.rs:179-203 append_trailing_section ## Working boundaries / ## Project instructions (AGENTS.md) ヘッダとレイアウト
crates/pod/src/agents_md.rs:19 TRUNCATION_NOTICE AGENTS.md が 64KB 超過したときの末尾注記

resources/prompts/default.md でユーザー向け instruction テンプレートは一元化された一方、Pod が Worker を拡張する側のプロンプトは並行した一元管理を持たない。結果として:

  • 全体を俯瞰しづらく、新しい injection 点を足すときに既存との一貫性が取れない
  • 多言語化や文体カスタマイズの導線がない
  • builtin から差分だけ書き換える軽量な手段がない

tool description は tool 宣言と併置が自然tool 追加 = 1 箇所追加なので本チケット対象外。Pod の Worker 拡張機構は異質なものが「Pod のトーン」として束になって振る舞う性質上、中央で扱う価値がある。

要件

1. 中央モジュール: PodPrompt enum

Pod が Worker に注入する全プロンプトを列挙する enum を 1 つ置く。variant の集合が「存在する injection 点」の master。新しい注入点を増やすときは variant 追加が必要 = コード上で勝手に散らかせない。

Pod 内部の各モジュールは PodPrompt::CompactSystem.render(&ctx) のように 1 本の API で引く。直接 include_str! / const &str / format! で prompt 文字列を書かない。

2. 翻訳パック形式: prompts.toml

全 variant を key-value で持つ TOML。値は minijinja テンプレート文字列builtin/ランタイム問わず一律 minijinja render

[prompt]
interrupt_system_note = "[The previous turn was interrupted by the user. The user's next request follows.]"

notify_wrapper = """[Notification]
{{ message }}

This is a notification, not a blocking request. ..."""

# 長文は外部ファイルに link
compact_system = "{% include '$insomnia/internal/compact_system.md' %}"

変数展開は各 variant ごとに定義された context を render 時に渡す(例: notify_wrappermessagecompact_system は tool 名リストなど。context の具体形は実装段階で variant ごとに確定する。

3. Builtin pack の網羅性をビルドエラーで強制

resources/prompts/internal.tomlPodPrompt 全 variant を網羅していないとビルド失敗。enum に variant を足したが builtin pack に key が無い、あるいは逆、はコンパイルが通らない。build.rs もしくは proc-macro で検査する(どちらを取るかは実装時判断)。

4. 4 段の置換マージ

key 単位で overlay。下層から順に apply、後勝ち:

builtin           resources/prompts/internal.toml                 (必須・網羅)
  ↓
user              $XDG_CONFIG_HOME/insomnia/prompts.toml          (任意・auto)
  ↓
workspace         <project>/.insomnia/prompts.toml                (任意・auto)
  ↓
manifest pack     manifest.pod.prompt_pack で指名                (任意)
  • 欠落 key: 下層から継承
  • ランタイム層 (user/workspace/manifest pack) の unknown key: tracing::warn! して無視
  • builtin 層の不整合はビルドエラー(前項)

5. 値 render は minijinja 統一、include は既存 prefix resolver 流用

全値を minijinja で parse / render。{% include "$prefix/..." %} によって外部ファイルを link 可能。resolver は既存 crates/pod/src/prompt_loader.rs を流用し、$insomnia / $user / $workspace すべてをどの層の pack からも参照できる。

6. manifest 露出

[pod]
prompt_pack = "$user/packs/japanese.toml"

任意フィールド。指定されていれば 4 段目 overlay として適用。auto-discovery (user/workspace) とは独立で共存する。子 Pod を spawn する際、親が子の役割に応じた pack を明示する用途を想定。

設計判断

prompt_pack は prefix 名前空間に載せない

既存 $insomnia/ / $user/ / $workspace/名前空間で、同じ key で複数の source を指し合うことはない。一方 pack はレイヤーで、同じ key を置き換える。この 2 つを同じ軸に混ぜるとユーザーが「どの書き方で上書きされるか」を予測できなくなる。

pack ファイルは固定パスの auto-discoverymanifest による明示指名の 2 経路のみ。各値内部で {% include "$prefix/..." %} を使うのは別軸なので prefix 体系の利点はそのまま享受できる。

長文ファイル分離用の独立フィールドを作らない

値が minijinja である以上、長文を別ファイルにしたければ "{% include '$insomnia/internal/foo.md' %}" と書けば済む。「TOML 値の文字列」と「ファイル参照」の 2 値型を用意する必要はない。1 種類で統一する。

ランタイム層の unknown key は warn

pack ファイルを書いた時点と Pod バージョンがずれたとき、hard error にすると古い pack で Pod が起動しなくなる。前方互換のため warn で無視する。builtin 層は build-time に同梱するので不整合はビルドで捕まえられる = error で問題ない。

tool description は本チケット対象外

tool 追加は tool ごとの 1 箇所の自然な単位で、description は tool の属性として宣言と併置が読みやすい。Pod 内部 injection は「異質なものが一体としてトーンを決める」共通軸なので中央化の価値が別にある。この差を混ぜない。

auto-discovery と manifest 指定を両立させる

auto-discovery は「ユーザー or プロジェクトの永続設定 (翻訳、文体)」、manifest 指定は「その Pod の役割による差し替え」と目的が別。どちらか一方では片方のユースケースが潰れるので両立する。key 単位の merge は共通なので実装コスト差は小さい。

Scope 外

  • tool description の resources 化(併置方針を維持)
  • 具体的な翻訳 pack の作成(本チケットは導線のみ)
  • pack 編集 GUI / TUI
  • user/workspace 以外の auto-discovery パス追加(別 XDG 層、bundle pack 等)

依存

  • crates/pod/src/prompt_loader.rs ($prefix resolver を minijinja include から流用)
  • crates/pod/src/system_prompt.rs (minijinja 使用パターン)
  • crates/manifest: pod.prompt_pack: Option<String> 追加のみで破壊的変更なし

影響範囲

  • crates/pod/src/prompts.rs (新設): PodPrompt enum、render API、pack loader、4 段 merge
  • resources/prompts/internal.toml (新設): builtin pack
  • resources/prompts/internal/*.md (新設): 長文外出し
  • crates/pod/ 各モジュール: 既存ハードコードを PodPrompt::...render(&ctx) 呼び出しに置換
  • crates/pod/build.rs もしくは proc-macro (新設): enum ⇔ builtin pack 網羅検査
  • crates/manifest/src/config.rs: prompt_pack フィールド追加

実装順序

  1. PodPrompt enum と render API を定義。builtin map (in-memory、include_str!) から引くだけの最小実装。variant の render 網羅を単体テストで確認
  2. resources/prompts/internal.toml に全 key を書き、既存ハードコードをそのまま文字列として移植。各モジュールの呼び出しを PodPrompt::...render に置換 (挙動は既存と完全同一)
  3. builtin 網羅を build-time 検査に格上げ (enum ⇔ pack の双方向で欠落/余剰を検出)
  4. user / workspace の auto-load と 3 段マージ。ランタイム unknown key の warn
  5. manifest.pod.prompt_pack を追加、4 段目 overlay として load
  6. 長文 variant (compact_system など) を {% include "$insomnia/internal/..." %} 形式に分離。$user / $workspace から include で override できることをテスト

各ステップ終了時点でビルド通過・既存テスト合格を維持する。

Review