@@ -344,6 +344,42 @@ function applyToolResult(
344344 model . bufferedResults . set ( id , { success, output, status, ...( error ? { error } : { } ) } )
345345}
346346
347+ /**
348+ * Materializes a subagent lane on first reference. Subagent-scoped content
349+ * (text/thinking/tool) can be reduced before its `subagent_start` under heavy
350+ * parallel bursts (many subagents streaming into one ordered channel); without
351+ * the owning `AgentNode` the serializer can't attribute the content, so it leaks
352+ * into the main lane and the subagent's thinking is dropped until the start
353+ * lands. The wire scope already carries the lane identity (Go tags every
354+ * forwarded subagent event with its agent id/span), so the lane is rebuilt
355+ * deterministically from the content event itself — the symmetric counterpart to
356+ * buffering a result before its call. The later `subagent_start` finds this node
357+ * and no-ops.
358+ */
359+ function ensureSubagentLane (
360+ model : TurnModel ,
361+ spanId : string ,
362+ scope : { agentId ?: string ; parentSpanId ?: string ; parentToolCallId ?: string } | undefined ,
363+ seq : number ,
364+ atMs ?: number
365+ ) : void {
366+ if ( spanId === MAIN_SPAN || model . agentBySpanId . has ( spanId ) ) return
367+ const node : AgentNode = {
368+ kind : 'agent' ,
369+ id : spanId ,
370+ spanId,
371+ parentSpanId : scope ?. parentSpanId ?? MAIN_SPAN ,
372+ agentId : scope ?. agentId ?? '' ,
373+ status : 'running' ,
374+ seq,
375+ ...( atMs !== undefined ? { startedAtMs : atMs } : { } ) ,
376+ ...( scope ?. parentToolCallId ? { triggerToolCallId : scope . parentToolCallId } : { } ) ,
377+ }
378+ model . nodes . set ( node . id , node )
379+ model . order . push ( node . id )
380+ model . agentBySpanId . set ( spanId , node . id )
381+ }
382+
347383/**
348384 * Folds one wire envelope into the model. Pure accumulator: it mutates and
349385 * returns the same `model` (the streaming hot path keeps one model per turn).
@@ -364,6 +400,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
364400 switch ( envelope . type ) {
365401 case MothershipStreamV1EventType . text : {
366402 const payload = envelope . payload
403+ ensureSubagentLane ( model , spanId , scope , seq , tsMs )
367404 appendText ( model , spanId , payload . channel as TextChannel , payload . text , seq , tsMs )
368405 break
369406 }
@@ -374,6 +411,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
374411 const rawToolCallId = asString ( payload . toolCallId )
375412 if ( ! rawToolCallId ) break
376413 const toolName = asString ( payload . toolName ) ?? ''
414+ ensureSubagentLane ( model , spanId , scope , seq , tsMs )
377415 const phase = payload . phase
378416 if ( phase === MothershipStreamV1ToolPhase . call ) {
379417 // edit_content folds into its span's workspace_file row (the write
0 commit comments