Skip to main content
renderFrame is a pure rendering function that converts a workflow’s JSX tree into a GraphSnapshot containing an XML representation and an ordered list of task descriptors. It does not execute any tasks, call any agents, or modify the database. This makes it useful for visualization, debugging, and building workflow inspectors.

Basic Usage

import { renderFrame } from "smithers-orchestrator";
import workflow from "./workflow";

const snapshot = await renderFrame(workflow, {
  runId: "preview",
  iteration: 0,
  input: { description: "Fix authentication bug" },
  outputs: {},
});

console.log(snapshot.frameNo);       // 0
console.log(snapshot.tasks.length);  // Number of tasks in the tree
console.log(snapshot.xml);           // XML tree representation

Signature

function renderFrame<Schema>(
  workflow: SmithersWorkflow<Schema>,
  ctx: RenderContext,
): Promise<GraphSnapshot>;

RenderContext

The second argument is a context object that provides the data your workflow’s JSX tree needs to render. It mirrors the SmithersCtx that runWorkflow builds internally.
FieldTypeDescription
runIdstringThe run ID to use in the snapshot. Can be any string for preview purposes.
iterationnumberThe current Ralph iteration number (use 0 for non-looping workflows).
iterationsRecord<string, number>Per-Ralph iteration counts, keyed by Ralph ID. Optional.
inputobjectThe input data the workflow expects from ctx.input.
outputsobjectPreviously computed outputs, keyed by table name. Pass {} to render the initial state.

GraphSnapshot

type GraphSnapshot = {
  runId: string;
  frameNo: number;
  xml: XmlNode | null;
  tasks: TaskDescriptor[];
};
FieldTypeDescription
runIdstringThe run ID from the context.
frameNonumberAlways 0 for renderFrame (frame numbering is managed by the engine during actual execution).
xmlXmlNode | nullThe rendered XML tree, or null if the workflow produced no output.
tasksTaskDescriptor[]Ordered list of all <Task> nodes found in the tree, in execution order.

XmlNode

The XML tree is a recursive structure of elements and text nodes:
type XmlNode = XmlElement | XmlText;

type XmlElement = {
  kind: "element";
  tag: string;           // e.g. "smithers:workflow", "smithers:task"
  props: Record<string, string>;
  children: XmlNode[];
};

type XmlText = {
  kind: "text";
  text: string;
};

TaskDescriptor

Each task in the tree produces a TaskDescriptor that describes its configuration:
type TaskDescriptor = {
  nodeId: string;
  ordinal: number;
  iteration: number;
  ralphId?: string;

  outputTable: Table | null;
  outputTableName: string;
  outputSchema?: ZodObject<any>;

  parallelGroupId?: string;
  parallelMaxConcurrency?: number;

  needsApproval: boolean;
  skipIf: boolean;
  retries: number;
  timeoutMs: number | null;
  continueOnFail: boolean;

  agent?: AgentLike;
  prompt?: string;
  staticPayload?: unknown;

  label?: string;
  meta?: Record<string, unknown>;
};
FieldDescription
nodeIdThe id prop from the <Task> component.
ordinalPosition in the task list (0-indexed).
iterationThe Ralph iteration this task belongs to.
ralphIdThe ID of the enclosing <Ralph> component, if any.
outputTableThe Drizzle table object for the task’s output.
outputTableNameString name of the output table.
outputSchemaOptional Zod schema for validating agent output.
parallelGroupIdID of the <Parallel> group this task belongs to.
parallelMaxConcurrencyPer-group concurrency limit from the enclosing <Parallel>.
needsApprovalWhether this task requires human approval before executing.
skipIfWhether this task should be skipped.
retriesNumber of retry attempts on failure.
timeoutMsPer-task timeout in milliseconds, or null for the global default.
continueOnFailWhether the workflow continues if this task fails.
agentThe AI agent assigned to this task.
promptThe resolved prompt string (from <Task> children).
staticPayloadStatic output data (for tasks without an agent).
labelHuman-readable label for display.
metaArbitrary metadata attached to the task.

Use Cases

Previewing the Execution Graph

Render a workflow to see what tasks will run before actually executing them:
const snapshot = await renderFrame(workflow, {
  runId: "dry-run",
  iteration: 0,
  input: { description: "Preview" },
  outputs: {},
});

for (const task of snapshot.tasks) {
  console.log(`${task.ordinal}. [${task.nodeId}] -> ${task.outputTableName}`);
  if (task.needsApproval) console.log("   (requires approval)");
  if (task.skipIf) console.log("   (skipped)");
}

Simulating Completed Outputs

Pass previously computed outputs to see how the tree changes:
// First render: no outputs
const snap1 = await renderFrame(workflow, {
  runId: "sim",
  iteration: 0,
  input: { description: "Bug" },
  outputs: {},
});

// Second render: simulate "analyze" completing
const snap2 = await renderFrame(workflow, {
  runId: "sim",
  iteration: 0,
  input: { description: "Bug" },
  outputs: {
    analyze: [{ runId: "sim", nodeId: "analyze", iteration: 0, summary: "Found null pointer" }],
  },
});

Building a Workflow Visualizer

The XML tree and task list can be used to build visual representations of the workflow DAG. The xml field mirrors the JSX structure, while tasks provides the flattened execution order.

CLI Graph Command

The smithers graph CLI command uses renderFrame internally:
smithers graph workflow.tsx --input '{"description": "Fix bug"}'
This prints the GraphSnapshot as JSON to stdout.