plan: semantic nix profiles

This commit is contained in:
Keisuke Hirata 2026-05-30 07:37:51 +09:00
parent 520e3f8294
commit 9db8cdc7f8
No known key found for this signature in database
2 changed files with 539 additions and 0 deletions

View File

@ -0,0 +1,533 @@
# Semantic Nix profiles implementation plan
## 1. Intended model vs current drift
The original profile intent was: a selected profile expresses role/model/tool/context policy, then the resolver turns that policy into a concrete, validated runtime manifest snapshot. The current implementation drifted into asking profile authors to write `PodManifestConfig` in Nix.
Evidence from the closed `manifest-profiles` work:
- The motivating problem was that low-level manifest knobs such as compaction thresholds and pruning sizes are poor authoring UX; profiles should expose high-level intent and presets.
- The intended runtime boundary was still `selected profile + explicit startup inputs => deterministic resolved manifest/config snapshot => Pod runtime`.
- The Nix profile artifact was meant to be portable and standalone, but the design direction also said semantic presets should be preferred for hard-to-tune values.
Current drift:
- `resources/nix/profile-lib.nix` documents `mkProfile { manifest = mkManifest { ... }; }`; `mkManifest` is currently an identity function.
- `resources/nix/profiles/default.nix` is a manifest-shaped blob under `manifest = insomnia.mkManifest { ... }`.
- The builtin default contains `pod.name = "insomnia"`, which is instance identity, not profile policy.
- Reasoning effort appears as `worker.reasoning = "high"` instead of model/quality policy.
- Compaction values are copied as raw manifest constants (`threshold = 200000`, `request_threshold = 240000`, `worker_context_max_tokens = 100000`) instead of being derived from the selected model's effective context window.
- Builtin default resolution currently goes through `NixProfileResolver`, which shells out to `nix eval` even for the normal no-argument startup path.
The fix should introduce a typed semantic profile artifact and a manifestization step. Nix may remain an authoring language for user/project profiles, but `manifest` must become resolver output, not the public authoring API.
## 2. Current code map
### Profile discovery and selection
- `crates/manifest/src/profile.rs`
- `ProfileRegistrySource`: `Builtin | User | Project`.
- `ProfileSelector`: CLI/TUI selector model: explicit path, named source, or default.
- `ProfileSource`: provenance saved into resolved snapshots; currently path or registry path.
- `ProfileRegistryEntry`: discovered entry with source/name/path/description/default flag.
- `ProfileRegistry`: stores entries and one default; implements `default_entry`, `select`, `select_named`, builtin fallback default.
- `ProfileDiscovery::for_cwd()` wires builtin dir, user `profiles.toml`, nearest project `.insomnia/profiles.toml`.
- `discover_profile_dir()` registers every builtin `.nix` file or `profile.nix` directory.
- `load_profile_registry_file()` parses user/project registry TOML; it is selection metadata only, not runtime config.
- `ProfileSelector::parse_cli()` supports `default`, explicit paths, source-qualified selectors, and path-like compatibility.
### Nix eval and artifact parsing
- `crates/manifest/src/profile.rs`
- `NixProfileResolver` always uses `std::process::Command` to execute:
- `nix eval --json --file <absolute-profile-path>`.
- Missing binary returns `ProfileError::NixUnavailable` with a clear diagnostic.
- Nonzero status returns `ProfileError::NixFailed` with stderr.
- JSON stdout is parsed into `serde_json::Value` and passed to `resolve_profile_artifact()`.
- There is no embedded evaluator and no dependency on `rnix`, `nix-compat`, `tvix`, or similar crates in the workspace `Cargo.toml` files.
### Manifest-shaped artifact parsing / manifestization today
- `crates/manifest/src/profile.rs`
- `resolve_profile_artifact(source, base_dir, raw_artifact)` currently treats the evaluated artifact as already manifest-shaped.
- `ProfileEnvelope` only validates optional `profile.format == "insomnia.nix-profile.v1"`.
- `extract_manifest_value()` accepts:
- `{ profile = ..., manifest = { ... } }`,
- `{ profile = ..., config = { ... } }`,
- or a raw manifest object.
- It deserializes the extracted value directly as `PodManifestConfig`.
- It merges `PodManifestConfig::builtin_defaults()`, resolves paths, converts to `PodManifest`, attaches `ProfileManifestSnapshot`, and serializes `manifest_snapshot`.
### Runtime startup path
- `crates/pod/src/main.rs`
- `resolve_manifest()` defaults to `ProfileSelector::Default` when neither `--profile` nor `--manifest` is provided.
- `load_profile()` constructs `NixProfileResolver::new().with_workspace_base(cwd)` and calls `resolve()`.
- `--profile-pod-name` overwrites `resolved.manifest.pod.name` after profile resolution.
- `--manifest` remains a one-file compatibility/debug path.
- `load_spawn_config_json()` is an internal typed adopted-spawn path that deserializes `PodManifestConfig` directly.
- `crates/client/src/spawn.rs`
- `SpawnConfig.profile` is passed to `insomnia-pod --profile`.
- Fresh profile spawns pass `--profile-pod-name <pod_name>` so profile evaluation and pod-name restore semantics remain separate.
- `crates/tui/src/spawn.rs`, `crates/tui/src/main.rs`
- TUI uses profile discovery to populate/cycle the fresh-spawn profile field and passes the chosen selector through the client spawn path.
### Manifest and runtime config types
- `crates/manifest/src/config.rs`
- `PodManifestConfig`: partial manifest/cascade type.
- `PodMetaConfig.name`: currently required by final `TryFrom<PodManifestConfig> for PodManifest`.
- `WorkerManifestConfig.reasoning`: low-level worker request setting.
- `CompactionConfigPartial`: raw numeric compaction fields.
- `TryFrom<PodManifestConfig> for PodManifest` requires `pod.name` and `scope.allow`, fills defaults, validates absolute paths, and materializes `CompactionConfig`.
- `crates/manifest/src/lib.rs`
- `PodManifest`: concrete runtime contract; includes `pod`, `model`, `worker`, `scope`, optional `compaction`, `memory`, `web`, `skills`, and profile provenance.
- `CompactionConfig`: runtime numeric thresholds and budgets.
- `WorkerManifest.reasoning`: copied into `llm_worker::RequestConfig` by pod code.
### Model catalog and context-window plumbing
- `crates/manifest/src/model.rs`
- `ModelManifest`: data representation for `model.ref`, inline model fields, auth, capability, `context_window`, `max_context_window`.
- This crate intentionally does not resolve model refs today.
- `crates/provider/src/catalog.rs`
- Owns builtin provider/model catalog loading from `resources/providers/builtin.toml` and `resources/models/builtin.toml`.
- `resolve_model_manifest()` resolves `ModelManifest` into `ModelConfig` with effective `context_window`.
- Context window resolution order: manifest override > model catalog > provider default > `DEFAULT_CONTEXT_WINDOW`; then clamped by `max_context_window`.
- Builtin `codex-oauth/gpt-5.5` has `context_window = 1000000`, `max_context_window = 272000`, so effective context window is `272000`.
- `crates/provider/src/lib.rs`
- Builds live `LlmClient` from resolved `ModelConfig`.
- `crates/pod/src/controller.rs`
- `build_greeting()` calls `provider::catalog::resolve_model_manifest()` to report effective context window.
- `crates/pod/src/pod.rs`
- `apply_worker_manifest()` maps `WorkerManifest` into `RequestConfig`; `config.reasoning = wm.reasoning.clone()`.
- Compaction/memory worker model overrides are resolved at consumer boundary via `provider::build_client()`.
## 3. Proposed semantic profile schema / API shape
Introduce a new artifact format, e.g. `insomnia.semantic-profile.v1`, whose top-level shape is semantic and does not contain `manifest` or `config`.
Suggested JSON shape after Nix evaluation:
```json
{
"profile": {
"format": "insomnia.semantic-profile.v1",
"name": "default",
"description": "Bundled default Insomnia coding profile"
},
"policy": {
"role": "coder",
"model": {
"ref": "codex-oauth/gpt-5.5",
"quality": "high",
"reasoning": { "effort": "high" }
},
"scope": {
"workspace": { "permission": "write", "recursive": true }
},
"tools": {
"web": { "enabled": true, "search": { "provider": "brave", "api_key_env": "BRAVE_SEARCH_API_KEY" } }
},
"context": {
"compaction": {
"preset": "coding-long-context",
"threshold_ratio": 0.74,
"request_threshold_ratio": 0.88,
"worker_context_ratio": 0.37
}
},
"memory": {
"enabled": true,
"extract_threshold_ratio": 0.18,
"consolidation_threshold_files": 5,
"consolidation_threshold_bytes": 50000
},
"session": { "record_event_trace": true }
}
}
```
The exact field names can change during implementation, but the separation should be strict:
- `profile`: metadata and format only.
- `policy`: semantic profile policy.
- No `pod.name` in `policy`.
- No top-level `manifest` / `config` in semantic v1.
- No `mkManifest` in builtin examples.
- Any raw manifest escape hatch, if kept, should be a separate explicit compatibility/debug resolver path, not `mkProfile`'s normal output.
Suggested Rust types in `crates/manifest/src/profile.rs` or a new `crates/manifest/src/profile/semantic.rs`:
```rust
pub struct SemanticProfileArtifact {
pub profile: ProfileMetadata,
pub policy: SemanticProfilePolicy,
}
pub struct SemanticProfilePolicy {
pub role: Option<ProfileRole>,
pub model: SemanticModelPolicy,
pub scope: SemanticScopePolicy,
pub worker: SemanticWorkerPolicy,
pub tools: SemanticToolPolicy,
pub context: SemanticContextPolicy,
pub memory: Option<SemanticMemoryPolicy>,
pub session: Option<SemanticSessionPolicy>,
pub advanced: Option<AdvancedProfileOverrides>,
}
```
Minimum viable semantic fields for this ticket:
- `model.ref`: catalog ref such as `codex-oauth/gpt-5.5`.
- `model.auth`: optional `AuthRef` override or secret ref, preserving current secret-reference behavior.
- `model.quality`: optional named policy (`low|balanced|high|max`) used to derive default reasoning/output/context behavior.
- `model.reasoning`: either named effort (`low|medium|high|xhigh`) or budget ratio/tokens. It becomes `WorkerManifest.reasoning` only during manifestization.
- `scope.workspace`: common workspace permission policy that maps to `scope.allow target = <workspace-base>`.
- `tools.web`: semantic enablement plus provider-specific search config already supported by `WebConfig`.
- `context.compaction`: ratios/presets against the selected model's effective context window.
- `memory`: either disabled/omitted or semantic thresholds, preferably ratios where they are context-window dependent.
- `session.record_event_trace`: acceptable profile policy because it controls session behavior, not instance identity.
- `advanced.manifest_overrides`: optional, if needed, as an explicitly named escape hatch. Do not expose it in builtin profiles or docs as the normal path.
Suggested Nix library surface:
```nix
insomnia.mkProfile {
name = "default";
description = "Bundled default Insomnia coding profile";
role = "coder";
model = insomnia.models.codexOAuth.gpt55 // {
quality = "high";
reasoning = insomnia.reasoning.effort "high";
};
scope = insomnia.scopes.workspaceWrite;
context = insomnia.context.longCoding;
tools.web = insomnia.web.braveFromEnv "BRAVE_SEARCH_API_KEY";
memory = insomnia.memory.defaultLongContext;
session.recordEventTrace = true;
}
```
`resources/nix/profile-lib.nix` should output semantic JSON only. `mkManifest` should be removed from the public/builtin style. If compatibility is retained temporarily, name it something noisy such as `unsafeRawManifestProfile` and do not use it in builtin docs/tests.
## 4. Manifestization design
### New resolver boundary
Add an explicit manifestization function that receives both the semantic artifact and runtime inputs:
```rust
pub struct ProfileRuntimeInputs {
pub pod_name: String,
pub workspace_base: PathBuf,
}
pub fn manifestize_semantic_profile(
source: ProfileSource,
artifact: SemanticProfileArtifact,
inputs: &ProfileRuntimeInputs,
model_catalogs: &dyn ModelCatalogResolver,
) -> Result<ResolvedProfile, ProfileError>;
```
The resolver output remains a concrete `PodManifest` plus serialized `manifest_snapshot`; session restore should continue using the snapshot rather than re-evaluating the profile.
### Pod name / identity
- `pod.name` must be supplied by runtime inputs, not profile policy.
- `insomnia-pod` should pass the effective fresh pod name into profile resolution, not patch the name afterward.
- For CLI no-argument/`--pod` fresh startup, `cli.pod` or a generated/default instance name remains a startup input.
- For TUI spawn, `client::SpawnConfig.pod_name` becomes `ProfileRuntimeInputs.pod_name` through `--profile-pod-name`.
- Builtin profile artifacts should not contain any `pod` section.
- In resolved manifest snapshots, `pod.name` is present because snapshots are runtime artifacts and are used for restore.
Implementation detail: `TryFrom<PodManifestConfig> for PodManifest` can keep requiring `pod.name`; manifestization should populate `PodManifestConfig.pod.name = Some(inputs.pod_name.clone())` before validation.
### Model and reasoning policy
- Semantic `model.ref` maps to `PodManifestConfig.model.ref_`.
- Auth overrides map to `PodManifestConfig.model.auth`.
- Manifestization resolves the model ref against the model catalog once to obtain effective context window and capability.
- `model.reasoning` / `model.quality` maps to `WorkerManifestConfig.reasoning`:
- If explicit effort is given, produce `ReasoningControl::Effort(...)`.
- If budget ratio is given and model capability supports token budgets, compute budget tokens from context window.
- If no explicit reasoning is given, derive from `quality` and model capability; e.g. high quality on effort-capable models -> `high`, high quality on budget-capable models -> a conservative token budget or leave unset until policy is defined.
- Provider-specific request serialization remains in `llm-worker`; the profile resolver should only produce the existing provider-neutral `ReasoningControl`.
Important dependency issue: `crates/manifest` currently cannot call `provider::catalog` because `provider` depends on `manifest`. To derive compaction inside the profile resolver, move the data-only catalog loading/resolution out of `provider` into `manifest` or a small new crate.
Recommended short-term refactor:
- Move `crates/provider/src/catalog.rs` into `crates/manifest/src/model_catalog.rs` or a new `crates/model-catalog` crate.
- Re-export `ModelConfig`, provider/model entries, `resolve_model_manifest`, and `DEFAULT_CONTEXT_WINDOW` from the new location.
- Update `provider` and `pod::controller::build_greeting()` to use the new location.
- Keep live client construction and auth dereferencing in `provider`.
### Context window and compaction policy
Semantic compaction should be derived from the selected model's effective context window.
Suggested policy type:
```rust
pub struct SemanticCompactionPolicy {
pub enabled: bool,
pub preset: Option<CompactionPreset>,
pub threshold_ratio: Option<f64>,
pub request_threshold_ratio: Option<f64>,
pub worker_context_ratio: Option<f64>,
pub retained_tokens: Option<u64>,
pub final_reserve_ratio: Option<f64>,
pub overview_preset: Option<OverviewPreset>,
pub model: Option<SemanticModelPolicy>,
}
```
Manifestization algorithm:
1. Resolve main model to effective `context_window`.
2. Expand preset defaults into ratios and fixed defaults.
3. Compute raw thresholds:
- `threshold = floor(context_window * threshold_ratio)`.
- `request_threshold = floor(context_window * request_threshold_ratio)`.
- `worker_context_max_tokens = floor(context_window * worker_context_ratio)`.
4. Clamp outputs to safe ranges:
- Ensure `threshold < request_threshold` when both are present; otherwise return a profile validation error instead of silently emitting suspicious config.
- Ensure `request_threshold < context_window` by reserving at least `final_reserve_tokens` or a minimum fixed reserve.
- Keep worker context below the main context window and above a minimum useful budget.
5. Fill `CompactionConfigPartial` with computed numeric fields and existing default-backed fields where appropriate.
6. If `compaction.model` is specified semantically, resolve it separately and derive any compactor-specific worker context budget from that model, not from the main model.
For the current builtin default intent using `codex-oauth/gpt-5.5`, effective context window is `272000`. Ratios close to the existing behavior would be approximately:
- proactive threshold: `200000 / 272000 ~= 0.735`.
- request threshold: `240000 / 272000 ~= 0.882`.
- worker context max: `100000 / 272000 ~= 0.368`.
Those should become preset values (e.g. `longCoding`) rather than magic constants in the Nix profile.
### Scope, tools, memory, session
- `scope.workspace` maps to a `ScopeRule` using `inputs.workspace_base`, not the profile file directory for builtin profiles.
- User/project profile relative paths still resolve against the profile file directory for explicit path fields, but semantic workspace scope should be explicit about using the launch workspace.
- `tools.web` can map directly to existing `WebConfig` because its current fields are already policy-like enough for the minimum implementation.
- `memory.extract_threshold` should either remain explicit for now or gain `extract_threshold_ratio`. If ratio exists, derive from the same effective context window.
- `session.record_event_trace` maps to existing `SessionConfigPartial`.
### Snapshot/provenance
- `ResolvedProfile.manifest_snapshot` remains the validated runtime snapshot.
- Consider replacing or narrowing `raw_artifact` retention for semantic profiles. It can remain for diagnostics in tests, but avoid logging or persisting raw Nix output beyond the validated snapshot.
- `ProfileManifestSnapshot` should continue recording source and profile metadata. For builtin semantic profiles, allow a source that does not imply a Nix path if builtin runtime no longer evals Nix.
## 5. Nix evaluator boundary
### Recommended short-term behavior
Builtin/default profiles should not require the external `nix` command during normal runtime.
Implement this by splitting profile evaluation into source-specific paths:
- Builtin profiles:
- Discovered as builtin profile names for UI/selection.
- Resolved from an in-process semantic definition, not by `nix eval`.
- `resources/nix/profiles/default.nix` can remain as the Nix authoring example/smoke artifact, but normal `ProfileSelector::Default` / `builtin:default` should not invoke it.
- Alternatively, if avoiding duplication is important, generate a checked-in static semantic JSON/TOML artifact from the Nix file at build/release time; runtime should still load the checked-in artifact directly.
- User/project explicit Nix profiles:
- Continue using external `nix eval --json --file <path>` for now.
- Keep diagnostics clear: selecting a Nix-authored user/project profile requires the `nix` command unless/until an embedded evaluator is implemented.
- Add a timeout around `nix eval` as a robustness follow-up or in this ticket if low-risk.
- Explicit non-Nix resolved semantic artifacts:
- Consider supporting `.json` / `.toml` semantic artifacts as test/debug inputs that bypass Nix entirely. This is useful for tests and for users without Nix.
### Embedded evaluator feasibility notes
Current local code has no embedded Nix evaluator dependency. Adding one is not a small drop-in change because the profile examples rely on imports and Nix language evaluation, not just parsing.
Practical options:
- `rnix`-style parser only: not sufficient; it parses Nix syntax but does not evaluate imports/functions/attribute merges.
- `nix-compat` / `tvix` ecosystem: may provide evaluation building blocks, but integrating an evaluator, file imports, builtins policy, purity constraints, and JSON conversion is a separate design task.
- Custom evaluator for a tiny subset: risky unless the supported subset is extremely small; it would create a second Nix-like language and likely fail on normal Nix idioms.
Recommendation: do not attempt embedded Nix evaluation in this ticket. Isolate external evaluation to user/project Nix-authored profiles, make builtin defaults in-process, and document the boundary.
## 6. Step-by-step implementation phases
### Phase 1: Extract model catalog resolution for manifestization
Likely changed files:
- `crates/provider/src/catalog.rs`
- `crates/provider/src/lib.rs`
- `crates/manifest/src/lib.rs`
- `crates/manifest/src/model.rs` or new `crates/manifest/src/model_catalog.rs`
- `crates/pod/src/controller.rs`
Tasks:
1. Move data-only provider/model catalog types and `resolve_model_manifest` into `manifest` or a new no-cycle crate.
2. Keep provider live client construction in `provider`.
3. Update imports in `provider` and `pod`.
4. Preserve current catalog behavior and tests, including context-window clamping.
### Phase 2: Add semantic artifact/types and manifestization
Likely changed files:
- `crates/manifest/src/profile.rs` or new `crates/manifest/src/profile/semantic.rs`
- `crates/manifest/src/config.rs`
- `crates/manifest/src/lib.rs`
Tasks:
1. Add `SEMANTIC_PROFILE_FORMAT_V1 = "insomnia.semantic-profile.v1"`.
2. Add typed semantic policy structs with serde support.
3. Add `ProfileRuntimeInputs`.
4. Add `manifestize_semantic_profile()`:
- fill `pod.name` from runtime inputs;
- map semantic model/ref/auth to `ModelManifest`;
- resolve model context window;
- derive reasoning;
- derive compaction thresholds from ratios/presets;
- map scope/tools/memory/session;
- merge `PodManifestConfig::builtin_defaults()` and validate into `PodManifest`;
- attach profile provenance and serialize snapshot.
5. Make semantic format reject top-level `manifest`/`config`.
6. Keep old v1 manifest-shaped resolver only as an explicit compatibility path if necessary; do not use it for builtin defaults or docs.
### Phase 3: Split builtin profile resolution from external Nix eval
Likely changed files:
- `crates/manifest/src/profile.rs`
- `crates/manifest/src/paths.rs`
- `crates/pod/src/main.rs`
- `crates/tui/src/spawn.rs` if entry metadata needs adjustment
Tasks:
1. Add a builtin profile source representation that can resolve without a path, or mark builtin registry entries with an internal resolver kind.
2. Change `ProfileDiscovery` so builtin `default` is still listed/selectable but does not imply `nix eval`.
3. Add `BuiltinProfileResolver` or `ProfileResolver` enum/trait:
- builtin semantic definitions -> in-process artifact -> manifestization;
- user/project/path `.nix` -> `NixProfileResolver` -> semantic artifact -> manifestization;
- optional `.json`/`.toml` semantic artifact -> parse -> manifestization.
4. Update `load_profile()` to pass the pod name and workspace base into resolution instead of overwriting `manifest.pod.name` afterward.
5. Ensure no-argument `insomnia-pod` and `builtin:default` do not spawn `nix`.
### Phase 4: Replace Nix authoring library and builtin default shape
Likely changed files:
- `resources/nix/profile-lib.nix`
- `resources/nix/profiles/default.nix`
- `docs/manifest-profiles.md`
- `docs/architecture.md`
- `docs/nix.md`
- `docs/pod-factory.md`
Tasks:
1. Rewrite `profile-lib.nix` so `mkProfile` emits `{ profile = { format = "insomnia.semantic-profile.v1"; ... }; policy = ...; }`.
2. Remove `mkManifest` from builtin examples and docs.
3. Provide semantic helper namespaces for model, reasoning, context/compaction, scopes, web, memory, secrets.
4. Rewrite builtin `default.nix` semantically and remove `pod.name`.
5. Update docs to describe semantic profiles and the evaluator boundary.
### Phase 5: Tighten compatibility and diagnostics
Likely changed files:
- `crates/manifest/src/profile.rs`
- `crates/pod/src/main.rs`
- profile-related tests
Tasks:
1. Decide whether old `insomnia.nix-profile.v1` manifest-shaped artifacts are rejected, accepted only via a compatibility flag, or accepted with a deprecation diagnostic.
2. Given the ticket's direction, prefer not preserving awkward authoring compatibility unless the parent explicitly wants a transition period.
3. Improve errors:
- semantic profile contains `pod.name` -> explain pod identity belongs to startup input;
- semantic profile contains raw compaction constants in the wrong place -> explain ratio/preset fields;
- missing `nix` for user/project `.nix` profile -> current diagnostic plus source selector/path;
- builtin profile resolution should never mention missing `nix`.
### Phase 6: Optional robustness follow-ups if still in scope
- Add timeout to external `nix eval`.
- Avoid retaining `ResolvedProfile::raw_artifact` outside debug/test APIs.
- Add explicit `insomnia-pod --profile-artifact <path.json>` if JSON semantic artifacts prove useful.
## 7. Tests and validation commands
### Tests to add/update
`crates/manifest/src/profile.rs` / semantic module:
- Builtin semantic default manifestizes without `pod.name` in the source artifact and with runtime input `pod_name` in the final `PodManifest`.
- Semantic `model.ref = "codex-oauth/gpt-5.5"` uses catalog-clamped context window `272000`.
- Semantic compaction preset/ratios derive expected thresholds from context window.
- Reasoning effort policy maps to `WorkerManifest.reasoning`.
- Semantic artifact containing top-level `manifest`/`config` or semantic `pod.name` is rejected.
- User/project `.nix` missing binary still returns `NixUnavailable`.
- Builtin default resolution with a missing/fake `nix_bin` still succeeds because builtin does not eval Nix.
- Source-qualified/default/ambiguous discovery behavior remains unchanged for user/project entries.
Model catalog tests:
- Moved catalog tests continue to prove provider/model loading, context-window override, clamp, and unknown provider behavior.
- Add a manifestization test using injected in-memory catalogs if the model catalog resolver is trait-based.
Pod/client/TUI tests:
- `insomnia-pod` no-argument default startup path uses profile default and does not require `nix` for builtin.
- `--profile-pod-name` is passed as runtime input, not a post-resolution patch.
- TUI profile picker still lists `builtin:default` and user/project profiles.
- Existing profile selection tests continue to pass.
Docs/Nix smoke:
- A manual or ignored test can run `nix eval --json --file resources/nix/profiles/default.nix` and assert it emits `insomnia.semantic-profile.v1` with `policy`, not `manifest`.
### Focused validation commands
Run after implementation:
```sh
cargo fmt --check
cargo test -p manifest profile -- --nocapture
cargo test -p manifest model -- --nocapture
cargo test -p provider catalog -- --nocapture
cargo test -p pod --bin insomnia-pod profile -- --nocapture
cargo test -p client spawn -- --nocapture
cargo test -p tui spawn -- --nocapture
cargo check -p session-store -p manifest -p provider -p pod -p client -p tui
nix eval --json --file resources/nix/profiles/default.nix
./tickets.sh doctor
git diff --check
```
If the catalog module moves out of `provider`, adjust package/test filters accordingly.
## 8. Risks and open questions
- **Where to house model catalog resolution:** manifestization needs effective context windows, but current catalog resolution lives in `provider`, which depends on `manifest`. The clean short-term answer is to move data-only catalog resolution into `manifest` or a new crate. Parent should choose whether a new crate is worth it; implementation-wise, moving into `manifest` is smaller.
- **Exact semantic field names:** the schema above is intentionally concrete but not final. The important decision is the boundary: semantic `policy` in, `PodManifestConfig` out.
- **Old manifest-shaped Nix artifacts:** preserving them will keep abstraction leaks alive. Recommendation is to reject them for the new semantic format and keep direct `--manifest` as the low-level escape hatch. If migration is required, make it explicit and temporary.
- **Builtin source provenance:** if builtin profiles no longer resolve from a Nix path, `ProfileSource::Registry { path }` may need to become `ProfileSource::Builtin { name }` or make `path` optional. This is a small schema migration for future snapshots.
- **Reasoning defaults by quality:** `quality = "high"` should not blindly force reasoning for every model. It should consult model capability and either choose an effort/budget policy or leave reasoning unset when unsupported.
- **Compaction ratio defaults:** exact preset ratios need product judgment. The current builtin constants imply ratios around 0.735/0.882/0.368 for `gpt-5.5`; using those as the first `longCoding` preset preserves behavior while moving the authoring surface to semantics.
- **External Nix timeout:** current `Command::output()` can hang indefinitely. This is already a known follow-up; include it in this ticket if touching resolver orchestration is easy, otherwise track separately.
- **Docs currently teach the wrong model:** `docs/manifest-profiles.md`, `docs/pod-factory.md`, `docs/architecture.md`, and `docs/nix.md` should be updated in the same implementation so future work does not copy the manifest-shaped API.

View File

@ -1,3 +1,9 @@
<!-- event: create author: hare at: 2026-05-29T22:28:50Z -->
Created as a follow-up to the closed manifest profiles work item after reviewing the original intent and the current built-in Nix profile shape.
---
<!-- event: plan author: planning-pod at: 2026-05-29T22:36:45Z -->
Implementation plan written to `artifacts/implementation-plan.md`. Key recommendation: introduce a typed semantic profile artifact and manifestization step, move/centralize model catalog context-window resolution so compaction can derive from model metadata, and resolve builtin profiles in-process so normal default startup does not require external `nix`.