Skip to content

Commit 468cc62

Browse files
committed
fix: sub-session thinking state derived from child agent, not parent session
When transferring a task to a sub-agent, the child session's thinking flag was inherited from the parent session via SubSessionConfig.Thinking. This caused agents without thinking_budget (e.g. haiku) to have thinking enabled when called from an agent with thinking_budget (e.g. opus). The thinking state also leaked back to the parent via back-propagation in runSubSessionForwarding. Fix this by removing the Thinking field from SubSessionConfig entirely and deriving it inside newSubSession from childAgent.ThinkingConfigured(). This makes the correct behavior the default and eliminates the error-prone pattern where every caller had to pass the right source. For skill sub-agents (which run as the same agent and should respect the user's /think toggle), the thinking state is set as an explicit override after newSubSession returns. Also simplify handleTaskTransfer by removing a redundant agent lookup, a redundant variable alias, and unnecessary error-swallowing guards. Assisted-By: docker-agent
1 parent 2efd1bb commit 468cc62

3 files changed

Lines changed: 49 additions & 25 deletions

File tree

pkg/runtime/agent_delegation.go

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ type SubSessionConfig struct {
7777
Title string
7878
// ToolsApproved overrides whether tools are pre-approved in the child session.
7979
ToolsApproved bool
80-
// Thinking propagates the parent's thinking-mode flag.
81-
Thinking bool
8280
// PinAgent, when true, pins the child session to AgentName via
8381
// session.WithAgentName. This is required for concurrent background
8482
// tasks that must not share the runtime's mutable currentAgent field.
@@ -110,7 +108,7 @@ func newSubSession(parent *session.Session, cfg SubSessionConfig, childAgent *ag
110108
session.WithMaxConsecutiveToolCalls(childAgent.MaxConsecutiveToolCalls()),
111109
session.WithTitle(cfg.Title),
112110
session.WithToolsApproved(cfg.ToolsApproved),
113-
session.WithThinking(cfg.Thinking),
111+
session.WithThinking(childAgent.ThinkingConfigured()),
114112
session.WithSendUserMessage(false),
115113
session.WithParentID(parent.ID),
116114
}
@@ -121,8 +119,8 @@ func newSubSession(parent *session.Session, cfg SubSessionConfig, childAgent *ag
121119
}
122120

123121
// runSubSessionForwarding runs a child session within the parent, forwarding all
124-
// events to the caller's event channel and propagating session state (tool
125-
// approvals, thinking) back to the parent when done.
122+
// events to the caller's event channel and propagating tool approval state
123+
// back to the parent when done.
126124
//
127125
// This is the "interactive" path used by transfer_task where the parent agent
128126
// loop is blocked while the child executes.
@@ -137,7 +135,6 @@ func (r *LocalRuntime) runSubSessionForwarding(ctx context.Context, parent, chil
137135
}
138136

139137
parent.ToolsApproved = child.ToolsApproved
140-
parent.Thinking = child.Thinking
141138

142139
parent.AddSubSession(child)
143140
evts <- SubSessionCompleted(parent.ID, child, callerAgent)
@@ -216,7 +213,6 @@ func (r *LocalRuntime) RunAgent(ctx context.Context, params agenttool.RunParams)
216213
AgentName: params.AgentName,
217214
Title: "Background agent task",
218215
ToolsApproved: true,
219-
Thinking: sess.Thinking,
220216
PinAgent: true,
221217
}
222218

