How the Generator-Critic Split Is Implemented in ADHD: A Deep Dive Into the Source Code
The generator-critic split in ADHD is implemented mechanically through two distinct LLM system prompts (DIVERGE_SYSTEM for generation, SCORE_SYSTEM and CLUSTER_SYSTEM for evaluation) and optional separate model instances selected at runtime in src/engine.ts.
ADHD (Attention-Driven Hyperdivergence) is an open-source TypeScript framework for structured brainstorming that enforces cognitive separation between creative generation and critical evaluation. This article examines exactly how the generator-critic split works at the code level, tracing the implementation through model selection, prompt engineering, and execution orchestration in the UditAkhourii/adhd repository.
Model Selection: Configuring Separate Generator and Critic Backends
The generator-critic split begins with model flexibility. In src/engine.ts, the run function accepts both a model parameter (for generation) and an optional criticModel parameter (for evaluation):
// src/engine.ts, lines 27-30
const critic = criticModel ?? model; // ← critic may be a different model
This single line enables three distinct configurations:
- Same model —
criticModelomitted; both phases use identical LLM backend - Different models —
criticModelspecified; typically a more capable model for evaluation - Same model, different prompts — model identical, but system prompts enforce role separation
The fallback pattern (criticModel ?? model) ensures backward compatibility while allowing power users to optimize each phase independently.
Generator Phase: Enforcing Divergence Through DIVERGE_SYSTEM
The generator phase isolates divergent thinking through a strict system prompt. In src/engine.ts, the DIVERGE_SYSTEM constant constrains the LLM to pure idea generation:
// src/engine.ts, lines 61-69
const DIVERGE_SYSTEM = `You are in DIVERGENT mode. You are a generator, not a critic.
Rules:
- Output a JSON array only. No prose before/after.
- Generate the requested number of distinct ideas.
- STRICT RULE: Do NOT evaluate, rank, or critique ideas.
- Focus: breadth, novelty, and variety.`;
Key mechanical enforcements in this prompt:
- Role identity — explicit "generator, not a critic" declaration
- Output format — JSON array constraint prevents evaluative commentary
- Prohibited behaviors — explicit ban on ranking, scoring, or quality judgments
Each frame calls callLLM with this prompt during the divergeBranch execution, creating multiple parallel generation streams.
Critic Phase: Parallel Evaluation With SCORE_SYSTEM and CLUSTER_SYSTEM
After divergence completes, the critic executes two passes in parallel, each with distinct system prompts designed for convergent thinking:
Scoring Pass (SCORE_SYSTEM)
// src/engine.ts, lines 71-88
const SCORE_SYSTEM = `You are in CONVERGENT mode. You are now the critic.
Score each idea on three axes 0-10:
1. relevance (to the core problem)
2. novelty (originality, non-obviousness)
3. feasibility (can this be built/deployed reasonably?)`;
Clustering Pass (CLUSTER_SYSTEM)
// src/engine.ts, lines 91-95
const CLUSTER_SYSTEM = `You group ideas into 3-6 clusters by their UNDERLYING ANGLE
or conceptual approach. Name each cluster with a short evocative title.`;
Both passes use the critic model instance (potentially different from the generator) and explicitly invoke evaluative cognitive modes.
Execution Flow: Orchestrating the Split in run()
The generator-critic split is mechanically enforced through temporal sequencing and parallel execution patterns in src/engine.ts:
// src/engine.ts, lines 52-63 and 66-70
// Phase 1: DIVERGE (generator)
const branches = await Promise.all(
frames.map(frame => divergeBranch(problem, frame, model, ideasPerFrame))
);
// Phase 2: CONVERGE (critic) — parallel scoring and clustering
const [scoreMap, clusters] = await Promise.all([
scoreIdeas(problem, allIdeas, critic), // uses SCORE_SYSTEM
clusterIdeas(problem, allIdeas, critic), // uses CLUSTER_SYSTEM
]);
This architecture guarantees no feedback loop from critic to generator during the initial burst. The generator completes fully before any evaluation occurs—preventing premature convergence that would limit creative exploration.
Practical Implementation: Running With Separate Models
Configure the generator-critic split explicitly by specifying both model parameters:
import { run } from "./engine";
await run({
problem: "Design a tool for remote pair-programming",
model: "gpt-4o-mini", // generator: fast, cost-effective
criticModel: "gpt-4o", // critic: stronger reasoning for evaluation
framesPerRun: 4,
ideasPerFrame: 5,
});
Inspect the role assignment in the engine source:
// Inside src/engine.ts execution
const critic = criticModel ?? model;
// Generator role
await callLLM({ model, systemPrompt: DIVERGE_SYSTEM, userPrompt });
// Critic roles
await callLLM({ model: critic, systemPrompt: SCORE_SYSTEM, ... });
await callLLM({ model: critic, systemPrompt: CLUSTER_SYSTEM, ... });
Key Source Files
src/engine.ts— Core orchestration: model selection, prompt constants (DIVERGE_SYSTEM,SCORE_SYSTEM,CLUSTER_SYSTEM), and therun()execution flowsrc/frames.ts— Cognitive frame definitions for the generator phasesrc/llm.ts—callLLMabstraction for model invocationsrc/types.ts— TypeScript interfaces for models, branches, scores, and clusters
Summary
- Mechanical separation — The split is enforced through distinct system prompts (
DIVERGE_SYSTEMvs.SCORE_SYSTEM/CLUSTER_SYSTEM) rather than architectural components - Model flexibility — Optional
criticModelparameter insrc/engine.tsline 27 enables backend specialization without code changes - Temporal isolation — Generator completes entirely before critic evaluation begins, preventing cognitive contamination
- Parallel critique — Scoring and clustering execute simultaneously via
Promise.allfor efficiency - Prompt-driven roles — System instructions, not fine-tuning or separate code paths, define generator and critic behaviors
Frequently Asked Questions
Can I use the same model for both generator and critic phases?
Yes. The criticModel parameter is optional; if omitted, the framework falls back to the generator model via const critic = criticModel ?? model in src/engine.ts. The generator-critic split is primarily enforced through prompt engineering rather than model separation.
Why does the critic run scoring and clustering in parallel?
Parallel execution via Promise.all([scoreIdeas(...), clusterIdeas(...)]) reduces latency without compromising quality. Both operations are read-only evaluations of the completed generator output, so no sequential dependency exists between them.
How does DIVERGE_SYSTEM prevent the model from self-critiquing?
The prompt contains explicit constraints: "STRICT RULE: Do NOT evaluate, rank, or critique ideas" plus format restrictions (JSON array only) that make evaluative commentary structurally invalid. This is prompt-level enforcement rather than architectural blocking.
What happens if I specify a weaker model as critic?
The framework will execute normally, but evaluation quality may degrade. The typical pattern uses equivalent or stronger models for the critic phase (e.g., gpt-4o-mini generator, gpt-4o critic) since judgment tasks often require more capability than generation tasks.
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 →