What Happens When a Parallel Branch Fails During the Diverge Phase in ADHD: Error Handling Explained
When a parallel branch fails during the diverge phase in ADHD, the engine catches the parsing error in divergeBranch and returns an empty ideas array for that specific cognitive frame, allowing the pipeline to continue uninterrupted with the remaining successful branches.
The ADHD engine orchestrates a multi-phase cognitive reasoning pipeline where the diverge phase fans out parallel LLM invocations across distinct cognitive frames defined in src/frames.ts. Understanding how the system handles individual branch failures is critical for building resilient AI workflows, as the implementation in src/engine.ts demonstrates a graceful degradation pattern rather than a hard failure model.
The Diverge Phase and Parallel Branch Execution
During the diverge phase, ADHD instantiates multiple parallel branches—each representing a different cognitive frame stored in src/frames.ts. The engine invokes the LLM independently for each branch, with no cross-branch visibility, to generate diverse ideas for the input problem. These branches execute concurrently under a p-limit concurrency guard to manage API rate limits and resource utilization.
The core branching logic resides in the divergeBranch helper function within src/engine.ts. Each branch receives a specific frame, constructs a tailored prompt, and awaits the LLM response. This architecture assumes that individual branch failures are inevitable in distributed LLM operations and must not compromise the entire pipeline.
How the Engine Handles Branch Failures
When a branch encounters an error—typically a JSON parsing failure when the LLM response does not conform to the expected DivergeRowSchema—the divergeBranch function implements a defensive catch block that isolates the failure:
// src/engine.ts – divergeBranch
async function divergeBranch(
problem: string,
context: string | undefined,
frame: Frame,
ideasPerFrame: number,
model: string | undefined,
): Promise<Branch> {
const raw = await callLLM({
model,
systemPrompt: DIVERGE_SYSTEM,
userPrompt: /* constructed prompt */,
});
let rows: z.infer<typeof DivergeRowSchema>;
try {
rows = parseJSON(raw, DivergeRowSchema);
} catch {
// **Failure path** – produce no ideas for this branch
return { frameId: frame.id, ideas: [] };
}
const ideas: Idea[] = rows.map((r) => ({
id: randomUUID(),
frameId: frame.id,
text: r.text,
rationale: r.rationale,
depth: 0,
}));
return { frameId: frame.id, ideas };
}
The failure path explicitly returns a Branch object containing the frameId but an empty ideas array. This design ensures type consistency while signaling that the particular cognitive frame produced no valid output.
Consequences of a Failed Branch
When parseJSON throws an exception and the branch returns empty, the following consequences propagate through the system:
-
Zero contribution from the failed frame. The specific cognitive frame contributes no ideas to the collective pool.
-
Pipeline continuity is preserved. The
runmethod insrc/engine.tscollects all branches viaPromise.all, aggregating results regardless of individual branch success:// src/engine.ts – divergence loop const branches = await Promise.all( frames.map((f) => limit(async () => { const b = await divergeBranch(divergeProblem, context, f, ideasPerFrame, model); return b; // May be empty if the branch failed }) ) ); -
Downstream phases operate on available data. The scoring, clustering, and deepening phases process only the ideas gathered from successful branches, as defined by the
BranchandIdeatypes insrc/types.ts.
Impact on Downstream Phases
The failure handling in the diverge phase creates a resilient foundation for subsequent operations. Since the allIdeas array (constructed by flattening successful branch outputs) simply excludes the empty arrays from failed branches, the scoring and clustering algorithms remain unaware of the missing frame. This approach prioritizes availability over completeness, ensuring that partial cognitive coverage is delivered rather than failing the entire request.
The type definitions in src/types.ts enforce this contract by requiring every branch to return an array of Idea objects, even if that array is empty. Consequently, the clustering logic in later phases can safely assume array structures without null checks for individual branch results.
Summary
- Graceful degradation: Failed branches return
{ frameId, ideas: [] }rather than throwing fatal errors, as implemented insrc/engine.ts. - Pipeline continuation: The
Promise.allaggregation in the divergence loop proceeds with successful branches even when individual frames fail. - No downstream impact: Scoring and clustering operate only on the subset of ideas successfully generated by valid branches.
- Source locations: Core logic resides in
src/engine.ts(divergeBranchfunction), with type definitions insrc/types.tsand frame configurations insrc/frames.ts.
Frequently Asked Questions
Does a failed branch stop the entire ADHD pipeline?
No. According to the src/engine.ts implementation, a failed branch is caught within the divergeBranch helper and returns an empty ideas array. The parent run function collects results via Promise.all, which waits for all branches—including failed ones—to settle before proceeding to the scoring phase.
What triggers the empty ideas fallback in the diverge phase?
The empty array fallback triggers when parseJSON(raw, DivergeRowSchema) throws an exception, typically because the LLM returned malformed JSON, unexpected schema shapes, or content that violates the Zod schema validation. Any parsing or validation error results in the catch block executing return { frameId: frame.id, ideas: [] }.
How does ADHD ensure other branches complete if one fails?
ADHD wraps each branch invocation in a p-limit concurrency guard and uses Promise.all to await all promises simultaneously. Since each divergeBranch call handles its own errors internally and never rejects, Promise.all always resolves successfully, allowing the engine to flatten all results—empty or populated—into the final ideas collection.
Are there retries for failed branches in the current implementation?
The current implementation in src/engine.ts does not implement automatic retries within the divergeBranch function. Upon catching a parsing error, the function immediately returns an empty array. Users seeking retry logic would need to implement it at the callLLM level or wrap the engine invocation in external orchestration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →