Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
216b8c4
feat(web-shell): persist reasoning effort
callmeYe Aug 25, 2026
74db5c0
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
60408ac
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
089e41a
fix(web-shell): scope reasoning preview refresh
callmeYe Aug 25, 2026
22fe234
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
b3a71e8
fix(web-shell): keep persisted reasoning state consistent
callmeYe Aug 25, 2026
5d75955
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
9c014e7
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
f702564
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
0b2f496
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
9c29e23
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
5668591
fix(core): register report findings permission aliases
callmeYe Aug 25, 2026
00960dc
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
272603c
test(cli): remove duplicate session swap fixture fields
callmeYe Aug 25, 2026
b6c6745
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
b145e66
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 25, 2026
ab8db9c
fix(cli): address reasoning persistence review
callmeYe Aug 25, 2026
9e49917
fix(cli): guard persisted reasoning defaults
callmeYe Aug 26, 2026
c388b9d
fix(cli): align mandatory reasoning defaults
callmeYe Aug 26, 2026
5fac2ce
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 26, 2026
89fb490
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 26, 2026
20a094d
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 26, 2026
709f984
fix(web-shell): keep persisted reasoning previews in sync
callmeYe Aug 26, 2026
4d85d4f
fix(web-shell): keep standalone effort session-scoped
callmeYe Aug 26, 2026
40b88a5
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 27, 2026
534459c
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 27, 2026
a4174ca
fix(web-shell): Hide ACP default effort instruction
callmeYe Aug 27, 2026
a2fb61e
test(web-shell): align reasoning smoke with hidden default
callmeYe Aug 27, 2026
59126b0
fix: handle incompatible reasoning persistence
callmeYe Aug 27, 2026
a313859
fix: preserve dormant reasoning preference
callmeYe Aug 27, 2026
034a141
fix(core): honor request reasoning opt-outs
callmeYe Aug 27, 2026
39108d3
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 27, 2026
680f58f
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 28, 2026
790b36e
fix(web-shell): clear effort for toggle-only models
callmeYe Aug 28, 2026
a159cdc
fix(cli): hide reasoning controls for unregistered Qwen models
callmeYe Aug 28, 2026
77bd395
fix(cli): clear incompatible reasoning on model switch
callmeYe Aug 28, 2026
3dc4e4d
test(cli): update worktree mock for model capability check
callmeYe Aug 28, 2026
7092e8e
Merge remote-tracking branch 'origin/main' into codex/fix-webshell-re…
callmeYe Aug 28, 2026
74ab5e9
fix(cli): preserve compatible reasoning preferences
callmeYe Aug 28, 2026
8d44a23
fix(web-shell): localize reasoning effort labels
callmeYe Aug 28, 2026
a45f1c5
fix(web-shell): preserve English effort labels
callmeYe Aug 28, 2026
97d7db3
fix(cli): harden reasoning cleanup after model switch
callmeYe Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/design/model-reasoning-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,20 @@ The model manifest distinguishes tiered reasoning from toggle-only reasoning
and continues to match exact model ids. Toggle-only models produce the same ACP
configuration option as tiered models, with two values: `none` and `default`.
Selecting `none` disables thinking for the live session. Selecting `default`
clears the session override so the existing model or provider default applies.
clears the live override for ordinary ACP clients so the existing model or
provider default applies. WebShell's persistent path also clears the selected
settings scope and adopts the resulting merged preference, which may be an
inherited setting.

The daemon marks toggle-only options in ACP metadata. WebShell maps them to an
empty effort list, renders only the Thinking switch, and shows `Thinking` or
`Thinking Off` on the model chip. Existing tiered controls retain their effort
rows and labels.

Opening the controls does not mutate generation settings. No provider,
authentication, persistence, or runtime-snapshot behavior changes.
Opening the controls does not mutate generation settings. Capability
registration itself does not change provider, authentication, or
runtime-snapshot behavior. WebShell selections follow the separate
[reasoning effort persistence contract](./webshell-reasoning-effort-persistence.md).

## Deferred models

