Skip to content

goto

goto

Tail-call jump to a named flow. When a step sequence reaches goto flow(name), execution transfers to the target flow and does not return. Any steps after the goto in the current flow are abandoned. This is the mechanism for looping, state machines, and pipeline stages where you want to move forward without building up a call stack.

When to use

Use goto when you need to:

  • Loop by jumping back to the start of a flow
  • Implement state machines where each state is a flow
  • Build pipeline stages that hand off to the next stage permanently
  • Avoid call-stack buildup in long-running iterative processes

Use run flow(name) instead when you want call-and-return semantics (execute the target flow, then continue with the remaining steps in the current flow).

goto vs run

goto flow(name)run flow(name)
SemanticsTail-call: transfer control, no returnCall-and-return: execute, then continue
Steps afterAbandoned (never execute)Execute after the called flow completes
StackConstant (tail-call optimized)Grows with each nested call
Use caseLoops, state transitions, pipelinesSubroutines, shared logic

Syntax

goto flow(<flow_name>)

The flow_name must reference a named flow defined in the same machine’s flows block.

Depth protection

Both goto and run are subject to flow depth protection. The runtime tracks the number of flow transitions and enforces a maximum depth (100 by default). If a goto loop exceeds this limit, execution halts with a depth error. This prevents infinite loops from consuming resources indefinitely.

Examples

Simple loop

machine poller
accepts
url as text, is required
max_attempts as number, default: 10
responds with
result as text
attempts as number
implements
state
attempt_count: number, default: 0
flows
flow check
ask status, from: "@mashin/actions/http/get"
url: input.url
decide done_or_retry
when steps.status.ready is true
{result: steps.status.data, attempts: state.attempt_count + 1}
when state.attempt_count >= input.max_attempts
{result: "timeout", attempts: state.attempt_count}
otherwise
compute increment
{attempt_count: state.attempt_count + 1}
goto flow(check)

State machine with flow-per-state

machine order_processor
accepts
order_id as text, is required
implements
flows
flow validate
ask check, from: "@mashin/actions/db/read"
query: "SELECT * FROM orders WHERE id = $1"
params: [input.order_id]
decide next_state
when steps.check.status is "paid"
goto flow(fulfill)
when steps.check.status is "pending"
goto flow(await_payment)
otherwise
compute reject
{status: "rejected", reason: "Invalid order state"}
flow await_payment
wait for payment_received
timeout: 86400000
goto flow(fulfill)
flow fulfill
ask ship, from: "@mashin/actions/shipping/create"
order_id: input.order_id
compute done
{status: "shipped", tracking: steps.ship.tracking_number}

Pipeline with goto (no return)

machine etl_pipeline
accepts
source as text, is required
implements
flows
flow extract
ask raw_data, from: "@mashin/actions/http/get"
url: input.source
goto flow(transform)
flow transform
compute cleaned
let rows = steps.raw_data.body.data
{records: rows.filter(r => r.valid).map(r => ({id: r.id, value: r.value.trim()}))}
goto flow(load)
flow load
ask save, from: "@mashin/actions/db/write"
records: steps.cleaned.records
compute report
{loaded: steps.cleaned.records.length, status: "complete"}

Governance

goto is a pure control-flow construct. It does not perform I/O, does not cross machine boundaries, and does not require any permissions. However, every goto transition is recorded in the behavioral ledger as a control-flow event with the source flow, target flow, and transition index (Inv 5: Control-Flow Observability).

Translations

LanguageKeyword
Englishgoto
Spanishir a
Frenchaller a
Germangehe zu
Japanese移動
Chinese跳转
Korean이동

See also

  • run - Call-and-return flow execution
  • flow / flows - Named flow definitions
  • decide - Conditional branching (often used before goto)
  • implements - Section where flows and steps live