Skip to main content
Smithers emits typed SmithersEvent objects throughout a run’s lifecycle. You can subscribe via the onProgress callback in runWorkflow, or read persisted events from the NDJSON log files after the fact.

Subscribing to Events

onProgress Callback

Pass an onProgress function to runWorkflow to receive events in real time:
import { runWorkflow } from "smithers-orchestrator";
import workflow from "./workflow";

await runWorkflow(workflow, {
  input: { description: "Fix bug" },
  onProgress: (event) => {
    console.log(`[${event.type}] at ${event.timestampMs}`);

    if (event.type === "NodeStarted") {
      console.log(`  node: ${event.nodeId}, attempt: ${event.attempt}`);
    }

    if (event.type === "NodeFailed") {
      console.error(`  node: ${event.nodeId}, error:`, event.error);
    }
  },
});

NDJSON Log Files

When logging is enabled (the default), every event is appended as a JSON line to:
.smithers/executions/<runId>/logs/stream.ndjson
Each line is a complete SmithersEvent object serialized as JSON. You can parse these files with standard tools:
# Watch events in real time
tail -f .smithers/executions/abc123/logs/stream.ndjson | jq .

# Filter for failures
cat .smithers/executions/abc123/logs/stream.ndjson | jq 'select(.type == "NodeFailed")'

# Count events by type
cat .smithers/executions/abc123/logs/stream.ndjson | jq -r .type | sort | uniq -c | sort -rn
To change the log directory, pass logDir to runWorkflow or --log-dir to the CLI. To disable log files entirely, pass logDir: null or --no-log.

Common Fields

Every SmithersEvent includes:
FieldTypeDescription
typestringThe event type discriminator (e.g. "NodeStarted", "RunFinished").
runIdstringThe run this event belongs to.
timestampMsnumberUnix timestamp in milliseconds when the event was emitted.
Node-scoped events additionally include:
FieldTypeDescription
nodeIdstringThe task node this event relates to.
iterationnumberThe Ralph iteration number.
Attempt-scoped events additionally include:
FieldTypeDescription
attemptnumberThe attempt number (starts at 1, increments on retry).

Event Types

Run Lifecycle

These events mark the overall run’s state transitions.

RunStarted

Emitted once at the beginning of every run (including resumes).
{ type: "RunStarted", runId: string, timestampMs: number }

RunStatusChanged

Emitted when the run’s status changes (e.g. from "running" to "waiting-approval").
{ type: "RunStatusChanged", runId: string, status: RunStatus, timestampMs: number }
RunStatus is one of: "running", "waiting-approval", "finished", "failed", "cancelled".

RunFinished

Emitted when all tasks complete successfully.
{ type: "RunFinished", runId: string, timestampMs: number }

RunFailed

Emitted when the run fails due to a task failure or engine error.
{ type: "RunFailed", runId: string, error: unknown, timestampMs: number }

RunCancelled

Emitted when the run is cancelled via AbortSignal.
{ type: "RunCancelled", runId: string, timestampMs: number }

Frame Events

FrameCommitted

Emitted each time the engine renders a new frame (a snapshot of the workflow tree).
{
  type: "FrameCommitted",
  runId: string,
  frameNo: number,
  xmlHash: string,
  timestampMs: number,
}
The xmlHash is a SHA-256 hex digest of the canonicalized XML tree. It changes whenever the tree structure changes (e.g. new tasks appear after a Branch resolves).

Node Lifecycle

These events track individual task execution.

NodePending

A task has been identified in the tree and is waiting to be scheduled.
{ type: "NodePending", runId: string, nodeId: string, iteration: number, timestampMs: number }

NodeStarted

A task has begun executing. The attempt number starts at 1 and increments on each retry.
{
  type: "NodeStarted",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  timestampMs: number,
}

NodeFinished

A task completed successfully and its output was persisted.
{
  type: "NodeFinished",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  timestampMs: number,
}

NodeFailed

A task attempt failed. Check the error field for details.
{
  type: "NodeFailed",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  error: unknown,
  timestampMs: number,
}

NodeCancelled

