Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions src/core/multi-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { v4 as uuidv4 } from 'uuid';
import { run } from './engine';
import {
RunConfig,
RunResult,
RunState,
createRunId,
createTraceId
} from './types';

// Helper to create initial run state for an agent
function createState<Ctx>(
agentName: string,
input: string,
context: Ctx
): RunState<Ctx> {
return {
runId: createRunId(uuidv4()),
traceId: createTraceId(uuidv4()),
messages: [{ role: 'user', content: input }],
currentAgentName: agentName,
context,
turnCount: 0
};
}

function outputToString(output: any): string {
return typeof output === 'string' ? output : JSON.stringify(output);
}

/**
* Sequential pipeline: A1 -> A2 -> A3
*/
export async function runSequentialPipeline<Ctx>(
agentNames: readonly string[],
input: string,
context: Ctx,
config: RunConfig<Ctx>
): Promise<RunResult<any>> {
let currentInput = input;
let lastResult: RunResult<any> | undefined;

for (const name of agentNames) {
const state = createState(name, currentInput, context);
const result = await run<Ctx, any>(state, config);
lastResult = result;
if (result.outcome.status === 'error') {
return result;
}
currentInput = outputToString(result.outcome.output);
}

return lastResult!;
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using non-null assertion operator (!) without proper guard. If agentNames is empty, lastResult will be undefined and this will throw a runtime error. Add a check for empty agentNames array at the beginning of the function.

Copilot uses AI. Check for mistakes.
}

/**
* Parallel pipeline: A1 -> (A2, A3) -> A4
*/
export async function runParallelPipeline<Ctx>(
first: string,
parallel: readonly [string, string],
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The parallel parameter is hardcoded to exactly 2 agents. Consider using 'readonly string[]' to allow for more flexible parallel execution with any number of agents.

Copilot uses AI. Check for mistakes.
final: string,
input: string,
context: Ctx,
config: RunConfig<Ctx>
): Promise<RunResult<any>> {
const firstState = createState(first, input, context);
const firstResult = await run<Ctx, any>(firstState, config);
if (firstResult.outcome.status === 'error') {
return firstResult;
}
const intermediate = outputToString(firstResult.outcome.output);

const [name2, name3] = parallel;
const state2 = createState(name2, intermediate, context);
const state3 = createState(name3, intermediate, context);
const [res2, res3] = await Promise.all([
run<Ctx, any>(state2, config),
run<Ctx, any>(state3, config)
]);
if (res2.outcome.status === 'error') {
return res2;
}
if (res3.outcome.status === 'error') {
return res3;
}
const combined = `${outputToString(res2.outcome.output)}\n${outputToString(res3.outcome.output)}`;
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The output combination logic is hardcoded for exactly 2 results. If the parallel parameter is changed to support more agents, this logic will need to be updated accordingly.

Copilot uses AI. Check for mistakes.

const finalState = createState(final, combined, context);
return await run<Ctx, any>(finalState, config);
}

/**
* Coordinator pattern: A1 -> A2 (if condition) -> A3 else A4 -> A5
*/
export async function runCoordinatorPipeline<Ctx>(
agents: { start: string; condition: string; onTrue: string; onFalse: string; end: string },
condition: (outputFromA2: string) => boolean,
input: string,
context: Ctx,
config: RunConfig<Ctx>
): Promise<RunResult<any>> {
const startResult = await run<Ctx, any>(createState(agents.start, input, context), config);
if (startResult.outcome.status === 'error') return startResult;
const condInput = outputToString(startResult.outcome.output);

const condResult = await run<Ctx, any>(createState(agents.condition, condInput, context), config);
if (condResult.outcome.status === 'error') return condResult;
const branchInput = outputToString(condResult.outcome.output);

const nextAgent = condition(branchInput) ? agents.onTrue : agents.onFalse;
const branchResult = await run<Ctx, any>(createState(nextAgent, branchInput, context), config);
if (branchResult.outcome.status === 'error') return branchResult;
const finalInput = outputToString(branchResult.outcome.output);

return await run<Ctx, any>(createState(agents.end, finalInput, context), config);
}

/**
* Parallel redundant pattern: Query -> A1, A2 -> A3
*/
export async function runParallelRedundant<Ctx>(
query: string,
agents: { parallel: [string, string]; evaluator: string },
context: Ctx,
config: RunConfig<Ctx>
): Promise<RunResult<any>> {
const [a1, a2] = agents.parallel;
const state1 = createState(a1, query, context);
const state2 = createState(a2, query, context);

const [res1, res2] = await Promise.all([
run<Ctx, any>(state1, config),
run<Ctx, any>(state2, config)
]);
if (res1.outcome.status === 'error') return res1;
if (res2.outcome.status === 'error') return res2;

const evaluationInput = `Agent ${a1}: ${outputToString(res1.outcome.output)}\nAgent ${a2}: ${outputToString(res2.outcome.output)}`;
const evalState = createState(agents.evaluator, evaluationInput, context);
return await run<Ctx, any>(evalState, config);
}

1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './core/tracing';
export * from './core/errors';
export * from './core/tool-results';
export * from './core/agent-as-tool';
export * from './core/multi-agent';

export * from './providers/model';
// export * from './providers/mcp'; // Commented out for test compatibility
Expand Down