29 KiB
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.nixdocumentsmkProfile { manifest = mkManifest { ... }; };mkManifestis currently an identity function.resources/nix/profiles/default.nixis a manifest-shaped blob undermanifest = 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 tonix evaleven 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.rsProfileRegistrySource: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; implementsdefault_entry,select,select_named, builtin fallback default.ProfileDiscovery::for_cwd()wires builtin dir, userprofiles.toml, nearest project.insomnia/profiles.toml.discover_profile_dir()registers every builtin.nixfile orprofile.nixdirectory.load_profile_registry_file()parses user/project registry TOML; it is selection metadata only, not runtime config.ProfileSelector::parse_cli()supportsdefault, explicit paths, source-qualified selectors, and path-like compatibility.
Nix eval and artifact parsing
crates/manifest/src/profile.rsNixProfileResolveralways usesstd::process::Commandto execute:nix eval --json --file <absolute-profile-path>.
- Missing binary returns
ProfileError::NixUnavailablewith a clear diagnostic. - Nonzero status returns
ProfileError::NixFailedwith stderr. - JSON stdout is parsed into
serde_json::Valueand passed toresolve_profile_artifact(). - There is no embedded evaluator and no dependency on
rnix,nix-compat,tvix, or similar crates in the workspaceCargo.tomlfiles.
Manifest-shaped artifact parsing / manifestization today
crates/manifest/src/profile.rsresolve_profile_artifact(source, base_dir, raw_artifact)currently treats the evaluated artifact as already manifest-shaped.ProfileEnvelopeonly validates optionalprofile.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 toPodManifest, attachesProfileManifestSnapshot, and serializesmanifest_snapshot.
Runtime startup path
-
crates/pod/src/main.rsresolve_manifest()defaults toProfileSelector::Defaultwhen neither--profilenor--manifestis provided.load_profile()constructsNixProfileResolver::new().with_workspace_base(cwd)and callsresolve().--profile-pod-nameoverwritesresolved.manifest.pod.nameafter profile resolution.--manifestremains a one-file compatibility/debug path.load_spawn_config_json()is an internal typed adopted-spawn path that deserializesPodManifestConfigdirectly.
-
crates/client/src/spawn.rsSpawnConfig.profileis passed toinsomnia-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.rsPodManifestConfig: partial manifest/cascade type.PodMetaConfig.name: currently required by finalTryFrom<PodManifestConfig> for PodManifest.WorkerManifestConfig.reasoning: low-level worker request setting.CompactionConfigPartial: raw numeric compaction fields.TryFrom<PodManifestConfig> for PodManifestrequirespod.nameandscope.allow, fills defaults, validates absolute paths, and materializesCompactionConfig.
-
crates/manifest/src/lib.rsPodManifest: concrete runtime contract; includespod,model,worker,scope, optionalcompaction,memory,web,skills, and profile provenance.CompactionConfig: runtime numeric thresholds and budgets.WorkerManifest.reasoning: copied intollm_worker::RequestConfigby pod code.
Model catalog and context-window plumbing
-
crates/manifest/src/model.rsModelManifest: data representation formodel.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.tomlandresources/models/builtin.toml. resolve_model_manifest()resolvesModelManifestintoModelConfigwith effectivecontext_window.- Context window resolution order: manifest override > model catalog > provider default >
DEFAULT_CONTEXT_WINDOW; then clamped bymax_context_window. - Builtin
codex-oauth/gpt-5.5hascontext_window = 1000000,max_context_window = 272000, so effective context window is272000.
- Owns builtin provider/model catalog loading from
-
crates/provider/src/lib.rs- Builds live
LlmClientfrom resolvedModelConfig.
- Builds live
-
crates/pod/src/controller.rsbuild_greeting()callsprovider::catalog::resolve_model_manifest()to report effective context window.
-
crates/pod/src/pod.rsapply_worker_manifest()mapsWorkerManifestintoRequestConfig;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:
{
"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.nameinpolicy. - No top-level
manifest/configin semantic v1. - No
mkManifestin 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:
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 ascodex-oauth/gpt-5.5.model.auth: optionalAuthRefoverride 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 becomesWorkerManifest.reasoningonly during manifestization.scope.workspace: common workspace permission policy that maps toscope.allow target = <workspace-base>.tools.web: semantic enablement plus provider-specific search config already supported byWebConfig.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:
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:
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.namemust be supplied by runtime inputs, not profile policy.insomnia-podshould pass the effective fresh pod name into profile resolution, not patch the name afterward.- For CLI no-argument/
--podfresh startup,cli.podor a generated/default instance name remains a startup input. - For TUI spawn,
client::SpawnConfig.pod_namebecomesProfileRuntimeInputs.pod_namethrough--profile-pod-name. - Builtin profile artifacts should not contain any
podsection. - In resolved manifest snapshots,
pod.nameis 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.refmaps toPodManifestConfig.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.qualitymaps toWorkerManifestConfig.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
qualityand 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.
- If explicit effort is given, produce
- Provider-specific request serialization remains in
llm-worker; the profile resolver should only produce the existing provider-neutralReasoningControl.
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.rsintocrates/manifest/src/model_catalog.rsor a newcrates/model-catalogcrate. - Re-export
ModelConfig, provider/model entries,resolve_model_manifest, andDEFAULT_CONTEXT_WINDOWfrom the new location. - Update
providerandpod::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:
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:
- Resolve main model to effective
context_window. - Expand preset defaults into ratios and fixed defaults.
- 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).
- Clamp outputs to safe ranges:
- Ensure
threshold < request_thresholdwhen both are present; otherwise return a profile validation error instead of silently emitting suspicious config. - Ensure
request_threshold < context_windowby reserving at leastfinal_reserve_tokensor a minimum fixed reserve. - Keep worker context below the main context window and above a minimum useful budget.
- Ensure
- Fill
CompactionConfigPartialwith computed numeric fields and existing default-backed fields where appropriate. - If
compaction.modelis 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.workspacemaps to aScopeRuleusinginputs.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.webcan map directly to existingWebConfigbecause its current fields are already policy-like enough for the minimum implementation.memory.extract_thresholdshould either remain explicit for now or gainextract_threshold_ratio. If ratio exists, derive from the same effective context window.session.record_event_tracemaps to existingSessionConfigPartial.
Snapshot/provenance
ResolvedProfile.manifest_snapshotremains the validated runtime snapshot.- Consider replacing or narrowing
raw_artifactretention for semantic profiles. It can remain for diagnostics in tests, but avoid logging or persisting raw Nix output beyond the validated snapshot. ProfileManifestSnapshotshould 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.nixcan remain as the Nix authoring example/smoke artifact, but normalProfileSelector::Default/builtin:defaultshould 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
nixcommand unless/until an embedded evaluator is implemented. - Add a timeout around
nix evalas a robustness follow-up or in this ticket if low-risk.
- Continue using external
-
Explicit non-Nix resolved semantic artifacts:
- Consider supporting
.json/.tomlsemantic artifacts as test/debug inputs that bypass Nix entirely. This is useful for tests and for users without Nix.
- Consider supporting
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/tvixecosystem: 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.rscrates/provider/src/lib.rscrates/manifest/src/lib.rscrates/manifest/src/model.rsor newcrates/manifest/src/model_catalog.rscrates/pod/src/controller.rs
Tasks:
- Move data-only provider/model catalog types and
resolve_model_manifestintomanifestor a new no-cycle crate. - Keep provider live client construction in
provider. - Update imports in
providerandpod. - 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.rsor newcrates/manifest/src/profile/semantic.rscrates/manifest/src/config.rscrates/manifest/src/lib.rs
Tasks:
- Add
SEMANTIC_PROFILE_FORMAT_V1 = "insomnia.semantic-profile.v1". - Add typed semantic policy structs with serde support.
- Add
ProfileRuntimeInputs. - Add
manifestize_semantic_profile():- fill
pod.namefrom 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 intoPodManifest; - attach profile provenance and serialize snapshot.
- fill
- Make semantic format reject top-level
manifest/config. - 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.rscrates/manifest/src/paths.rscrates/pod/src/main.rscrates/tui/src/spawn.rsif entry metadata needs adjustment
Tasks:
- Add a builtin profile source representation that can resolve without a path, or mark builtin registry entries with an internal resolver kind.
- Change
ProfileDiscoveryso builtindefaultis still listed/selectable but does not implynix eval. - Add
BuiltinProfileResolverorProfileResolverenum/trait:- builtin semantic definitions -> in-process artifact -> manifestization;
- user/project/path
.nix->NixProfileResolver-> semantic artifact -> manifestization; - optional
.json/.tomlsemantic artifact -> parse -> manifestization.
- Update
load_profile()to pass the pod name and workspace base into resolution instead of overwritingmanifest.pod.nameafterward. - Ensure no-argument
insomnia-podandbuiltin:defaultdo not spawnnix.
Phase 4: Replace Nix authoring library and builtin default shape
Likely changed files:
resources/nix/profile-lib.nixresources/nix/profiles/default.nixdocs/manifest-profiles.mddocs/architecture.mddocs/nix.mddocs/pod-factory.md
Tasks:
- Rewrite
profile-lib.nixsomkProfileemits{ profile = { format = "insomnia.semantic-profile.v1"; ... }; policy = ...; }. - Remove
mkManifestfrom builtin examples and docs. - Provide semantic helper namespaces for model, reasoning, context/compaction, scopes, web, memory, secrets.
- Rewrite builtin
default.nixsemantically and removepod.name. - Update docs to describe semantic profiles and the evaluator boundary.
Phase 5: Tighten compatibility and diagnostics
Likely changed files:
crates/manifest/src/profile.rscrates/pod/src/main.rs- profile-related tests
Tasks:
- Decide whether old
insomnia.nix-profile.v1manifest-shaped artifacts are rejected, accepted only via a compatibility flag, or accepted with a deprecation diagnostic. - Given the ticket's direction, prefer not preserving awkward authoring compatibility unless the parent explicitly wants a transition period.
- 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
nixfor user/project.nixprofile -> current diagnostic plus source selector/path; - builtin profile resolution should never mention missing
nix.
- semantic profile contains
Phase 6: Optional robustness follow-ups if still in scope
- Add timeout to external
nix eval. - Avoid retaining
ResolvedProfile::raw_artifactoutside 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.namein the source artifact and with runtime inputpod_namein the finalPodManifest. - Semantic
model.ref = "codex-oauth/gpt-5.5"uses catalog-clamped context window272000. - Semantic compaction preset/ratios derive expected thresholds from context window.
- Reasoning effort policy maps to
WorkerManifest.reasoning. - Semantic artifact containing top-level
manifest/configor semanticpod.nameis rejected. - User/project
.nixmissing binary still returnsNixUnavailable. - Builtin default resolution with a missing/fake
nix_binstill 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-podno-argument default startup path uses profile default and does not requirenixfor builtin.--profile-pod-nameis passed as runtime input, not a post-resolution patch.- TUI profile picker still lists
builtin:defaultand 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.nixand assert it emitsinsomnia.semantic-profile.v1withpolicy, notmanifest.
Focused validation commands
Run after implementation:
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 onmanifest. The clean short-term answer is to move data-only catalog resolution intomanifestor a new crate. Parent should choose whether a new crate is worth it; implementation-wise, moving intomanifestis smaller. - Exact semantic field names: the schema above is intentionally concrete but not final. The important decision is the boundary: semantic
policyin,PodManifestConfigout. - 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
--manifestas 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 becomeProfileSource::Builtin { name }or makepathoptional. 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 firstlongCodingpreset 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, anddocs/nix.mdshould be updated in the same implementation so future work does not copy the manifest-shaped API.