-
Notifications
You must be signed in to change notification settings - Fork 0
Add optional async sub-agent delegation with report-back and wait control #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,22 +29,26 @@ export async function handleInvocation(input: { | |
| agentId: string | ||
| input: string | ||
| streamToken: string | ||
| parentAgentId?: string | null | ||
| parentRunId?: string | null | ||
| parentToolId?: string | null | ||
| parentToolCallId?: string | null | ||
| parentStream?: WritableStream<UIMessageChunk> | null | ||
| reportBackToParent?: boolean | ||
| replyToken?: string | null | ||
| callStack: string[] | ||
| depth: number | ||
| }): Promise<void> { | ||
| const { | ||
| agentId, | ||
| input: instruction, | ||
| parentAgentId, | ||
| streamToken, | ||
| parentRunId, | ||
| parentToolId, | ||
| parentToolCallId, | ||
| parentStream, | ||
| reportBackToParent, | ||
| replyToken, | ||
| callStack, | ||
| depth, | ||
|
|
@@ -151,6 +155,13 @@ export async function handleInvocation(input: { | |
| }) | ||
| await finishInvocationStreams(streamNamespaces) | ||
| await forwardPromise | ||
| await maybeReportBackToParent({ | ||
| childAgentId: agentId, | ||
| childName: built.meta.name, | ||
| finalText: extractFinalAssistantText(result.uiMessages), | ||
| parentAgentId: parentAgentId ?? null, | ||
| reportBackToParent: reportBackToParent ?? false, | ||
| }) | ||
| } catch (err) { | ||
| await failInvocation({ | ||
| err, | ||
|
|
@@ -163,6 +174,72 @@ export async function handleInvocation(input: { | |
| } | ||
| } | ||
|
|
||
| async function maybeReportBackToParent(input: { | ||
| childAgentId: string | ||
| childName: string | ||
| finalText: string | null | ||
| parentAgentId: string | null | ||
| reportBackToParent: boolean | ||
| }): Promise<void> { | ||
| if (!input.reportBackToParent || !input.parentAgentId || !input.finalText) { | ||
| return | ||
| } | ||
| const { dispatchInvocation } = await import( | ||
| '@/agent-runtime/server/session-events' | ||
| ) | ||
| await dispatchInvocation({ | ||
| childAgentId: input.parentAgentId, | ||
| childUserId: await resolveAgentUserId(input.parentAgentId), | ||
| parentAgentId: input.childAgentId, | ||
| parentUserId: await resolveAgentUserId(input.parentAgentId), | ||
| parentRunId: null, | ||
| parentToolId: 'sub_agent_report_back', | ||
| instruction: [ | ||
| `Sub-agent "${input.childName}" completed delegated async work.`, | ||
| `Result: ${input.finalText}`, | ||
| ].join('\n'), | ||
| streamToken: `report_back_${Date.now().toString(36)}`, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The report-back Useful? React with 👍 / 👎. |
||
| callStack: [input.parentAgentId], | ||
| depth: 0, | ||
| }) | ||
| } | ||
|
|
||
| async function resolveAgentUserId(agentId: string): Promise<string> { | ||
| const { db } = await import('@/shared/db') | ||
| const { agent } = await import('@/shared/db/schema') | ||
| const { eq } = await import('drizzle-orm') | ||
| const [row] = await db.select().from(agent).where(eq(agent.id, agentId)).limit(1) | ||
| if (!row) { | ||
| throw new Error(`resolveAgentUserId: agent ${agentId} not found`) | ||
| } | ||
| return row.userId | ||
| } | ||
|
|
||
| function extractFinalAssistantText( | ||
| messages: readonly UIMessage[] | undefined | ||
| ): string | null { | ||
| if (!messages) { | ||
| return null | ||
| } | ||
| for (let i = messages.length - 1; i >= 0; i--) { | ||
| const message = messages[i] | ||
| if (message.role !== 'assistant') { | ||
| continue | ||
| } | ||
| const chunks: string[] = [] | ||
| for (const part of message.parts ?? []) { | ||
| if (part.type === 'text' && typeof part.text === 'string') { | ||
| chunks.push(part.text) | ||
| } | ||
| } | ||
| const text = chunks.join('').trim() | ||
| if (text.length > 0) { | ||
| return text | ||
| } | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| async function finishInvocationStreams( | ||
| namespaces: readonly string[] | ||
| ): Promise<void> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awaiting
maybeReportBackToParentin the maintrymeans any dispatch error (for example, parent agent deleted/disabled between queue and completion, or a transient DB failure) is treated as a child-run failure, because control falls intofailInvocationafter the child result has already been finalized. This mislabels successful async sub-agent runs as failed and can break monitoring/automation that depends on run status.Useful? React with 👍 / 👎.