ticket: use base32 project record ids

This commit is contained in:
2026-06-09 22:10:47 +09:00
parent 0803bc3725
commit 4203988d74
798 changed files with 477 additions and 105 deletions
@@ -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.
+71
View File
@@ -0,0 +1,71 @@
---
title: "Make Nix profiles semantic and manifestize them in the resolver"
state: "closed"
created_at: "2026-05-29T22:28:50Z"
updated_at: "2026-05-30T03:52:39Z"
---
## Background
The closed manifest profiles work item (`work-items/closed/20260527-000022-manifest-profiles`) introduced profile discovery/selection, built-in profile files, and Nix evaluation as the profile artifact path. Its original direction described profiles as role/model/policy selections that resolve into a precise runtime manifest. The current built-in Nix shape has drifted from that intent: it effectively asks authors to write a Manifest-shaped object in Nix.
Current examples include:
- `resources/nix/profile-lib.nix` exposes `mkProfile` and a `mkManifest` helper, where `mkManifest` is currently an identity function.
- `resources/nix/profiles/default.nix` uses `insomnia.mkProfile { manifest = insomnia.mkManifest { ... }; }`.
- The built-in profile currently contains low-level or instance-specific Manifest fields such as `pod.name = "insomnia"`.
- Model policy fields such as high reasoning effort and compaction thresholds are represented as low-level manifest settings rather than being derived from a profile's model/context policy.
- Profile evaluation currently shells out to `nix eval` for Nix artifacts, so the boundary between profile language support and a runtime dependency on the `nix` command is unclear.
The issue is not that profile artifacts are eventually serialized as manifests. The issue is that the authoring API currently exposes the output shape as if the profile itself were a manifest. Profiles should be semantic, and the resolver should perform the manifestization step.
## Goal
Redesign the Nix profile authoring surface so profiles express role/model/tool/context policy at the appropriate level of abstraction, and the manifest resolver turns that semantic profile into the concrete runtime manifest/config used to start a Pod.
This should restore the intended boundary:
- Profile: selectable role/policy/model/context/tool behavior.
- Resolver/manifestization: derives concrete defaults and token thresholds from model metadata and profile policy.
- Runtime manifest/config: low-level concrete values consumed by Pod startup.
## Desired direction
- Remove `mkManifest` from the public/built-in authoring style. A profile should not look like `mkProfile { manifest = mkManifest { ... }; }`.
- Built-in profiles should be written as semantic profiles, not Manifest-shaped blobs.
- Instance-specific values such as `pod.name` must not live in built-in profiles. Pod names come from CLI/TUI/SpawnPod creation, not profile identity.
- Model behavior such as reasoning effort belongs to the profile's model/quality policy and should be manifestized into provider-specific request config by the resolver.
- Compaction and context budgeting should be derived from the selected model's context window and profile policy, e.g. ratios/multipliers/strategy names, rather than hard-coded token thresholds copied into the profile.
- Nix evaluation should be treated as a profile authoring/evaluation mechanism, not as a requirement that the runtime default path always depends on an external `nix` command.
## Acceptance criteria
- Audit the original manifest profiles work item and current implementation to identify where the implementation became Manifest-shaped instead of semantic-profile-shaped.
- Replace the built-in Nix profile shape with a semantic profile API. The exact field names may be redesigned, but they must clearly distinguish profile policy from runtime manifest output.
- Remove `pod.name` and any other instance-specific Pod identity/configuration from built-in profiles.
- Remove `mkManifest` from built-in examples and docs. If a compatibility helper remains internally, it must not be the recommended authoring API and must be justified.
- Implement or adjust the resolver so semantic profile data is manifestized into the concrete runtime manifest/config.
- Ensure model-derived settings are resolved in one place. In particular, reasoning effort and context/compaction policy should be interpreted as profile/model policy and converted into concrete request/compaction settings by the resolver.
- Add or update model catalog/context-window plumbing as needed so compaction thresholds can be derived from the selected model rather than duplicated as raw profile constants.
- Decide and document the Nix evaluator boundary:
- built-in/default profiles must not require the external `nix` command at normal runtime unless that dependency is deliberately accepted and documented;
- user/project Nix profiles may use `nix eval` if no lightweight in-process evaluator is chosen, but diagnostics must clearly state that Nix is required for Nix-authored profiles;
- do not silently make all profile selection depend on a missing or slow `nix` binary.
- Investigate whether a lightweight embedded evaluator is practical enough for the supported Nix subset. If it is not, document why and keep the external-command dependency isolated to Nix-authored profile evaluation.
- Preserve previous profile selection semantics where still relevant: built-in/user/project profile discovery, default profile selection, source-qualified selectors, ambiguity errors, and manifest cascade removal.
- Update docs/tests so the examples present profiles as semantic policy, not manifests written in Nix syntax.
- Add tests covering:
- built-in profile manifestization without `pod.name`;
- semantic compaction policy derived from model context window;
- reasoning/model policy manifestization;
- missing `nix` command behavior for user/project Nix profiles, if external evaluation remains;
- no regression in profile discovery/default/source-qualified selection.
- Run focused validation for the manifest crate and any affected pod/tui/client paths, plus `./tickets.sh doctor` and `git diff --check`.
## Non-goals
- Do not reintroduce manifest cascade or manifest overlay selection as a replacement for profiles.
- Do not turn Nix profiles into a general low-level manifest DSL.
- Do not add profile alias registry complexity back unless a separate ticket re-justifies it.
- Do not implement encrypted secret storage here; keep it in the existing encrypted secrets work item.
- Do not require solving every possible provider-specific model option in this ticket; implement the minimum necessary semantic-to-runtime mapping and leave explicit follow-ups for broader model policy coverage.
+78
View File
@@ -0,0 +1,78 @@
---
id: 20260529-222850-semantic-nix-profiles
slug: semantic-nix-profiles
title: Make Nix profiles semantic and manifestize them in the resolver
status: closed
kind: task
priority: P2
labels: [manifest, profiles, nix, architecture]
created_at: 2026-05-29T22:28:50Z
updated_at: 2026-05-30T03:52:39Z
assignee: null
legacy_ticket: null
---
## Background
The closed manifest profiles work item (`work-items/closed/20260527-000022-manifest-profiles`) introduced profile discovery/selection, built-in profile files, and Nix evaluation as the profile artifact path. Its original direction described profiles as role/model/policy selections that resolve into a precise runtime manifest. The current built-in Nix shape has drifted from that intent: it effectively asks authors to write a Manifest-shaped object in Nix.
Current examples include:
- `resources/nix/profile-lib.nix` exposes `mkProfile` and a `mkManifest` helper, where `mkManifest` is currently an identity function.
- `resources/nix/profiles/default.nix` uses `insomnia.mkProfile { manifest = insomnia.mkManifest { ... }; }`.
- The built-in profile currently contains low-level or instance-specific Manifest fields such as `pod.name = "insomnia"`.
- Model policy fields such as high reasoning effort and compaction thresholds are represented as low-level manifest settings rather than being derived from a profile's model/context policy.
- Profile evaluation currently shells out to `nix eval` for Nix artifacts, so the boundary between profile language support and a runtime dependency on the `nix` command is unclear.
The issue is not that profile artifacts are eventually serialized as manifests. The issue is that the authoring API currently exposes the output shape as if the profile itself were a manifest. Profiles should be semantic, and the resolver should perform the manifestization step.
## Goal
Redesign the Nix profile authoring surface so profiles express role/model/tool/context policy at the appropriate level of abstraction, and the manifest resolver turns that semantic profile into the concrete runtime manifest/config used to start a Pod.
This should restore the intended boundary:
- Profile: selectable role/policy/model/context/tool behavior.
- Resolver/manifestization: derives concrete defaults and token thresholds from model metadata and profile policy.
- Runtime manifest/config: low-level concrete values consumed by Pod startup.
## Desired direction
- Remove `mkManifest` from the public/built-in authoring style. A profile should not look like `mkProfile { manifest = mkManifest { ... }; }`.
- Built-in profiles should be written as semantic profiles, not Manifest-shaped blobs.
- Instance-specific values such as `pod.name` must not live in built-in profiles. Pod names come from CLI/TUI/SpawnPod creation, not profile identity.
- Model behavior such as reasoning effort belongs to the profile's model/quality policy and should be manifestized into provider-specific request config by the resolver.
- Compaction and context budgeting should be derived from the selected model's context window and profile policy, e.g. ratios/multipliers/strategy names, rather than hard-coded token thresholds copied into the profile.
- Nix evaluation should be treated as a profile authoring/evaluation mechanism, not as a requirement that the runtime default path always depends on an external `nix` command.
## Acceptance criteria
- Audit the original manifest profiles work item and current implementation to identify where the implementation became Manifest-shaped instead of semantic-profile-shaped.
- Replace the built-in Nix profile shape with a semantic profile API. The exact field names may be redesigned, but they must clearly distinguish profile policy from runtime manifest output.
- Remove `pod.name` and any other instance-specific Pod identity/configuration from built-in profiles.
- Remove `mkManifest` from built-in examples and docs. If a compatibility helper remains internally, it must not be the recommended authoring API and must be justified.
- Implement or adjust the resolver so semantic profile data is manifestized into the concrete runtime manifest/config.
- Ensure model-derived settings are resolved in one place. In particular, reasoning effort and context/compaction policy should be interpreted as profile/model policy and converted into concrete request/compaction settings by the resolver.
- Add or update model catalog/context-window plumbing as needed so compaction thresholds can be derived from the selected model rather than duplicated as raw profile constants.
- Decide and document the Nix evaluator boundary:
- built-in/default profiles must not require the external `nix` command at normal runtime unless that dependency is deliberately accepted and documented;
- user/project Nix profiles may use `nix eval` if no lightweight in-process evaluator is chosen, but diagnostics must clearly state that Nix is required for Nix-authored profiles;
- do not silently make all profile selection depend on a missing or slow `nix` binary.
- Investigate whether a lightweight embedded evaluator is practical enough for the supported Nix subset. If it is not, document why and keep the external-command dependency isolated to Nix-authored profile evaluation.
- Preserve previous profile selection semantics where still relevant: built-in/user/project profile discovery, default profile selection, source-qualified selectors, ambiguity errors, and manifest cascade removal.
- Update docs/tests so the examples present profiles as semantic policy, not manifests written in Nix syntax.
- Add tests covering:
- built-in profile manifestization without `pod.name`;
- semantic compaction policy derived from model context window;
- reasoning/model policy manifestization;
- missing `nix` command behavior for user/project Nix profiles, if external evaluation remains;
- no regression in profile discovery/default/source-qualified selection.
- Run focused validation for the manifest crate and any affected pod/tui/client paths, plus `./tickets.sh doctor` and `git diff --check`.
## Non-goals
- Do not reintroduce manifest cascade or manifest overlay selection as a replacement for profiles.
- Do not turn Nix profiles into a general low-level manifest DSL.
- Do not add profile alias registry complexity back unless a separate ticket re-justifies it.
- Do not implement encrypted secret storage here; keep it in the existing encrypted secrets work item.
- Do not require solving every possible provider-specific model option in this ticket; implement the minimum necessary semantic-to-runtime mapping and leave explicit follow-ups for broader model policy coverage.
+129
View File
@@ -0,0 +1,129 @@
<!-- 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`.
<!-- event: plan author: hare at: 2026-05-30T00:45:10Z -->
## Plan
## Implementation direction
Use the worktree + sibling coder/reviewer flow. The investigation Pod found the main boundary issue to be concentrated in `crates/manifest/src/profile.rs`: profile artifacts are currently deserialized as `PodManifestConfig`, while built-in Nix files expose a manifest-shaped authoring API.
Decisions for the implementation pass:
- Move data-only model catalog resolution to a cycle-free place owned by `manifest` so profile manifestization can derive context-window-dependent settings. Keep live provider/client construction in `provider`.
- Introduce a semantic profile artifact format such as `insomnia.semantic-profile.v1`; top-level semantic artifacts must use `profile` metadata plus `policy`, not `manifest` / `config`.
- Resolve built-in `default` in-process so the normal default startup path does not require an external `nix` command. User/project/path Nix profiles may still require `nix eval` with clear diagnostics.
- Remove `pod.name` from built-in profiles. Direct no-arg `insomnia-pod` should preserve the current effective default Pod name `insomnia` as runtime input, while `--profile-pod-name` remains the explicit fresh-spawn override.
- Do not preserve manifest-shaped Nix profiles as the normal authoring API. `--manifest` remains the low-level concrete manifest escape hatch.
- Keep the initial semantic policy narrow: explicit model ref, explicit reasoning effort, workspace scope policy, compaction ratios/preset derived from effective context window, memory/session/web policy sufficient to reproduce current builtin behavior.
- Update docs and tests so built-in examples no longer recommend `mkManifest` or raw manifest-shaped profiles.
Implementation should remain narrow: preserve existing profile selection semantics, source-qualified selectors, ambiguity errors, TUI/client profile selection flow, and `SpawnPod.scope` authority.
---
<!-- event: decision author: hare at: 2026-05-30T01:39:47Z -->
## Decision
Implementation is paused after design discussion. The attempted direction of introducing a semantic JSON-profile-to-Manifest projection is not accepted as the specification: it risks preserving nearly the same information as Manifest and therefore failing the intended abstraction.
The current design question has been split into `profile-authoring-requirements-sync`, which records the shared requirements and open questions before choosing Lua/external Nix/another authoring language. Do not proceed with the existing child-worktree implementation until that specification is decided.
---
<!-- event: close author: hare at: 2026-05-30T03:52:39Z status: closed -->
## Closed
---
id: 20260529-222850-semantic-nix-profiles
slug: semantic-nix-profiles
title: Make Nix profiles semantic and manifestize them in the resolver
status: closed
kind: task
priority: P2
labels: [manifest, profiles, nix, architecture]
created_at: 2026-05-29T22:28:50Z
updated_at: 2026-05-30T03:52:39Z
assignee: null
legacy_ticket: null
---
## Background
The closed manifest profiles work item (`work-items/closed/20260527-000022-manifest-profiles`) introduced profile discovery/selection, built-in profile files, and Nix evaluation as the profile artifact path. Its original direction described profiles as role/model/policy selections that resolve into a precise runtime manifest. The current built-in Nix shape has drifted from that intent: it effectively asks authors to write a Manifest-shaped object in Nix.
Current examples include:
- `resources/nix/profile-lib.nix` exposes `mkProfile` and a `mkManifest` helper, where `mkManifest` is currently an identity function.
- `resources/nix/profiles/default.nix` uses `insomnia.mkProfile { manifest = insomnia.mkManifest { ... }; }`.
- The built-in profile currently contains low-level or instance-specific Manifest fields such as `pod.name = "insomnia"`.
- Model policy fields such as high reasoning effort and compaction thresholds are represented as low-level manifest settings rather than being derived from a profile's model/context policy.
- Profile evaluation currently shells out to `nix eval` for Nix artifacts, so the boundary between profile language support and a runtime dependency on the `nix` command is unclear.
The issue is not that profile artifacts are eventually serialized as manifests. The issue is that the authoring API currently exposes the output shape as if the profile itself were a manifest. Profiles should be semantic, and the resolver should perform the manifestization step.
## Goal
Redesign the Nix profile authoring surface so profiles express role/model/tool/context policy at the appropriate level of abstraction, and the manifest resolver turns that semantic profile into the concrete runtime manifest/config used to start a Pod.
This should restore the intended boundary:
- Profile: selectable role/policy/model/context/tool behavior.
- Resolver/manifestization: derives concrete defaults and token thresholds from model metadata and profile policy.
- Runtime manifest/config: low-level concrete values consumed by Pod startup.
## Desired direction
- Remove `mkManifest` from the public/built-in authoring style. A profile should not look like `mkProfile { manifest = mkManifest { ... }; }`.
- Built-in profiles should be written as semantic profiles, not Manifest-shaped blobs.
- Instance-specific values such as `pod.name` must not live in built-in profiles. Pod names come from CLI/TUI/SpawnPod creation, not profile identity.
- Model behavior such as reasoning effort belongs to the profile's model/quality policy and should be manifestized into provider-specific request config by the resolver.
- Compaction and context budgeting should be derived from the selected model's context window and profile policy, e.g. ratios/multipliers/strategy names, rather than hard-coded token thresholds copied into the profile.
- Nix evaluation should be treated as a profile authoring/evaluation mechanism, not as a requirement that the runtime default path always depends on an external `nix` command.
## Acceptance criteria
- Audit the original manifest profiles work item and current implementation to identify where the implementation became Manifest-shaped instead of semantic-profile-shaped.
- Replace the built-in Nix profile shape with a semantic profile API. The exact field names may be redesigned, but they must clearly distinguish profile policy from runtime manifest output.
- Remove `pod.name` and any other instance-specific Pod identity/configuration from built-in profiles.
- Remove `mkManifest` from built-in examples and docs. If a compatibility helper remains internally, it must not be the recommended authoring API and must be justified.
- Implement or adjust the resolver so semantic profile data is manifestized into the concrete runtime manifest/config.
- Ensure model-derived settings are resolved in one place. In particular, reasoning effort and context/compaction policy should be interpreted as profile/model policy and converted into concrete request/compaction settings by the resolver.
- Add or update model catalog/context-window plumbing as needed so compaction thresholds can be derived from the selected model rather than duplicated as raw profile constants.
- Decide and document the Nix evaluator boundary:
- built-in/default profiles must not require the external `nix` command at normal runtime unless that dependency is deliberately accepted and documented;
- user/project Nix profiles may use `nix eval` if no lightweight in-process evaluator is chosen, but diagnostics must clearly state that Nix is required for Nix-authored profiles;
- do not silently make all profile selection depend on a missing or slow `nix` binary.
- Investigate whether a lightweight embedded evaluator is practical enough for the supported Nix subset. If it is not, document why and keep the external-command dependency isolated to Nix-authored profile evaluation.
- Preserve previous profile selection semantics where still relevant: built-in/user/project profile discovery, default profile selection, source-qualified selectors, ambiguity errors, and manifest cascade removal.
- Update docs/tests so the examples present profiles as semantic policy, not manifests written in Nix syntax.
- Add tests covering:
- built-in profile manifestization without `pod.name`;
- semantic compaction policy derived from model context window;
- reasoning/model policy manifestization;
- missing `nix` command behavior for user/project Nix profiles, if external evaluation remains;
- no regression in profile discovery/default/source-qualified selection.
- Run focused validation for the manifest crate and any affected pod/tui/client paths, plus `./tickets.sh doctor` and `git diff --check`.
## Non-goals
- Do not reintroduce manifest cascade or manifest overlay selection as a replacement for profiles.
- Do not turn Nix profiles into a general low-level manifest DSL.
- Do not add profile alias registry complexity back unless a separate ticket re-justifies it.
- Do not implement encrypted secret storage here; keep it in the existing encrypted secrets work item.
- Do not require solving every possible provider-specific model option in this ticket; implement the minimum necessary semantic-to-runtime mapping and leave explicit follow-ups for broader model policy coverage.
---