Skip to main content
SmithersEvent is the discriminated union of lifecycle events the runtime and observability layer understand. Most variants are emitted by the runtime; reserved variants are called out in Event Types. The full type definition lives in Types; that’s the source of truth for field shapes.

Subscribe via onProgress

import { runWorkflow } from "smithers-orchestrator";
import { Effect } from "effect";
import workflow from "./workflow";

await Effect.runPromise(runWorkflow(workflow, {
  input: { task: "fix bug" },
  onProgress: (event) => {
    if (event.type === "NodeStarted")  console.log(`▶ ${event.nodeId} (attempt ${event.attempt})`);
    if (event.type === "NodeFinished") console.log(`✓ ${event.nodeId}`);
    if (event.type === "NodeFailed")   console.error(`✗ ${event.nodeId}`, event.error);
  },
}));

Read from the NDJSON log

Events append to .smithers/executions/<runId>/logs/stream.ndjson (configure with logDir / --log-dir; disable with --no-log).
tail -f .smithers/executions/<runId>/logs/stream.ndjson | jq .
jq 'select(.type == "NodeFailed")' .smithers/executions/<runId>/logs/stream.ndjson
jq -r .type .smithers/executions/<runId>/logs/stream.ndjson | sort | uniq -c | sort -rn
Or with the CLI:
bunx smithers-orchestrator events RUN_ID --json
bunx smithers-orchestrator events RUN_ID --type tool-call --node analyze

Common fields

type CommonFields    = { type: string; runId: string; timestampMs: number };
type NodeScoped      = CommonFields & { nodeId: string; iteration: number };
type AttemptScoped   = NodeScoped   & { attempt: number };
Every event includes type, runId, timestampMs. Node-scoped events add nodeId and iteration. Attempt-scoped add attempt.

Event categories

Used by bunx smithers-orchestrator events --type <category> and the metrics layer.
CategoryEvents
runRunAutoResumed, RunAutoResumeSkipped, RunStarted, RunStatusChanged, RunStateChanged, RunFinished, RunFailed, RunCancelled, RunContinuedAsNew, RunHijackRequested, RunHijacked, RetryTaskStarted, RetryTaskFinished, RunForked, ReplayStarted
frameFrameCommitted
nodeNodePending, NodeStarted, TaskHeartbeat, TaskHeartbeatTimeout, NodeFinished, NodeFailed, NodeCancelled, NodeSkipped, NodeRetrying, NodeWaitingApproval, NodeWaitingTimer
approvalApprovalRequested, ApprovalGranted, ApprovalAutoApproved, ApprovalDenied
tool-callToolCallStarted, ToolCallFinished
agentAgentEvent, AgentTraceEvent, AgentTraceSummary, AgentSessionEvent
outputNodeOutput
revertRevertStarted, RevertFinished, TimeTravelStarted, TimeTravelFinished, TimeTravelJumped
workflowWorkflowReloadDetected, WorkflowReloaded, WorkflowReloadFailed, WorkflowReloadUnsafe
scorerScorerStarted, ScorerFinished, ScorerFailed
tokenTokenUsageReported
timerTimerCreated, TimerFired, TimerCancelled
memoryMemoryFactSet, MemoryRecalled, MemoryMessageSaved
openapiOpenApiToolCalled
sandboxSandboxCreated, SandboxShipped, SandboxHeartbeat, SandboxBundleReceived, SandboxCompleted, SandboxFailed, SandboxDiffReviewRequested, SandboxDiffAccepted, SandboxDiffRejected
snapshotSnapshotCaptured
supervisorSupervisorStarted, SupervisorPollCompleted
RunStateChanged is categorized as run because the type is part of the public event union, but the current runtime does not emit it. Use RunStatusChanged from the stream plus getRun / computeRunState for derived run state. OpenApiToolCalled is categorized as openapi for forward compatibility, but the current OpenAPI tool factory records Effect metrics and log spans rather than emitting that event onto the run event bus.

Built-in metrics

EventMetric
RunStartedsmithers.runs.total
NodeStartedsmithers.nodes.started
NodeFinishedsmithers.nodes.finished
NodeFailedsmithers.nodes.failed
ApprovalGranted / ApprovalDeniedApproval counters
TokenUsageReportedToken usage counters per model/agent
trackSmithersEvent from smithers-orchestrator/observability exposes this mapping for custom integrations. See Observability for the full OTLP/Prometheus setup.