Expand All @@ -58,6 +63,9 @@ whose selected value is not sent correctly.
Qwen aliases, dated variants, coder models, and models with a preset default
that differs from the model default are also deferred. They require separate
capability or resolved-configuration semantics rather than broadened matching.
Deferred Qwen-family models expose no ACP reasoning option until their exact
model id is registered. The generic ACP reasoning option remains available for
non-Qwen models so existing provider-specific behavior is preserved.

## Compatibility

Expand Down
52 changes: 37 additions & 15 deletions docs/design/webshell-qwen38-reasoning-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ Thinking and the native effort values `low`, `medium`, and `xhigh`, with
does not apply to preview, dated, aliased, or runtime models.

The agent projects that entry through ACP's existing `reasoning_effort`
configuration option. For this model only, the option contains `none` plus the
three manifest values. WebShell renders `none` as Thinking off and renders the
remaining values as effort choices. No second effort configuration id is
introduced.
configuration option. For this model only, the option contains `none`,
`default`, and the three manifest values. `default` remains an ACP instruction
for clearing an override, not a model effort. WebShell therefore renders
`none` as Thinking off and the manifest values as effort choices; when ACP
reports `default`, it displays the manifest's default effort. No second effort
configuration id is introduced.

Both workspace-provider producers expose that same manifest-built option as
an optional, per-model `configOptions` preview. The field is an additive v1
Expand Down Expand Up @@ -49,9 +51,17 @@ WebShell releases the unused session, keeps the composer and intent available
for retry, and does not send the prompt.

The intent is cleared after successful preparation or after an unrelated
session is attached. It is not a workspace default and is never persisted or
broadcast. If the user changes models before submission, the model-bound
intent is not applied to the other model.
session is attached. Before it is applied, this local intent is not a workspace
default and is neither persisted nor broadcast. Once WebShell applies it
through the daemon-owned config-option mutation, the live session changes.
`none`, native efforts, and custom efforts are persisted as the shared default
for later sessions. WebShell does not offer ACP's `default` instruction as an
effort choice, and settings can still clear the selected scope. Ordinary ACP
clients may send `default` to restore the live session's model or provider
baseline without persistence; a private persistent request also clears its
settings scope and applies the resulting merged preference. If the user changes
models before submission, the model-bound intent is not applied to the other
model.

WebShell retains PR #8675's interaction design: the current reasoning state is
shown as a suffix on the model chip, reasoning options occupy the first model
Expand Down Expand Up @@ -79,6 +89,11 @@ and unrelated request parameters, and applies the selected value. This keeps
the control truthful instead of acknowledging a choice that a higher-priority
field would silently shadow.

When an ACP client selects `default`, those static thinking fields are restored
from the current model baseline in the live session. This makes clearing an
explicit preference consistent with a new session or daemon restart while
preserving unrelated request parameters and the stored model configuration.

When the active model requires thinking, ACP omits `none` from the option and
marks that constraint in metadata. WebShell keeps the Thinking switch checked
and disabled while leaving every supported effort selectable. An unmarked
Expand All @@ -90,8 +105,12 @@ mandatory before the first prompt.

The daemon exposes one owner-routed config-option mutation. Its public route is
restricted to `reasoning_effort`; the response carries fresh `configOptions`,
which becomes the caller's authoritative UI state. No observer or broadcast is
added.
which becomes the caller's authoritative UI state. WebShell marks this update
for persistence, while ordinary ACP clients remain session-only. A successful
settings write emits the existing scope-aware `settings_changed` event; a
failed write leaves the live session updated but does not broadcast. The full
settings and lifecycle contract is defined in
[WebShell reasoning effort persistence](./webshell-reasoning-effort-persistence.md).

## Scope

Expand All @@ -102,21 +121,24 @@ Included:
- application after lazy attach and model selection but before the first
prompt;
- authoritative replacement by same-session context;
- the current WebShell conversation;
- the current WebShell conversation and the shared reasoning default for later
sessions;
- scope-aware settings refresh after successful WebShell persistence;
- effort changes after completed messages and while a prompt is running;
- Thinking on/off and `low`, `medium`, `xhigh` effort;
- browser coverage for welcome, live override, model switching, old daemons,
and the existing live mutation behavior.

Excluded:

