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 — criticModel omitted; both phases use identical LLM backend
  • Different models — criticModel specified; 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 the run() execution flow
  • src/frames.ts — Cognitive frame definitions for the generator phase
  • src/llm.ts — callLLM abstraction for model invocation
  • src/types.ts — TypeScript interfaces for models, branches, scores, and clusters

Summary

  • Mechanical separation — The split is enforced through distinct system prompts (DIVERGE_SYSTEM vs. SCORE_SYSTEM/CLUSTER_SYSTEM) rather than architectural components
  • Model flexibility — Optional criticModel parameter in src/engine.ts line 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.all for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →