Skip to content

Commit d309f27

Browse files
authored
Merge pull request #2149 from dgageot/board/i-have-a-yaml-with-two-agents-one-uses-o-2e1b1b77
fix: sub-session thinking state derived from child agent, not parent session
2 parents f81fb4f + 468cc62 commit d309f27

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)