From 9db8cdc7f884328ff48ee57b39ccebe43753b62e Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 30 May 2026 07:37:51 +0900 Subject: [PATCH] plan: semantic nix profiles --- .../artifacts/implementation-plan.md | 533 ++++++++++++++++++ .../thread.md | 6 + 2 files changed, 539 insertions(+) create mode 100644 work-items/open/20260529-222850-semantic-nix-profiles/artifacts/implementation-plan.md diff --git a/work-items/open/20260529-222850-semantic-nix-profiles/artifacts/implementation-plan.md b/work-items/open/20260529-222850-semantic-nix-profiles/artifacts/implementation-plan.md new file mode 100644 index 00000000..c19dfbeb --- /dev/null +++ b/work-items/open/20260529-222850-semantic-nix-profiles/artifacts/implementation-plan.md @@ -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 `. + - 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 ` 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 for PodManifest`. + - `WorkerManifestConfig.reasoning`: low-level worker request setting. + - `CompactionConfigPartial`: raw numeric compaction fields. + - `TryFrom 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, + pub model: SemanticModelPolicy, + pub scope: SemanticScopePolicy, + pub worker: SemanticWorkerPolicy, + pub tools: SemanticToolPolicy, + pub context: SemanticContextPolicy, + pub memory: Option, + pub session: Option, + pub advanced: Option, +} +``` + +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 = `. +- `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; +``` + +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 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, + pub threshold_ratio: Option, + pub request_threshold_ratio: Option, + pub worker_context_ratio: Option, + pub retained_tokens: Option, + pub final_reserve_ratio: Option, + pub overview_preset: Option, + pub model: Option, +} +``` + +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 ` 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 ` 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. diff --git a/work-items/open/20260529-222850-semantic-nix-profiles/thread.md b/work-items/open/20260529-222850-semantic-nix-profiles/thread.md index bc2ed793..e587e993 100644 --- a/work-items/open/20260529-222850-semantic-nix-profiles/thread.md +++ b/work-items/open/20260529-222850-semantic-nix-profiles/thread.md @@ -1,3 +1,9 @@ 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. + +--- + + + +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`.