Skip to main content
Version: 0.5.0

πŸ“– Verb Reference

This page documents all 31 saga step types ("verbs") supported by the engine.

Quick-reference table​

VerbLicense groupOne-liner
actioncommonDispatch a step to an external worker and pause until it replies.
decisioncommonEvaluate a stored rule table and branch on its output.
switchcommonEvaluate a CEL expression and branch on the string result.
errorcommonImmediately fail the saga with a code and message.
noopcommonPlaceholder β€” do nothing and advance.
end(terminal)End the run as succeeded.
set_varcommonWrite a literal or CEL-computed value into a variable.
transformcommonEvaluate a CEL expression and store the result.
mergecommonMerge a CEL-evaluated map into an existing variable.
filtercommonKeep list elements that satisfy a CEL predicate.
mapcommonTransform each element of a list with a CEL expression.
assertcommonFail the saga if a CEL expression is not truthy.
logcommonEmit a structured log line at a chosen level.
metric_emitobservabilityAppend a named metric event to the run's audit stream.
http_requestcommon / external_io_advancedΒΉIssue a synchronous outbound HTTP request.
webhook_emitexternal_io_advancedPOST a JSON payload to an external URL.
wait_durationwaitsPause for a Go duration (e.g. "5s", "1h30m").
wait_untilwaitsPause until an RFC3339 absolute timestamp.
wait_for_signalevents_and_signalsPause until a named external signal arrives (with optional timeout).
wait_for_eventevents_and_signalsPause until a matching event topic + header subset arrives.
emit_signalevents_and_signalsSend a signal to another (or the same) run.
emit_eventevents_and_signalsPublish an event via the configured EventEmitter.
whileloops_and_recoveryLoop while a CEL condition holds; exit via branching.
try_catchloops_and_recoveryPush an error-handler frame; jump to catch on any step error.
cancelloops_and_recoveryCancel a run (self or a target).
parallelparallel_controlFan out N branches and join when a strategy is satisfied.
foreachparallel_controlFan out one branch per element of a CEL-evaluated list.
sub_sagacompositionsStart a child workflow and pause the parent until it finishes.
spawn_sagacompositionsFire-and-forget: start a child workflow and continue immediately.
manual_approvalhuman_interactionCreate a user task; pause until an assignee submits it.
collect_inputhuman_interactionLike manual_approval but form_schema is required.

ΒΉ http_request is common when method is GET with no secret_ref; all other configurations require the external_io_advanced group.


πŸ’‘ License groups gate verbs at publish time and at runtime. In development the StubAllowAll licensing resolver (used by saga.InMemory() and saga.New with no Licensing set) permits every group. In production, override per-request with the X-Feature-Override header or the FeatureOverrides field on the run start request.


Core / Control​

action​

Dispatches the step to a named worker process and pauses the saga until the worker replies.

InputRequiredNotes
step.Actionβœ…"service.name" β€” must contain a dot. Identifies the worker queue.
step.InputsoptionalForwarded verbatim to the worker as inputs.

Output: The worker's result map is merged directly into Variables. There is no out_var β€” whatever keys the worker returns become top-level variables.

⚠️ In embedded mode (saga.InMemory()) the action verb publishes to an in-process publisher. You still need a worker goroutine (or service mode) to actually handle the dispatch; a plain in-memory saga with no registered worker handler will leave the run paused indefinitely.

Example: examples/workflows/action.json


decision​

Evaluates a stored decision-table rule and returns its output map. The engine reads result["branch"] to pick a route from step.Branches.

InputRequiredNotes
rule_idβœ…Stable ID of a published RuleDefinition.
inputs_mapoptionalmap[string]string β€” maps rule input keys to variable names. Omit to pass Variables directly.

Output: The rule's full output map (including branch) is merged into Variables.

Example: examples/workflows/decision.json


switch​

Evaluates a CEL expression to a string and routes via step.Branches. Simpler than decision when no rule table is needed.

InputRequiredNotes
exprβœ…CEL expression over Variables; must produce a string.

Output: {"branch": "<result>"} β€” the engine picks step.Branches[<result>].Next. An unknown branch value is a runtime error.

