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:
- You can't see the actual resolved value an inherited field will use, nor where it comes from (List vs Global).
- 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 · Listvsinherited · 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;
DefaultMaxTurnsalready 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: addint? MaxTurns.TaskEntity: addint? MaxTurns(nullable override).- EF Core migration adding
max_turnscolumn tolist_configandtasks(nullable, no default — null = inherit). TaskRunnerBuildRunConfig:MaxTurns: task.MaxTurns ?? listConfig?.MaxTurns ?? global.DefaultMaxTurns.ClaudeRunConfig.MaxTurnsandClaudeArgsBuilderalready accept/emit--max-turnswhen> 0— no change needed there.ListRepository.SetConfigAsync(upsert) andTaskRepository.UpdateAgentSettingsAsyncextend to carrymaxTurns.
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 · Listorinherited · 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.cssrc/ClaudeDo.Data/Migrations/(new migration)src/ClaudeDo.Data/Repositories/ListRepository.cs,TaskRepository.cssrc/ClaudeDo.Data/Configuration/(column mapping formax_turns)src/ClaudeDo.Worker/Runner/TaskRunner.cssrc/ClaudeDo.Worker/Hub/WorkerHub.cssrc/ClaudeDo.Worker/External/ConfigMcpTools.cssrc/ClaudeDo.Ui/Services/WorkerClient.cs(+Interfaces/IWorkerClient.cs)src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs+ viewsrc/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs+DetailsIslandView.axamlsrc/ClaudeDo.Ui/Views/Controls/(newInheritedFieldHeader)src/ClaudeDo.Ui/resolution helperlocales/en.json,locales/de.json
Testing
- Data: migration applies;
MaxTurnsround-trips throughListRepository.SetConfigAsyncandTaskRepository.UpdateAgentSettingsAsync. - Worker:
BuildRunConfigresolves MaxTurns via task → list → global precedence (unit test on the resolution). ExistingClaudeArgsBuilder--max-turnsbehavior 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
IWorkerClientfakes 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
SelectedItemnull when inherited).