Skip to main content

Start and monitor

Starting a saga returns a trace_id immediately; workers execute steps asynchronously via the outbox. Integrators poll path GETs by trace_id until the saga reaches a terminal status or pauses at AWAITING_HUMAN.

After start, trace_id is your handle for everything — a 32-character hex token, not the manifest name or version. Copy it from the start response and use it for poll, HITL, and recovery calls.

Routing reference​

OperationMethodLiteral pathtrace_id
StartPOST/v1/sagas/startResponse body
Get one sagaGET/v1/sagas/{trace_id}Path segment (404 if missing)
List sagasGET/v1/sagasFleet filters (in_flight, failed, status, trace_id, …)
List stepsGET/v1/sagas/{trace_id}/stepsPath segment

trace_id must match ^[a-f0-9]{32}$ (32-character lowercase hex); invalid values → 422.

Prefer path GETs when you already know trace_id. Collection GET /v1/sagas is for fleet filters (in_flight, failed, status, parent_trace_id, …). Once you have a step_span_id from the step list, fetch one step via Step detail.

Start a saga​

curl -sS -X POST "$ENGINE_URL/v1/sagas/start" \
-H "Content-Type: application/json" \
-d '{
"namespace": "default",
"name": "minimal-saga",
"version": "0.0.1",
"input": {}
}'

Response (202 Accepted):

{ "trace_id": "7f3a9c2e1b4d8f0a6e5c3b2a1d9f8e7c", "created": true }

created is false when the same (namespace, name, version, idempotency_key) was already used — the response still returns the existing trace_id.

Request body​

Definition lookup uses composite key (namespace, name, version). Omitted namespace resolves to "default".

FieldRequiredDescription
nameyesSaga definition name
versionyesSaga definition version
namespaceyes (defaults to "default" if omitted)Definition namespace
inputno (default {})Initial saga context object
idempotency_keynoDuplicate starts with the same (namespace, name, version, idempotency_key) return the existing trace_id. Reusing a key for a different definition in the same namespace returns 409.

When you start a saga, Warden stores your payload under the input key in saga context — not at the context root. Manifest bindings use JSONPath like $.input.repo; when.cel and policy CEL expose the same shape as top-level input (for example input.owner). See Saga manifests → Bindings.

Definition-scoped idempotency

Start idempotency keys are scoped to (namespace, name, version) — not global across all saga definitions. The same idempotency_key string in two different namespaces starts two independent instances. Reusing a key for a different definition in the same namespace is rejected with 409.

CLI equivalent: warden start saga -n minimal-saga -v 0.0.1 --namespace default.

Poll saga status​

ENGINE_URL=http://127.0.0.1:8000
TRACE_ID=<from start response>

curl -sS "$ENGINE_URL/v1/sagas/$TRACE_ID"

Returns one saga object (404 if unknown). Optional ?namespace= must match the instance row when set.

For fleet browsing (not a single known id), use the collection endpoint:

curl -sS "$ENGINE_URL/v1/sagas?in_flight=true"
# or: ?failed=true ?status=RUNNING ?parent_trace_id=...

Optional query parameters on the collection:

ParameterDescription
trace_idFilter to one saga (prefer path GET when that is all you need)
in_flighttrue — non-terminal sagas (PENDING, RUNNING, AWAITING_HUMAN, AWAITING_RECOVERY, COMPENSATING); do not combine with status filters
failedtrue — only FAILED sagas
statusFilter by saga status (repeatable)
namespaceFilter by namespace

Path GET returns 404 when the id is unknown. Collection filters return an empty items array when nothing matches.

Each saga object includes definition_id plus definition_name / definition_version (copied from the catalog at saga start; may be null on pre-migration rows). Those labels survive a later hard-delete of the definition row.

CLI equivalent: warden list sagas --trace-id $TRACE_ID (uses path GET; table shows name@version when labels are present).

Poll step rows​

curl -sS "$ENGINE_URL/v1/sagas/$TRACE_ID/steps"

Optional query filters: namespace=default (must match instance row if set), repeatable status=IN_PROGRESS (etc.), limit / offset (same defaults as other list endpoints). Returns 404 if no saga row exists for that trace_id.

Each item includes step_span_id, step_id, status, order_index, step_kind, worker, compensates_span_id (set on undo rows), timestamps, and error_details (nullable — present when the step failed).

CLI equivalent: warden list steps --trace-id $TRACE_ID --json — use it to inspect error_details on failed steps.

Step detail​

When you need resolved inputs, outputs, or prompt refs for one step (not just list metadata), call the single-step route — this is separate from saga-level polling above:

GET /v1/sagas/{trace_id}/steps/{step_span_id}?namespace=default

Returns one step row with resolved_arguments, output_payload, and prompt_ref in addition to the list fields (status, timing, error_details, etc.).

CLI equivalent: warden show step <trace_id> <step_span_id> or warden show step <trace_id> --step-id <step_id>.

Poll loop example​

Poll saga and step endpoints until saga status is terminal (COMPLETED, FAILED, or COMPENSATED). Parse JSON with your HTTP client or language library — do not rely on shell grep against raw JSON.

ENGINE_URL=http://127.0.0.1:8000
TRACE_ID=<your trace_id>

# Example: poll every 2s (stop when saga status is terminal)
while true; do
curl -sS "$ENGINE_URL/v1/sagas/$TRACE_ID"
curl -sS "$ENGINE_URL/v1/sagas/$TRACE_ID/steps"
sleep 2
done

For interactive polling from the terminal, prefer warden list sagas --trace-id $TRACE_ID --watch and warden list steps --trace-id $TRACE_ID --watch — see Start and monitor (CLI).

Saga and step statuses​

Saga statusMeaning
PENDINGInstance created; scheduling in progress
RUNNINGActively executing steps
AWAITING_HUMANHITL hold — HITL
COMPENSATINGRolling back completed steps
COMPLETEDTerminal success
FAILEDTerminal failure
COMPENSATEDTerminal after successful undo

For compensation behavior on FAILED / COMPENSATING, see the Compensation guide.

Filter in-flight sagas: GET /v1/sagas?trace_id=$TRACE_ID&in_flight=true (do not combine with status filters).

Stuck steps​

If a step stays IN_PROGRESS while the worker is healthy, wait for recovery timeouts first, then call operator recovery:

curl -sS -X POST "$ENGINE_URL/v1/sagas/$TRACE_ID/steps/$STEP_SPAN_ID/retry-step?namespace=default" \
-H "Content-Type: application/json" \
-d '{}'

If the response is 202 with "status": "claim_active", a worker still holds a non-stale claim — wait for automatic reap or resend with "force": true (commit steps also need "allow_destructive": true). Full ladder: Recovery.

What's next​

If the saga pauses at AWAITING_HUMAN, submit a decision via HITL. If a step is stuck in IN_PROGRESS, see Recovery. CLI equivalent: Start and monitor. Schema details: API Reference — Post Sagas Start, Get Saga, Get Sagas, Get Saga Steps.