Files
ClaudeDo/docs/superpowers/specs/2026-06-04-inherited-settings-and-turns-design.md
2026-06-04 12:04:06 +02:00

7.4 KiB

Inherited settings display, per-task overrides, and Turns

Date: 2026-06-04 Status: Approved (design)

Problem

Config inheritance is three-tier (Task → List → Global app settings). Today the UI only signals inheritance with a placeholder sentinel ((inherit) for tasks, (default) for lists) and, for tasks, a faint "Effective if inherited: {value}" hint under Model and Agent. Two gaps:

  1. You can't see the actual resolved value an inherited field will use, nor where it comes from (List vs Global).
  2. Max turns is global-only (AppSettingsEntity.DefaultMaxTurns = 100). It is not overridable per list or per task, unlike Model / SystemPrompt / AgentPath.

Goals

  • Show the real inherited value in-place, muted, with a source-aware marker (inherited · List vs inherited · Global). Picking a value turns it into an override; a reset affordance clears it back to inherited.
  • Add Turns (max turns) as an overridable field at both List and Task levels, inheriting from the global default. Numeric box; empty = inherit.
  • Keep SystemPrompt as-is (it is additive, not override) but show what gets prepended.

Non-goals

  • No change to SystemPrompt merge semantics (stays additive/concatenated).
  • No new global settings; DefaultMaxTurns already exists.
  • No change to PermissionMode handling.

Inheritance semantics (reference)

Resolved in TaskRunner.BuildRunConfig (~line 388):

Field Semantics Resolution
Model override task.Model ?? listConfig?.Model ?? global.DefaultModel
AgentPath override task.AgentPath ?? listConfig?.AgentPath (no global)
MaxTurns override new: task.MaxTurns ?? listConfig?.MaxTurns ?? global.DefaultMaxTurns
SystemPrompt additive merged: global + list + task + agent (unchanged)

Lists inherit only from Global (no tier above them), so a list's inherited marker is always inherited · Global.

Design

1. Data layer

  • ListConfigEntity: add int? MaxTurns.
  • TaskEntity: add int? MaxTurns (nullable override).
  • EF Core migration adding max_turns column to list_config and tasks (nullable, no default — null = inherit).
  • TaskRunner BuildRunConfig: MaxTurns: task.MaxTurns ?? listConfig?.MaxTurns ?? global.DefaultMaxTurns. ClaudeRunConfig.MaxTurns and ClaudeArgsBuilder already accept/emit --max-turns when > 0 — no change needed there.
  • ListRepository.SetConfigAsync (upsert) and TaskRepository.UpdateAgentSettingsAsync extend to carry maxTurns.

2. DTOs / transport

Add int? MaxTurns to (Worker + Ui copies kept in sync):

  • UpdateListConfigDto, ListConfigDto (WorkerHub.cs + WorkerClient.cs)
  • UpdateTaskAgentSettingsDto (WorkerHub.cs + WorkerClient.cs)
  • TaskConfigDto (ConfigMcpTools.cs)

WorkerHub.UpdateListConfig / UpdateTaskAgentSettings persist the new field via the repositories above. MCP SetListConfig / SetTaskConfig gain an optional maxTurns parameter to keep the agent-facing API at parity with the UI.

3. Resolution helper (Ui)

A small helper that, given (taskValue, listValue, globalValue), returns (effectiveValue, source) where source ∈ { Override, List, Global }. Drives the marker text and muted/normal styling for Model, Agent, and Turns so the logic isn't duplicated per field or per editor. Lives in the Ui layer beside its consumers.

4. UI rendering — inherited marker (source-aware)

For Model, Agent, Turns in both ListSettingsModalView and the DetailsIsland "Agent settings (overrides)" expander:

  • Remove the (inherit) / (default) sentinel row from the control's item source.
  • When no override is set: control shows the resolved value muted/greyed (dropdown shows e.g. "sonnet" dimmed; Turns box shows e.g. "100" as a muted placeholder), and a small badge beside the field label reads inherited · List or inherited · Global.
  • On picking a value / typing a number: it becomes an override — text returns to normal color, the badge flips to override (or hides), and a small "↺ reset to inherited" affordance appears that clears the value back to null.
  • List modal: source is always Global → badge reads inherited · Global; reset clears to the global default.
  • Turns: numeric box, empty = inherit (muted resolved number as placeholder); a typed number is the override.

Rendering approach: a small reusable InheritedFieldHeader control (label + badge + reset button), fed by the resolution helper's source, wraps each field. Keeps the three fields consistent and avoids per-field XAML duplication. Badge / muted styling uses existing design tokens. Visual polish pass is the user's.

5. SystemPrompt (stays plain)

SystemPrompt keeps its plain multi-line text box (additive, not override). Below it, a small read-only, collapsed-by-default hint shows the inherited prompts that will be prepended (global + list), labeled e.g. "Prepended automatically:". No marker, no reset — it never replaces, only appends.

6. Localization

New keys in locales/en.json + locales/de.json (parity enforced by Localization.Tests): marker text (inherited · List, inherited · Global, override), reset affordance label, Turns field label, and the SystemPrompt "prepended automatically" hint. Retire the now-unused vm.details.effectiveIfInherited key (and its German counterpart) if nothing else references it.

Affected files (indicative)

  • src/ClaudeDo.Data/Models/ListConfigEntity.cs, TaskEntity.cs
  • src/ClaudeDo.Data/Migrations/ (new migration)
  • src/ClaudeDo.Data/Repositories/ListRepository.cs, TaskRepository.cs
  • src/ClaudeDo.Data/Configuration/ (column mapping for max_turns)
  • src/ClaudeDo.Worker/Runner/TaskRunner.cs
  • src/ClaudeDo.Worker/Hub/WorkerHub.cs
  • src/ClaudeDo.Worker/External/ConfigMcpTools.cs
  • src/ClaudeDo.Ui/Services/WorkerClient.cs (+ Interfaces/IWorkerClient.cs)
  • src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs + view
  • src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs + DetailsIslandView.axaml
  • src/ClaudeDo.Ui/Views/Controls/ (new InheritedFieldHeader)
  • src/ClaudeDo.Ui/ resolution helper
  • locales/en.json, locales/de.json

Testing

  • Data: migration applies; MaxTurns round-trips through ListRepository.SetConfigAsync and TaskRepository.UpdateAgentSettingsAsync.
  • Worker: BuildRunConfig resolves MaxTurns via task → list → global precedence (unit test on the resolution). Existing ClaudeArgsBuilder --max-turns behavior unchanged.
  • Ui: resolution helper returns correct (value, source) for each of the override / list / global cases across Model, Agent, Turns.
  • Localization: en/de key parity (existing Localization.Tests).
  • Test fakes: update hand-rolled IWorkerClient fakes in both test projects for the new DTO fields (per known gotcha).
  • Visual verification of the marker / muted styling: flagged for the user (cannot be asserted programmatically).

Open risks

  • DTO/ctor changes ripple into hand-rolled test fakes in Worker.Tests and Ui.Tests — must be updated in the same change.
  • Removing the sentinel row from dropdowns changes selection binding; ensure null/empty override state is represented without a sentinel item (e.g. dropdown SelectedItem null when inherited).