feat(dev): global Claude model + effort selector for new lab sessions #156

Closed
opened 2026-06-13 21:19:24 +02:00 by dominik.polakovics · 1 comment

This was generated by AI during triage.

Summary

lab spawns every new session — manual Start, New instance, and AFK runs — with a hardcoded --model claude-opus-4-8[1m] --effort max. This adds a global UI control to choose the Claude model and effort for newly-spawned sessions. Sessions already running are unaffected, and the machine-wide interactive claude default is left alone.

Design (converged in a grill-me session)

  • Scope: lab spawns only. The manual and AFK paths both read one shared start command, so making it dynamic covers AFK for free. The home-manager settings.json default is a read-only Nix-store symlink at runtime and is intentionally not touched.
  • Running sessions keep their model — it is fixed in the session's process at spawn; the setting only governs future spawns.
  • Family aliases, not pinned ids. Model options are Claude Code aliases (opus, sonnet, fable, haiku) so the list tracks the latest of each family and never goes stale. This deliberately reverses the current "pin the exact id" choice, now that the model is a visible, user-chosen setting.
  • 1M where it is included. Opus maps to opus[1m] (1M context, included on the plan). Sonnet stays plain sonnet (its 1M would bill extra usage credits, so it is left off). Fable is inherently 1M. Haiku has no 1M variant.
  • Effort passed independently. All five levels are offered; Claude Code clamps an unsupported model+effort combination per its own documented rule, so lab does no per-model coupling.
  • Closed allowlist. No free-text model id; no best / opusplan / sonnet[1m].

The authoritative contract is in the Agent Brief comment below.

> *This was generated by AI during triage.* ## Summary lab spawns every new session — manual **Start**, **New instance**, and **AFK runs** — with a hardcoded `--model claude-opus-4-8[1m] --effort max`. This adds a global UI control to choose the Claude **model** and **effort** for newly-spawned sessions. Sessions already running are unaffected, and the machine-wide interactive `claude` default is left alone. ## Design (converged in a grill-me session) - **Scope: lab spawns only.** The manual and AFK paths both read one shared start command, so making it dynamic covers AFK for free. The home-manager `settings.json` default is a read-only Nix-store symlink at runtime and is intentionally not touched. - **Running sessions keep their model** — it is fixed in the session's process at spawn; the setting only governs future spawns. - **Family aliases, not pinned ids.** Model options are Claude Code aliases (`opus`, `sonnet`, `fable`, `haiku`) so the list tracks the latest of each family and never goes stale. This deliberately reverses the current "pin the exact id" choice, now that the model is a visible, user-chosen setting. - **1M where it is included.** Opus maps to `opus[1m]` (1M context, included on the plan). Sonnet stays plain `sonnet` (its 1M would bill extra usage credits, so it is left off). Fable is inherently 1M. Haiku has no 1M variant. - **Effort passed independently.** All five levels are offered; Claude Code clamps an unsupported model+effort combination per its own documented rule, so lab does no per-model coupling. - **Closed allowlist.** No free-text model id; no `best` / `opusplan` / `sonnet[1m]`. The authoritative contract is in the **Agent Brief** comment below.
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Add a global, persisted UI control that sets the Claude model and effort for every newly-spawned lab session (manual and AFK), leaving running sessions and the machine-wide claude default untouched.

Current behavior:
lab builds one fixed start command for spawned sessions — equivalent to claude --remote-control <name> --permission-mode auto --effort max --model claude-opus-4-8[1m]. Both the manual Start path and the AFK-run base-argv helper read this single fixed command, so model and effort are effectively compile-time constants.

Desired behavior:

  • One global setting (a single model + a single effort) governs every new spawn: manual Start, New instance, and AFK runs (auto and manual).
  • A change takes effect on the next spawn with no lab restart. Already-running sessions are unaffected (a session's model is fixed at spawn).
  • The setting persists across lab restarts.
  • Model options are Claude Code family aliases, shown with these labels → values: "Opus (1M)" → opus[1m], "Sonnet" → sonnet, "Fable" → fable, "Haiku" → haiku.
  • Effort options are low, medium, high, xhigh, max, passed through verbatim. No per-model filtering — Claude Code clamps an unsupported level itself.
  • When unset (fresh state), the effective default is opus[1m] + max, preserving today's behavior.
  • An invalid model or effort (anything outside the two allowlists) is rejected loudly with the error surfaced in the UI's existing error banner, and is never persisted — the value is global and persisted, so a bad one would otherwise break every future spawn.

Key interfaces:

  • The component that owns the per-session start command (today a fixed argv field on the Sessions type, consumed by both its manual-start method and its AFK base-argv helper) must instead obtain model + effort dynamically at spawn time, via an injected accessor that reads the persisted setting. Both spawn paths must go through that accessor so AFK is covered automatically and the two paths can't drift.
  • The persistence layer (the Store type that already serialises per-project state to a JSON file) gains a single global (not per-project) setting — a model string and an effort string — with a getter that returns the documented defaults when unset, and a validated setter.
  • A new HTTP endpoint accepts model + effort from the UI, validates both against the allowlists, persists them, and returns the standard live fragment; it rejects unknown values.
  • A single server-side source of truth for the two allowlists (model alias values + display labels, and the effort levels) backs BOTH the dropdown rendering and the endpoint's validation — not duplicated.
  • The UI gains a compact, always-visible control (two labelled native <select>s) placed OUTSIDE the poll-morphed live region, so a background refresh never disturbs the user's selection (same rationale as the existing filter input). It auto-saves on change through the existing intercepted-form/fetch path; a submit button is the no-JS fallback, hidden when JS is active.

Acceptance criteria:

  • A manual Start issued after choosing model X / effort Y spawns claude with --model X --effort Y (verifiable from the spawned process argv).
  • An AFK run started after the same choice spawns with the same --model / --effort.
  • A session already running before the change keeps its original model/effort.
  • The chosen model + effort survive a lab restart.
  • With no prior choice, new sessions spawn as opus[1m] + max.
  • The model dropdown offers exactly Opus (1M), Sonnet, Fable, Haiku → opus[1m], sonnet, fable, haiku.
  • The effort dropdown offers exactly low, medium, high, xhigh, max.
  • POSTing an out-of-allowlist model or effort is rejected, the error shows in the UI banner, and nothing is persisted.
  • The control is usable on a narrow mobile viewport (native selects, >=44px targets) and its selection is not reset by the background poll.
  • The machine-wide claude default (home-manager settings) is not modified.
  • Any new inline JS is covered by the project's jsdom verification approach (ADR-0004).

Out of scope:

  • Per-project or per-instance overrides — the setting is global only.
  • Changing the machine-wide interactive claude default or anything in home-manager settings.json.
  • A free-text / custom model-id field — the dropdown is a closed allowlist by design.
  • The best and opusplan aliases, and a paid sonnet[1m] option — explicitly excluded.
  • Retroactively changing the model of already-running sessions.
  • Working around the upstream Claude Code bug where subagents strip the [1m] suffix (claude-code#45169) — noted, not lab's to fix.
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Add a global, persisted UI control that sets the Claude model and effort for every newly-spawned lab session (manual and AFK), leaving running sessions and the machine-wide claude default untouched. **Current behavior:** lab builds one fixed start command for spawned sessions — equivalent to `claude --remote-control <name> --permission-mode auto --effort max --model claude-opus-4-8[1m]`. Both the manual Start path and the AFK-run base-argv helper read this single fixed command, so model and effort are effectively compile-time constants. **Desired behavior:** - One global setting (a single model + a single effort) governs every new spawn: manual Start, New instance, and AFK runs (auto and manual). - A change takes effect on the *next* spawn with no lab restart. Already-running sessions are unaffected (a session's model is fixed at spawn). - The setting persists across lab restarts. - Model options are Claude Code family aliases, shown with these labels → values: "Opus (1M)" → `opus[1m]`, "Sonnet" → `sonnet`, "Fable" → `fable`, "Haiku" → `haiku`. - Effort options are `low`, `medium`, `high`, `xhigh`, `max`, passed through verbatim. No per-model filtering — Claude Code clamps an unsupported level itself. - When unset (fresh state), the effective default is `opus[1m]` + `max`, preserving today's behavior. - An invalid model or effort (anything outside the two allowlists) is rejected loudly with the error surfaced in the UI's existing error banner, and is never persisted — the value is global and persisted, so a bad one would otherwise break every future spawn. **Key interfaces:** - The component that owns the per-session start command (today a fixed argv field on the `Sessions` type, consumed by both its manual-start method and its AFK base-argv helper) must instead obtain model + effort dynamically at spawn time, via an injected accessor that reads the persisted setting. Both spawn paths must go through that accessor so AFK is covered automatically and the two paths can't drift. - The persistence layer (the `Store` type that already serialises per-project state to a JSON file) gains a single *global* (not per-project) setting — a model string and an effort string — with a getter that returns the documented defaults when unset, and a validated setter. - A new HTTP endpoint accepts model + effort from the UI, validates both against the allowlists, persists them, and returns the standard live fragment; it rejects unknown values. - A single server-side source of truth for the two allowlists (model alias values + display labels, and the effort levels) backs BOTH the dropdown rendering and the endpoint's validation — not duplicated. - The UI gains a compact, always-visible control (two labelled native `<select>`s) placed OUTSIDE the poll-morphed live region, so a background refresh never disturbs the user's selection (same rationale as the existing filter input). It auto-saves on `change` through the existing intercepted-form/fetch path; a submit button is the no-JS fallback, hidden when JS is active. **Acceptance criteria:** - [ ] A manual Start issued after choosing model X / effort Y spawns claude with `--model X --effort Y` (verifiable from the spawned process argv). - [ ] An AFK run started after the same choice spawns with the same `--model` / `--effort`. - [ ] A session already running before the change keeps its original model/effort. - [ ] The chosen model + effort survive a lab restart. - [ ] With no prior choice, new sessions spawn as `opus[1m]` + `max`. - [ ] The model dropdown offers exactly Opus (1M), Sonnet, Fable, Haiku → `opus[1m]`, `sonnet`, `fable`, `haiku`. - [ ] The effort dropdown offers exactly low, medium, high, xhigh, max. - [ ] POSTing an out-of-allowlist model or effort is rejected, the error shows in the UI banner, and nothing is persisted. - [ ] The control is usable on a narrow mobile viewport (native selects, >=44px targets) and its selection is not reset by the background poll. - [ ] The machine-wide `claude` default (home-manager settings) is not modified. - [ ] Any new inline JS is covered by the project's jsdom verification approach (ADR-0004). **Out of scope:** - Per-project or per-instance overrides — the setting is global only. - Changing the machine-wide interactive `claude` default or anything in home-manager `settings.json`. - A free-text / custom model-id field — the dropdown is a closed allowlist by design. - The `best` and `opusplan` aliases, and a paid `sonnet[1m]` option — explicitly excluded. - Retroactively changing the model of already-running sessions. - Working around the upstream Claude Code bug where subagents strip the `[1m]` suffix (claude-code#45169) — noted, not lab's to fix.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos#156
No description provided.