Skip to content
Tracon

RunStatus

Namespace Tracon · Assembly Tracon.Abstractions.dll

Status of a run.

[JsonConverter(typeof(JsonStringEnumConverter<RunStatus>))]
public enum RunStatus

AwaitingApproval = 6

The run waits for an operator to decide on a tool call and cannot advance without that decision.

Seen on any root (Depth == 0) agent run, whatever started it — the management API, an OpenAI-compatible endpoint, MCP, A2A, or the queue (Prefer: respond-async). The first version covered only the queue path: the synchronous paths did not reflect this status at all, and a tool call waiting for approval silently looked RunStatus.Completed. However the decision arrives — POST /api/approvals/{id}/decide for the queue, the caller’s own next turn for synchronous callers — the status label follows the same principle.

It follows the SAME principle as RunStatus.AwaitingInput: an answered run stays in this status, because rewriting history would break the append-only rule of the event stream. On the queue path the decision is made through POST /api/approvals/{id}/decide, which enqueues a new run (same sessionId, new RunId).

The value was appended at the end for the same reason as RunStatus.AwaitingInput.

AwaitingInput = 4

The run waits for human input and cannot advance without it.

Seen on RunKind.Workflow rows, and on the root agent run of a tool call an MCP server suspended to ask the user for input (MCP elicitation). When a workflow reaches an external request port — Magentic plan approval, for example — execution stops, its state is written to a checkpoint and the stream closes. The answer arrives through POST /api/workflows/runs/{runId}/respond, which opens a new run that resumes from the checkpoint.

On an agent run the request is the next turn’s input, the same as an approval: the caller answers in the inputResponses of the next run request of the same session, and the suspended tool call runs again from its start with the answer. Only a run started through the management API’s run endpoint can wait this way; on every other surface (an OpenAI-compatible call, A2A, an MCP tool call, a queued run) and below the root of an agent tree the server’s request is declined.

The value was appended at the end: statuses are stored as smallint in the database and shifting the existing values would misread old rows. An answered run stays AwaitingInput; rewriting history would break the append-only rule of the event stream. The continued work shows up in the new run row.

Canceled = 3

The run was cancelled.

Completed = 1

The run finished successfully.

Failed = 2

The run ended with an error.

Queued = 5

The run has not started its work: it waits for a worker, or for a concurrency slot.

Seen for runs started with Prefer: respond-async. The row is written as Queued at enqueue time; when the worker actually runs the job the same id is written again and moves to RunStatus.Running.

Seen too for a workflow run that waits, in the process that runs it, for a slot of Tracon:Workflows:MaxConcurrentRuns or of MaxConcurrentRunsPerTenant. No job holds that run; the process does, and writes a heartbeat for it, so run reconciliation closes the row if the process is gone. The row moves to RunStatus.Running when the run has its slots.

The value was appended at the end for the same reason as RunStatus.AwaitingInput.

Running = 0

The run is in progress.

Written to JSON by name ("Code"), not by number. The wire contract explains itself that way and survives a change in value order. The converter sits on the type, so the format is the same everywhere without touching the consumer’s application-wide JSON options. No enum is PERSISTED as JSON (RunStatus and RunEventType are smallint in the database, AgentDefinitionOrigin is rebuilt on read), so a format change does not affect stored data.