How to Debug Failed Branches Using frameId and ideaId Tracking in ADHD
ADHD emits frameId and ideaId identifiers through every phase of its divergence-and-deepening engine, allowing you to trace exact failure points in the event stream without guessing which frame or idea caused the pipeline to collapse.
When the ADHD code-generation pipeline drops a branch or returns empty results, guessing which prompt failed wastes engineering time. The repository solves this by propagating stable frameId and ideaId values through every divergence, scoring, and deepening step. This guide shows you how to debug failed branches using frameId and ideaId tracking so you can pinpoint malformed LLM outputs and zero-idea frames by reading the event stream and the source files in src/engine.ts and src/render.ts.
How Identifiers Are Assigned in the Source Code
Frame Definitions in src/frames.ts
Each frame carries a stable id field that becomes the frameId for every branch spawned from it. In src/frames.ts (lines 5‑23), the frame objects are declared with unique identifiers the engine references during the diverge phase.
Branch and Idea Objects in src/engine.ts and src/types.ts
During the diverge phase, src/engine.ts (lines 352‑364) returns each branch as an object containing { frameId, ideas }. Every idea inside that branch receives a generated id and inherits the parent frameId. The type definitions in src/types.ts (lines 3‑22) enforce these fields, ensuring every Idea and Branch payload remains traceable.
Event Emission and CLI Visibility
The engine fires lifecycle events—frame:start, frame:done, deepen:start, and deepen:done—from src/engine.ts (lines 356‑361) that embed these IDs. On the consumption side, src/render.ts (lines 35‑44) formats branch output for the CLI so IDs remain visible when a branch fails, while src/cli.ts (lines 124‑126) writes concise status lines to process.stderr for live debugging.
Common Failure Points and How IDs Expose Them
Diverge Phase: Zero-Ideas or Malformed JSON
If the LLM call for a frame returns no ideas or unparseable JSON, the frame:done event reports count = 0. The embedded frameId tells you exactly which frame’s prompt caused the problem, so you can inspect the raw LLM response for that specific frame.
Scoring Phase: Missing or Unparseable Idea Fields
When an idea cannot be parsed or is missing required fields, the deepen:start event still surfaces the ideaId. You can locate the offending idea object in the raw LLM response and compare it against the expected shape defined in src/types.ts.
Deepen Phase: Empty Sub-Ideas
A follow-up LLM call that fails to produce sub-ideas results in a deepen:done event containing the same ideaId but a zero-length childIdeas array. This signal means the seed idea identified by that ideaId could not be expanded, and you should inspect its idea.text and the prompt sent to the model.
Rendering Phase: Omitted Branches
If the final report omits a branch, src/render.ts reveals which frameId (and optionally ideaId) were dropped. Comparing the rendered output against the earlier event log lets you verify whether the omission happened during generation or formatting.
Step-by-Step Debugging Workflow
Enable Verbose Event Logging
The default CLI already prints events, but you should run with the --verbose flag to ensure every frame:start and deepen:done line is emitted:
adhd "my problem" --frames 5 --ideas 6 --verbose
Capture the Event Stream
Pipe stderr and stdout to a file so you can grep the sequence later:
adhd "my problem" … 2>&1 | tee debug.log
Search the Log for Failure Signatures
Look for lines that indicate zero output or deepening failures:
frame:done 0 ideas (hardware-eyes)
deepen:start a1b2c3 :: some promising idea
deepen:done a1b2c3 (no child ideas)
frameId = hardware-eyestells you which frame produced zero ideas.ideaId = a1b2c3tells you which seed idea produced no children.
Re-Run the Problematic Piece in Isolation
You can call the low-level functions directly from a Node REPL or a small script using the stored IDs. Import divergeBranch, deepenIdea, and the FRAMES array to target the exact failure:
import { divergeBranch, deepenIdea } from "./src/engine";
import { FRAMES } from "./src/frames";
// Re-run the failed frame:
const frame = FRAMES.find(f => f.id === "hardware-eyes")!;
const branch = await divergeBranch(problem, context, frame, 6, model);
console.log(branch); // inspect raw LLM output
// Re-run the failing idea:
const idea = branch.ideas.find(i => i.id === "a1b2c3")!;
const deep = await deepenIdea(problem, idea, [], model);
console.log(deep);
Inspect Raw LLM Prompts and Responses
The frame.prompt defined in src/frames.ts and the generated idea.text give direct clues about why the model returned nothing or malformed JSON. Compare the prompt template against the actual LLM response captured in your log or REPL output.
Adjust Parameters and Iterate
After identifying the faulty frameId or ideaId, tweak the prompt, increase ideasPerFrame, or adjust the temperature. Re-run the engine and verify that the event log now shows non-zero counts for all frameIds and successful deepen:done entries.
Automating Failure Detection
A post-run script can flag any frame that produced zero ideas or any idea that failed to deepen:
import * as fs from "fs";
const log = fs.readFileSync("debug.log", "utf-8").split("\n");
log.forEach(line => {
if (line.includes("frame:done") && /0 ideas/.test(line)) {
const id = line.match(/\(([^)]+)\)/)![1];
console.warn(`⚠️ Frame "${id}" produced no ideas`);
}
if (line.includes("deepen:done") && /no child ideas/.test(line)) {
const id = line.match(/ideaId: (\w+)/)![1];
console.warn(`⚠️ Idea "${id}" could not be deepened`);
}
});
Running this script after each execution highlights exactly which frameId or ideaId needs attention before you dive into manual log inspection.
Summary
frameIdoriginates from the stableidfield insrc/frames.tsand tags every branch during the diverge phase insrc/engine.ts.ideaIdis generated per idea and inherited from the parentframeId, with shapes enforced insrc/types.ts.- The engine emits
frame:start,frame:done,deepen:start, anddeepen:doneevents that carry these IDs, consumed bysrc/cli.tsand rendered bysrc/render.ts. - A
frame:doneevent showing0 ideasmaps directly to a failedframeId. - A
deepen:doneevent showingno child ideasmaps directly to a failedideaId. - You can replay isolated failures by importing
divergeBranchanddeepenIdeafromsrc/engine.tsand targeting the exact IDs.
Frequently Asked Questions
Where are frameId and ideaId defined in the ADHD source code?
The frameId values come from the stable id fields declared in src/frames.ts (lines 5‑23). The ideaId values are generated during branch creation in src/engine.ts (lines 352‑364) and enforced by the type definitions in src/types.ts (lines 3‑22).
How do I know if a specific frame failed to produce ideas?
Listen for the frame:done event in the output stream. If the event reports 0 ideas and includes a frameId such as hardware-eyes, that frame’s LLM call returned no parseable ideas. You can then inspect the prompt for that exact frameId in src/frames.ts.
Can I re-run a single failed branch without executing the entire engine?
Yes. Import divergeBranch and deepenIdea from src/engine.ts, look up the specific frame from FRAMES in src/frames.ts, and call the functions with the problematic frameId or ideaId. This lets you debug the raw LLM response in isolation without rerunning the full pipeline.
What does a zero-length childIdeas array indicate during the deepen phase?
In the deepen:done event, a zero-length childIdeas array tied to a specific ideaId means the follow-up LLM call could not generate sub-ideas for that seed idea. You should inspect the idea.text and the deepening prompt to determine why the model returned an empty result.
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 →