# 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).
