From 4ae2a46ccb4e58874387d809f5b389edd876a6e5 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 22 Apr 2026 17:43:42 +0900 Subject: [PATCH] =?UTF-8?q?pod-prompt-catalog=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 - tickets/pod-prompt-catalog.md | 145 --------------------------- tickets/pod-prompt-catalog.review.md | 46 --------- 3 files changed, 192 deletions(-) delete mode 100644 tickets/pod-prompt-catalog.md delete mode 100644 tickets/pod-prompt-catalog.review.md diff --git a/TODO.md b/TODO.md index 6f41409d..3378fd54 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,6 @@ - [ ] LLM プロバイダ/モデルカタログ → [tickets/llm-provider-catalog.md](tickets/llm-provider-catalog.md) - [ ] Pod オーケストレーション - [ ] 動的 Scope 変更 → [tickets/dynamic-scope.md](tickets/dynamic-scope.md) - - [ ] Pod 内部プロンプトのカタログ化 → [tickets/pod-prompt-catalog.md](tickets/pod-prompt-catalog.md) - [ ] ネイティブ GUI クライアント MVP → [tickets/native-gui-mvp.md](tickets/native-gui-mvp.md) - [ ] TUI 拡充 - [ ] フルスクリーン化によるオーバーホール → [tickets/tui-fullscreen-overhaul.md](tickets/tui-fullscreen-overhaul.md) diff --git a/tickets/pod-prompt-catalog.md b/tickets/pod-prompt-catalog.md deleted file mode 100644 index 9905a9de..00000000 --- a/tickets/pod-prompt-catalog.md +++ /dev/null @@ -1,145 +0,0 @@ -# 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 /.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` 追加のみで破壊的変更なし - -## 影響範囲 - -- `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 -- 状態: Approve -- レビュー詳細: [./pod-prompt-catalog.review.md](./pod-prompt-catalog.review.md) -- 日付: 2026-04-22 diff --git a/tickets/pod-prompt-catalog.review.md b/tickets/pod-prompt-catalog.review.md deleted file mode 100644 index bb09da08..00000000 --- a/tickets/pod-prompt-catalog.review.md +++ /dev/null @@ -1,46 +0,0 @@ -# Review: Pod 内部プロンプトのカタログ化 - -## 前提・要件の確認 - -1. **`PodPrompt` enum (要件1)** — 満たされている。7 variant を列挙し (`crates/pod/src/prompts.rs:60-81`)、各 variant が `key()` とともに declaration-order の `ALL`/`KEYS` 定数に連動する。呼び出し側 (pod/notification_buffer/pod_interceptor/interrupt_and_run/system_prompt/agents_md) はすべてカタログ経由に置換済み。`grep` で `SUMMARY_SYSTEM_PROMPT` / `TRUNCATION_NOTICE` / ハードコード文字列は production コードから一掃されている (テスト文字列のみ残存)。 - -2. **`internal.toml` builtin pack (要件2)** — 満たされている。`resources/prompts/internal.toml` に 7 key 全てを配置 (`resources/prompts/internal.toml:10-35`)。minijinja テンプレートとして評価され、`compact_system` は `{% include "$insomnia/internal/compact_system" %}` 経由で外部 `.md` を参照。長文分離の設計判断「`include` 一本化」は忠実に実装されている。 - -3. **ビルド時網羅性検査 (要件3)** — 満たされている。`build.rs` が TOML を parse して `INTERNAL_KEYS` slice を emit、`prompts.rs:122-145` の `const _: ()` が `PodPrompt::KEYS ↔ INTERNAL_KEYS` を双方向で検査する。ユーザー報告通り、片側除去/余剰で `panic!` メッセージ 2 種が発火することを確認済み。const eval ベースで proc-macro を回避しているのは軽量かつ依存最小で筋が良い。 - -4. **4 段 overlay merge (要件4)** — 満たされている。`PromptCatalog::load` が builtin → user (`user_pack_file`) → workspace (`workspace_pack_file`) → manifest pack の順で `merge_into` を呼ぶ (`prompts.rs:260-284`)。未知 key は `tracing::warn!` + ignore (`prompts.rs:370-386`)。builtin 層の不整合は build 時 error (要件3 と一体)。テスト `user_pack_overrides_builtin` / `workspace_pack_wins_over_user_pack` / `manifest_pack_wins_over_workspace_pack` / `unknown_key_in_runtime_pack_is_ignored_with_warning` が precedence と warn-ignore を裏打ちしている。 - -5. **minijinja 統一 + prefix resolver 流用 (要件5)** — 満たされている。`build_catalog` (`prompts.rs:440-482`) が `Environment` の `path_join_callback`/`loader` に既存 `PromptLoader` を配線し、値内の `{% include "$prefix/..." %}` が `$insomnia` / `$user` / `$workspace` 全てから引ける。テスト `value_can_pull_long_text_via_include` が builtin の `compact_system` を runtime pack 側から再参照できる挙動を確認している。 - -6. **`manifest.pod.prompt_pack` (要件6)** — 満たされている。`PodMeta.prompt_pack: Option` を `#[serde(default)]` で追加 (`crates/manifest/src/lib.rs:36-46`)、`PodMetaConfig` のカスケード merge にも反映 (`crates/manifest/src/config.rs:43, 199-206, 340, 415`)。`Pod::from_manifest` / `from_manifest_spawned` は `PromptCatalog::load(&loader, manifest.pod.prompt_pack.as_deref())` を呼ぶ。`$user/` プレフィックス経由で解決するテスト (`manifest_pack_supports_user_prefix`) 付き。 - -## アーキテクチャ・スコープ - -- **レイヤー境界** — 変更は `crates/pod` と `crates/manifest` (フィールド追加のみ) に収まり、`llm-worker` は触っていない。Pod 内部プロンプトは Pod 層で扱う、というスコープ方針を守っている。 -- **prefix × layer の分離方針** — 設計判断「prefix 名前空間 (resolve where) と layer (merge precedence) は混ぜない」が正しく反映されている。pack 自体は **固定パスの auto-discovery** と **manifest での明示指名** の 2 経路に限定され、`PromptLoader` の prefix 名前空間 (`$insomnia`/`$user`/`$workspace`) は pack 値内部の `{% include %}` でのみ再利用される。 -- **ランタイム vs ビルド時エラーの使い分け** — builtin 不整合は const-eval panic、runtime pack の unknown key は `warn + ignore`。前方互換性の判断通り。 -- **tool description に手を入れていない** — scope 外宣言を遵守。`resources/prompts/internal/` ディレクトリは builtin 長文のみ (`compact_system.md` 一件)。 -- **cargo add 運用** — `[build-dependencies] toml = "1.1.2"` が追加されているが、`cargo add --build` 経由で追加されているかは diff からは直接確認できない。既に `[dependencies]` に `toml = "1.1.2"` が存在するので workspace の既存バージョンに揃っており、挙動上の懸念は無い (手動編集だったとしても結果は同じ)。 -- **影響範囲との一致** — ticket の「影響範囲」リスト (`prompts.rs` 新設 / `internal.toml` / `internal/*.md` / 呼び出し置換 / `build.rs` / `manifest/config.rs`) はすべて diff 上に存在。未対応の項目は無い。 - -## 指摘事項 - -### Blocking -- なし。 - -### Non-blocking / Follow-up - -- **[API 表面の二重化]** `PromptCatalog::render(PodPrompt, Value)` と `compact_system()` 等の typed accessor が同時に `pub` 公開されている (`prompts.rs:289-342`)。ticket は `PodPrompt::CompactSystem.render(&ctx)` を 1 本の API として期待していた。実装の「variant ごとに context 型が固定なので typed accessor が筋」という判断は妥当で、現状の呼び出し元もすべて typed を使っている。`render(PodPrompt, Value)` を `pub(crate)` まで降格するか、typed accessor だけを公開面として残す方が、将来「新しい variant を追加したら typed accessor も実装しないとコンパイル的には気づかない」という弱さを防げる。 -- **[`append_trailing_section` の可視性]** `pub fn append_trailing_section` (`system_prompt.rs:188`) は module 内でしか呼ばれていない。`pub(crate)` か private へ落として API 表面を絞るのが望ましい。 -- **[factory 側の `.is_file()` と catalog 側の `.is_file()` の二重フィルタ]** `PodFactory::build_prompt_loader` (`factory.rs:202-211`) が既に `.is_file()` で絞った `PathBuf` を渡すのに、`PromptCatalog::load` も `path.is_file()` を再チェックしている (`prompts.rs:267,273`)。冗長で、仕様として「渡されたら読む」なのか「存在チェックは catalog 側の責務」なのかが曖昧。後者に寄せるなら loader 側は無条件に `PathBuf` を渡し、catalog 側だけで分岐させる (既存の挙動を保つ)。前者にするなら loader 側は「pack file が無ければ `None`」という契約を DocComment 化する。ticket にとって致命傷ではない。 -- **[`$insomnia/` manifest pack の `.toml` 読み取り経路]** `load_raw_builtin` は拡張子を付けない raw loader だが、これは元々 `$insomnia/` prefix が `.md` 前提で設計されていた `PromptLoader` を「拡張子ごと渡せば読める `.toml` 用 shortcut」として拡張している (`prompt_loader.rs:175-204`)。`$insomnia/` prefix の 2 つの意味 (テンプレートとしての `.md` 参照と、pack ファイルとしての `.toml` 参照) がひとつのローダに同居する形になっている。機能上は問題ないが、`$insomnia/default` (`.md` 付く) と `$insomnia/internal/foo.toml` (拡張子必須) で path の書き方が視覚的に揺れる。ドキュメント or 命名で区別してもよい。 - -### Nits - -- `PromptCatalog::builtins_only()` は `load(&PromptLoader::builtins_only(), None)` を thin-wrap するだけだが、テスト用に便利なので残しておいて問題なし (多用されている)。 -- `CatalogError::UnknownKey` は `PromptCatalog::render` が未登録テンプレートを引いた場合用だが、現状 `PodPrompt::key()` 経由でしか呼ばれないので到達しづらい。将来外部入力を受ける経路を増やしたときに活きる。 -- `single` 関数 (`prompts.rs:345-350`) は `BTreeMap<&'static str, Value>` を毎回組み立てている。typed accessor の呼び出し頻度 (LLM request ごとに高々数回) ではマイクロ最適化する必要はない。 - -## 判断 - -**Approve** — 要件 1〜6 はすべて満たされ、設計判断 (prefix × layer 分離 / 単一値型 / ビルド時 vs ランタイム分岐 / tool description 非対象 / auto + manifest 両立) が正確に実装されている。ワークスペーステストは全緑。Non-blocking な API 整理の余地 (render の可視性、loader-catalog 間の is_file 二重チェック) はあるが、ticket を閉じる上の障害ではない。