From c7e0c8317495d8a5bbbfd55b9b4ba691b3c8c62f Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Sat, 13 Jun 2026 11:10:14 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(agent-runtime):?= =?UTF-8?q?=20clarify=20virtual=20sub-agent=20naming=20(#15737)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/AgentRuntime/RuntimeExecutors.ts | 29 +++++++------ apps/server/src/services/aiAgent/index.ts | 43 ++++++++++++------- .../serverRuntimes/agentManagement.ts | 6 +-- .../src/agents/GeneralChatAgent.ts | 10 ++--- .../src/executor.ts | 8 ++-- src/store/chat/agents/createAgentExecutors.ts | 11 +++-- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts b/apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts index d4f384b0e8..95551df219 100644 --- a/apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts +++ b/apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts @@ -324,7 +324,7 @@ const buildPostProcessUrl = ( }; /** - * Build the per-tool-call server sub-agent runner injected into the tool + * Build the per-tool-call server virtual sub-agent runner injected into the tool * execution context. Closes over the current tool payload + parent message so * the `callSubAgent` server tool can fork a child op without re-deriving the * message anchor (which it cannot do correctly from its own context). @@ -336,7 +336,7 @@ const buildPostProcessUrl = ( * not available (no `execVirtualSubAgent` callback, or missing agent/topic * context). */ -const buildServerSubAgentRunner = ( +const buildServerVirtualSubAgentRunner = ( ctx: RuntimeExecutorContext, state: AgentState, chatToolPayload: ChatToolPayload, @@ -388,7 +388,7 @@ const buildServerSubAgentRunner = ( await ctx.messageModel.deleteMessage(placeholder.id); } catch (error) { log( - 'buildServerSubAgentRunner: failed to clean up placeholder %s: %O', + 'buildServerVirtualSubAgentRunner: failed to clean up placeholder %s: %O', placeholder.id, error, ); @@ -2483,7 +2483,7 @@ export const createRuntimeExecutors = ( scope: state.metadata?.scope, serverDB: ctx.serverDB, skipResultTruncation: true, - subAgent: buildServerSubAgentRunner( + subAgent: buildServerVirtualSubAgentRunner( ctx, state, chatToolPayload, @@ -2725,14 +2725,15 @@ export const createRuntimeExecutors = ( log('[%s:%d] Tool execution completed', operationId, stepIndex); - // When the tool result carries an execSubAgent / execSubAgents state the - // GeneralChatAgent needs `stop: true` in the payload to detect it and - // emit the matching exec_sub_agent / exec_sub_agents instruction. Without - // this flag the agent falls through to the normal LLM-call path and the - // sub-agent is never spawned. - const execTaskStateType = executionResult.state?.type as string | undefined; - const isExecTaskState = - execTaskStateType === 'execSubAgent' || execTaskStateType === 'execSubAgents'; + // When a legacy callAgent task result carries execSubAgent / execSubAgents + // state, the GeneralChatAgent needs `stop: true` in the payload to detect + // it and emit the matching exec_sub_agent / exec_sub_agents instruction. + // Without this flag the agent falls through to the normal LLM-call path + // and the background agent run is never spawned. + const legacyAgentInvocationStateType = executionResult.state?.type as string | undefined; + const isLegacyAgentInvocationState = + legacyAgentInvocationStateType === 'execSubAgent' || + legacyAgentInvocationStateType === 'execSubAgents'; executeToolSpan.setAttributes( buildExecuteToolResultAttributes({ attempts: execution.attempts, success: isSuccess }), @@ -2748,7 +2749,7 @@ export const createRuntimeExecutors = ( isSuccess, // Pass tool message ID as parentMessageId for the next LLM call parentMessageId: toolMessageId, - ...(isExecTaskState && { stop: true }), + ...(isLegacyAgentInvocationState && { stop: true }), toolCall: chatToolPayload, toolCallId: chatToolPayload.id, }, @@ -3055,7 +3056,7 @@ export const createRuntimeExecutors = ( scope: state.metadata?.scope, serverDB: ctx.serverDB, skipResultTruncation: true, - subAgent: buildServerSubAgentRunner( + subAgent: buildServerVirtualSubAgentRunner( ctx, state, chatToolPayload, diff --git a/apps/server/src/services/aiAgent/index.ts b/apps/server/src/services/aiAgent/index.ts index 1ae3c4356f..2ebbdac012 100644 --- a/apps/server/src/services/aiAgent/index.ts +++ b/apps/server/src/services/aiAgent/index.ts @@ -417,9 +417,10 @@ export class AiAgentService { * Execute a single agent step against this service's runtime. * * Delegates to the internal AgentRuntimeService, which is already wired with - * the `execSubAgent` fork callback. The QStash step worker drives stepping - * through here so `lobe-agent.callSubAgent` can fork sub-agents — building a - * bare runtime there would lose the callback and fail with SUB_AGENT_UNAVAILABLE. + * the agent-invocation fork callbacks. The QStash step worker drives stepping + * through here so `lobe-agent.callSubAgent` can fork virtual sub-agents — + * building a bare runtime there would lose the callback and fail with + * SUB_AGENT_UNAVAILABLE. */ executeStep(params: AgentExecutionParams): Promise { return this.agentRuntimeService.executeStep(params); @@ -2298,7 +2299,7 @@ export class AiAgentService { : undefined; // 13. Create user message in database - // Include threadId if provided (for SubAgent task execution in isolated Thread) + // Include threadId if provided (for isolated agent execution) const userMessageRecord = runFromHistory ? undefined : await this.messageModel.create({ @@ -2346,7 +2347,7 @@ export class AiAgentService { } // 14. Create assistant message placeholder in database - // Include threadId if provided (for SubAgent task execution in isolated Thread) + // Include threadId if provided (for isolated agent execution) const assistantMessageRecord = await this.messageModel.create({ agentId: persistAgentId, content: LOADING_FLAT, @@ -2940,7 +2941,12 @@ export class AiAgentService { }); // 3. Create hooks for updating Thread metadata and source message - const threadHooks = this.createThreadHooks(thread.id, startedAt, parentMessageId); + const threadHooks = this.createThreadHooks( + thread.id, + startedAt, + parentMessageId, + options.logScope, + ); // For the virtual sub-agent path, also register the completion bridge that // backfills the parent's placeholder tool message and resumes the parked // parent op once the child run is done. Registered last so its tool-message @@ -3063,6 +3069,7 @@ export class AiAgentService { threadId: string, startedAt: string, sourceMessageId: string, + logScope: 'execSubAgent' | 'execVirtualSubAgent' = 'execSubAgent', ): StepLifecycleCallbacks { // Accumulator for tracking metrics across steps let accumulatedToolCalls = 0; @@ -3088,9 +3095,9 @@ export class AiAgentService { totalToolCalls: accumulatedToolCalls, }, }); - log('execSubAgent: updated thread %s metadata after step %d', threadId, state.stepCount); + log('%s: updated thread %s metadata after step %d', logScope, threadId, state.stepCount); } catch (error) { - log('execSubAgent: failed to update thread metadata: %O', error); + log('%s: failed to update thread metadata: %O', logScope, error); } }, @@ -3124,7 +3131,7 @@ export class AiAgentService { // Log error when the isolated run fails if (reason === 'error' && finalState.error) { - console.error('execSubAgent: run failed for thread %s:', threadId, finalState.error); + console.error('%s: run failed for thread %s:', logScope, threadId, finalState.error); } try { @@ -3138,7 +3145,7 @@ export class AiAgentService { await this.messageModel.update(sourceMessageId, { content: lastAssistantMessage.content, }); - log('execSubAgent: updated source message %s with summary', sourceMessageId); + log('%s: updated source message %s with summary', logScope, sourceMessageId); } // Format error for proper serialization (Error objects don't serialize with JSON.stringify) @@ -3161,13 +3168,14 @@ export class AiAgentService { }); log( - 'execSubAgent: thread %s completed with status %s, reason: %s', + '%s: thread %s completed with status %s, reason: %s', + logScope, threadId, status, reason, ); } catch (error) { - console.error('execSubAgent: failed to update thread on completion: %O', error); + console.error('%s: failed to update thread on completion: %O', logScope, error); } }, }; @@ -3181,6 +3189,7 @@ export class AiAgentService { threadId: string, startedAt: string, sourceMessageId: string, + logScope: 'execSubAgent' | 'execVirtualSubAgent', ): AgentHook[] { let accumulatedToolCalls = 0; @@ -3207,7 +3216,7 @@ export class AiAgentService { }, }); } catch (error) { - log('Thread hook afterStep: failed to update metadata: %O', error); + log('%s: thread hook afterStep failed to update metadata: %O', logScope, error); } }, id: 'thread-metadata-update', @@ -3247,7 +3256,8 @@ export class AiAgentService { if (event.reason === 'error' && finalState.error) { console.error( - 'Thread hook onComplete: run failed for thread %s:', + '%s: thread hook onComplete run failed for thread %s:', + logScope, threadId, finalState.error, ); @@ -3284,13 +3294,14 @@ export class AiAgentService { }); log( - 'Thread hook onComplete: thread %s status=%s reason=%s', + '%s: thread hook onComplete thread %s status=%s reason=%s', + logScope, threadId, status, event.reason, ); } catch (error) { - console.error('Thread hook onComplete: failed to update: %O', error); + console.error('%s: thread hook onComplete failed to update: %O', logScope, error); } }, id: 'thread-completion', diff --git a/apps/server/src/services/toolExecution/serverRuntimes/agentManagement.ts b/apps/server/src/services/toolExecution/serverRuntimes/agentManagement.ts index 5b16924d53..995d4b237c 100644 --- a/apps/server/src/services/toolExecution/serverRuntimes/agentManagement.ts +++ b/apps/server/src/services/toolExecution/serverRuntimes/agentManagement.ts @@ -43,9 +43,9 @@ export const agentManagementRuntime: ServerRuntimeRegistration = { ): Promise => { const { agentId, instruction, taskTitle, timeout } = params; - // Server runtime always uses the task path because there is no - // client-side `registerAfterCompletion` callback available to execute - // synchronous agent calls. + // Server runtime always uses the legacy async invocation path because + // there is no client-side `registerAfterCompletion` callback available + // to execute synchronous agent calls. return { content: `🚀 Triggered async task to call agent "${agentId}"${taskTitle ? `: ${taskTitle}` : ''}`, state: { diff --git a/packages/agent-runtime/src/agents/GeneralChatAgent.ts b/packages/agent-runtime/src/agents/GeneralChatAgent.ts index 0e8764ad92..0388c16e08 100644 --- a/packages/agent-runtime/src/agents/GeneralChatAgent.ts +++ b/packages/agent-runtime/src/agents/GeneralChatAgent.ts @@ -570,13 +570,13 @@ export class GeneralChatAgent implements Agent { const { data, parentMessageId, stop } = context.payload as GeneralAgentCallToolResultPayload; - // Check if this is a sub-agent dispatch request (lobe-agent.callSubAgent - // and similarly-shaped tools emit state.type=execSubAgent* with stop=true - // so the runtime forks a sub-agent here). + // Legacy async agent invocation path. `callAgent({ runAsTask: true })` + // emits state.type=execSubAgent* with stop=true so the runtime can fork + // a background agent run after the tool call is persisted. if (stop && data?.state) { const stateType = data.state.type; - // Server-side sub-agent (single) + // Server-side legacy agent invocation (single) if (stateType === 'execSubAgent') { const { parentMessageId: execParentId, task } = data.state as { parentMessageId: string; @@ -588,7 +588,7 @@ export class GeneralChatAgent implements Agent { }; } - // Server-side sub-agents (multiple) + // Server-side legacy agent invocations (multiple) if (stateType === 'execSubAgents') { const { parentMessageId: execParentId, tasks } = data.state as { parentMessageId: string; diff --git a/packages/builtin-tool-agent-management/src/executor.ts b/packages/builtin-tool-agent-management/src/executor.ts index 5d93e3f722..bcbc554c4e 100644 --- a/packages/builtin-tool-agent-management/src/executor.ts +++ b/packages/builtin-tool-agent-management/src/executor.ts @@ -119,7 +119,7 @@ class AgentManagementExecutor extends BaseExecutor