Skip to main content
When a Smithers workflow does not behave as expected, you have three levels of inspection: the CLI commands, NDJSON log files, and direct SQLite queries against the internal tables.

CLI Inspection

The Smithers CLI provides four inspection commands:

status

View the current state of a run, including all node statuses:
bunx smithers status workflow.tsx --run-id <id>
Output shows the run status and each node’s current state (pending, in-progress, finished, failed, skipped, waiting-approval, cancelled).

frames

View the render frames for a run. Each frame is a snapshot of the JSX tree at a point in time, showing which tasks were mounted and their states:
bunx smithers frames workflow.tsx --run-id <id> --tail 10
Use --tail N to limit output to the most recent N frames. Frames are useful for understanding the re-render cycle — you can see when tasks mount, when outputs become available, and how the tree evolves.

list

List all runs, optionally filtered by status:
# List recent runs
bunx smithers list workflow.tsx --limit 20

# List only running or failed runs
bunx smithers list workflow.tsx --status running
bunx smithers list workflow.tsx --status failed

graph

View the execution graph for a run. This shows the task dependency structure:
bunx smithers graph workflow.tsx --run-id <id>
You can also preview the graph for a new input without running the workflow:
bunx smithers graph workflow.tsx --input '{"description": "Fix bugs"}'

NDJSON Log Inspection

Smithers writes every event to an NDJSON log file at:
.smithers/executions/<runId>/logs/stream.ndjson
Each line is a JSON-encoded SmithersEvent. You can tail the log in real time during execution:
tail -f .smithers/executions/<runId>/logs/stream.ndjson
Filter for specific event types:
# See only node lifecycle events
grep '"type":"Node' .smithers/executions/<runId>/logs/stream.ndjson

# See failures only
grep '"type":"NodeFailed"' .smithers/executions/<runId>/logs/stream.ndjson

# See tool calls
grep '"type":"ToolCall' .smithers/executions/<runId>/logs/stream.ndjson
Parse events with jq for structured inspection:
# Pretty-print the last 5 events
tail -5 .smithers/executions/<runId>/logs/stream.ndjson | jq .

# Extract node IDs and their final statuses
cat .smithers/executions/<runId>/logs/stream.ndjson | jq 'select(.type == "NodeFinished" or .type == "NodeFailed") | {nodeId, type}'
You can customize or disable the log location:
# Custom log directory
bunx smithers run workflow.tsx --log-dir ./my-logs

# Disable log output entirely
bunx smithers run workflow.tsx --no-log

SQLite Inspection

Smithers stores all internal state in tables prefixed with _smithers_. You can query them directly for deep debugging.

Internal Tables

TablePurpose
_smithers_runsRun metadata: status, created/updated timestamps
_smithers_nodesPer-node state: status, iteration, attempt count
_smithers_attemptsPer-attempt details: status, started/finished times, error messages, JJ pointer
_smithers_framesRender frame snapshots (XML representation of the JSX tree)
_smithers_approvalsApproval decisions: approved/denied, decided_by, note
_smithers_cacheCached task results (when caching is enabled)
_smithers_tool_callsTool invocations: tool name, arguments, result, duration
_smithers_eventsAll events with sequence numbers and JSON payloads
_smithers_ralphRalph loop state: iteration counts, termination reasons

Useful Queries

View run status:
sqlite3 smithers.db "SELECT run_id, status, created_at_ms, updated_at_ms FROM _smithers_runs ORDER BY created_at_ms DESC LIMIT 5;"
View node states for a run:
sqlite3 smithers.db "SELECT node_id, status, iteration FROM _smithers_nodes WHERE run_id = '<id>' ORDER BY updated_at_ms;"
View failed attempts with error details:
sqlite3 smithers.db "SELECT node_id, attempt, status, error_message FROM _smithers_attempts WHERE run_id = '<id>' AND status = 'failed';"
View tool calls for a specific task:
sqlite3 smithers.db "SELECT tool_name, arguments, duration_ms FROM _smithers_tool_calls WHERE run_id = '<id>' AND node_id = '<node-id>';"
View events in order:
sqlite3 smithers.db "SELECT seq, type, payload_json FROM _smithers_events WHERE run_id = '<id>' ORDER BY seq LIMIT 50;"
Check approval status:
sqlite3 smithers.db "SELECT node_id, approved, decided_by, note FROM _smithers_approvals WHERE run_id = '<id>';"
View Ralph loop state:
sqlite3 smithers.db "SELECT * FROM _smithers_ralph WHERE run_id = '<id>';"
Check output tables: Your task outputs are stored in the tables you defined in your schemas. Query them directly:
sqlite3 smithers.db "SELECT * FROM analysis WHERE run_id = '<id>';"
sqlite3 smithers.db "SELECT * FROM report WHERE run_id = '<id>';"

Common Issues

Run stuck at “waiting-approval”

A task with needsApproval is blocking the workflow. Check which node is waiting:
bunx smithers status workflow.tsx --run-id <id>
Then approve or deny it:
bunx smithers approve workflow.tsx --run-id <id> --node-id <node-id>
bunx smithers deny workflow.tsx --run-id <id> --node-id <node-id>
After the decision, resume the run:
bunx smithers resume workflow.tsx --run-id <id>

Duplicate task IDs

Task IDs must be unique within a workflow. Duplicate IDs cause an error at render time:
Error: Duplicate Task id detected: "analyze"
Common causes:
  • Two <Task> elements with the same id prop.
  • Dynamic tasks generated from an array where the IDs collide (e.g., using array indices).
  • A task inside a <Ralph> loop that should be outside (or vice versa).
Fix: ensure every <Task> has a globally unique id within the workflow. For dynamic tasks, derive IDs from unique identifiers: id={$:process}.

Missing output rows

If ctx.output() throws “No output found for nodeId”, the task has not completed yet. Common causes:
  • The task is still pending or in-progress.
  • The task failed (check _smithers_nodes for its status).
  • The task’s id prop changed between renders.
  • The output table is missing the required runId or nodeId column (when using the manual Drizzle API).
Use ctx.outputMaybe() for safe conditional access:
const result = ctx.outputMaybe("analysis", { nodeId: "analyze" });
if (result) {
  // safe to use result
}

Task keeps retrying

If a task retries indefinitely (up to its retry limit), check:
  1. Schema validation errors — The agent may be returning invalid JSON. Check the NDJSON logs for NodeRetrying events.
  2. Timeout — The agent call may be timing out. Check timeoutMs settings.
  3. Tool errors — A tool used by the agent may be failing. Check _smithers_tool_calls for errors.
# Check retry events
grep "NodeRetrying" .smithers/executions/<runId>/logs/stream.ndjson | jq .

# Check failed attempts
sqlite3 smithers.db "SELECT node_id, attempt, error_message FROM _smithers_attempts WHERE run_id = '<id>' AND status = 'failed';"

Stale in-progress tasks

If a run was interrupted and tasks are stuck in in-progress, resume the run. Tasks in-progress for longer than 15 minutes are automatically cancelled and retried:
bunx smithers resume workflow.tsx --run-id <id>
If the task is recent (less than 15 minutes old), you may need to wait for the timeout or start a fresh run.

Next Steps