diff --git a/crates/manifest/src/paths.rs b/crates/manifest/src/paths.rs index 71b607c4..5a2d2d37 100644 --- a/crates/manifest/src/paths.rs +++ b/crates/manifest/src/paths.rs @@ -125,7 +125,7 @@ pub fn user_prompts_dir() -> Option { Some(config_dir()?.join("prompts")) } -/// Root resource directory used for bundled prompts/Nix support files. +/// Root resource directory used for bundled prompts, profiles, catalogs, and docs. pub fn resource_dir() -> Option { if let Some(p) = env_path(RESOURCE_DIR_ENV) { return Some(p); diff --git a/crates/manifest/src/profile.rs b/crates/manifest/src/profile.rs index 9578a0ed..bd974e74 100644 --- a/crates/manifest/src/profile.rs +++ b/crates/manifest/src/profile.rs @@ -382,7 +382,7 @@ impl ProfileResolver { .map(str::to_string); match extension.as_deref() { Some("lua") => {} - Some("nix") => return Err(ProfileError::UnsupportedProfileType { path: absolute_path, message: "Nix profile evaluation is not part of the primary Profile path; use a Lua profile or --manifest for a complete Manifest".into() }), + Some("nix") => return Err(ProfileError::UnsupportedProfileType { path: absolute_path, message: "Nix profile files are no longer supported; use a Lua profile or --manifest for a complete Manifest".into() }), other => return Err(ProfileError::UnsupportedProfileType { path: absolute_path, message: format!("unsupported profile extension {}; Lua profiles must end in .lua", other.map_or("".to_string(), |s| format!(".{s}"))) }), } let profile_dir = absolute_path diff --git a/crates/pod-store/src/lib.rs b/crates/pod-store/src/lib.rs index 07517b4d..4a2f249a 100644 --- a/crates/pod-store/src/lib.rs +++ b/crates/pod-store/src/lib.rs @@ -416,7 +416,7 @@ mod tests { ); metadata.resolved_manifest_snapshot = Some(serde_json::json!({ "pod": { "name": "profile-pod" }, - "profile": { "source": { "kind": "path", "path": "/profiles/coder.nix" } } + "profile": { "source": { "kind": "path", "path": "/profiles/coder.lua" } } })); let json = serde_json::to_string(&metadata).unwrap(); diff --git a/crates/pod/src/main.rs b/crates/pod/src/main.rs index ac316a1d..0511a915 100644 --- a/crates/pod/src/main.rs +++ b/crates/pod/src/main.rs @@ -444,7 +444,7 @@ permission = "write" #[test] fn profile_uses_selected_profile() { let tmp = TempDir::new().unwrap(); - let profile = tmp.path().join("profile.nix"); + let profile = tmp.path().join("profile.lua"); let cli = Cli::try_parse_from([ "insomnia-pod", "--profile", @@ -633,12 +633,12 @@ permission = "write" fn profile_conflicts_with_manifest_and_restore_modes() { let segment_id = session_store::new_segment_id().to_string(); for args in [ - vec!["insomnia-pod", "--profile", "p.nix", "--manifest", "m.toml"], - vec!["insomnia-pod", "--profile", "p.nix", "--pod", "agent"], + vec!["insomnia-pod", "--profile", "p.lua", "--manifest", "m.toml"], + vec!["insomnia-pod", "--profile", "p.lua", "--pod", "agent"], vec![ "insomnia-pod", "--profile", - "p.nix", + "p.lua", "--session", &segment_id, ], @@ -659,7 +659,7 @@ permission = "write" let cli = Cli::try_parse_from([ "insomnia-pod", "--profile", - "p.nix", + "p.lua", "--profile-pod-name", "agent", ]) diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index a3332ed1..d9119729 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -1181,9 +1181,9 @@ mod tests { #[test] fn parse_profile_spawn_mode() { - match parse_args_from(["--profile", "/profiles/coder.nix"]).unwrap() { + match parse_args_from(["--profile", "/profiles/coder.lua"]).unwrap() { Mode::Spawn { profile } => { - assert_eq!(profile, Some("/profiles/coder.nix".to_string())); + assert_eq!(profile, Some("/profiles/coder.lua".to_string())); } _ => panic!("expected Spawn mode"), } @@ -1196,7 +1196,7 @@ mod tests { ( vec![ "--profile".to_string(), - "p.nix".to_string(), + "p.lua".to_string(), "--resume".to_string(), ], "--profile can only be used for fresh spawn", @@ -1204,7 +1204,7 @@ mod tests { ( vec![ "--profile".to_string(), - "p.nix".to_string(), + "p.lua".to_string(), "--session".to_string(), segment_id, ], @@ -1213,7 +1213,7 @@ mod tests { ( vec![ "--profile".to_string(), - "p.nix".to_string(), + "p.lua".to_string(), "--socket".to_string(), "/tmp/insomnia/sock".to_string(), ], @@ -1222,7 +1222,7 @@ mod tests { ( vec![ "--profile".to_string(), - "p.nix".to_string(), + "p.lua".to_string(), "agent".to_string(), ], "--profile can only be used for fresh spawn", diff --git a/crates/tui/src/spawn.rs b/crates/tui/src/spawn.rs index fd0937d3..4e0cc84e 100644 --- a/crates/tui/src/spawn.rs +++ b/crates/tui/src/spawn.rs @@ -655,7 +655,7 @@ mod tests { r#" default = "coder" [profile] -coder = "profiles/coder.nix" +coder = "profiles/coder.lua" "#, ) .unwrap(); @@ -679,7 +679,7 @@ coder = "profiles/coder.nix" r#" default = "coder" [profile.coder] -path = "profiles/coder.nix" +path = "profiles/coder.lua" description = "Project coder" "#, ) diff --git a/docs/manifest-profiles.md b/docs/manifest-profiles.md index 098d0b95..3b34b8fb 100644 --- a/docs/manifest-profiles.md +++ b/docs/manifest-profiles.md @@ -31,7 +31,7 @@ insomnia-pod --profile ./coder.lua insomnia --profile ./coder.lua ``` -`--profile` accepts an explicit path, `path:`, a discovered profile name, `default`, or a source-qualified name such as `project:coder`, `user:coder`, or `builtin:coder`. Path-like values containing `/`, starting with `.`, or ending in `.lua` are explicit paths. `.nix` paths are no longer the primary profile layer and fail with a diagnostic that points users at Lua profiles or `--manifest`. +`--profile` accepts an explicit path, `path:`, a discovered profile name, `default`, or a source-qualified name such as `project:coder`, `user:coder`, or `builtin:coder`. Path-like values containing `/`, starting with `.`, or ending in `.lua` are explicit paths. ``.nix` paths are no longer supported as profiles and fail with a diagnostic that points users at Lua profiles or `--manifest`. `--profile` conflicts with `insomnia-pod --manifest` and with restore/session/adopt modes. Use `--profile-pod-name ` when a launcher needs a creation-time Pod name override without invoking `--pod` restore semantics. Profile evaluation is a creation-time path; Pod resume restores saved Pod state/resolved snapshots rather than re-evaluating the profile source. diff --git a/docs/plan/network-peering.md b/docs/plan/network-peering.md index f12007ae..570cd0fd 100644 --- a/docs/plan/network-peering.md +++ b/docs/plan/network-peering.md @@ -206,11 +206,11 @@ host_a (spawner) host_b (remote) # 1. session + profile/manifest input を転送 ssh insomnia@host-b "mkdir -p ~/workspaces/task-123/store" tar cz session/ | ssh insomnia@host-b "tar xz -C ~/workspaces/task-123/store" -scp profile.nix insomnia@host-b:~/workspaces/task-123/profile.nix +scp profile.lua insomnia@host-b:~/workspaces/task-123/profile.lua # 2. Pod を起動(detach) ssh insomnia@host-b "insomnia-pod --store ~/workspaces/task-123/store \ - --profile ~/workspaces/task-123/profile.nix &" + --profile ~/workspaces/task-123/profile.lua &" # 3. socket を tunnel で引っ張る ssh -L /tmp/pod-b.sock:/run/insomnia/task-123/pod.sock insomnia@host-b diff --git a/docs/pod-factory.md b/docs/pod-factory.md index 1915806d..d6d966c5 100644 --- a/docs/pod-factory.md +++ b/docs/pod-factory.md @@ -1,385 +1,189 @@ -# Pod Factory: カスケード設定とプロンプト資産 +# Pod Factory: Profile resolution and prompt assets -`PodFactory` は、複数の層に分かれた `manifest.toml` とプログラマティック -overlay をマージして、検証済みの `PodManifest` と `PromptLoader` を生成する -ビルダー。これにより Pod 起動ごとに TOML を手書きする必要がなくなる。 +`PodFactory` は、選択された Profile または明示的な one-file Manifest から、検証済みの `PodManifest` と `PromptLoader` を生成する境界である。通常の fresh spawn は profile discovery/default selection を使い、user/project `manifest.toml` の ambient cascade は使わない。 + +`PodManifest` は Pod 起動に必要な完全な runtime recipe で、Pod 名、具体的な scope、解決済みパス、model/provider 設定、worker 設定などを含む。Profile はその前段の再利用可能な recipe template であり、Pod 名や concrete scope authority など runtime-bound な値は resolver が起動入力から埋める。 --- -## カスケード層 +## 起動モード -優先順位が低い順(上位ほど下位を上書き): - -| 優先度 | 層 | 位置 | 典型的な内容 | +| モード | 入力 | 用途 | ambient manifest cascade | |---|---|---|---| -| 1 | ビルトインのデフォルト | `manifest::defaults` モジュールの `pub const` 群を `PodManifestConfig::builtin_defaults()` が cascade 層として注入 | `tool_output.default_max_bytes = 64 KiB`, `file_upload.max_bytes = 256 KiB` など | -| 2 | ユーザー manifest | `/manifest.toml`(解決ルールは `manifest::paths`) | プロバイダ指定、デフォルトモデル、常用ツール設定 | -| 3 | プロジェクト manifest | 起動ディレクトリから上方向に探索した最初の `/.insomnia/manifest.toml` | scope、compaction、プロジェクト固有の instruction | -| 4 | プログラマティック overlay | CLI / GUI / 別 Pod からの spawn 等 | `pod.name`、spawn 時の `worker.instruction` のような Pod 固有値 | +| Profile | `--profile ` または省略時 default | 通常の fresh spawn。Lua profile を解決して runtime Manifest を作る | 使わない | +| One-file Manifest | `--manifest ` | デバッグ/互換用の完全 Manifest escape hatch | 使わない | +| Restore/attach | `--pod` / `--session` 等 | 保存済み Pod state / session snapshot から再開 | profile を再評価しない | +| SpawnPod internal | hidden `--spawn-config-json` | 親 Pod が子用 config を解決して渡す | 使わない | -デフォルト値はすべて `crates/manifest/src/defaults.rs` の `pub const` として集約 -されており、serde `#[default = "..."]` 経路(`PodManifest` の直接 deserialize) -と `TryFrom` 経路(cascade 解決)の両方が同じ constants を -参照する。デフォルトを変更するときは `defaults.rs` の 1 行を書き換えるだけで -全経路に反映される。 +`profiles.toml` は profile registry/default selection のための UX 設定であり、Pod Manifest 層ではない。Profile の中身へ merge されない。 -どの層も TOML スキーマは `PodManifest` と同じ(全フィールド省略可)。 +## Profile resolution -## マージセマンティクス +Profile は Lua で書かれる。Rust resolver は selected profile を restricted Lua VM 内で評価し、返り値が Profile-shaped であることを検証してから `PodManifest` に変換する。 -| フィールド種別 | 規則 | +```lua +local profile = require("insomnia.profile") +local models = require("insomnia.models") +local scope = require("insomnia.scope") +local compact = require("insomnia.compact") + +local model = models.catalog("codex-oauth/gpt-5.5") + +return profile { + slug = "coder", + description = "Project coding profile", + + model = model, + worker = { + reasoning = "high", + }, + compaction = compact.ratio { + threshold = 0.8, + request = 0.9, + }, + scope = scope.workspace_write(), +} +``` + +Profile が表現してよいのは reusable な recipe fields である。 + +- model selection / provider policy +- worker settings / reasoning +- compaction policy +- memory / web policy +- permissions / tool policy +- session diagnostics policy +- scope intent such as `scope.workspace_write()` + +Profile に入れてはいけないもの: + +- `pod.name` +- concrete `scope.allow` / `scope.deny` +- runtime directories, sockets, callback addresses +- active/pending/session/pod-store runtime state +- resolved absolute paths that bind the profile to one machine/workspace +- raw resolved secret material + +これらが必要な場合は、resolver の runtime input または `--manifest` の explicit Manifest path で扱う。 + +## Profile selector semantics + +`--profile` は以下を受け付ける。 + +| 形 | 意味 | |---|---| -| スカラー(`String`, `u32`, `bool` 等) | 上層に値があれば丸ごと置換 | -| `Option` | 上層が `Some` なら置換、`None` なら据え置き | -| 配列スカラー(`worker.stop_sequences` 等) | 上層に値があれば配列ごと置換。追記マージはしない | -| マップ(`tool_output.per_tool` 等) | キー単位でマージ、同一キーは上層優先 | -| `scope.allow` / `scope.deny` | **union**(各層から全部足す)。上位層は `deny` で下位層の `allow` を必ず削れる | -| `permissions.rule` | **union**(下位層の rule → 上位層の rule の順に評価)。`permissions.default_action` は上位層があれば上書き | +| 省略 / `default` | registry default を使う。通常は bundled `builtin:default` | +| `` | unqualified profile name。ambiguous なら fail closed | +| `builtin:` / `user:` / `project:` | source-qualified selector | +| `path:` / `./profile.lua` / `/abs/profile.lua` | 明示 path profile | -各層をマージした結果(`PodManifestConfig`)を `TryFrom -for PodManifest` が必須フィールド検証と絶対パス検証をかけて `PodManifest` -に変換する。 +`.nix` profile files are no longer supported. Reusable profiles are Lua; complete low-level recipes belong behind `--manifest`. -## パス解決 +Discovery は bundled builtin profiles、user registry (`/profiles.toml`)、project registry (`/.insomnia/profiles.toml`) を読む。後段の default が前段の default を上書きするため、project default は user/default builtin より優先される。unqualified ambiguous names は source-qualified suggestion を出して失敗する。 -manifest 中のパス(`model.auth.file` / `scope.*.target` / -`compaction.model.auth.file`)は相対記述を許容する。相対パスは -**各層のベース基準**で層ごとに絶対化され、そのあとで cascade merge に -かかる。層をまたいだ相対の意味ブレ(user 層の `./keys` が project 層の -どこを指すのか曖昧)を避けるための設計。 +Example `.insomnia/profiles.toml`: -| 層 | ベース | +```toml +default = "coder" + +[profile] +coder = "profiles/coder.lua" +reviewer = "profiles/reviewer.lua" + +[profile.orchestrator] +path = "profiles/orchestrator.lua" +description = "Project orchestrator" +``` + +Relative registry paths are resolved against the `profiles.toml` file that declares them. + +## Controlled Lua environment + +Profile evaluation runs with controlled host-provided `require`. + +Host virtual modules: + +- `require("insomnia")` +- `require("insomnia.profile")` +- `require("insomnia.models")` +- `require("insomnia.compact")` +- `require("insomnia.scope")` + +Profile-local modules can be reused with dotted names such as `require("shared")` or `require("shared.models")`; they resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, unrestricted `package`, `dofile`, `loadfile`, `load`, and `collectgarbage` are unavailable by default. + +## One-file Manifest mode + +`--manifest ` reads exactly one TOML file, resolves relative paths against that file's parent directory, applies builtin defaults, and validates through `PodManifestConfig -> PodManifest`. It does not load user/project `manifest.toml` files and conflicts with `--profile`. + +Use `--manifest` only when you need the complete low-level Manifest escape hatch or a focused debug fixture. + +## Builtin defaults + +Base defaults that are independent of profile choice live in Rust constants under `crates/manifest/src/defaults.rs` and in `PodManifestConfig::builtin_defaults()`. The bundled default role profile lives at `resources/profiles/default.lua` and is discovered as `builtin:default`. + +デフォルト値を変更するときは、次のどちらを変更するのかを明確にする。 + +- all manifests/profiles の baseline default: Rust defaults +- ordinary dogfooding/default role: `resources/profiles/default.lua` + +## Path resolution + +Profile-local Lua module paths are resolved relative to the selected profile file's directory and are constrained to that directory tree. + +Manifest paths in one-file Manifest mode are resolved relative to the manifest file's parent directory before validation. `scope.*.target` and auth file paths must be absolute by the time `PodManifest` is constructed; a remaining relative path indicates a resolver bug and returns `ResolveError::RelativePath`. + +Pod cwd is not part of the Profile. It is the process `current_dir()` for manual startup, or the parent-selected `Command::current_dir` for spawned Pods. + +## Unknown fields and type errors + +Profile validation is stricter than old ambient manifest cascade semantics at the top-level boundary. Complete-Manifest/runtime-authority fields such as `manifest`, `config`, `pod`, and concrete `scope.allow`/`scope.deny` are diagnosed rather than silently treated as reusable profile data. + +One-file Manifest deserialization keeps the Manifest compatibility behavior: unknown fields may warn/ignore where the Manifest schema allows it, while type mismatches are hard errors with file/path context. + +## Prompt assets + +`worker.instruction` refers to a prompt asset used as the main system prompt body. Import-map prefixes: + +| Prefix | Resolution | |---|---| -| user manifest (`/manifest.toml`) | そのファイルの親ディレクトリ | -| project manifest (`/.insomnia/manifest.toml`) | **プロジェクトルート**(`.insomnia/` の親)。`target = "."` がワークスペース全体を指すように | -| overlay(inline TOML・programmatic) | プロセスの `current_dir()` | - -Pod の作業ディレクトリは manifest に含まれない。プロセス起動時の -`std::env::current_dir()` がそのまま Pod の pwd となるため、別の作業 -ディレクトリで Pod を走らせたい場合は `cd` してから `insomnia-pod` を起動する -(または `SpawnPod` が子に対して行っているように、親プロセス側で -`Command::current_dir` を明示する)。 - -cascade merge 後の `TryFrom` では `ensure_absolute` -が不変条件チェックとしてだけ働く。相対パスが残っていれば上流の -resolve 段を取りこぼしている証拠なので `ResolveError::RelativePath` を -返す。 - -## 未知フィールドと型エラー - -- **未知フィールド**: `tracing::warn!` を出して無視。将来バージョンアップで読めない - 旧設定が出るとユーザー体験が悪いため、`#[serde(deny_unknown_fields)]` は使わない。 -- **型ミスマッチ**: `max_tokens = "100"` のような型エラーは hard error として - resolve 失敗させる。ファイルパスと位置情報をエラーメッセージに含める。 - ---- - -## manifest.toml 例 - -### ユーザー層(最小) - -`/manifest.toml`: - -```toml -[model] -ref = "anthropic/claude-sonnet-4-6" -auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" } -``` - -`ref = "/"` はプロバイダ / モデルカタログを引く短縮形。 -`scheme` / `base_url` / `model_id` は provider 側の宣言から引かれ、`auth` も -カタログの `auth_hint` を起点に解決する。ここでは env 既定(`INSOMNIA_API_KEY_ANTHROPIC`) -ではなく file から読みたいので `auth` だけ override している。詳細は -`crates/provider/README.md` と `resources/{providers,models}/builtin.toml` を参照。 - -### プロジェクト層(最小) - -`/.insomnia/manifest.toml`: - -```toml -[[scope.allow]] -target = "/abs/path/to/project" -permission = "write" - -[[scope.deny]] -target = "/abs/path/to/project/secrets" -permission = "read" - -[compaction] -compact_threshold = 80000 -``` - -### 全オプション例 - -```toml -[pod] -name = "reviewer" - -# Form A: ref のみ(カタログから scheme / base_url / auth_hint / capability を全部引く) -# [model] -# ref = "anthropic/claude-sonnet-4-6" -# -# Form B: ref + 部分 override(ここで示している形) -# カタログ起点に個別フィールドだけ上書き。ref 指定時は scheme / model_id / auth は任意 override。 -# -# Form C: 完全 inline(カタログ無視。実験用 / カタログに無いモデル) -# [model] -# scheme = "anthropic" -# model_id = "claude-sonnet-4-6" -# auth = { kind = "api_key", file = "..." } -[model] -ref = "anthropic/claude-sonnet-4-6" -base_url = "https://api.anthropic.com" -auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" } - -[worker] -instruction = "$user/reviewer" -max_tokens = 4096 -max_turns = 50 -temperature = 0.3 -top_p = 0.9 -top_k = 40 -stop_sequences = ["\n\n", ""] -reasoning = "medium" # 文字列 = effort label / 整数 = thinking budget tokens。詳細は docs/reasoning.md - -[worker.tool_output] -default_max_bytes = 65536 - -[worker.tool_output.per_tool] -Read = 131072 -Grep = 8192 - -[worker.file_upload] -max_bytes = 262144 - -[[scope.allow]] -target = "/abs/path/to/project" -permission = "write" - -[[scope.allow]] -target = "/abs/path/to/docs" -permission = "read" -recursive = false - -[[scope.deny]] -target = "/abs/path/to/project/secrets" -permission = "write" - -[permissions] -default_action = "allow" # allow | deny | ask - -[[permissions.rule]] -tool = "Bash" -pattern = "rm *" -action = "deny" - -[[permissions.rule]] -tool = "Write" -pattern = "*.env" -action = "deny" - -[web] -enabled = true - -[web.search] -provider = "brave" -api_key_env = "BRAVE_SEARCH_API_KEY" -timeout_secs = 15 - -[web.fetch] -timeout_secs = 20 -redirect_limit = 5 -max_response_bytes = 2097152 -max_output_bytes = 65536 - -[compaction] -prune_protected_tokens = 8000 -prune_min_savings = 4096 -compact_threshold = 80000 -compact_request_threshold = 90000 -compact_retained_tokens = 8000 -compact_auto_read_budget = 8000 -compact_worker_max_input_tokens = 50000 -compact_worker_max_turns = 20 - -[compaction.model] -scheme = "gemini" -model_id = "gemini-2.0-flash" -auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/gemini" } -``` - ---- - -## `[worker]` 設定 - -`[worker]` は Pod 内の `llm_worker::RequestConfig` とターン制御へ渡す設定を持つ。 -Provider ごとの wire 名の違い(OpenAI の `max_completion_tokens` / -Responses の `max_output_tokens` / Gemini の `generation_config` など)は -scheme 側が吸収する。 - -| key | 型 | 既定 | 内容 | -|---|---|---|---| -| `instruction` | `String` | `$insomnia/default` | システムプロンプト本体として使う prompt asset 参照 | -| `max_tokens` | `u32` | 未指定 | 1 request の最大出力 token。scheme が provider の該当 wire field に投影。scheme ごとのセマンティクス差は `docs/reasoning.md` | -| `max_turns` | `NonZeroU32` | 未指定 | 1 run 内で Worker が進められる最大 turn 数 | -| `temperature` | `f32` | 未指定 | sampling temperature | -| `top_p` | `f32` | 未指定 | nucleus sampling | -| `top_k` | `u32` | 未指定 | top-k sampling。未対応 scheme では warning または provider 側挙動に任せる | -| `stop_sequences` | `Vec` | `[]` | stop sequence。cascade では上層指定が配列ごと置換する | -| `reasoning` | `String` または `i32` | 未指定 | reasoning / thinking 制御。詳細は `docs/reasoning.md` | -| `tool_output.default_max_bytes` | `usize` | `65536` | tool result `content` の既定 byte cap | -| `tool_output.per_tool` | `Map` | `{}` | tool 名ごとの byte cap override | -| `file_upload.max_bytes` | `usize` | `262144` | submit 時の FileRef (`@`) upload / attachment の byte cap | - -生成設定は provider 別の値域検証を行わない。型が TOML と合わない場合は manifest -parse error になるが、provider が受け付けない値や組み合わせは API 応答で検出する。 - -## `[web]` 設定 - -`WebSearch` / `WebFetch` は通常の built-in function tool として登録されるが、manifest で明示的に有効化されるまでネットワークアクセスしない。無効または未設定の場合、tool call は「設定されていない」旨の明示的なエラーを返す。 - -```toml -[web] -enabled = true - -[web.search] -provider = "brave" -api_key_env = "BRAVE_SEARCH_API_KEY" # API key は env 参照に置き、manifest に raw secret を書かない -timeout_secs = 15 - -[web.fetch] -timeout_secs = 20 -redirect_limit = 5 -max_response_bytes = 2097152 -max_output_bytes = 65536 -``` - -`WebSearch` の最初の provider は Brave Search API(`https://api.search.brave.com/res/v1/web/search`)で、入力は `query` と任意の `limit` / `offset`。Brave の制約に合わせて `query` は 400 文字 / 50 words まで、`limit` は 1-20、`offset` は 0-9 に制限される。`timeout_secs` を省略した場合は安全な既定値が使われ、provider response は固定上限内で読み込まれる。 - -`WebFetch` は http/https URL のみを fetch し、timeout・redirect・response/output byte limit を適用する。localhost / private / link-local などの host/IP は fetch 前と各 redirect で拒否される。テストや明示的に信頼した環境では `[web] allow_private_addresses = true` または `[web.fetch] allow_private_addresses = true` を指定できる。 - -## `[permissions]` 設定 - -`[permissions]` が無い場合、ツール permission 層は無効で従来通り実行する。`[permissions]` を書く場合は `default_action = "allow" | "deny" | "ask"` が必須で、`[[permissions.rule]]` は宣言順に最初に一致した rule が採用される。一致しなければ `default_action` を使う。 - -```toml -[permissions] -default_action = "allow" - -[[permissions.rule]] -tool = "Bash" -pattern = "rm *" -action = "deny" -``` - -`tool` は実行時に登録されているツール名(`Bash`, `Read`, `Write`, `Edit`, `Glob`, `Grep`, `WebSearch`, `WebFetch` 等)に対して大小文字を無視して照合する。`pattern` は built-in tool では主に `command` / `file_path` / `path` / `pattern` / `query` / `url` 引数に対する `*` / `?` ワイルドカードとして評価される。 - -`allow` は通常実行、`deny` はその tool call を実行せず `is_error = true` の synthetic tool result を履歴へ追加してターンを継続する。`ask` は型として受け付けるが、承認 protocol は未実装のため現在は headless に待機せず fail-closed(synthetic error result)になる。 - -## instruction とプロンプト資産 - -### `worker.instruction` フィールド - -Pod のシステムプロンプトの**本体**として使うプロンプト資産への参照。 -import-map 形式のプレフィックスで指定する: - -| プレフィックス | 解決先 | -|---|---| -| `$insomnia` | バイナリ同梱の `resources/prompts/`(`include_dir!`) | -| `$user` | `/prompts/`(`manifest::paths` で解決) | +| `$insomnia` | bundled `resources/prompts/` (`include_dir!`) | +| `$user` | `/prompts/` | | `$workspace` | `/.insomnia/prompts/` | -- `.md` 拡張子は省略する(例: `$insomnia/default` → `resources/prompts/default.md`) -- 省略時のデフォルト値は `$insomnia/default`(`defaults::DEFAULT_INSTRUCTION`) -- 指定した prefix の dir に該当ファイルが無ければ **hard error**(fallthrough しない) +`.md` extension can be omitted, e.g. `$insomnia/default` resolves to `resources/prompts/default.md`. Missing files are hard errors; prefixes do not fall through. -### ビルトインプロンプト +Profile and one-file Manifest CLI paths currently use builtin prompt assets only for initial loader construction. `$insomnia/...` works; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery. -`resources/prompts/` 以下に同梱: - -| 名前 | 用途 | -|---|---| -| `default` | デフォルトの instruction 本体。workspace / tool-usage をインクルード | -| `common/workspace` | cwd・日付の注入 | -| `common/tool-usage` | ツール使用の共通ガイダンス | - -### `{% include %}` の相対解決 - -テンプレート内で `{% include "name" %}` のようにプレフィックス無しで書いた場合、 -**include を書いたファイル自身のプレフィックスとディレクトリ**からの相対で解決する: - -- `$insomnia/default.md` 内の `{% include "common/workspace" %}` → `$insomnia/common/workspace` -- `$user/custom.md` 内の `{% include "$insomnia/common/tool-usage" %}` → 明示的プレフィックスが優先 - -### システムプロンプトの最終構造 - -`instruction` テンプレートのレンダリング結果に、Rust 側で以下の**固定セクション**を付加する。 -ユーザーテンプレートからは触れない領域: - -``` - - ---- -## Working boundaries - - - ---- ← AGENTS.md が不在なら省略 -## Project instructions (AGENTS.md) - - ← AGENTS.md が不在なら省略 -``` - -- scope セクションは**必ず**出力される -- AGENTS.md セクションは不在時に区切り `---` ごと省略 - ---- +The rendered instruction body is followed by fixed Rust-provided sections for working boundaries and, when present, `AGENTS.md`. User templates cannot remove the scope section. ## `insomnia-pod` CLI -`insomnia-pod` の通常起動は profile discovery/default から runtime manifest を作る。user/project `manifest.toml` の ambient cascade は通常起動では使わない。 +Normal fresh startup uses profile discovery/default selection: -``` -insomnia-pod [--profile ] [--profile-pod-name ] [-s/--store ] [--session ] +```text +insomnia-pod [--profile ] [--profile-pod-name ] [-s/--store ] ``` -| フラグ | 説明 | +| Flag | Description | |---|---| -| `--profile ` | builtin/user/project profile registry から Lua profile を選択。省略時は registry default(通常は `builtin:default`) | -| `--profile-pod-name ` | profile resolution 時に fresh spawn 用の runtime `pod.name` を指定 | -| `-s, --store ` | セッション永続化ディレクトリ(デフォルト: `/sessions/`、`manifest::paths` で解決) | -| `--session ` | 既存 session id から Pod を復元し、同じ jsonl に後続 turn を追記する | +| `--profile ` | Select a Lua profile. Omitted means registry default, normally `builtin:default` | +| `--profile-pod-name ` | Fresh-spawn runtime `pod.name` override for profile resolution | +| `-s, --store ` | Session persistence directory, default `/sessions/` | -単一ファイルだけで起動したい場合は `--manifest` を指定する。 +Restore/attach uses Pod/session state and does not re-evaluate profile sources. -``` -insomnia-pod --manifest [-s/--store ] [--session ] +```text +insomnia-pod --pod +insomnia-pod --session ``` -`--manifest` は指定 TOML 1 枚だけを読み、builtin defaults を merge したうえで `PodManifestConfig -> PodManifest` の required validation を通す。user / project manifest layer は読まない。`--profile`、`--project` とは併用不可。 +Spawn children use hidden `--spawn-config-json`, `--adopt`, and `--callback ` flags. These are internal handoff details used by `SpawnPod` after the parent has allocated scope and prepared the child config. -spawn 子 Pod 用の内部フラグとして `--adopt` と `--callback ` がある。これらは `SpawnPod` が scope allocation と親 callback socket を引き継がせるために使うもので、通常の手動起動では使わない。 +## Programmatic boundary -Pod の作業ディレクトリは `insomnia-pod` 起動時の cwd が直接使われる。別ディレクトリで -動かしたい場合は `cd && insomnia-pod ...` のように外側で `cd` してから起動する。 - -引数無しで起動すると、profile registry default(通常は bundled `builtin:default`)で起動する。 - ---- - -## プログラマティック API +New code should resolve profiles through the profile resolver and then construct Pods from the resulting `PodManifest` and `PromptLoader`. One-file Manifest helpers remain for tests/debugging. Avoid reintroducing user/project manifest cascade APIs as normal startup behavior. ```rust -use pod::{Pod, PodFactory}; - -let (manifest, loader) = PodFactory::new() - .with_user_manifest_auto()? // manifest::paths から自動読み込み、不在 OK - .with_project_manifest_auto()? // cwd から上方向に .insomnia/ を探索、不在 OK - .with_overlay_toml(overlay)? // programmatic な最上層 overlay - .resolve()?; // -> (PodManifest, PromptLoader) - +let (manifest, loader) = resolve_profile_or_manifest(cli_inputs)?; let pod = Pod::from_manifest(manifest, store, loader).await?; ``` - -`Pod::from_manifest_toml(toml, store)` は単層 manifest を TOML 文字列で直接投げる -便利関数(テスト・デバッグ向け)。builtins-only のプロンプトローダで動く。 diff --git a/resources/nix/profile-lib.nix b/resources/nix/profile-lib.nix deleted file mode 100644 index 22512131..00000000 --- a/resources/nix/profile-lib.nix +++ /dev/null @@ -1,67 +0,0 @@ -# Insomnia Nix profile helpers. -# -# A profile file can use: -# -# let insomnia = import ./path/to/profile-lib.nix {}; -# in insomnia.mkProfile { -# name = "coder"; -# manifest = insomnia.mkManifest { ... }; -# } -# -# The output is consumed by `insomnia-pod --profile ` via -# `nix eval --json --file `. - -{ }: - -let - profileFormat = "insomnia.nix-profile.v1"; - - optional = name: value: - if value == null then {} else { ${name} = value; }; - - secretRef = ref: { - kind = "secret_ref"; - inherit ref; - }; - - mkManifest = manifest: manifest; - - mkProfile = - { name ? null - , description ? null - , manifest ? null - , config ? null - , ... - }@args: - let - resolvedManifest = - if manifest != null then manifest - else if config != null then config - else removeAttrs args [ "name" "description" "manifest" "config" ]; - in - { - profile = ({ format = profileFormat; } - // optional "name" name - // optional "description" description); - manifest = resolvedManifest; - }; - - semanticPresets = { - # Skeleton for users to extend in their own Nix. Rust does not attach any - # hidden semantic meaning to these helpers; they only generate manifest JSON. - codingAssistant = { modelId ? "claude-sonnet-4-20250514", authRef ? null }: - { - model = { - scheme = "anthropic"; - model_id = modelId; - } // (if authRef == null then {} else { auth = secretRef authRef; }); - }; - }; - -in -{ - inherit profileFormat mkProfile mkManifest semanticPresets; - secrets = { - ref = secretRef; - }; -} diff --git a/resources/nix/profiles/default.nix b/resources/nix/profiles/default.nix deleted file mode 100644 index 6c5bcd0a..00000000 --- a/resources/nix/profiles/default.nix +++ /dev/null @@ -1,40 +0,0 @@ -let - insomnia = import ../profile-lib.nix {}; -in -insomnia.mkProfile { - name = "default"; - description = "Bundled default Insomnia coding profile"; - manifest = insomnia.mkManifest { - pod.name = "insomnia"; - - scope.allow = [ - { target = "."; permission = "write"; recursive = true; } - ]; - - session.record_event_trace = true; - - worker.reasoning = "high"; - - model.ref = "codex-oauth/gpt-5.5"; - - compaction = { - threshold = 200000; - request_threshold = 240000; - worker_context_max_tokens = 100000; - }; - - memory = { - extract_threshold = 50000; - consolidation_threshold_files = 5; - consolidation_threshold_bytes = 50000; - }; - - web = { - enabled = true; - search = { - provider = "brave"; - api_key_env = "BRAVE_SEARCH_API_KEY"; - }; - }; - }; -}