Example: examples/workflows/switch.json


error​

Immediately fails the saga as a non-retryable error.

InputRequiredNotes
codeβœ…Error code string surfaced in the run's error record.
messageoptionalHuman-readable description.

Output: None β€” the run terminates as failed.

Example: examples/workflows/error.json


noop​

Does nothing. Advances to step.Next. Useful as a placeholder during development or as a join point for multiple branches.

Inputs: none. Output: empty map.

Example: examples/workflows/noop.json


end​

Marks the run as succeeded. Every workflow needs at least one end step. It is dispatched inline by the engine (not via a queue) so no license group applies.

Inputs: none. Output: none β€” the saga terminates.


Data​

set_var​

Writes a value to a variable. Use it to seed variables before CEL verbs read them.

InputRequiredNotes
out_varβœ…Destination variable name. Dotted keys (a.b.c) write into nested maps.
valueone of value/exprLiteral value β€” passed through unchanged.
exprone of value/exprCEL expression over Variables; result is written. expr wins when both are present.

Output: {out_var: <value>}.

Example: examples/workflows/set_var.json


transform​

Evaluates a CEL expression and writes the result to a named variable. Equivalent to set_var with expr.

InputRequiredNotes
exprβœ…CEL expression over Variables.
out_varβœ…Destination variable name.

Output: {out_var: <result>}.

Example: examples/workflows/transform.json


merge​

Evaluates a CEL expression that must produce a map, then deep-merges it into an existing variable.

InputRequiredNotes
fromβœ…CEL expression β†’ map.
intoβœ…Name of the destination variable. Dotted paths are supported.

Output: The merged variable value under its existing key.

Example: examples/workflows/merge.json


filter​

Keeps list elements where a CEL predicate is truthy.

InputRequiredNotes
listβœ…CEL expression β†’ list.
exprβœ…CEL predicate; the current element is bound as _.
out_varβœ…Variable to write the filtered list to.

Output: {out_var: [filtered list]}.

Example: examples/workflows/filter.json


map​

Transforms every element of a list with a CEL expression.

InputRequiredNotes
listβœ…CEL expression β†’ list.
exprβœ…CEL transform; element bound as _.
out_varβœ…Variable to write the mapped list to.

Output: {out_var: [mapped list]}.

Example: examples/workflows/map.json


assert​

Fails the saga if a CEL expression is not truthy. Use it for invariant checks mid-workflow.

InputRequiredNotes
exprβœ…CEL boolean expression.
codeoptionalError code emitted on failure (default "assertion_failed").

Output: Empty map on success; non-retryable error on failure.

Example: examples/workflows/assert.json


Observability​

log​

Emits a structured log line via the engine's logger.

InputRequiredNotes
messageβœ…Log message string.
leveloptional"info" (default) | "warn" | "error".

Output: Empty map.

Example: examples/workflows/log.json


metric_emit​

Appends a named metric event to the run's audit stream. (Prometheus side-channel wiring is planned for a future release.)

InputRequiredNotes
nameβœ…Metric name string.
valueβœ…Numeric value.
labelsoptionalmap[string]string of label key/value pairs.

Output: Empty map.

Example: examples/workflows/metric_emit.json


I/O​

http_request​

Issues a synchronous outbound HTTP request and merges the response into Variables.

InputRequiredNotes
urlβœ…Target URL.
methodoptionalHTTP method (default "GET").
headersoptionalmap[string]any β†’ request headers.
bodyoptionalAny value; JSON-marshalled into the request body.
timeout_soptionalRequest timeout in seconds (default 30).
secret_refoptionalSecret key resolved via the Secrets resolver β†’ set as Authorization header.
out_varoptionalOutput prefix (default "http_result").

Output keys (with out_var = "http_result"):

  • http_result β€” parsed JSON body (or raw string if non-JSON).
  • http_result_status β€” int64 HTTP status code.
  • http_result_headers β€” map[string]string of response headers.

⚠️ License group is common only for GET with no secret_ref. Any other method or authenticated request requires the external_io_advanced group.

Example: examples/workflows/http_request.json


webhook_emit​

POSTs a JSON payload to an external URL, with optional HMAC-SHA256 request signing.