A task was cancelled (e.g. because the run was aborted or the task was unmounted from the tree between frames).
{
  type: "NodeCancelled",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt?: number,
  reason?: string,
  timestampMs: number,
}
The reason field may contain "unmounted" if the task was cancelled because it disappeared from the tree after a re-render.

NodeSkipped

A task was skipped because its skipIf condition evaluated to true or its approval was denied with continueOnFail.
{ type: "NodeSkipped", runId: string, nodeId: string, iteration: number, timestampMs: number }

NodeRetrying

A task failed but has remaining retry attempts. This event fires before the next attempt starts.
{
  type: "NodeRetrying",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  timestampMs: number,
}
The attempt field is the upcoming attempt number (e.g. 2 for the first retry).

NodeWaitingApproval

A task requires human approval and is now paused.
{
  type: "NodeWaitingApproval",
  runId: string,
  nodeId: string,
  iteration: number,
  timestampMs: number,
}

Approval Events

ApprovalRequested

An approval request was created for a task.
{
  type: "ApprovalRequested",
  runId: string,
  nodeId: string,
  iteration: number,
  timestampMs: number,
}

ApprovalGranted

A human approved the task. The task will execute on the next engine cycle.
{
  type: "ApprovalGranted",
  runId: string,
  nodeId: string,
  iteration: number,
  timestampMs: number,
}

ApprovalDenied

A human denied the task. The task is marked as failed (or skipped if continueOnFail is set).
{
  type: "ApprovalDenied",
  runId: string,
  nodeId: string,
  iteration: number,
  timestampMs: number,
}

Tool Events

ToolCallStarted

An agent invoked a tool (e.g. read, bash, edit).
{
  type: "ToolCallStarted",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  toolName: string,
  seq: number,
  timestampMs: number,
}
The seq field is a sequential counter for tool calls within the attempt.

ToolCallFinished

A tool call completed.
{
  type: "ToolCallFinished",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  toolName: string,
  seq: number,
  status: "success" | "error",
  timestampMs: number,
}

Output Events

NodeOutput

Streaming text output from an agent (stdout or stderr).
{
  type: "NodeOutput",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  text: string,
  stream: "stdout" | "stderr",
  timestampMs: number,
}

Revert Events

RevertStarted

A workspace revert operation has begun.
{
  type: "RevertStarted",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  jjPointer: string,
  timestampMs: number,
}

RevertFinished

A workspace revert operation completed.
{
  type: "RevertFinished",
  runId: string,
  nodeId: string,
  iteration: number,
  attempt: number,
  jjPointer: string,
  success: boolean,
  error?: string,
  timestampMs: number,
}

Event Type Reference

Quick reference table of all event types and their extra fields (beyond type, runId, timestampMs):
Event TypeExtra Fields
RunStarted
RunStatusChangedstatus
RunFinished
RunFailederror
RunCancelled
FrameCommittedframeNo, xmlHash
NodePendingnodeId, iteration
NodeStartednodeId, iteration, attempt
NodeFinishednodeId, iteration, attempt
NodeFailednodeId, iteration, attempt, error
NodeCancellednodeId, iteration, attempt?, reason?
NodeSkippednodeId, iteration
NodeRetryingnodeId, iteration, attempt
NodeWaitingApprovalnodeId, iteration
ApprovalRequestednodeId, iteration
ApprovalGrantednodeId, iteration
ApprovalDeniednodeId, iteration
ToolCallStartednodeId, iteration, attempt, toolName, seq
ToolCallFinishednodeId, iteration, attempt, toolName, seq, status
NodeOutputnodeId, iteration, attempt, text, stream
RevertStartednodeId, iteration, attempt, jjPointer
RevertFinishednodeId, iteration, attempt, jjPointer, success, error?

Persistence

Events are persisted in two places:
  1. SQLite database — Every event is inserted into the _smithers_events table with a sequential seq number, enabling replay and querying.
  2. NDJSON log file — Each event is appended as a JSON line to stream.ndjson in the run’s log directory.
Both persistence mechanisms happen asynchronously. The onProgress callback fires synchronously on the event bus before persistence.
  • runWorkflow — Where onProgress is configured.
  • Monitoring and Logs — Practical guide to monitoring workflows.
  • CLI — View run status and frames from the command line.