11 KiB
11 KiB
instruction のファイル参照化と system prompt 末尾の固定セクション
背景
pod-factory で worker.system_prompt を minijinja 文字列として扱う形にしたが、その後の運用方針の整理で以下のズレが見えてきた:
- manifest を手で触らない前提なので、
system_promptに minijinja 文字列をインラインで書く設計そのものが過剰。人間が書くのはプロンプトの中身ファイルだけで十分 - AGENTS.md を
files.agents_mdとして user template に露出しているが、ユーザー領域のテンプレートに AGENTS.md 埋め込みを任せると、guard 忘れで静かに消えたり、preset を差し替えるたびに AGENTS.md の扱いが破綻する - scope はセキュリティ境界なので、user の template が覚えていてくれる前提にすべきでない
- prompt loader が by-name fallthrough(project → user → builtin を順に探索)になっており、「同名上書き」と「偶然同名」が区別できない曖昧さがある
- 現在バイナリに同梱されている
coder/reviewer/planner/common/tool-usageは AI 任せで書いた placeholder で、設計者の意図が乗っていない - 既存の
Scope::summary()は許可されたパスを列挙するだけで、recursive = falseの情報が失われており、非再帰ルールの意味が LLM に伝わらない
ゴール
worker.system_promptをファイル参照フィールド (worker.instruction) に置き換える$insomnia/$user/$workspaceの import-map 形式の prefix でプロンプト資産を addressing- scope と AGENTS.md を コード側で system prompt 末尾に付加する固定セクションとして注入し、user template が触れない領域にする
- 組み込みプロンプトを
$insomnia/defaultの 1 本に整理 Scope::summary()のフォーマットを改善し、recursive = falseを明示する
方針
instruction フィールド
- manifest schema:
worker.system_prompt: Option<String>をworker.instruction: Option<String>に置き換える(minijinja 文字列を持つフィールドを消し、ファイル参照だけを受ける) - 値は import-map 記法のファイル参照:
$insomnia/default/$user/my-style/$workspace/custom .md拡張子は省略する- デフォルト値は
$insomnia/default。manifest でinstructionを書かなければこれが使われる(defaults.rsにDEFAULT_INSTRUCTION: &str = "$insomnia/default"を追加) - サブディレクトリ許容:
$insomnia/common/headerはresources/prompts/common/header.mdを指す
Prefix 解決
| prefix | 解決先 |
|---|---|
$insomnia |
バイナリ同梱の resources/prompts/(include_dir!) |
$user |
$XDG_CONFIG_HOME/insomnia/prompts/(factory が設定した user prompts dir) |
$workspace |
<project>/.insomnia/prompts/(factory が設定した project prompts dir) |
- 指定した prefix の dir に該当ファイルが無ければ hard error(fallthrough しない)
- 現在の「by-name で層を fallthrough して探す」ロジックは撤廃
Unqualified include の相対解決
{% include "name" %} のように prefix 無しで書かれた場合は、include を書いたファイル自身の prefix + ディレクトリからの相対で解決する:
$insomnia/default.md内の{% include "sub" %}→$insomnia/sub$insomnia/common/header.md内の{% include "nested/foo" %}→$insomnia/common/nested/foo$user/custom.md内の{% include "$insomnia/default" %}→ 明示的 prefix が優先
これにより「同じディレクトリ内でかたまって動くプロンプト集」を自然に書ける。
system prompt 末尾の固定セクション
SystemPromptTemplate のレンダリング後に、Rust 側で以下の構造を必ず付加する(user template からは触れない):
<instruction file のレンダ結果>
---
## Working boundaries
{scope.summary()}
{% if agents_md %}
---
## Project instructions (AGENTS.md)
{agents_md 本文}
{% endif %}
- 付加部分は Rust の
constまたはハードコードされたフォーマット文字列として実装し、resources/には置かない(user が触れる場所に置くと、触らないでほしいものが触れる場所に置かれる矛盾になる) - scope セクションは 必ず 出力される
- AGENTS.md セクションは不在時に省略(区切り
---ごと省略)
SystemPromptContext.files の削除
filesフィールドごと撤廃(元々 AGENTS.md 専用の予約席)- user template から AGENTS.md を参照する手段は無くなる(末尾セクションが面倒を見る)
Scope::summary() フォーマットの改善
crates/manifest/src/scope.rs の Scope::summary() を以下の方針で書き直す:
- 非再帰ルールを
[non-recursive]でマークする。例:Readable: - <local-path> [non-recursive] Writable: - <repo> - マーカーの位置はパスの末尾(パースしやすさより人間可読性を優先)
- recursive = true の場合は何も足さない(デフォルトなので無印)
- deny ルールは現行どおり summary には出さない(
from_configの時点で effective permission に焼き込まれるため) Readable/Writableのセクション分けは現行どおり
この変更により、最終セクションが出力する scope 情報が LLM にとって正確になる。
組み込みプロンプトの整理
- 削除:
resources/prompts/coder.md/reviewer.md/planner.md/common/tool-usage.md - 新規:
resources/prompts/default.md1 本のみ(内容は本チケットの実装時に author が記述) - 将来、役割ごとの preset を復活させたくなったら別チケットで追加する(preset 概念そのものが pod-factory の範囲外だった経緯もあり、安易に戻さない)
要件
Schema
manifest::WorkerManifestとmanifest::WorkerManifestConfigからsystem_promptを削除、instruction: Option<String>を追加(部分形は Option、resolve 時にdefaults::DEFAULT_INSTRUCTIONで埋める)manifest::defaultsにDEFAULT_INSTRUCTION: &str = "$insomnia/default"を追加
Loader
PromptLoaderを prefix addressing 版に書き換える- API:
PromptLoader::resolve(ref: &str, current: Option<&PromptRef>) -> Result<String, Error>refが$prefix/path形式なら該当 dir を引く- 素の
nameならcurrentの prefix + dir を前置して再帰 resolve currentが無い状態で素のnameが来たら error
- minijinja の
Environment::set_loader内でcurrentを追跡する
末尾セクションの組み立て
SystemPromptTemplate::render相当の経路で、user template 出力 + 固定末尾セクションを連結する- scope / AGENTS.md を formatter に渡す
Pod::ensure_system_prompt_materializedの既存フローを素直に置き換える
Scope::summary()
recursive = falseのルールに[non-recursive]マーカーを付ける- 既存の
summary形式は維持(Readable:/Writable:のセクション + インデントのフォーマット) crates/manifest/src/scope.rsの既存テストsummary_lists_readable_and_writable/summary_excludes_deny_rulesを更新または補完
テスト
- 既存:
include_resolves_builtin_prompt/agents_md_is_injected_when_present/files_reserved_namespace_is_empty/files_map_*系を書き換えまたは削除 - 新規:
instruction_default_resolves_to_insomnia_defaultinstruction_prefix_addressing_{insomnia,user,workspace}include_unqualified_resolves_relative_to_current_prefixinclude_explicit_prefix_overrides_relativeprefix_with_missing_file_is_hard_errortrailing_section_always_contains_scope_summarytrailing_section_contains_agents_md_when_presenttrailing_section_omits_agents_md_when_absentscope_summary_marks_non_recursive_rules
ドキュメント
docs/pod-factory.mdを更新:files.agents_mdの記述を削除- loader の by-name fallthrough 説明を prefix addressing に置き換え
instructionフィールドと$insomnia/defaultデフォルトの記述を追加- system prompt 末尾の固定セクション構造を図示
- 必要なら
docs/system-prompt-template.mdも追随
他チケットとの関係
- pod-factory (完了済み): 本チケットは pod-factory で実装した
PromptLoaderの一部書き換えになる。特に「3 層 by-name fallthrough」は撤廃される。pod-factory 自体をロールバックしない - tickets/native-gui-mvp.md: 直接の交差なし。GUI が instruction を差し替えたいときは同じ prefix 形式で渡せば良い
- tickets/protocol-design.md: protocol 非依存
- tickets/agents-md-ingestion.md (完了済み): AGENTS.md 読み取り経路は維持。表面の user template 経路だけが消える
完了条件
- manifest
[worker]からsystem_promptが消え、instructionでファイル参照を渡す形になっている instructionを省略すれば$insomnia/defaultが使われる$insomnia/$user/$workspaceの 3 種の prefix でプロンプト資産を参照できる- prefix 無しの
{% include %}が include 元ファイルの prefix に相対解決される - 未知の prefix や不在ファイルは hard error になる
- Pod の system prompt は常に「instruction の render 結果 + scope 要約 + (AGENTS.md があれば)」の構造で組み立てられる
SystemPromptContextからfilesが削除され、user template から AGENTS.md を参照する手段は残っていないScope::summary()がrecursive = falseルールに[non-recursive]マーカーを付けるresources/prompts/はdefault.md1 本のみ- 上記挙動がすべて単体テストで担保されている
docs/pod-factory.mdが新方針に追随している
範囲外
- preset 概念の復活: 削除した
coder/reviewer/plannerを preset として体系化する議論は別チケット - instruction をファイル参照以外にする拡張: インライン minijinja、TOML 配列、等は入れない
- CLI 変更:
podバイナリの flag は--overlayで instruction を上書きできる(--overlay 'worker.instruction = "$user/foo"')形でそのまま使える。新規 flag は追加しない - テンプレートエンジンの差し替え: minijinja を維持
default.md本文の著者判断: ticket の範囲は枠組み。本文は実装者が書く- scope summary に deny ルールを出す改善: 現行どおり deny は effective permission に焼き込むだけで、summary には出さない