Skip to content

Running Workflows

Runsight has two execution modes: production runs on the main branch and simulation runs on disposable branches. Every run is persisted as a database record with per-block node tracking, parent-child linkage for sub-workflows, and a commit SHA tying the run back to the workflow YAML that executed.

A run moves through a fixed state machine:

StateMeaning
pendingCreated in the database, waiting for a concurrency slot
runningActively executing blocks
completedAll blocks finished successfully
failedA block raised an unhandled error or a budget limit was exceeded
cancelledStopped by user action or server shutdown

Terminal states (completed, failed, cancelled) are final --- no further transitions are allowed. The valid transitions are:

  • pendingrunning, cancelled, failed
  • runningcompleted, failed, cancelled

A production run executes the workflow YAML as committed on the main branch.

Production runs can be started from the GUI when there are no unsaved workflow changes, or through the external Direct API. Direct API invocation uses POST /api/workflows/{workflow_id}/runs, always records source: "api", always resolves branch: "main", and only runs workflows committed on main and explicitly enabled with enabled: true. Callers cannot supply source, branch, provenance, delivery, simulation, or idempotency fields in the request body.

When a production run starts:

  1. The API resolves the committed main workflow YAML for the requested workflow and verifies that the committed snapshot has enabled: true.
  2. A Run record is created with status: pending and branch: "main".
  3. The execution service acquires a concurrency slot (default: 5 concurrent runs), then transitions the run to running.
  4. The engine parses the YAML, builds the workflow graph, and wraps every LLM block in an IsolatedBlockWrapper backed by UnixLocalHarness.
  5. Each block executes sequentially through the transition graph. A RunNode record is created per block.
  6. For LLM blocks (linear, gate, synthesize, dispatch), the wrapper creates a WorkspaceRunRequest. UnixLocalHarness creates a workspace session, materializes declared files, and starts a local Unix worker inside the session workspace.
  7. The worker discovers host IPC through RUNSIGHT_IPC_CONFIG_B64=<base64-json IPCClientConfig>. Model calls, HTTP access, file access, and tool execution are mediated by host-side handlers built from per-run host bindings and registries.
  8. The harness validates the worker ResultEnvelope, cleans up the workspace according to policy, and returns the result to the normal block wrapper path.
  9. On completion, the observer writes status: completed with final cost and token totals.

Direct API runs ignore dirty working tree edits to workflow YAML and nested workflow YAML. Referenced workflow blocks are resolved through the same committed snapshot. Parser and discovery paths receive the resolved git ref for snapshot-capable workflow assets, while provider settings, server settings, and API keys remain live runtime configuration from the running server environment. Omitted enabled is treated as disabled for Direct API invocation.

For the exact external HTTP contract, request body, success response, and error codes, see Direct API Invocation.

When the workflow has unsaved changes (the canvas shows an uncommitted badge), clicking Run creates a simulation run:

  1. The GUI calls POST /api/git/sim-branch with the current YAML draft.
  2. A simulation branch is created with the naming convention sim/{workflow-slug}/{YYYYMMDD}/{short-id} (e.g., sim/research-pipeline/20260407/a3f1b).
  3. The run is created with branch: "sim/research-pipeline/20260407/a3f1b" and source: "simulation".
  4. Execution proceeds identically to a production run, but reads the YAML from the sim branch snapshot.

Simulation branches are disposable --- they capture the exact state of the workflow at the time of the run, including any unsaved edits. See Git Integration for details on how sim branches work.

Each run is stored as a Run row in the SQLite database. Important fields include:

FieldTypeDescription
idstrPrimary key (UUID)
workflow_idstrWhich workflow was executed
workflow_namestrHuman-readable workflow name
statusRunStatusCurrent state (pending / running / completed / failed / cancelled)
branchstrGit branch this run executed against; production launch paths set "main"
sourcestrHow the run was triggered, such as manual, simulation, or api
commit_shastr?Git commit SHA of the YAML that executed
total_cost_usdfloatAccumulated LLM cost
total_tokensintAccumulated token count
errorstr?Error message if the run failed
fail_reasonstr?Structured failure category (e.g., "budget_exceeded")
parent_run_idstr?Parent run ID for sub-workflow runs
root_run_idstr?Top-level ancestor run ID
depthintNesting depth (0 for top-level runs)

Each block execution within a run creates a RunNode record:

FieldTypeDescription
idstrComposite key: {run_id}:{node_id}
node_idstrBlock ID from the workflow YAML
block_typestrBlock type (linear, gate, synthesize, dispatch, code, loop, workflow)
statusNodeStatuspending / running / completed / failed
cost_usdfloatCost for this block’s LLM calls
tokensdictToken breakdown: {"prompt": N, "completion": N, "total": N}
outputstr?Block output text
eval_scorefloat?Assertion evaluation score
eval_passedbool?Whether all assertions passed
child_run_idstr?For workflow blocks: the child run’s ID
exit_handlestr?Which exit port this block took

When a workflow contains a workflow block, the child workflow executes as a nested run. The parent RunNode records the child_run_id, and the child Run stores parent_run_id, root_run_id, and depth. This creates a tree of runs that you can query via the API:

API - list child runs
curl http://localhost:8000/api/runs/{parent_run_id}/children

The child run response includes parent_run_id, root_run_id, and depth fields so you can reconstruct the full execution tree.

If the server restarts while runs are in pending or running status, those runs become “ghosts” --- they will never complete because the asyncio task is gone. On startup, the execution service calls fail_ghost_runs() to mark pending and running runs as failed with error: "API server restarted during execution".

MethodHow
GUIClick the Run button on the canvas topbar. Committed clean workflows run on main; dirty workflows create a simulation branch.
Direct APIPOST /api/workflows/{workflow_id}/runs with a required body containing only inputs. Direct API runs require a committed main workflow with enabled: true, and use source: "api" and branch: "main".

See Direct API Invocation for the copyable curl example and full request reference.

Every LLM block runs through the workspace isolation path. API keys, HTTP credentials, URL allowlists, and executable tool references stay in host-only WorkspaceHostBindings and host execution registries. The worker receives only serializable manifests, policy, worker-visible tool metadata, and its config-based IPC connection details.

Request-backed custom HTTP tools seed allowed hosts from their host-side request URL. Dynamic built-in http calls require RUNSIGHT_HTTP_URL_ALLOWLIST on the host; otherwise mediated HTTP is denied before network access.

UnixLocalHarness is the current local workspace harness. It gives each run a fresh workspace root, starts the worker with cwd inside that workspace, scopes mediated file I/O to the same root, validates the result envelope, and cleans up afterward. Unix-local isolation protects host-mediated credentials and workspace access, but it is not a container-grade OS sandbox.

See Workspace Isolation for the full architecture.