InputRequiredNotes
urlβœ…Target URL.
bodyβœ…Any value; JSON-marshalled.
secret_refoptionalSecret key β†’ X-Webhook-Sig: sha256=<hex> header.
timeout_soptionalTimeout in seconds (default 15).
headersoptionalAdditional request headers.
asyncoptionalbool; default false. When true, fires the request in a goroutine and returns immediately (failures are logged only).
out_varoptionalOutput prefix (default "webhook_result").

Output (sync mode): {out_var}_status β€” int64 HTTP status code.
Output (async mode): {out_var}_async: true.

Example: examples/workflows/webhook_emit.json


Timing​

wait_duration​

Pauses the saga for a duration expressed as a Go duration string.

InputRequiredNotes
durationβœ…Go duration string e.g. "5s", "1h30m", "72h".

The engine's timer dispatcher wakes the run when the deadline passes.

Example: examples/workflows/wait_duration.json


wait_until​

Pauses the saga until an absolute point in time.

InputRequiredNotes
timestampβœ…RFC3339 datetime string, e.g. "2026-01-01T09:00:00Z".

Example: examples/workflows/wait_until.json


Events & Signals​

wait_for_signal​

Pauses the saga until a named external signal arrives via POST /api/v1/sagas/{run_id}/signal/{name}.

InputRequiredNotes
nameβœ…Signal name to await.
timeout_soptionalMax seconds to wait. Omit to wait indefinitely.

Timeout routing: when the deadline fires while the signal has not yet arrived, the engine checks step.Branches["timeout"].Next first. If that branch exists, the run routes there instead of step.Next β€” handy for escalation paths.

πŸ’‘ Wire a timeout branch to an escalation step to handle missed approvals or SLA breaches without any extra polling.

Example: examples/workflows/wait_for_signal.json


wait_for_event​

Pauses the saga until an event with a matching topic (and optional header subset) arrives via the event bus.

InputRequiredNotes
topicβœ…Event topic / RabbitMQ routing key to await.
headersoptionalmap[string]any β€” incoming event headers must contain all of these key/value pairs (string equality).
timeout_soptionalNumber of seconds to wait. On timeout the run routes to the step's timeout branch if defined, else to next. Omitted = wait indefinitely.

πŸ’‘ Add a "timeout" entry to the step's branches to escalate when no matching event arrives before timeout_s (same pattern as wait_for_signal).

Example: examples/workflows/wait_for_event.json


emit_signal​

Sends a signal to another run (the send-side complement of wait_for_signal). If the target is currently paused awaiting that signal, it is consumed and the target advances immediately.

InputRequiredNotes
run_idβœ…UUID of the target run.
nameβœ…Signal name.
payloadoptionalmap[string]any carried with the signal.

Output: Empty map.

Example: examples/workflows/emit_signal.json


emit_event​

Publishes an event via the configured EventEmitter (in-process when embedded; RabbitMQ in service mode).

InputRequiredNotes
topicβœ…Event topic / routing key.
headersoptionalmap[string]any β†’ map[string]string.
payloadoptionalmap[string]any event payload.

Output: Empty map.

πŸ’‘ In embedded mode the in-process emitter both wakes runs awaiting the topic and runs the trigger dispatcher, so matching record_transition triggers start new runs β€” parity with service mode (no broker needed).

Example: examples/workflows/emit_event.json


Loops & Recovery​

while​

Loops while a CEL condition evaluates to true.

InputRequiredNotes
conditionβœ…CEL boolean expression over Variables.
max_iterationsoptionalDefault 100; hard cap 10000. Prevents runaway loops.

Output: {"branch": "continue" | "exit"}.

Wiring pattern:

  • step.Branches.continue β†’ next β€” first step of the loop body.
  • step.Branches.exit β†’ next β€” first step after the loop.
  • The body's last step sets next back to the while step.

An iteration counter is maintained at Variables._while.<step_id>.iter.

Example: examples/workflows/while.json


try_catch​

Pushes an error-handler frame. If any step inside the protected region errors, the saga jumps to the catch step instead of failing. The error context is written to Variables._error.

