MCPcopy
hub / github.com/docker/docker-agent / runForwarding

Method runForwarding

pkg/runtime/agent_delegation.go:265–329  ·  view source on GitHub ↗

runForwarding runs a child session synchronously, forwarding all of its events to evts and propagating tool-approval state back to the parent on completion. This is the "interactive" path used by transfer_task and run_skill: the parent loop is blocked while the child executes, and the user sees the

(ctx context.Context, parent *session.Session, evts EventSink, req delegationRequest)

Source from the content-addressed store, hash-verified

263// building the sub-session, driving RunStream, and recording the
264// sub-session on the parent.
265func (r *LocalRuntime) runForwarding(ctx context.Context, parent *session.Session, evts EventSink, req delegationRequest) (*tools.ToolCallResult, error) {
266 span := trace.SpanFromContext(ctx)
267
268 callerAgent, err := r.team.Agent(r.currentAgentName())
269 if err != nil {
270 return nil, fmt.Errorf("current agent not found: %w", err)
271 }
272 child, err := r.team.Agent(req.AgentName)
273 if err != nil {
274 return nil, err
275 }
276
277 if req.SwitchCurrentAgent {
278 defer r.swapCurrentAgent(ctx, parent.ID, callerAgent, child, evts)()
279 }
280
281 s := newSubSession(parent, req.SubSessionConfig, child)
282
283 // subagent_stop fires after the child's stream has fully drained,
284 // using the *parent* agent's executor so handlers configured on the
285 // orchestrator see every child completion in one place — success or
286 // failure. The deferred call ensures we don't lose the event when an
287 // ErrorEvent triggers an early return below; handlers can detect a
288 // failed run by an empty stop_response (or by correlating with the
289 // session-level error event the parent already received).
290 defer func() {
291 r.executeSubagentStopHooks(ctx, parent, s, callerAgent, req.AgentName, s.GetLastAssistantMessageContent())
292 }()
293
294 childEvents := r.RunStream(ctx, s)
295 var subSessionErr error
296 for event := range childEvents {
297 evts.Emit(event)
298 if errEvent, ok := event.(*ErrorEvent); ok && subSessionErr == nil {
299 // Capture the first ErrorEvent but keep draining the channel so
300 // the sub-session's full transcript still streams through. The
301 // child's run loop may emit additional events (e.g. notifications,
302 // hook output) after the error before its channel closes; dropping
303 // them here would leave the TUI's streamDepth counter unbalanced
304 // and the user without context for what actually went wrong.
305 subSessionErr = fmt.Errorf("%s", errEvent.Error)
306 }
307 }
308
309 // Persist the sub-session unconditionally — even on error, the partial
310 // transcript is the most valuable artifact for debugging. The persistence
311 // pipeline relies on SubSessionCompleted to write the sub-session's
312 // messages to the store; without this emission they are silently dropped.
313 parent.AddSubSession(s)
314 evts.Emit(SubSessionCompleted(parent.ID, s, callerAgent.Name()))
315
316 if subSessionErr != nil {
317 span.RecordError(subSessionErr)
318 span.SetStatus(codes.Error, "sub-session error")
319 return nil, subSessionErr
320 }
321
322 // Only propagate ToolsApproved on success. A failed sub-session must not

Callers 2

handleTaskTransferMethod · 0.95
RunSkillForkMethod · 0.95

Calls 13

currentAgentNameMethod · 0.95
swapCurrentAgentMethod · 0.95
RunStreamMethod · 0.95
ResultSuccessFunction · 0.92
newSubSessionFunction · 0.85
SubSessionCompletedFunction · 0.85
AgentMethod · 0.80
EmitMethod · 0.65
AddSubSessionMethod · 0.65
NameMethod · 0.65

Tested by

no test coverage detected