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 exact 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. When you click Run in the GUI with no unsaved changes, or call the API with branch: "main":

  1. The API reads the workflow YAML from git show main:custom/workflows/{id}.yaml.
  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 a Workflow graph, and calls Workflow.run().
  5. Each block executes sequentially through the transition graph. A RunNode record is created per block.
  6. On completion, the observer writes status: completed with final cost and token totals.
API — create a production run
curl -X POST http://localhost:8321/api/runs \
-H "Content-Type: application/json" \
-d '{
"workflow_id": "research-pipeline",
"task_data": { "instruction": "Research quantum computing trends" },
"branch": "main"
}'

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: "manual".
  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 with these fields:

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 (default: "main")
sourcestrHow the run was triggered (default: "manual")
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, 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:8321/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 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 all running runs as failed with the error message "Ghost run: server restarted while running".

MethodHow
GUIClick the Run button on the canvas topbar. Enter an instruction in the run dialog.
APIPOST /api/runs with workflow_id, task_data (must include instruction), optional branch and source.