InputRequiredNotes
tryβœ…[]string of step IDs in the protected region. Used by ValidateDefinition to reject disallowed nesting (e.g. parallel inside try).
catchβœ…Step ID to jump to on error.

Wiring: set step.Next to the first step inside the try body. The body's last step sets next to whatever comes after the protected region.

Example: examples/workflows/try_catch.json


cancel​

Cancels a run.

InputRequiredNotes
run_idoptionalUUID of the target run. Omit (or set to the current run's ID) to self-cancel β€” the current run ends as cancelled.
reasonoptionalHuman-readable reason string.

Self-cancel: returns ErrSagaCancelled; the run ends immediately as cancelled.
Target-cancel: cancels the target run and the current run continues to step.Next.

⚠️ When you cancel a run that is a child of a parallel join, the join may be left waiting if the join strategy expects all children. The cancelled child is counted as terminal, so with join_strategy: "all" the parent will eventually time out or remain paused unless all other children also complete.

Example: examples/workflows/cancel.json


Parallelism​

parallel​

Fans out N branches as child runs and pauses the parent until the join strategy is satisfied.

InputRequiredNotes
branchesβœ…[]any of branch objects or a CEL string β†’ list. Each branch: long-form {"start": "step_id", "steps": [...]} or short-form {"type": "...", "inputs": {...}}.
join_strategyoptional"all" (default) β€” wait for every branch. "quorum" β€” wake after quorum_n successes.
quorum_nrequired when quorumPositive integer ≀ branch count. Can also be a CEL string evaluated at runtime.

Output: Each branch's variables are aggregated into Variables._parallel.<step_id>.branches when the parent wakes.

⚠️ Remaining branches continue to run after a quorum wake β€” they are not cancelled.

Example: examples/workflows/parallel.json


foreach​

Fans out one child run per element of a CEL-evaluated list (parallel mode only in v1).

InputRequiredNotes
listβœ…CEL expression β†’ list.
bodyβœ…[]any of step objects forming the loop body.
startβœ…ID of the first step inside body.
paralleloptionalbool; default true. Sequential mode is not yet supported β€” use while with an index counter for sequential loops.

Each child run receives Variables._foreach_item (the element) and Variables._foreach_index (zero-based index). An empty list advances without spawning.

Example: examples/workflows/foreach.json


Composition (Call Tree)​

sub_saga​

Starts a named workflow as a child saga and pauses the parent until the child reaches a terminal state.

InputRequiredNotes
workflow_idβœ…Stable ID of the child WorkflowDefinition.
inputsoptionalmap[string]any passed as the child's initial inputs.
entrypointoptionalNamed entry point on the child definition (see Entrypoints). Defaults to Start.

Output: Empty map on parent resume (child variables are not automatically merged; wire a set_var/transform after if needed).

Example: examples/workflows/sub_saga.json


spawn_saga​

Starts a named workflow as a fire-and-forget child. The parent continues immediately to step.Next without waiting.

InputRequiredNotes
workflow_idβœ…Stable ID of the child workflow.
inputsoptionalmap[string]any passed as the child's initial inputs.
entrypointoptionalNamed entry point on the child. Defaults to Start.

Output: Empty map; parent is not paused.

Example: examples/workflows/spawn_saga.json


Human Interaction​

manual_approval​

Creates a user task and pauses the saga until the assignee submits it via POST /api/v1/sagas/{run_id}/user_task/{task_id}/submit.

InputRequiredNotes
assigneeβœ…User ID or role expected to submit.
due_inoptionalGo duration string β€” sets due_at = now + due_in.
form_schemaoptionalmap[string]any rendered in the admin UI.

Output: None on pause; the submitted form data is available after the run resumes.

Example: examples/workflows/manual_approval.json


collect_input​

Like manual_approval but form_schema is required. Use this when the workflow needs structured data from the user (remediation notes, parameters, etc.) rather than a simple approve/reject.

InputRequiredNotes
assigneeβœ…User ID or role.
form_schemaβœ…map[string]any schema β€” must be non-empty.
due_inoptionalGo duration deadline.

Output: None on pause.

Example: examples/workflows/collect_input.json