Skip to content

Commit 4ad9f17

Browse files
committed
Show orchestrator-chosen subagent names on agent groups
A subagent_start whose payload data carries a name (the orchestrator's new name trigger parameter) now labels the agent group with that mission name — the agent-type icon stays. The name flows through the live stream path, the turn model (AgentNode.displayName) and its serialize/rebuild round-trip, and persisted transcripts (PersistedContentBlock.name), so reloads keep the label.
1 parent 7f9f11f commit 4ad9f17

9 files changed

Lines changed: 60 additions & 2 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ function parseBlocksWithSpanTree(blocks: ContentBlock[]): MessageSegment[] {
380380
const dispatchToolName = SUBAGENT_DISPATCH_TOOLS[block.content]
381381
if (dispatchToolName) absorbDispatchTool(dispatchToolName, block.parentSpanId)
382382
const g = ensureSpanGroup(block.content, block.spanId, block.parentSpanId)
383+
if (block.subagentName) g.agentLabel = block.subagentName
383384
if (block.endedAt !== undefined) {
384385
// Persisted backend path: the lane was stamped closed (endedAt) without
385386
// a separate subagent_end block (the Sim backend stamps endedAt only;
@@ -623,6 +624,7 @@ function parseBlocksLegacy(blocks: ContentBlock[]): MessageSegment[] {
623624
}
624625
groupsByKey.delete(groupKey('mothership', undefined))
625626
const { group: g } = ensureGroup(key, block.parentToolCallId)
627+
if (block.subagentName) g.agentLabel = block.subagentName
626628
if (inheritedDelegation) g.isDelegating = true
627629
g.isOpen = true
628630
activeGroupKey = resolveGroupKey(key, block.parentToolCallId)

apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model-serialize.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export function modelToContentBlocks(model: TurnModel): ContentBlock[] {
167167
block: {
168168
type: 'subagent',
169169
content: node.agentId,
170+
...(node.displayName ? { subagentName: node.displayName } : {}),
170171
spanId: node.spanId,
171172
parentSpanId: node.parentSpanId,
172173
...(node.triggerToolCallId ? { parentToolCallId: node.triggerToolCallId } : {}),
@@ -267,7 +268,10 @@ export function contentBlocksToModel(blocks: ContentBlock[]): TurnModel {
267268
kind: 'subagent',
268269
event: 'start',
269270
agent: block.content,
270-
data: block.parentToolCallId ? { tool_call_id: block.parentToolCallId } : {},
271+
data: {
272+
...(block.parentToolCallId ? { tool_call_id: block.parentToolCallId } : {}),
273+
...(block.subagentName ? { name: block.subagentName } : {}),
274+
},
271275
},
272276
scopeFor(block),
273277
block.timestamp

apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,29 @@ describe('reduceEvent — subagent lifecycle', () => {
237237
expect(agent(m, 'S1').parentSpanId).toBe(MAIN_SPAN)
238238
})
239239

240+
it('captures the orchestrator-chosen display name from span start data', () => {
241+
const m = apply([
242+
envelope(
243+
1,
244+
'span',
245+
{
246+
kind: 'subagent',
247+
event: 'start',
248+
agent: 'research',
249+
data: { tool_call_id: 'tc-r', name: 'Pricing research' },
250+
},
251+
{
252+
lane: 'subagent',
253+
spanId: 'S1',
254+
parentSpanId: MAIN_SPAN,
255+
parentToolCallId: 'tc-r',
256+
agentId: 'research',
257+
}
258+
),
259+
])
260+
expect(agent(m, 'S1').displayName).toBe('Pricing research')
261+
})
262+
240263
it('settles an agent error when span end carries an error', () => {
241264
const m = apply([
242265
spanStart(1, 'S1', 'file', 'tc-file'),

apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export interface AgentNode extends NodeBase {
8585
agentId: string
8686
/** The outer delegation tool_use that triggered this run; links the trigger tool node. */
8787
triggerToolCallId?: string
88+
/** Orchestrator-chosen display name for this delegation (falls back to the agent label). */
89+
displayName?: string
8890
status: NodeStatus
8991
/** Wire seq at which the run terminated (span end), for ordering the close marker. */
9092
endSeq?: number
@@ -563,6 +565,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
563565
const triggerToolCallId =
564566
scope?.parentToolCallId ?? asString(data?.tool_call_id) ?? asString(data?.toolCallId)
565567
const agentId = asString(payload.agent) ?? scope?.agentId ?? ''
568+
const displayName = asString(data?.name)
566569
const resolvedSpanId =
567570
scope?.spanId ?? (triggerToolCallId ? `span:${triggerToolCallId}` : `span:${seq}`)
568571
const parentSpanId = scope?.parentSpanId ?? MAIN_SPAN
@@ -581,6 +584,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
581584
// scope.agentId can name the forwarding caller (e.g. superagent),
582585
// while this start's payload.agent is the authoritative lane owner.
583586
if (agentId && existing.agentId !== agentId) existing.agentId = agentId
587+
if (displayName && !existing.displayName) existing.displayName = displayName
584588
if (!existing.triggerToolCallId && triggerToolCallId) {
585589
existing.triggerToolCallId = triggerToolCallId
586590
}
@@ -602,6 +606,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
602606
seq: seq,
603607
...(tsMs !== undefined ? { startedAtMs: tsMs } : {}),
604608
...(triggerToolCallId ? { triggerToolCallId } : {}),
609+
...(displayName ? { displayName } : {}),
605610
}
606611
model.nodes.set(node.id, node)
607612
model.order.push(node.id)

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export interface ContentBlock {
114114
type: ContentBlockType
115115
content?: string
116116
subagent?: string
117+
/** Orchestrator-chosen display name for a `subagent` start block (shown instead of the generic agent label). */
118+
subagentName?: string
117119
toolCall?: ToolCallInfo
118120
options?: OptionItem[]
119121
timestamp?: number

apps/sim/lib/copilot/chat/display-message.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ function toDisplayBlockBody(block: PersistedContentBlock): ContentBlock | undefi
9292
if (block.lifecycle === MothershipStreamV1SpanLifecycleEvent.end) {
9393
return { type: ContentBlockType.subagent_end }
9494
}
95-
return { type: ContentBlockType.subagent, content: block.content }
95+
return {
96+
type: ContentBlockType.subagent,
97+
content: block.content,
98+
...(block.name ? { subagentName: block.name } : {}),
99+
}
96100
case MothershipStreamV1EventType.complete:
97101
if (block.status === MothershipStreamV1CompletionStatus.cancelled) {
98102
return { type: ContentBlockType.stopped }

apps/sim/lib/copilot/chat/persisted-message.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export interface PersistedContentBlock {
5353
lifecycle?: MothershipStreamV1SpanLifecycleEvent
5454
status?: MothershipStreamV1CompletionStatus
5555
content?: string
56+
/** Orchestrator-chosen display name on a subagent start block. */
57+
name?: string
5658
toolCall?: PersistedToolCall
5759
timestamp?: number
5860
endedAt?: number
@@ -245,6 +247,7 @@ function mapContentBlockBody(block: ContentBlock): PersistedContentBlock {
245247
kind: MothershipStreamV1SpanPayloadKind.subagent,
246248
lifecycle: MothershipStreamV1SpanLifecycleEvent.start,
247249
content: block.content,
250+
...(block.subagentName ? { name: block.subagentName } : {}),
248251
}
249252
case 'subagent_text':
250253
return {
@@ -436,6 +439,9 @@ interface RawBlock {
436439
type: string
437440
lane?: string
438441
agent?: string
442+
/** Orchestrator-chosen subagent display name (legacy blocks store it as `subagentName`). */
443+
name?: string
444+
subagentName?: string
439445
content?: string
440446
/** Go persists text blocks with key "text" instead of "content" */
441447
text?: string
@@ -503,6 +509,7 @@ function normalizeCanonicalBlock(block: RawBlock): PersistedContentBlock {
503509
result.lane = block.lane
504510
}
505511
if (block.agent) result.agent = block.agent
512+
if (block.name) result.name = block.name
506513
const blockContent = block.content ?? block.text
507514
if (blockContent !== undefined) result.content = blockContent
508515
if (block.channel) result.channel = block.channel as MothershipStreamV1TextChannel
@@ -584,6 +591,7 @@ function normalizeLegacyBlock(block: RawBlock): PersistedContentBlock {
584591
kind: MothershipStreamV1SpanPayloadKind.subagent,
585592
lifecycle: MothershipStreamV1SpanLifecycleEvent.start,
586593
content: block.content,
594+
...(block.subagentName ? { name: block.subagentName } : {}),
587595
}
588596
}
589597

apps/sim/lib/copilot/request/go/stream.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,17 @@ export async function runStreamLoop(
436436
const openParents = (context.openSubagentParents ??= new Set<string>())
437437
if (!openParents.has(toolCallId)) {
438438
openParents.add(toolCallId)
439+
const payloadData = streamEvent.payload.data
440+
const displayName =
441+
payloadData && typeof payloadData === 'object' && !Array.isArray(payloadData)
442+
? (payloadData as Record<string, unknown>).name
443+
: undefined
439444
context.contentBlocks.push({
440445
type: 'subagent',
441446
content: subagentName,
447+
...(typeof displayName === 'string' && displayName
448+
? { subagentName: displayName }
449+
: {}),
442450
parentToolCallId: toolCallId,
443451
...(spanId ? { spanId } : {}),
444452
...(parentSpanId ? { parentSpanId } : {}),

apps/sim/lib/copilot/request/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export interface ContentBlock {
8080
* `subagent` start block is missing (resume legs re-emit text without start).
8181
*/
8282
subagent?: string
83+
/** Orchestrator-chosen display name for a `subagent` start block. */
84+
subagentName?: string
8385
/**
8486
* Deterministic agent-run identity. `spanId` is the stable per-invocation id
8587
* of the subagent that produced the block; `parentSpanId` links it to the run

0 commit comments

Comments
 (0)