# How the `onEvent` Callback Streams Progress Through ADHD Phases

> Learn how the onEvent callback streams progress through ADHD phases like reframe, diverge, score, cluster, and deepen in real-time from the tree-of-thought pipeline.

- Repository: [Udit Akhouri/adhd](https://github.com/UditAkhourii/adhd)
- Tags: deep-dive
- Published: 2026-08-19

---

**The ADHD engine's `onEvent` callback emits a typed `RunEvent` at every major phase transition—reframe, diverge, score, cluster, and deepen—allowing callers to stream real-time progress from the tree-of-thought pipeline.**

The open-source `UditAkhourii/adhd` repository implements a tree-of-thought reasoning skill that breaks complex problems into distinct cognitive phases. By supplying an `onEvent` callback through `RunOptions`, consumers of the engine receive granular, real-time updates as the system moves from reframing through deepening. Understanding how this callback streams progress through ADHD phases is essential for building responsive CLIs, dashboards, and automated logging around the engine.

## The Four Phases of the ADHD Tree-of-Thought Workflow

The engine orchestrates its work across four logical stages:

- **Reframe (Phase 0)** – Optionally strips incidental anchors from the problem statement.
- **Diverge (Phase 1)** – Generates ideas in parallel under different cognitive frames.
- **Score + Cluster (Phase 2)** – Evaluates each idea and groups them into clusters.
- **Deepen (Phase 3)** – Expands the top-K ideas in a focused pass.

At each transition, the `run` function in [`src/engine.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/engine.ts) dispatches a specific event to the caller-supplied callback.

## The `RunEvent` Type Contract in [`src/types.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/types.ts)

The payload shape is defined as a discriminated union in [`src/types.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/types.ts):

```ts
export type RunEvent =
  | { kind: "reframe:done"; changed: boolean }
  | { kind: "frame:start"; frameId: string; frameLabel: string }
  | { kind: "frame:done"; frameId: string; count: number }
  | { kind: "score:done"; total: number }
  | { kind: "cluster:done"; clusters: number }
  | { kind: "deepen:start"; ideaId: string; text: string }
  | { kind: "deepen:done"; ideaId: string }
  | { kind: "warn"; message: string };

```

Every event carries exactly the data needed to render meaningful progress for its respective phase.

## Phase-by-Phase Event Emission in [`src/engine.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/engine.ts)

The `run` function emits events at precise line locations as it advances through the pipeline.

### Reframe (`reframe:done`)

After optionally calling `reframeProblem`, the engine fires:

```ts
onEvent?.({ kind: "reframe:done", changed: Boolean(reframe) })

```

This occurs at `src/engine.ts:46-47` and signals whether the problem statement was modified.

### Diverge (`frame:start` and `frame:done`)

For each selected frame, the engine emits `frame:start` at `src/engine.ts:56` before invoking `divergeBranch`:

```ts
onEvent?.({ kind: "frame:start", frameId, frameLabel })

```

Once the branch completes, it emits `frame:done` at `src/engine.ts:58`:

```ts
onEvent?.({ kind: "frame:done", frameId, count })

```

This pair creates a per-frame progress stream.

### Score and Cluster (`score:done` and `cluster:done`)

After all branches are collected, `run` scores ideas via `scoreIdeas` and clusters them via `clusterIdeas`. It then emits:

```ts
onEvent?.({ kind: "score:done", total: allIdeas.length })

```

at `src/engine.ts:77`, followed by:

```ts
onEvent?.({ kind: "cluster:done", clusters: clusters.length })

```

at `src/engine.ts:78`.

### Deepen (`deepen:start` and `deepen:done`)

For each top-K idea passed to `deepenIdea`, the engine emits `deepen:start` at `src/engine.ts:100`:

```ts
onEvent?.({ kind: "deepen:start", ideaId: idea.id, text: idea.text })

```

After the call returns, it signals completion at `src/engine.ts:102`:

```ts
onEvent?.({ kind: "deepen:done", ideaId: idea.id })

```

## Default Progress Logger in [`src/cli.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/cli.ts)

The command-line interface constructs the `onEvent` handler in [`src/cli.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/cli.ts). When `--quiet` is absent, it builds a logger that writes to `stderr`:

```ts
const onEvent = flags.quiet ? undefined : (e: RunEvent) => {
  switch (e.kind) {
    case "reframe:done":      if (e.changed) process.stderr.write(`  ↺ anchors stripped from problem\n`); break;
    case "frame:start":       process.stderr.write(`  ▸ ${e.frameLabel}…\n`); break;
    case "frame:done":        process.stderr.write(`    ${e.count} ideas (${e.frameId})\n`); break;
    case "score:done":        process.stderr.write(`  scored ${e.total} ideas\n`); break;
    case "cluster:done":      process.stderr.write(`  ${e.clusters} clusters\n`); break;
    case "deepen:start":      process.stderr.write(`  ◎ focus → ${e.text}\n`); break;
    case "deepen:done":       process.stderr.write(`  deepening completed\n`); break;
    case "warn":              process.stderr.write(`  ! ${e.message}\n`); break;
  }
};

```

The CLI then passes this function into `RunOptions` before calling `run`:

```ts
const opts: RunOptions = {
  problem: flags.problem,
  // …other options…
  onEvent,
};
const result = await run(opts);

```

## Practical Integration Examples

### Stream Progress from the Command Line

Running the tool without `--quiet` automatically streams phase progress:

```bash
npx adhd "design a rate limiter for leader election"

```

The default `onEvent` logger prints reframe confirmations, frame labels, idea counts, and deepening status directly to `stderr`.

### Persist Events to a Log File

You can attach a custom callback to archive every event:

```ts
import { run } from "./src/engine.js";
import { writeFileSync } from "node:fs";

const logPath = "./adhd-events.log";

const onEvent = (e) => {
  writeFileSync(logPath, JSON.stringify(e) + "\n", { flag: "a" });
};

const result = await run({
  problem: "how should we shard this queue?",
  onEvent,
});

console.log("Result:", result);

```

This script receives the same sequence of events emitted by [`src/engine.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/engine.ts) but persists them for later analysis.

### Render Real-Time Updates in React

A web UI can accumulate events in state to show live status:

```tsx
import { useEffect, useState } from "react";
import { run } from "./engine";

function ADHDRunner({ problem }: { problem: string }) {
  const [events, setEvents] = useState<RunEvent[]>([]);
  const [result, setResult] = useState<RunResult | null>(null);

  useEffect(() => {
    const onEvent = (e) => setEvents((prev) => [...prev, e]);
    run({ problem, onEvent }).then(setResult);
  }, [problem]);

  return (
    <div>
      <h2>Progress</h2>
      <ul>
        {events.map((e, i) => (
          <li key={i}>{JSON.stringify(e)}</li>
        ))}
      </ul>
      {result && <pre>{JSON.stringify(result, null, 2)}</pre>}
    </div>
  );
}

```

The component updates in real time as the engine moves through reframe, divergence, scoring, clustering, and deepening.

## Summary

- The ADHD engine exposes a single `onEvent` callback that streams typed `RunEvent` objects through every phase of the tree-of-thought pipeline.
- Events are emitted from [`src/engine.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/engine.ts) at exact phase boundaries: `reframe:done`, `frame:start` / `frame:done`, `score:done`, `cluster:done`, and `deepen:start` / `deepen:done`.
- [`src/types.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/types.ts) defines the discriminated union that guarantees each event payload matches its respective phase.
- [`src/cli.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/cli.ts) demonstrates a practical logger, but the callback is fully optional and can be replaced with custom handlers for logging, UI updates, or silent execution.

## Frequently Asked Questions

### What triggers the `onEvent` callback during the Diverge phase?

During the Diverge phase, the engine loops over each selected cognitive frame and invokes `onEvent?.({ kind: "frame:start", frameId, frameLabel })` at `src/engine.ts:56` before calling `divergeBranch`. After the branch returns, it emits `frame:done` at `src/engine.ts:58` with the resulting idea count, giving per-frame streaming progress.

### Can I disable the `onEvent` progress stream?

Yes. In [`src/cli.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/cli.ts), the CLI passes `undefined` for `onEvent` when the `--quiet` flag is set. The engine safely handles missing callbacks by using optional chaining (`onEvent?.(...)`), so progress streaming is completely optional.

### What information does each `RunEvent` object carry?

The payload depends on the event `kind`. For instance, `reframe:done` includes a `changed` boolean, `frame:done` carries `frameId` and `count`, and `deepen:start` provides the `ideaId` and full `text` being expanded. The exact shape is defined as a discriminated union in [`src/types.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/types.ts).

### Is the `onEvent` callback required to execute the ADHD engine?

No. The `run` function in [`src/engine.ts`](https://github.com/UditAkhourii/adhd/blob/main/src/engine.ts) invokes the callback with optional chaining, so omitting it does not raise an error. Callers can run the full pipeline silently when real-time progress updates are not needed.