Ralph Loops: Working Through a Task List
The Ralph pattern runs an agent over and over against a list of tasks: pick a task, attempt it, check the result, learn from failures, move on. In FYAI you build it from two workflows:
Orchestrator workflow Task workflow (one task)
───────────────────── ────────────────────────
Trigger Trigger
└─ Loop Start (For Each over tasks) └─ Loop Start (Count, up to 3 attempts)
└─ Mission ──── runs once per task ──► └─ Implement (Agent)
└─ Loop End └─ Check (Agent or Function)
└─ Summarise (Agent) └─ Loop End (break when PASS)
The task workflow decides what "done" means — its checks are yours to define. The orchestrator is generic: it hands each task to the task workflow and collects the outcomes.
Building blocks
| Feature | Where | What it gives you |
|---|---|---|
| For Each loop | Loop Start → Loop Type | One pass per list item, with ${current_item}, ${current_index} (from 0), ${items_remaining} and ${loop_total}. |
| Count loop with a break condition | Loop Start + Loop End Break Condition | Retry until a check passes, up to Max Iterations. |
| Track output across iterations | Loop Start | ${<name>} = the last pass's output, ${<name>_history} = every pass's output, as a list. |
| Mission node | Palette | Runs the task workflow as a sub-workflow for each task. |
| Output variable name | Function and Mission nodes | Keeps a result under a name; JSON results are parsed, with ${<name>_<key>} for each top-level key. |
| Run state | API / Platform MCP | A key–value store per workflow that persists between runs. |
See the node reference and Passing Data Between Steps for details.
1. Build the task workflow
This workflow receives one task as its initial message and keeps trying until its check passes.
-
Create a workflow named, say, "Implement one task". Remove the default attributes — sub-workflows don't receive attribute values.
-
Add Trigger → Loop Start → Implement (Agent) → Check (Agent or Function) → Loop End, connected in that order.
-
Loop Start: Loop Type Count, Max Iterations
3. Turn on Track output across iterations with Output variable nameattempts. -
Implement — your implementing agent. Input Message Template:
${initial_message}Previous attempts and their check results (empty on the first try):${attempts_history} -
Check — an agent (or a function) that runs your quality checks: tests, lint, a review. Its instructions must end with exactly one line,
VERDICT: PASSorVERDICT: FAIL — <reason>. Input message:Check this work against the task's acceptance criteria and run the checks.Task:${initial_message}Work:${node_3_result}(Type
$and pick the Implement node rather than typingnode_3, so the reference points at the right node.) -
Loop End: Break Condition — Source Check → result, operator contains, value
VERDICT: PASS. -
Save. To test it, click ▶ on any node and type a sample task into Input message. This runs the whole task workflow with that text as its initial message — see Testing Workflows.
Each attempt's output (implementation plus verdict) is added to ${attempts_history}, so the next attempt sees why the last one failed.
2. Build the orchestrator
-
Create a workflow named, say, "Work through tasks".
-
Replace the default attributes with one Text Input attribute named
tasks, Number of Lines10, Description "Tasks (JSON list)". Add a Button attribute with Trigger ticked if you want a labelled launch button. -
Add Trigger → Loop Start → Mission → Loop End → Summarise (Agent).
-
Loop Start: Loop Type For Each, Collection Variable
tasks, Max Iterations (safety cap)50. Turn on Track output across iterations with Output variable nametask_results. -
Mission: Workflow "Implement one task". Input Message Template:
Task ${current_index} (${items_remaining} remaining after this one):${current_item} -
Summarise: connected from Loop End. Input message:
Below is the output for each task, in order. For each, report the task,whether its final verdict was PASS or FAIL, and the reason.${task_results_history} -
Save.
3. Run it
Go to Build → Missions, choose "Work through tasks", and paste the task list into Tasks (JSON list):
[
{"id": "1", "title": "Add a /health endpoint", "acceptance_criteria": "GET /health returns 200"},
{"id": "2", "title": "Add request logging", "acceptance_criteria": "Method, path, status and duration are logged"}
]
Launch. The orchestrator's canvas shows the loop working through the list; each task starts its own child mission, which you can open from mission history to watch its attempts.
How it behaves
- The list is read once, when the loop starts. Changing the variable inside the loop doesn't add or remove items.
- A task that never passes uses up its attempts and returns its last
VERDICT: FAIL; the orchestrator moves on to the next task. - A failed node stops everything. If an agent or function errors (as opposed to reporting
FAIL), the whole run fails. Give flaky nodes Max Retries. ${current_item}is inserted as text. Templates can't read a field such ascurrent_item.titledirectly; pass the whole item and let the agent read it. Condition rules can read fields (for example${current_item.priority}), but only rules written through the API.- The Mission node's output is a JSON object of every node result in the task workflow, keyed by node ID. That's why the check's verdict is a recognisable line you can search for with contains.
- Costs add up. Every attempt of every task is a full agent run. Set Max Iterations deliberately and watch Avg Cost on the workflow list.
Keeping progress between runs
Loop variables last for one run. To carry a progress log or learned patterns across runs, use the workflow's run state — a key–value store that persists between runs:
| Method | Path |
|---|---|
| List all keys | GET /workflows/{workflow_id}/run-state |
| Read a key | GET /workflows/{workflow_id}/run-state/{key} |
| Write a key | PUT /workflows/{workflow_id}/run-state/{key} with body {"value": ...} (any JSON) |
| Delete a key | DELETE /workflows/{workflow_id}/run-state/{key} |
| Clear everything | DELETE /workflows/{workflow_id}/run-state |
The Platform MCP server exposes the same operations (get_workflow_run_state, get_workflow_run_state_key, set_workflow_run_state, delete_workflow_run_state_key, clear_workflow_run_state). There is no built-in node for run state; read and write it from outside the run, or from a function or agent that calls the API.