The Generator API
Generators are the core abstraction of Eigenvue. A generator takes an
algorithm’s inputs and produces an ordered sequence of Step objects, each
representing one “interesting moment” in the algorithm’s execution.
This page is the API reference. For a hands-on tutorial, see Adding an Algorithm.
TypeScript Generator API
Section titled “TypeScript Generator API”createGenerator<TInputs>(config)
Section titled “createGenerator<TInputs>(config)”Factory function that produces a type-safe GeneratorDefinition. This is the
entry point for every TypeScript generator.
Location: web/src/engine/generator/types.ts
import { createGenerator } from "@/engine/generator";
interface MyInputs extends Record<string, unknown> { array: readonly number[]; target: number;}
export default createGenerator<MyInputs>({ id: "my-algorithm", // Must match meta.json "id" *generate(inputs, step) { // Yield steps here... },});Parameters:
| Field | Type | Description |
|---|---|---|
id | string | Algorithm ID. Must match ^[a-z0-9][a-z0-9-]*$. |
generate | GeneratorFunction<TInputs> | A generator function (function*). |
Returns: GeneratorDefinition<TInputs>
Validation: The id is validated at definition time. An invalid ID throws
immediately, so you catch mistakes before any test runs.
GeneratorDefinition<TInputs>
Section titled “GeneratorDefinition<TInputs>”The object returned by createGenerator(). It bundles the algorithm ID with the
generator function.
interface GeneratorDefinition<TInputs extends Record<string, unknown>> { readonly id: string; readonly generate: GeneratorFunction<TInputs>;}You never construct this directly. Always use createGenerator().
GeneratorFunction<TInputs>
Section titled “GeneratorFunction<TInputs>”The signature of the generator function itself:
type GeneratorFunction<TInputs extends Record<string, unknown>> = (inputs: TInputs, step: StepBuilderFn) => Generator<Step, void, undefined>;Parameters passed to your generator:
| Parameter | Type | Description |
|---|---|---|
inputs | TInputs | The algorithm’s validated input parameters. |
step | StepBuilderFn | Function to create steps. Call this for each step. |
Contract:
- Must yield at least one step.
- The last yielded step must have
isTerminal: true. - No step other than the last may have
isTerminal: true. - Must terminate (no infinite loops). Default safety limit: 10,000 steps.
- State objects must be independent snapshots (no shared references).
StepBuilderFn (the step parameter)
Section titled “StepBuilderFn (the step parameter)”The step parameter passed to your generator is a function that converts a
StepInput into a complete Step object:
type StepBuilderFn = (input: StepInput) => Step;The step builder:
- Assigns the correct
index(auto-incrementing from 0). - Defaults
isTerminaltofalseif not provided. - Validates the step ID format and title length.
- Validates code highlight line numbers (must be positive integers).
- Returns the complete
Stepobject.
You never construct Step objects directly. Always use the step() builder.
StepInput
Section titled “StepInput”The fields you provide when yielding a step:
interface StepInput { readonly id: string; // Template identifier (e.g., "compare_mid") readonly title: string; // Short heading (max 200 chars) readonly explanation: string; // Plain-language narration readonly state: Record<string, unknown>; // Variable snapshot readonly visualActions: readonly VisualAction[]; readonly codeHighlight: CodeHighlight; readonly isTerminal?: boolean; // Only true on the last step readonly phase?: string; // Optional grouping label}Field Details
Section titled “Field Details”id — Template identifier matching ^[a-z0-9][a-z0-9_-]*$. The same
ID can appear multiple times in a sequence (e.g., "compare" appears once
per loop iteration). This is intentional: the ID identifies the type of step,
not the specific instance.
title — Short heading shown in the UI. Must be non-empty and at most
200 characters. Make it descriptive: "Compare Middle Element (Iteration 3)"
is better than "Step 7".
explanation — Educational narration shown below the visualization.
Reference concrete values: "mid = floor((2 + 8) / 2) = 5. Checking array[5] = 11."
is far better than "Calculate the middle index.".
state — A snapshot of ALL algorithm variables at this point. Must be a
JSON-serializable object. Critical rule: this must be an independent copy.
// CORRECT: spread copy of the arraystate: { array: [...array], target, left, right, mid }
// CORRECT: deep clone for nested mutable structuresstate: structuredClone({ matrix, weights, biases })
// WRONG: reference to a mutable variablestate: { array, target, left, right }// ^^^^^ if mutated later, ALL steps see the mutationvisualActions — Rendering instructions for the layout. See the
Visual Actions section below.
codeHighlight — Maps this step to source code lines. See
CodeHighlight below.
isTerminal — Set to true only on the final step. The runner
validates that exactly one step is terminal and that it is the last one.
phase — Optional grouping label. Consecutive steps with the same phase
are visually grouped in the UI. Common values: "initialization", "search",
"comparison", "result".
runGenerator(definition, inputs, options?)
Section titled “runGenerator(definition, inputs, options?)”Executes a generator and returns a validated StepSequence.
Location: web/src/engine/generator/GeneratorRunner.ts
import { runGenerator } from "@/engine/generator";import binarySearchGenerator from "@/algorithms/classical/binary-search/generator";
const result = runGenerator(binarySearchGenerator, { array: [1, 3, 5, 7, 9, 11, 13], target: 7,});
console.log(result.steps.length); // Number of stepsconsole.log(result.algorithmId); // "binary-search"console.log(result.generatedBy); // "typescript"Parameters:
| Parameter | Type | Description |
|---|---|---|
definition | GeneratorDefinition<TInputs> | From createGenerator() |
inputs | TInputs | Algorithm input parameters |
options | RunOptions (optional) | Configuration (e.g., maxSteps) |
Returns: StepSequence
Throws: GeneratorError with the algorithm ID and step index for easy
debugging.
RunOptions:
interface RunOptions { readonly maxSteps?: number; // Default: 10,000}StepSequence
Section titled “StepSequence”The complete output of a generator run:
interface StepSequence { readonly formatVersion: number; // Currently 1 readonly algorithmId: string; // e.g., "binary-search" readonly inputs: Record<string, unknown>; readonly steps: readonly Step[]; readonly generatedAt: string; // ISO 8601 timestamp readonly generatedBy: "typescript" | "python" | "precomputed";}A single step in the execution trace:
interface Step { readonly index: number; // 0-based, auto-assigned readonly id: string; // Template identifier readonly title: string; // Short heading readonly explanation: string; // Educational narration readonly state: Record<string, unknown>; // Variable snapshot readonly visualActions: readonly VisualAction[]; readonly codeHighlight: CodeHighlight; readonly isTerminal: boolean; // true only on last step readonly phase?: string; // Optional grouping label}Invariants enforced by the runner:
steps[i].index === ifor alli(index contiguity).- Exactly one step has
isTerminal === true, and it is the last. - At least one step exists.
Visual Actions
Section titled “Visual Actions”Visual actions are an open vocabulary. The type field is a plain string,
not a closed enum. Renderers silently ignore action types they do not recognize.
This means you can define new action types in a generator before any renderer
supports them.
Base Interface
Section titled “Base Interface”interface VisualAction { readonly type: string; // Action type identifier (camelCase) readonly [key: string]: unknown; // Action-specific parameters}Common Action Types
Section titled “Common Action Types”Array actions:
| Type | Parameters | Description |
|---|---|---|
highlightElement | index, color? | Highlight a single element |
highlightRange | from, to, color? | Highlight a contiguous range |
dimRange | from, to | Visually de-emphasize a range |
movePointer | id, to | Move a named pointer to an index |
swapElements | i, j | Swap two elements (sorting) |
compareElements | i, j, result | Compare two elements |
markFound | index | Mark an element as found |
markNotFound | (none) | Indicate target was not found |
showMessage | text, messageType | Display a transient text message |
Graph actions:
| Type | Parameters | Description |
|---|---|---|
visitNode | nodeId, color? | Mark a graph node as visited |
highlightEdge | from, to, color? | Highlight a graph edge |
updateNodeValue | nodeId, value | Update a node’s displayed value |
Neural network actions:
| Type | Parameters | Description |
|---|---|---|
activateNeuron | layer, index, value | Activate a neuron |
propagateSignal | fromLayer, toLayer | Signal propagation |
showGradient | layer, values | Display gradient values |
showWeights | fromLayer, toLayer, weights | Display weight matrix |
showLoss | loss, lossFunction, … | Show computed loss value |
Attention actions:
| Type | Parameters | Description |
|---|---|---|
showAttentionWeights | queryIdx, weights | Attention weights (must sum to 1) |
highlightToken | index, color? | Highlight a token |
showProjectionMatrix | projectionType, matrix | Show Q/K/V projection |
showAttentionScores | scores | Raw attention scores |
showFullAttentionMatrix | weights | Full attention matrix |
Validated constraints:
highlightRange/dimRange:from <= to(enforced by runner)compareElements:resultmust be"less","greater", or"equal"showAttentionWeights: weights must sum to 1.0 within+/-1e-6(validated using Kahan summation for numerical stability)updateBarChart: iflabelsis present,labels.length === values.length
CodeHighlight
Section titled “CodeHighlight”Maps a step to source code lines:
interface CodeHighlight { readonly language: string; // Key in meta.json code.implementations readonly lines: readonly number[]; // 1-indexed line numbers to highlight}Example:
codeHighlight: { language: "pseudocode", lines: [5, 6] }Lines are 1-indexed to match editor conventions (line 1 is the first line). Order does not matter; the renderer highlights all listed lines.
Python Generator API
Section titled “Python Generator API”Python generators follow a different pattern than TypeScript. Instead of
function* / yield, they are regular functions that return a list[Step].
Function Signature
Section titled “Function Signature”def generate(inputs: dict[str, Any]) -> list[Step]: """Generate algorithm steps.
Parameters ---------- inputs : dict Algorithm input parameters.
Returns ------- list[Step] Ordered list of Step dataclass objects. """Step Dataclass
Section titled “Step Dataclass”The Python Step class mirrors the TypeScript Step interface but uses
snake_case field names. The to_dict() method converts to camelCase for
JSON wire format.
from eigenvue._step_types import Step, VisualAction, CodeHighlight
step = Step( index=0, id="initialize", title="Initialize Search", explanation="Setting up the algorithm...", state={"array": list(array), "target": target}, visual_actions=[ VisualAction(type="highlightRange", from_=0, to=n - 1, color="highlight"), ], code_highlight=CodeHighlight(language="pseudocode", lines=[1]), is_terminal=False, phase="initialization",)snake_case to camelCase Conversion
Section titled “snake_case to camelCase Conversion”The Python Step and VisualAction dataclasses use snake_case internally but
convert to camelCase when serialized to JSON via to_dict():
| Python (snake_case) | JSON (camelCase) |
|---|---|
is_terminal | isTerminal |
visual_actions | visualActions |
code_highlight | codeHighlight |
step_index | stepIndex |
message_type | messageType |
For VisualAction fields, from_ (with trailing underscore to avoid the
Python keyword) maps to from in JSON.
Running a Python Generator
Section titled “Running a Python Generator”from eigenvue.runner import run_generator
# With custom inputssteps = run_generator("binary-search", { "array": [1, 3, 5, 7, 9], "target": 5,})
# With default inputs from meta.jsonsteps = run_generator("binary-search")The runner:
- Looks up the generator in the registry.
- Resolves inputs (custom or defaults from
meta.json). - Calls the generator function.
- Serializes
Stepobjects to camelCase dicts viato_dict(). - Validates step sequence invariants (index contiguity, single terminal).
Key Differences from TypeScript
Section titled “Key Differences from TypeScript”| Concern | TypeScript | Python |
|---|---|---|
| Function type | Generator function (function*) | Regular function returning list |
| Step creation | yield step({ ... }) | steps.append(Step(...)) |
| Index assignment | Automatic (runner assigns) | Manual (you set index) |
| State snapshots | [...array] or structuredClone() | list(array) or copy.deepcopy() |
| Field naming | camelCase | snake_case (auto-converted on serialize) |
| Validation timing | Runner validates after all yields | Runner validates after function returns |
| Registry | Auto-discovered from file path | Manual entry in __init__.py |
Inputs Contract
Section titled “Inputs Contract”Both TypeScript and Python generators receive the same logical inputs. The
inputs are validated against the JSON Schema defined in meta.json before
reaching the generator.
Input Schema Example
Section titled “Input Schema Example”From meta.json:
{ "inputs": { "schema": { "array": { "type": "array", "items": { "type": "number", "minimum": -999, "maximum": 999 }, "minItems": 1, "maxItems": 20 }, "target": { "type": "number", "minimum": -999, "maximum": 999 } }, "defaults": { "array": [1, 3, 5, 7, 9], "target": 5 } }}Type Safety
Section titled “Type Safety”In TypeScript, define an interface that matches your input schema:
interface BinarySearchInputs extends Record<string, unknown> { readonly array: readonly number[]; readonly target: number;}The extends Record<string, unknown> is required by createGenerator’s
generic constraint. It ensures inputs are always a plain object.
In Python, inputs arrive as a plain dict[str, Any]. Type-narrow them at the
top of your generator:
def generate(inputs: dict[str, Any]) -> list[Step]: array: list[int] = list(inputs["array"]) target: int = inputs["target"]Step Ordering Rules
Section titled “Step Ordering Rules”Steps must follow these ordering rules, enforced by the runner:
-
Index contiguity.
steps[i].index === ifor alli. No gaps, no duplicates, no reordering. -
Single terminal. Exactly one step has
isTerminal: true. It must be the last step in the sequence. -
Non-empty. At least one step must be yielded.
-
Phase consistency. While not enforced at the type level, phases should flow logically:
"initialization"->"search"/"computation"->"result". Consecutive steps with the same phase are visually grouped. -
Determinism. Given the same inputs, a generator must produce the exact same steps every time. No random behavior, no time-dependent logic, no external state. This is essential for cross-language parity and reproducibility.