Lesson 5: Module 05: Control Flow and Loops
Chain steps together, branch conditionally, and build iterative pipelines.
Learning Objectives
- Organize steps into named flows (a named sequence of steps)
- Use multiple flows for organization and control
- Branch with
decidesteps for conditional routing - Iterate with control flow patterns
Complexity Ladder: Levels 2-4 (Conditional, Iterative, Resilient); branching, loops, and error handling.
The Concept: Controlling the Flow
Until now, our machines have been linear: Step A, then Step B, then Step C. Real workflows need branching (“if this, do that”), routing (“send billing issues here, support issues there”), and iteration (“process each item”). Mashin provides all of these through chains and control flow constructs.
Think of chains as chapters in a recipe book. The main chapter (:main) is your starting point. You can reference other chapters (run) — follow those instructions, then come back. Or you can jump to a chapter (goto) — like “start over from step 1” without coming back.
Chains are named sequences of steps. Every machine has at least one flow — :main — which is the entry point. You can define additional chains for organization, error handling, or looping.
Here is how run (call-and-return) and goto (tail-call / jump) differ visually:
run flow(:process) goto flow(:iterate) (Call-and-return) (Tail-call / jump)
flow :main flow :iterate ┌─────────────────┐ ┌─────────────────┐ │ step :setup │ │ step :work │ │ │ │ │ │ │ │ ▼ │ │ ▼ │ │ run :process ───┼──► flow :process │ goto :iterate ──┼──┐ │ │ │ (runs, then │ │ │ │ ▼ │ returns here) │ step :never_runs│ │ │ step :finalize │ │ (unreachable!) │ │ └─────────────────┘ └─────────────────┘ │ ▲ │ └──────────────┘ (loops back)Notice that run returns to the calling flow so the next step (:finalize) can execute, while goto jumps away permanently — any steps after it are unreachable.
Start With Koda
Koda requires a free account. Sign in or create an account to use Koda exercises throughout this course. If you’re not signed in yet, read on; the exercises will be here when you’re ready.
Ask Koda:
“Build a machine that processes a list of URLs. For each URL, fetch the content and summarize it. If a fetch fails, skip that URL and continue with the next one.”
Check that Koda uses for_each (iterate over each item in a list) for iteration and proper error handling. The machine should use loop(:item) (access the current element in a for_each loop) inside the for_each block to access each URL.
Chains and Chain Control
Multiple Chains
machine processor "Pipeline Processor"
accepts data as string, is required
responds with result as map
implements flows flow main compute validate {valid: length(input.data) > 0}
ask process_data, using: "anthropic:claude-haiku-4" with task "Process this data: ${input.data}" returns output as map
compute finalize {result: steps.process_data.output}
flow process ask process_data, using: "anthropic:claude-haiku-4" with task "Process this data: ${input.data}" returns output as maprun vs goto
These two control flow primitives have critically different behavior:
Multiple flows are used for organization and modularity.
Conditional Execution
decide Steps
Use decide steps for conditional routing:
decide route_by_severity when steps.check.severity == "critical" ask alert, from: "@mashin/actions/http/post" url: "https://alerts.example.com/critical" otherwise compute log_info {logged: true}Branching Patterns
decide process_or_reject when steps.validate.valid == true ask process, using: "anthropic:claude-haiku-4" with task "Process: ${input.data}" otherwise compute reject {error: "Invalid input", rejected: true}Multi-Way Routing
Route execution based on a value:
decide route_by_format when input.output_format == "summary" ask summarize, using: "anthropic:claude-haiku-4" with task "Summarize: ${input.text}" when input.output_format == "structured" ask extract, using: "anthropic:claude-sonnet-4" with task "Extract structured data from: ${input.text}" returns entities as list topics as list otherwise compute passthrough {result: input.text}Building It: Multi-Step Pipeline with Branching
A document processor that validates, routes by format, and handles errors:
machine document_pipeline "Document Pipeline"
accepts document as string, is required format as string
responds with result as map format_detected as string
implements compute validate {valid: length(input.document) > 10}
ask detect_format, using: "anthropic:claude-haiku-4" with task "What format is this document? Options: markdown, json, plain.\n\n${input.document}" returns format as string
compute resolve_format {format: input.format != "auto" ? input.format : steps.detect_format.format}
decide route_by_format when steps.resolve_format.format == "json" compute process_json {result: {type: "json", parsed: true}, format_detected: "json"} when steps.resolve_format.format == "markdown" ask process_markdown, using: "anthropic:claude-haiku-4" with task "Extract headings and structure from this markdown:\n${input.document}" returns headings as list sections as number otherwise ask process_plain, using: "anthropic:claude-haiku-4" with task "Summarize this plain text document:\n${input.document}" returns summary as stringKey Syntax
// Flow definition (under implements)implements flows flow name // steps go here
// Single flow (implicit, no flows wrapper needed)implements compute step_a {result: "done"}
// Decide step for branchingdecide name when condition // steps for this branch otherwise // fallback stepsCommon Mistakes
-
Overcomplicating control flow. Keep flows linear when possible. Use
decidesteps only when you need conditional routing based on runtime values. -
Infinite loops without limits. Always include a check-and-exit condition for iterative flows. The flow depth limit (100) will catch runaway loops, but your logic should stop before that.
What’s Next
In Module 06, you’ll combine everything from Modules 01-05 to build a full ReAct (Reason-Act-Observe) agent, the loop pattern that powers autonomous AI systems.