From 8038d52495f0f759f4f7874fd77901cb13b6f994 Mon Sep 17 00:00:00 2001 From: akhisud3195 Date: Tue, 8 Jul 2025 18:03:24 +0530 Subject: [PATCH] Add separate stack for start of agent computation --- apps/rowboat/app/lib/agents.ts | 115 ++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/apps/rowboat/app/lib/agents.ts b/apps/rowboat/app/lib/agents.ts index e6cb4ff8..bb0d085e 100644 --- a/apps/rowboat/app/lib/agents.ts +++ b/apps/rowboat/app/lib/agents.ts @@ -399,17 +399,33 @@ function convertMsgsInput(messages: z.infer[]): AgentInputItem[] // Helper to determine the next agent name based on control settings function getStartOfTurnAgentName( logger: PrefixLogger, - stack: string[], + messages: z.infer[], agentConfig: Record>, workflow: z.infer, ): string { - logger = logger.child(`getStartOfTurnAgentName`); - logger.log(`stack: ${JSON.stringify(stack)}`); - + function createAgentCallStack(messages: z.infer[]): string[] { + const stack: string[] = []; + for (const msg of messages) { + if (msg.role === 'assistant' && msg.agentName) { + // skip duplicate entries + if (stack.length > 0 && stack[stack.length - 1] === msg.agentName) { + continue; + } + // add to stack + stack.push(msg.agentName); + } + } + return stack; + } + + logger = logger.child(`getStartOfTurnAgentName`); + const startAgentStack = createAgentCallStack(messages); + logger.log(`startAgentStack: ${JSON.stringify(startAgentStack)}`); + // if control type is retain, return last agent - const lastAgentName = stack.pop() || workflow.startAgent; - logger.log(`popped agent from stack: ${lastAgentName} || reason: last agent logic`); + const lastAgentName = startAgentStack.pop() || workflow.startAgent; + logger.log(`setting last agent name initially to: ${lastAgentName}`); const lastAgentConfig = agentConfig[lastAgentName]; if (!lastAgentConfig) { logger.log(`last agent ${lastAgentName} not found in agent config, returning start agent: ${workflow.startAgent}`); @@ -420,8 +436,12 @@ function getStartOfTurnAgentName( logger.log(`last agent ${lastAgentName} control type is retain, returning last agent: ${lastAgentName}`); return lastAgentName; case 'relinquish_to_parent': - const parentAgentName = stack.pop() || workflow.startAgent; - logger.log(`popped agent from stack: ${lastAgentName} || reason: relinquish to parent triggered`); + const parentAgentName = startAgentStack.pop() || workflow.startAgent; + if (startAgentStack.length > 0) { + logger.log(`popped agent from stack: ${lastAgentName} || reason: relinquish to parent triggered`); + } else { + logger.log(`using start agent: ${lastAgentName} || reason: empty stack`); + } logger.log(`last agent ${lastAgentName} control type is relinquish_to_parent, returning most recent parent: ${parentAgentName}`); return parentAgentName; case 'relinquish_to_start': @@ -591,9 +611,11 @@ function createAgents( tools: Record, projectTools: z.infer[], promptConfig: Record>, -): { agents: Record, mentions: Record[]> } { +): { agents: Record, mentions: Record[]>, originalInstructions: Record, originalHandoffs: Record } { const agents: Record = {}; const mentions: Record[]> = {}; + const originalInstructions: Record = {}; + const originalHandoffs: Record = {}; // create agents for (const [agentName, config] of Object.entries(agentConfig)) { @@ -607,17 +629,20 @@ function createAgents( ); agents[agentName] = agent; mentions[agentName] = entities; - logger.log(`created agent: ${agentName}`); + originalInstructions[agentName] = agent.instructions as string; + // handoffs will be set after all agents are created } // set handoffs for (const [agentName, agent] of Object.entries(agents)) { const connectedAgentNames = (mentions[agentName] || []).filter(e => e.type === 'agent').map(e => e.name); + // Only store Agent objects in handoffs (filter out Handoff if present) agent.handoffs = connectedAgentNames.map(e => agents[e]).filter(Boolean) as Agent[]; - logger.log(`set handoffs for ${agentName}: ${connectedAgentNames.join(',')}`); + originalHandoffs[agentName] = agent.handoffs.filter(h => h instanceof Agent); + logger.log(`set handoffs for ${agentName}: ${JSON.stringify(connectedAgentNames)}`); } - return { agents, mentions }; + return { agents, mentions, originalInstructions, originalHandoffs }; } // Helper to get give up control instructions for child agents @@ -638,10 +663,42 @@ function getGiveUpControlInstructions( const { TRANSFER_GIVE_UP_CONTROL_INSTRUCTIONS } = require('./agent_instructions'); dynamicInstructions = dynamicInstructions + '\n\n' + TRANSFER_GIVE_UP_CONTROL_INSTRUCTIONS(parentBlock); // For tracking - logger.log(`[inject] Added give up control instructions for ${agent.name} with parent ${parentAgentName}`); + logger.log(`Added give up control instructions for ${agent.name} with parent ${parentAgentName}`); return dynamicInstructions; } +// Helper to dynamically inject give up control instructions and handoff +function maybeInjectGiveUpControlInstructions( + agents: Record, + agentConfig: Record>, + childAgentName: string, + parentAgentName: string, + logger: PrefixLogger, + originalInstructions: Record, + originalHandoffs: Record +) { + // Reset to original before injecting + agents[childAgentName].instructions = originalInstructions[childAgentName]; + agents[childAgentName].handoffs = [...originalHandoffs[childAgentName]]; + + const agentConfigObj = agentConfig[childAgentName]; + const isInternal = agentConfigObj?.outputVisibility === 'internal'; + const isRetain = agentConfigObj?.controlType === 'retain'; + const injectLogger = logger.child(`inject`); + injectLogger.log(`isInternal: ${isInternal}`); + injectLogger.log(`isRetain: ${isRetain}`); + if (!isInternal && isRetain) { + // inject give up control instructions + agents[childAgentName].instructions = getGiveUpControlInstructions(agents[childAgentName], parentAgentName, injectLogger); + injectLogger.log(`Added give up control instructions for ${childAgentName} with parent ${parentAgentName}`); + // add the parent agent to the handoff list if not already present + if (!agents[childAgentName].handoffs.includes(agents[parentAgentName])) { + agents[childAgentName].handoffs.push(agents[parentAgentName]); + } + injectLogger.log(`Added parent ${parentAgentName} to handoffs for ${childAgentName}`); + } +} + // Main function to stream an agentic response // using OpenAI Agents SDK export async function* streamResponse( @@ -668,8 +725,6 @@ export async function* streamResponse( // create map of agent, tool and prompt configs const { agentConfig, toolConfig, promptConfig } = mapConfig(workflow, projectTools); - // create agent call stack from input messages - TODO - remove this - // const stack = createAgentCallStack(messages); const stack: string[] = []; logger.log(`initialized stack: ${JSON.stringify(stack)}`); @@ -678,7 +733,7 @@ export async function* streamResponse( const tools = createTools(logger, workflow, toolConfig); // create agents - const { agents } = createAgents(logger, workflow, agentConfig, tools, projectTools, promptConfig); + const { agents, originalInstructions, originalHandoffs } = createAgents(logger, workflow, agentConfig, tools, projectTools, promptConfig); // track agent to agent calls const transferCounter = new AgentTransferCounter(); @@ -687,7 +742,7 @@ export async function* streamResponse( const usageTracker = new UsageTracker(); // get next agent name - let agentName = getStartOfTurnAgentName(logger, stack, agentConfig, workflow); + let agentName = getStartOfTurnAgentName(logger, messages, agentConfig, workflow); // set up initial state for loop logger.log('@@ starting agent turn @@'); @@ -712,19 +767,8 @@ export async function* streamResponse( } const agent: Agent = agents[agentName]!; - // --- DYNAMICALLY INJECT GIVE UP CONTROL INSTRUCTIONS FOR CHILD AGENTS --- - // Only inject for non-internal agents with controlType 'retain' - const agentConfigObj = agentConfig[agentName]; - const isInternal = agentConfigObj?.outputVisibility === 'internal'; - const isRetain = agentConfigObj?.controlType === 'retain'; - loopLogger.log(`isInternal: ${isInternal}`); - loopLogger.log(`isRetain: ${isRetain}`); - loopLogger.log(`stack.length: ${stack.length}`); - if (!isInternal && isRetain && stack.length > 0) { - const parentAgentName = stack[stack.length - 1]; - agents[agentName].instructions = getGiveUpControlInstructions(agent, parentAgentName, loopLogger); - } - // --- END DYNAMIC INJECTION --- + // Dynamically inject give up control instructions for child agents and add parent to handoffs + // (No longer called here; will be called at handoff) // convert messages to agents sdk compatible input const inputs = convertMsgsInput(turnMsgs); @@ -789,6 +833,17 @@ export async function* streamResponse( break; } + // inject give up control instructions if needed (parent handing off to child) + maybeInjectGiveUpControlInstructions( + agents, + agentConfig, + event.item.targetAgent.name, // child + agentName, // parent + eventLogger, + originalInstructions, + originalHandoffs + ); + // emit transfer tool call invocation const [transferStart, transferComplete] = createTransferEvents(agentName, event.item.targetAgent.name);