ADHD Engine Architecture: Understanding the Diverge→Score→Cluster→Deepen Loop
The ADHD engine implements a four-stage creative pipeline—diverge, score, cluster, deepen—inside src/engine.ts to generate, evaluate, and expand non-obvious ideas using parallel LLM calls and weighted scoring.
The ADHD engine is a TypeScript-based creative engine that transforms vague problem statements into structured, high-quality idea clusters. According to the UditAkhourii/adhd repository, the architecture separates wild generation from critical evaluation, enabling the run() function to orchestrate a complete creative-to-critical loop in a single execution.
The Four-Stage Pipeline
The core run() function in src/engine.ts orchestrates a strict sequence: diverge (generate), score (evaluate), cluster (group), and deepen (expand). Each stage corresponds to a specific LLM prompt and validation schema.
Stage 1: Divergence (Parallel Generation)
The diverge stage generates raw ideas across multiple cognitive frames simultaneously. The selectFrames helper (defined in src/frames.ts) selects N frames—biased toward "code" tags when codeMode is enabled—and always injects at least one "wild" frame to force unconventional thinking.
For each selected frame, divergeBranch constructs a prompt using DIVERGE_SYSTEM, calls the LLM via callLLM, and parses the JSON array of ideas. Each idea is tagged with its frame ID for traceability. All branches execute concurrently via p-limit with a default concurrency of 4 to prevent rate-limiting while maximizing throughput.
// From src/engine.ts - parallel divergence across frames
const branches = await Promise.all(
frames.map(frame => divergeBranch(problem, frame, ideasPerFrame))
);
Stage 2: Scoring (Convergent Evaluation)
Once raw ideas are collected, the score stage critically evaluates every leaf on three dimensions: novelty, viability, and fit. The scoreIdeas function sends the entire idea set to the LLM using SCORE_SYSTEM and parses the results against ScoreRowSchema.
The engine computes a weighted total score using the formula: novelty * 0.35 + viability * 0.4 + fit * 0.25. This weighting explicitly favors non-obvious but shippable ideas, storing results in a Map<string, Score> keyed by idea UUID.
Stage 3: Clustering (Thematic Grouping)
The cluster stage shapes the solution space by grouping the top-scored ideas into 3–6 thematic clusters. The clusterIdeas function transmits the problem statement and idea list to the LLM with CLUSTER_SYSTEM, expecting a JSON array of clusters with label and ideaIds properties.
This step exposes the "shape" of the solution space rather than presenting isolated leaves, making it easier to identify dominant themes and gaps.
Stage 4: Deepening (Focus and Expansion)
In the deepen stage, the engine selects the top-K ideas (sorted by total score) and expands each into a rich sketch plus child ideas. The deepenIdea function processes one idea at a time, returning a structured object containing:
- A detailed sketch (4–8 sentences)
- An array of child ideas with increased
depthand aparentIdreferencing the original
// From src/engine.ts - deepening top-K ideas
const deepened = await Promise.all(
shortlist.slice(0, topK).map(idea => deepenIdea(idea, problem))
);
Optionally, the engine generates a provocation from the highest-novelty leaf (ignoring traps) to give the user a final creative nudge.
Execution Flow and Concurrency
The loop executes in src/engine.ts with clear separation of concerns: divergence and deepening run under p-limit concurrency controls, while scoring and clustering operate on the full dataset. The run() function optionally begins with reframeProblem to strip incidental anchors from the input before entering the main pipeline.
All LLM interactions flow through callLLM in src/llm.js and are validated against Zod schemas (DivergeRowSchema, ScoreRowSchema, ClusterSchema, DeepenSchema). Failures fall back to safe defaults—empty arrays or placeholder sketches—ensuring the pipeline remains robust against malformed JSON.
Data Structures and Type Safety
The engine relies on strict TypeScript definitions in src/types.ts. Key interfaces include:
Idea: Containsid,text,frameId,depth, and optionalparentIdScore: Tracksnovelty,viability,fit, and computedtotalRunResult: Returnsbranches,clusters,shortlist,deepened, andprovocation
Implementation Example
To invoke the engine, import run from src/engine.ts and configure the options:
import { run } from "./engine";
const result = await run({
problem: "Add real-time collaborative editing to our note-app.",
framesPerRun: 5, // 5 divergent frames
ideasPerFrame: 6, // 6 ideas per frame
topK: 3, // Deepen top 3 ideas
concurrency: 4, // Parallel LLM calls
codeMode: true, // Bias toward code-related frames
});
console.log("Best pick:", result.nonObviousPick?.text);
console.log("Deepened sketches:", result.deepened.map(d => d.sketch));
Summary
- The diverge stage uses
selectFramesanddivergeBranchto generate parallel ideas across multiple perspectives, always including a "wild" frame. - The score stage evaluates ideas on novelty (35%), viability (40%), and fit (25%), storing weighted totals in a typed Map.
- The cluster stage groups ideas into 3–6 thematic clusters using
clusterIdeasto reveal solution-space patterns. - The deepen stage expands the top-K scored ideas into detailed sketches and child ideas with parent-child relationships.
- Concurrency is managed via
p-limit(default 4), and all LLM outputs are validated against Zod schemas for type safety.
Frequently Asked Questions
What is the purpose of the frame system in ADHD?
Frames are cognitive perspectives defined in src/frames.ts that force the LLM into specific corners during divergence. When codeMode is enabled, the selectFrames function biases selection toward code-related tags while guaranteeing at least one "wild" frame appears in every run to ensure unconventional outputs.
How does the scoring algorithm weight different criteria?
The scoreIdeas function computes a total score using fixed weights: viability at 40%, novelty at 35%, and fit at 25%. This prioritizes ideas that are technically feasible and original over those that merely match initial expectations.
What happens during the deepening stage?
The deepenIdea function takes each top-K idea and prompts the LLM to write a 4–8 sentence sketch plus generate child ideas. These children become full Idea objects with incremented depth and a parentId linking back to the source, creating a hierarchical tree of concept variations.
How does the engine handle concurrent LLM calls?
Both the divergence and deepening stages use p-limit to restrict concurrent LLM calls (defaulting to 4 simultaneous requests). This prevents API rate limits while still exploiting parallelism across frames or top-K ideas, with all async operations wrapped in Promise.all for efficiency.
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 →