- persistence across sessions or restarts;
- persisted/default-model semantics;
- workspace-level, reusable, or cross-client reasoning defaults;
- a per-model persisted/default mapping beyond the shared
`model.reasoningEffort` setting;
- persistence for ordinary ACP config-option mutations;
- preview, aliases, and future reasoning-control shapes;
- route and runtime models;
- TUI, channel, provider, auth-refresh, and runtime-snapshot behavior;
- capability flags and cross-client model/config broadcasts.
- TUI and channel behavior;
- capability flags and general model/config broadcasts outside the existing
scope-aware settings refresh.

## Compatibility

Expand Down
39 changes: 39 additions & 0 deletions docs/design/webshell-reasoning-effort-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# WebShell reasoning effort persistence

## Problem

WebShell currently applies a reasoning effort selection only to the active ACP session. A new session therefore falls back to the previously configured model default. The settings schema also treats effort as one global five-value scale, even though providers can expose model-specific values such as `ultra` or `minimal`.

## Semantics

`model.reasoningEffort` is an open string setting:

- An absent value leaves reasoning at the model or provider default.
- `none` disables reasoning by setting the runtime configuration to `reasoning: false`.
- Every other non-empty value is preserved verbatim as `reasoning.effort`.
Comment thread
callmeYe marked this conversation as resolved.
Resolved

There is no global normalization, ordering, allowlist, or fallback for persisted values. The existing five built-in effort tiers remain available to `/effort` and provider adapters that explicitly implement that scale. Provider adapters may translate built-in tiers for their own wire protocol, but must pass an unknown value through or reject it explicitly rather than silently changing it.

ACP uses `default` only as an interaction instruction to remove `model.reasoningEffort`; that ACP path never writes the sentinel. The open setting does not reserve `default` globally, so a value authored directly in settings remains an opaque provider value. After clearing the selected scope, the live session adopts the resulting merged value, including an inherited opaque string from a lower-precedence scope.

Persisting a model switch to a toggle-only Qwen model clears `model.reasoningEffort` from every active writable settings scope and clears the live override after the switch succeeds. Switching to a non-Qwen model performs the same cleanup only for an opaque provider-specific effort; built-in tiers remain portable. Session-only and managed-session switches clear only an incompatible live override without mutating the persisted default. Read-only managed settings are also left unchanged while their incompatible live override is removed. Switches to tiered qwen3.8-max models preserve the effort value.

## Persistence boundary

The WebShell daemon route marks its ACP `reasoning_effort` update as persistent. The ACP agent applies the selection to the live session first, then writes through the `Session` instance's own loaded settings using the same scope selection as model persistence. Direct ACP calls without the private marker remain session-only.

After a successful persisted update, the bridge broadcasts `settings_changed` for `model.reasoningEffort` with the actual user or workspace scope that was written. User-scope changes fan out across every live workspace bridge in the daemon; workspace-scope changes stay on their owning bridge. This refreshes retained workspace provider state and the no-session welcome preview without creating an empty session.

If the settings write fails, the live session remains updated and the request reports that the current session changed but the default was not saved. No settings-change event is broadcast.

## Runtime lifecycle

The runtime keeps an explicit reasoning override across authentication refreshes and both hot and full model rebuilds. The override distinguishes disabled reasoning, an opaque effort string, and the provider default. This prevents `none` from being lost merely because it has no `effort` field.

When an explicit preference is active for a tiered qwen3.8 model on DashScope, the runtime removes higher-priority `extra_body` and `samplingParams` thinking knobs from its live configuration with copy-on-write semantics. This keeps the persisted preference effective after new-session construction and authentication refresh without rewriting the model's stored generation configuration. With no explicit preference, those static model defaults remain unchanged.

Model reasoning capabilities advertise options as independent `value` and `name` pairs. WebShell stores and submits `value` while rendering `name`, so model-specific values do not depend on global translation keys.

## Validation

Unit and integration coverage verifies opaque string round trips, `none`, clearing to provider defaults, lifecycle rebuilds, capability labels, the private persistence marker, write failures, welcome previews, provider wire behavior, and persisted model switches into toggle-only Qwen models. Real-daemon E2E compares a clean baseline and this change with isolated configuration directories for both Medium and Thinking Off across a daemon restart and a newly created session, then verifies that switching to a toggle-only Qwen model removes the persisted effort before the next request.
Loading
Loading