workflows/reduce

Source: src/workflows/reduce.ts

reduce: N → 1. One agent synthesises the results of a fan-out.

formatBranches

function

export function formatBranches(results: readonly Result[], input: string): string { /* … */ }

The default rendering: the instruction, then one numbered section per branch, failures kept and marked.

reduce

function

export async function reduce(options: ReduceOptions): Promise<WorkflowResult> { /* … */ }

Hands every branch to one agent and asks it for a single answer.

Failed branches are shown, not dropped. A synthesis built from six branches when two of them crashed, with nothing saying so, is a confident lie - and it is the caller who is then unable to tell a thin answer from a thin body of evidence. The reducer sees the failures, labelled, and can say its coverage was incomplete. Pass only the successes if that is really what you want: filtering an array needs no option.

The returned Result is the synthesis itself; steps holds the branches it was given followed by that synthesis, so the usage of the whole N→1 is sumUsage(result.steps.map(s => s.usage), wallMs).

Lifetime is passed down but cannot change the shape of this combinator: one agent, one turn, one spawn either way. It only matters here as an option a caller may already be threading through a larger workflow.

ReduceOptions

type

export type ReduceOptions = WorkflowOptions & {
	/** The agent doing the synthesis. */
	agent: Agent;
	/** What is being synthesised - typically a {@link fanOut}'s results. */
	results: Result[];
	/** The instruction, placed **before** the branches. */
	input: string;
	/**
	 * Turns the branches into the prompt the reducer receives.
	 *
	 * Defaults to {@link formatBranches}. Override it when the branches are not
	 * prose - a list of files, a diff - and the default headings would get in
	 * the way.
	 */
	format?: (results: readonly Result[], input: string) => string;
};

The synthesiser, the branches it folds, and the instruction that frames them.