8.5 KiB
Review: Pod 内部プロンプトのカタログ化
前提・要件の確認
-
PodPromptenum (要件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 コードから一掃されている (テスト文字列のみ残存)。 -
internal.tomlbuiltin 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) — 満たされている。
build.rsが TOML を parse してINTERNAL_KEYSslice を emit、prompts.rs:122-145のconst _: ()がPodPrompt::KEYS ↔ INTERNAL_KEYSを双方向で検査する。ユーザー報告通り、片側除去/余剰でpanic!メッセージ 2 種が発火することを確認済み。const eval ベースで proc-macro を回避しているのは軽量かつ依存最小で筋が良い。 -
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 を裏打ちしている。 -
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 側から再参照できる挙動を確認している。 -
manifest.pod.prompt_pack(要件6) — 満たされている。PodMeta.prompt_pack: Option<String>を#[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 を閉じる上の障害ではない。