Skip to main content
A workflow that runs long, unattended, or in a loop can wedge silently and nobody notices until morning. A monitor is a Smithers workflow whose job is to watch one other run: it samples the run’s health on an interval, classifies what it sees, and either applies a small reversible repair or escalates to a human. Monitors are opt-in per workflow and cost nothing until you write one. A workspace with no monitor files behaves exactly as it did before.

10-second quickstart

Write the monitor at .smithers/monitor/<workflowId>.tsx, named after the workflow it watches:
The flag lives on every launch path that starts a run: up, workflow run, and the overridable oneshot workflow.

What a monitor is

A monitor is an ordinary workflow. Nothing about it is special except where the file lives and how it is launched:
  • It starts with the watched run, as a sibling run executing in parallel.
  • Its parent_run_id is the watched run, so bunx smithers-orchestrator ps and bunx smithers-orchestrator inspect RUN_ID show the pairing, the Gateway can render it, and bunx smithers-orchestrator cancel RUN_ID cascades to the monitor.
  • It is torn down when the watched run finishes, so a monitor never outlives what it watches.
  • Its run id is <watchedRunId>-monitor, and it carries a smithersMonitorFor annotation naming the watched run.
  • A monitor never gets a monitor of its own. Three independent guards enforce that: the file’s location under monitor/, the --no-monitor flag it is launched with, and the SMITHERS_MONITOR_SUPPRESS environment variable set on its process.
Smithers passes the monitor its subject as run input:

Writing one

Compose the shipped <Monitor> component rather than hand-rolling a poll loop. A monitor file is a few lines:
nodeId, runId, and iteration are reserved output columns, which is why the implicated node is reported as targetNodeId.

How the heartbeat works

Each beat is three steps, and the first beat samples immediately rather than sleeping first:
  1. A durable <Timer> paces the loop.
  2. One agent task samples the watched run and classifies it into exactly one condition.
  3. A <DecisionTable> routes that condition to its handler, first match wins.
The loop exits when the watched run reaches a terminal status, or after maxChecks beats.

The condition set

The classifier must pick exactly one, which is what makes routing deterministic:

What a monitor may do on its own

The shipped defaults are deliberately timid, because a monitor that guesses wrong makes an incident worse. Only stalled and wedged-node heal without a human, and only because both repairs are idempotent and reversible: resuming re-enters the same durable frame, and retrying a node adds an attempt without discarding prior state. Running either twice is harmless. Everything else escalates through a durable human request, which shows up in bunx smithers-orchestrator human list and in the Gateway. Cancelling, rewinding, reverting, time-travelling, resolving an approval on a human’s behalf, and editing the repository are never done autonomously. Widen that authority explicitly when you mean to:
Adding runaway-loop swaps its handler from “escalate” to “cancel the run”, which is destructive by design and therefore never a default. Replace any handler outright, or silence one with null:

Reading run state

A monitor reads the watched run through the Gateway client (smithers-orchestrator/gateway-client) or the public CLI (status, inspect, events, node, why, ps). It must never open the store directly: .smithers/*.db, .smithers/pg, and smithers.db are private engine state, and reading them races the very run being watched. The prompt shipped with <Monitor> binds the agent to that rule, along with what healthy and unhealthy look like, what evidence to gather before acting, and when to escalate instead of guessing. Extend it with repo-specific doctrine rather than replacing it:
Pass prompt only when you need to replace the doctrine entirely.

Testing a monitor

A monitor is a workflow, so it tests like one, with coverWorkflow or renderWorkflow from smithers-orchestrator/testing. Mock the classifier’s verdict and assert which handler ran:
See examples/monitor-workflow.jsx for a complete, runnable monitor.