yoi/tickets/pod-prompt-catalog.md

141 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```toml
[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_wrapper``message`、`compact_system` は tool 名リストなど。context の具体形は実装段階で variant ごとに確定する。
### 3. Builtin pack の網羅性をビルドエラーで強制
`resources/prompts/internal.toml``PodPrompt` 全 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 露出
```toml
[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-discovery** と **manifest による明示指名**の 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 できることをテスト
各ステップ終了時点でビルド通過・既存テスト合格を維持する。