@@ -252,43 +248,35 @@ func (r *LocalRuntime) handleTaskTransfer(ctx context.Context, sess *session.Ses
252248

253249
slog.Debug("Transferring task to agent", "from_agent", a.Name(), "to_agent", params.Agent, "task", params.Task)
254250

255-
ca := r.CurrentAgentName()
256-
257251
// Emit agent switching start event
258-
evts <- AgentSwitching(true, ca, params.Agent)
252+
evts <- AgentSwitching(true, a.Name(), params.Agent)
259253

260254
r.setCurrentAgent(params.Agent)
261255
defer func() {
262-
r.setCurrentAgent(ca)
256+
r.setCurrentAgent(a.Name())
263257

264258
// Emit agent switching end event
265-
evts <- AgentSwitching(false, params.Agent, ca)
259+
evts <- AgentSwitching(false, params.Agent, a.Name())
266260

267261
// Restore original agent info in sidebar
268-
if originalAgent, err := r.team.Agent(ca); err == nil {
269-
evts <- AgentInfo(originalAgent.Name(), getAgentModelID(originalAgent), originalAgent.Description(), originalAgent.WelcomeMessage())
270-
}
262+
evts <- AgentInfo(a.Name(), getAgentModelID(a), a.Description(), a.WelcomeMessage())
271263
}()
272264

273265
// Emit agent info for the new agent
274-
if newAgent, err := r.team.Agent(params.Agent); err == nil {
275-
evts <- AgentInfo(newAgent.Name(), getAgentModelID(newAgent), newAgent.Description(), newAgent.WelcomeMessage())
276-
}
277-
278-
slog.Debug("Creating new session with parent session", "parent_session_id", sess.ID, "tools_approved", sess.ToolsApproved, "thinking", sess.Thinking)
279-
280266
child, err := r.team.Agent(params.Agent)
281267
if err != nil {
282268
return nil, err
283269
}
270+
evts <- AgentInfo(child.Name(), getAgentModelID(child), child.Description(), child.WelcomeMessage())
271+
272+
slog.Debug("Creating new session with parent session", "parent_session_id", sess.ID, "tools_approved", sess.ToolsApproved)
284273

285274
cfg := SubSessionConfig{
286275
Task: params.Task,
287276
ExpectedOutput: params.ExpectedOutput,
288277
AgentName: params.Agent,
289278
Title: "Transferred task",
290279
ToolsApproved: sess.ToolsApproved,
291-
Thinking: sess.Thinking,
292280
}
293281

294282
s := newSubSession(sess, cfg, child)

pkg/runtime/agent_delegation_test.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,14 @@ func TestNewSubSession(t *testing.T) {
7373
AgentName: "worker",
7474
Title: "Test task",
7575
ToolsApproved: true,
76-
Thinking: true,
7776
}
7877

7978
s := newSubSession(parent, cfg, childAgent)
8079

8180
assert.Equal(t, parent.ID, s.ParentID)
8281
assert.Equal(t, "Test task", s.Title)
8382
assert.True(t, s.ToolsApproved)
84-
assert.True(t, s.Thinking)
83+
assert.False(t, s.Thinking) // childAgent has no ThinkingConfigured
8584
assert.False(t, s.SendUserMessage)
8685
assert.Equal(t, 10, s.MaxIterations)
8786
// AgentName should NOT be set when PinAgent is false
@@ -162,6 +161,40 @@ func TestSubSessionConfig_DefaultValues(t *testing.T) {
162161
assert.Empty(t, s.AgentName)
163162
}
164163

164+
func TestSubSessionConfig_ThinkingFromChildAgent(t *testing.T) {
165+
parent := session.New(session.WithUserMessage("hello"))
166+
167+
t.Run("child agent without thinking configured gets thinking=false even if parent has thinking=true", func(t *testing.T) {
168+
parent.Thinking = true
169+
170+
childAgent := agent.New("haiku-worker", "")
171+
172+
cfg := SubSessionConfig{
173+
Task: "simple task",
174+
AgentName: "haiku-worker",
175+
Title: "Transferred task",
176+
}
177+
178+
s := newSubSession(parent, cfg, childAgent)
179+
assert.False(t, s.Thinking, "sub-session should NOT inherit parent's thinking when child agent has no thinking_budget")
180+
})
181+
182+
t.Run("child agent with thinking configured gets thinking=true", func(t *testing.T) {
183+
parent.Thinking = false
184+
185+
childAgent := agent.New("opus-worker", "", agent.WithThinkingConfigured(true))
186+
187+
cfg := SubSessionConfig{
188+
Task: "complex task",
189+
AgentName: "opus-worker",
190+
Title: "Transferred task",
191+
}
192+
193+
s := newSubSession(parent, cfg, childAgent)
194+
assert.True(t, s.Thinking, "sub-session should have thinking enabled when child agent has thinking_budget")
195+
})
196+
}
197+
165198
func TestSubSessionConfig_InheritsAgentLimits(t *testing.T) {
166199
parent := session.New(session.WithUserMessage("hello"))
167200

pkg/runtime/skill_runner.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ func (r *LocalRuntime) handleRunSkill(ctx context.Context, sess *session.Session
7474
AgentName: ca,
7575
Title: "Skill: " + params.Name,
7676
ToolsApproved: sess.ToolsApproved,
77-
Thinking: sess.Thinking,
7877
}
7978

8079
s := newSubSession(sess, cfg, a)
80+
// Skills run as the same agent, so they inherit the session's current
81+
// thinking state (which may have been toggled by the user via /think)
82+
// rather than the agent's static config default.
83+
s.Thinking = sess.Thinking
8184

8285
return r.runSubSessionForwarding(ctx, sess, s, span, evts, ca)
8386
}

0 commit comments

Comments
 (0)