detached 26974cc

fix(subagents): expose reasoning as the spawn override

commit
26974cce75389278d7d8e4ad8be6cf6ec830e209
author
brice · · 2026-08-12 03:51 UTC
parent
d205e1c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +15 -23
M docs/subagents.md +2 -2
@@ -44,7 +44,7 @@ Supported metadata:
4444 | `tools` | Comma-separated or list-form tool names. zut enforces its built-in `read`, `write`, `edit`, `bash`, `create_worktree`, `lsp`, and `web_search` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. |
4545 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |
4646 | `provider` | Optional separate provider ID for a model without a provider prefix. |
47-| `thinking` / `reasoning` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. |
47+| `thinking` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. This is applied as the child's `reasoning` setting. |
4848 | `systemPromptMode` | `append` (default) or `replace`. |
4949 | `inheritProjectContext` | Set to `false` to omit `AGENTS.md` context from the child. |
5050 | `inheritSkills` | Set to `false` to omit skill discovery and the conditional `skill` loader from the child; `--no-skill` has the same effect for a run. |
@@ -122,7 +122,7 @@ Per-spawn reasoning and fast-mode overrides are also accepted:
122122 }
123123 ```
124124
125-`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.
125+If `reasoning` is omitted from `subagent_spawn`, the child inherits the host reasoning level for an unnamed spawn, or the selected profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.
126126
127127 The interactive command also supports the same selection explicitly:
128128
@@ -44,7 +44,7 @@ Supported metadata:
44 | `tools` | Comma-separated or list-form tool names. zut enforces its built-in `read`, `write`, `edit`, `bash`, `create_worktree`, `lsp`, and `web_search` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. |44 | `tools` | Comma-separated or list-form tool names. zut enforces its built-in `read`, `write`, `edit`, `bash`, `create_worktree`, `lsp`, and `web_search` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. |
45 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |45 | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. |
46 | `provider` | Optional separate provider ID for a model without a provider prefix. |46 | `provider` | Optional separate provider ID for a model without a provider prefix. |
47-| `thinking` / `reasoning` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. |47+| `thinking` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. This is applied as the child's `reasoning` setting. |
48 | `systemPromptMode` | `append` (default) or `replace`. |48 | `systemPromptMode` | `append` (default) or `replace`. |
49 | `inheritProjectContext` | Set to `false` to omit `AGENTS.md` context from the child. |49 | `inheritProjectContext` | Set to `false` to omit `AGENTS.md` context from the child. |
50 | `inheritSkills` | Set to `false` to omit skill discovery and the conditional `skill` loader from the child; `--no-skill` has the same effect for a run. |50 | `inheritSkills` | Set to `false` to omit skill discovery and the conditional `skill` loader from the child; `--no-skill` has the same effect for a run. |
@@ -122,7 +122,7 @@ Per-spawn reasoning and fast-mode overrides are also accepted:
122 }122 }
123 ```123 ```
124 124
125-`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.125+If `reasoning` is omitted from `subagent_spawn`, the child inherits the host reasoning level for an unnamed spawn, or the selected profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.
126 126
127 The interactive command also supports the same selection explicitly:127 The interactive command also supports the same selection explicitly:
128 128
M packages/agent/tools/subagent_spawn.go +6 -20
@@ -59,7 +59,6 @@ type subagentSpawnArgs struct {
5959 Model string `json:"model,omitempty"`
6060 Provider string `json:"provider,omitempty"`
6161 Reasoning string `json:"reasoning,omitempty"`
62- Thinking string `json:"thinking,omitempty"`
6362 FastMode *bool `json:"fast_mode,omitempty"`
6463 Required bool `json:"required,omitempty"`
6564 Isolation string `json:"isolation,omitempty"`
@@ -89,12 +88,7 @@ const subagentSpawnSchemaTemplate = `{
8988 "reasoning": {
9089 "type": "string",
9190 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
92- "description": "Optional reasoning/thinking level for the child. Overrides the selected profile's thinking level when provided."
93- },
94- "thinking": {
95- "type": "string",
96- "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
97- "description": "Alias for reasoning, accepted for compatibility with common agent profile terminology. Prefer reasoning when both are available."
91+ "description": "Optional reasoning level for the child. Overrides the selected profile's thinking level when provided."
9892 },
9993 "fast_mode": {
10094 "type": "boolean",
@@ -231,18 +225,18 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, _
231225 providerID = strings.TrimSpace(t.DefaultProvider())
232226 }
233227
234- reasoning, err := reasoningOverride(a.Reasoning, a.Thinking)
228+ reasoning, err := normalizeReasoning(a.Reasoning)
235229 if err != nil {
236230 return protocolToolError(prefix + ": " + err.Error())
237231 }
238232 if reasoning == "" && profile != nil && strings.TrimSpace(profile.Thinking) != "" {
239- reasoning, err = reasoningOverride(profile.Thinking, "")
233+ reasoning, err = normalizeReasoning(profile.Thinking)
240234 if err != nil {
241235 return protocolToolError(prefix + ": profile " + profile.Name + ": " + err.Error())
242236 }
243237 }
244238 if reasoning == "" && t.DefaultReasoning != nil {
245- reasoning, err = reasoningOverride(t.DefaultReasoning(), "")
239+ reasoning, err = normalizeReasoning(t.DefaultReasoning())
246240 if err != nil {
247241 return protocolToolError(prefix + ": host " + err.Error())
248242 }
@@ -326,16 +320,8 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, _
326320 }, nil
327321 }
328322
329-func reasoningOverride(reasoning, thinking string) (string, error) {
330- reasoning = strings.TrimSpace(reasoning)
331- thinking = strings.TrimSpace(thinking)
332- if reasoning != "" && thinking != "" && provider.NormalizeReasoning(reasoning) != provider.NormalizeReasoning(thinking) {
333- return "", fmt.Errorf("reasoning and thinking disagree; provide only one")
334- }
335- value := reasoning
336- if value == "" {
337- value = thinking
338- }
323+func normalizeReasoning(value string) (string, error) {
324+ value = strings.TrimSpace(value)
339325 if value == "" {
340326 return "", nil
341327 }
@@ -59,7 +59,6 @@ type subagentSpawnArgs struct {
59 Model string `json:"model,omitempty"`59 Model string `json:"model,omitempty"`
60 Provider string `json:"provider,omitempty"`60 Provider string `json:"provider,omitempty"`
61 Reasoning string `json:"reasoning,omitempty"`61 Reasoning string `json:"reasoning,omitempty"`
62- Thinking string `json:"thinking,omitempty"`
63 FastMode *bool `json:"fast_mode,omitempty"`62 FastMode *bool `json:"fast_mode,omitempty"`
64 Required bool `json:"required,omitempty"`63 Required bool `json:"required,omitempty"`
65 Isolation string `json:"isolation,omitempty"`64 Isolation string `json:"isolation,omitempty"`
@@ -89,12 +88,7 @@ const subagentSpawnSchemaTemplate = `{
89 "reasoning": {88 "reasoning": {
90 "type": "string",89 "type": "string",
91 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],90 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
92- "description": "Optional reasoning/thinking level for the child. Overrides the selected profile's thinking level when provided."91+ "description": "Optional reasoning level for the child. Overrides the selected profile's thinking level when provided."
93- },
94- "thinking": {
95- "type": "string",
96- "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
97- "description": "Alias for reasoning, accepted for compatibility with common agent profile terminology. Prefer reasoning when both are available."
98 },92 },
99 "fast_mode": {93 "fast_mode": {
100 "type": "boolean",94 "type": "boolean",
@@ -231,18 +225,18 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, _
231 providerID = strings.TrimSpace(t.DefaultProvider())225 providerID = strings.TrimSpace(t.DefaultProvider())
232 }226 }
233 227
234- reasoning, err := reasoningOverride(a.Reasoning, a.Thinking)228+ reasoning, err := normalizeReasoning(a.Reasoning)
235 if err != nil {229 if err != nil {
236 return protocolToolError(prefix + ": " + err.Error())230 return protocolToolError(prefix + ": " + err.Error())
237 }231 }
238 if reasoning == "" && profile != nil && strings.TrimSpace(profile.Thinking) != "" {232 if reasoning == "" && profile != nil && strings.TrimSpace(profile.Thinking) != "" {
239- reasoning, err = reasoningOverride(profile.Thinking, "")233+ reasoning, err = normalizeReasoning(profile.Thinking)
240 if err != nil {234 if err != nil {
241 return protocolToolError(prefix + ": profile " + profile.Name + ": " + err.Error())235 return protocolToolError(prefix + ": profile " + profile.Name + ": " + err.Error())
242 }236 }
243 }237 }
244 if reasoning == "" && t.DefaultReasoning != nil {238 if reasoning == "" && t.DefaultReasoning != nil {
245- reasoning, err = reasoningOverride(t.DefaultReasoning(), "")239+ reasoning, err = normalizeReasoning(t.DefaultReasoning())
246 if err != nil {240 if err != nil {
247 return protocolToolError(prefix + ": host " + err.Error())241 return protocolToolError(prefix + ": host " + err.Error())
248 }242 }
@@ -326,16 +320,8 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, _
326 }, nil320 }, nil
327 }321 }
328 322
329-func reasoningOverride(reasoning, thinking string) (string, error) {323+func normalizeReasoning(value string) (string, error) {
330- reasoning = strings.TrimSpace(reasoning)324+ value = strings.TrimSpace(value)
331- thinking = strings.TrimSpace(thinking)
332- if reasoning != "" && thinking != "" && provider.NormalizeReasoning(reasoning) != provider.NormalizeReasoning(thinking) {
333- return "", fmt.Errorf("reasoning and thinking disagree; provide only one")
334- }
335- value := reasoning
336- if value == "" {
337- value = thinking
338- }
339 if value == "" {325 if value == "" {
340 return "", nil326 return "", nil
341 }327 }
M packages/agent/tools/subagent_spawn_test.go +7 -1
@@ -350,7 +350,7 @@ func TestSubagentSpawnAppliesProfileFastModeRestriction(t *testing.T) {
350350 }
351351 }
352352
353-func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {
353+func TestSubagentSpawnSchemaUsesReasoningAndKeepsProfileThinkingInternal(t *testing.T) {
354354 var schema struct {
355355 Properties map[string]struct {
356356 Type string `json:"type"`
@@ -360,6 +360,12 @@ func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {
360360 if err := json.Unmarshal((&SubagentSpawnTool{}).Schema(), &schema); err != nil {
361361 t.Fatal(err)
362362 }
363+ if _, ok := schema.Properties["reasoning"]; !ok {
364+ t.Fatal("subagent_spawn schema does not advertise reasoning")
365+ }
366+ if _, ok := schema.Properties["thinking"]; ok {
367+ t.Fatal("subagent_spawn schema exposes profile-only thinking")
368+ }
363369 fastMode, ok := schema.Properties["fast_mode"]
364370 if !ok {
365371 t.Fatal("subagent_spawn schema does not advertise fast_mode")
@@ -350,7 +350,7 @@ func TestSubagentSpawnAppliesProfileFastModeRestriction(t *testing.T) {
350 }350 }
351 }351 }
352 352
353-func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {353+func TestSubagentSpawnSchemaUsesReasoningAndKeepsProfileThinkingInternal(t *testing.T) {
354 var schema struct {354 var schema struct {
355 Properties map[string]struct {355 Properties map[string]struct {
356 Type string `json:"type"`356 Type string `json:"type"`
@@ -360,6 +360,12 @@ func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {
360 if err := json.Unmarshal((&SubagentSpawnTool{}).Schema(), &schema); err != nil {360 if err := json.Unmarshal((&SubagentSpawnTool{}).Schema(), &schema); err != nil {
361 t.Fatal(err)361 t.Fatal(err)
362 }362 }
363+ if _, ok := schema.Properties["reasoning"]; !ok {
364+ t.Fatal("subagent_spawn schema does not advertise reasoning")
365+ }
366+ if _, ok := schema.Properties["thinking"]; ok {
367+ t.Fatal("subagent_spawn schema exposes profile-only thinking")
368+ }
363 fastMode, ok := schema.Properties["fast_mode"]369 fastMode, ok := schema.Properties["fast_mode"]
364 if !ok {370 if !ok {
365 t.Fatal("subagent_spawn schema does not advertise fast_mode")371 t.Fatal("subagent_spawn schema does not advertise fast_mode")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close