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) | |
|---|---|---|
| Semantics | Tail-call: transfer control, no return | Call-and-return: execute, then continue |
| Steps after | Abandoned (never execute) | Execute after the called flow completes |
| Stack | Constant (tail-call optimized) | Grows with each nested call |
| Use case | Loops, state transitions, pipelines | Subroutines, 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
| Language | Keyword |
|---|---|
| English | goto |
| Spanish | ir a |
| French | aller a |
| German | gehe 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