> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberdesk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Looping Tools

> Repeat workflow steps deterministically over arrays or counts

Looping tools allow you to repeat the same sequence of actions multiple times over a collection of items or a fixed count. The system learns the pattern once during the first iteration, then deterministically replays it for all remaining iterations.

## Overview

Instead of manually repeating actions in a focused\_action step, use `start_loop` and `end_loop_iteration` to automate repetitive tasks:

* **Learn once, replay many**: The agent maps out one iteration, then the system replays it automatically
* **Access loop items**: Use the special `{{loop_item}}` runtime variable to reference the current item in a focused\_action step
* **Structured results**: Get detailed summaries of all iterations including successes and failures
* **Cache friendly**: Loops work seamlessly with trajectories for lightning-fast execution
* **Error handling**: Loops exit early if errors occur, returning partial results

## Actions

### start\_loop

Begin a loop iteration over a non-empty array or positive integer count.

**Parameters:**

* `text`: JSON array like `["item1", "item2", "item3"]` or integer like `5` for counting loops. If the loop source is a variable, do not wrap the variable in quotes: use `text={items}`, `text={$items}`, or `text={{items}}`.

**Usage:**

```
Use start_loop with text=["Alice", "Bob", "Carol"]
```

**What Happens:**

1. System creates a loop context with all items
2. Sets current iteration to 0 (first item)
3. Agent completes ONE full iteration manually
4. Agent calls `end_loop_iteration` when done
5. System automatically replays remaining iterations

If the resolved `text` contains no items to process, the loop is skipped instead of started. That includes values like `[]`, `null`, an empty string, or `0` / negative integer counts. In that case there is no active loop context, so you should continue with the steps after the loop and **not** call `end_loop_iteration`.

**The `{{loop_item}}` Variable:**

Access the current loop item using the special `{{loop_item}}` runtime variable:

* Simple items: `Type {{loop_item}}` → types "Alice" during iteration 0
* Nested objects: `{{loop_item.name}}` → accesses name field
* Array elements: `{{loop_item[0]}}` → accesses first array element
* Deep nesting: `{{loop_item.user.emails[0]}}` → accesses nested data

### end\_loop\_iteration

Mark the current iteration as complete and trigger replay of remaining iterations.

**Parameters:**

* `text`: Summary of this iteration (can use runtime variables for dynamic info)

**Usage:**

```
Use end_loop_iteration with text="Processed user {{loop_item}}: status={{status}}"
```

**What Happens:**

1. System evaluates runtime variables in the summary text
2. Adds iteration result to the results array
3. If iteration 0: Captures trajectory steps and replays iterations 1 to N-1
4. If recovery iteration: Replays from current + 1 to N-1
5. Returns JSON summary of all iterations when complete

**Summary Format:**

```json theme={null}
{
  "total_iterations": 3,
  "completed_iterations": 3,
  "successful": 3,
  "skipped": 0,
  "failed": 0,
  "iteration_results": [
    {"iteration": 0, "status": "success", "summary": "Processed user Alice: status=complete"},
    {"iteration": 1, "status": "success", "summary": "Processed user Bob: status=complete"},
    {"iteration": 2, "status": "success", "summary": "Processed user Carol: status=complete"}
  ],
  "message": "Loop completed: 3 successful, 0 skipped, 0 failed"
}
```

### skip\_loop\_iteration

Skip the **rest of the current loop iteration** and continue to the next loop item.

This is intended for **focused agents inside a loop context** (i.e., when a `focused_action` is running during an active `start_loop`). Use it when the current loop item is not actionable (missing data, not found on screen, wrong state, etc.) and you want to move on gracefully.

**Parameters:**

* `text`: Brief reason why the iteration is being skipped (this will appear in the loop summary)

**What Happens:**

1. The system records the skip reason for the current iteration
2. Any remaining steps for that iteration are not executed
3. The loop proceeds to the next item (remaining iterations continue as usual)
4. The final loop summary includes `status: "skipped"` entries and a `skipped` count

<Tip>
  **Important (how loop replay works):** The system records what you do in the **first** loop iteration (iteration `0`) and replays those recorded steps for iterations `1..N-1`.

  If iteration `0` immediately calls `skip_loop_iteration`, then the system didn’t learn any “real work” steps to replay — it mostly learned “skip”, so later iterations may also end up doing nothing useful.

  **Recommendation:** make sure iteration `0` is a normal “happy path” item (one that should be processed). Put a known‑good item first, or reorder/filter your input list so the first item won’t be skipped.

  If you're running from a **cached trajectory (muscle memory)**, this specific “iteration 0 teaches the replay” concern is not an issue — it’s fine if iteration `0` ends up being a `skip_loop_iteration`, because our system has already learned the full loop actions.
</Tip>

**Example – Skip when the current item is missing:**

```
1. Use start_loop with text=["Alice", "Bob", "Carol"]
2. Use focused_action: "Check whether {{loop_item}} exists in the list. If not found, skip this loop iteration."
3. (Inside focused_action, if not found) Use skip_loop_iteration with text="User not found: {{loop_item}}"
4. Continue with next item automatically
5. Use end_loop_iteration with text="Processed {{loop_item}}"
```

## Loop Types

### Array Loop

Iterate over a collection of items.

**Example - Loop over names:**

```
1. Use start_loop with text=["Alice", "Bob", "Carol"]
2. Click on user {{loop_item}} in the list
3. Extract their email and save as {{user_email}}
4. Use end_loop_iteration with text="Processed {{loop_item}}: {{user_email}}"
```

**Result:** System executes the pattern for Alice, then automatically replays for Bob and Carol.

### Integer Loop

Repeat actions a fixed number of times. The `{{loop_item}}` variable will be 0, 1, 2, etc. Positive integers create that many iterations; `0` or a negative count skips the loop entirely.

**Example - Download 5 reports:**

```
1. Navigate to reports page
2. Use start_loop with text=5
3. Click on report number {{loop_item}}
4. Download the report
5. Use end_loop_iteration with text="Downloaded report {{loop_item}}"
```

**Result:** Executes for items 0, 1, 2, 3, 4.

### Variable-Based Loop

Loop over data collected during the workflow using runtime or input variables.

**Example - Loop over runtime variable:**

```
1. Use focused_action to extract all order IDs from the screen. Save as {{order_ids}} as a JSON array
2. Use start_loop with text={{order_ids}}
3. Navigate to order {{loop_item}}
4. Extract order details
5. Use end_loop_iteration with text="Processed order {{loop_item}}"
```

**Example - Loop over input variable:**

```
Input variables: {patient_ids: ["P001", "P002", "P003"]}

1. Use start_loop with text={patient_ids}
2. Search for patient {{loop_item}}
3. Extract medical records
4. Use end_loop_iteration with text="Extracted data for {{loop_item}}"
```

### Passing Complex JSON Arrays via SDK

When creating a run programmatically, you can pass arrays of complex objects as input variables. The workflow can then loop over these objects and access nested fields.

**Workflow Prompt:**

```
1. Use start_loop with text={patients}
2. Search for patient {{loop_item.mrn}}
3. Verify name matches {{loop_item.first_name}} {{loop_item.last_name}}
4. Navigate to the {{loop_item.department}} department
5. Extract the latest lab results
6. Use end_loop_iteration with text="Processed {{loop_item.first_name}} {{loop_item.last_name}}"
```

**SDK Code to Create the Run:**

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { createCyberdeskClient } from 'cyberdesk';

    const client = createCyberdeskClient('YOUR_API_KEY');

    // Define your complex array of objects
    const patients = [
      { mrn: 'MRN-001', first_name: 'Alice', last_name: 'Smith', department: 'Cardiology' },
      { mrn: 'MRN-002', first_name: 'Bob', last_name: 'Johnson', department: 'Neurology' },
      { mrn: 'MRN-003', first_name: 'Carol', last_name: 'Williams', department: 'Oncology' }
    ];

    // Create the run with the array as an input variable
    const { data: run } = await client.runs.create({
      workflow_id: 'your-workflow-id',
      machine_id: 'your-machine-id',
      input_values: {
        patients: patients  // Pass the array directly - SDK handles JSON serialization
      }
    });

    console.log('Run created:', run.id);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from cyberdesk import CyberdeskClient, RunCreate
    import asyncio

    async def create_run_with_loop_data():
        client = CyberdeskClient('YOUR_API_KEY')
        
        # Define your complex array of objects
        patients = [
            {'mrn': 'MRN-001', 'first_name': 'Alice', 'last_name': 'Smith', 'department': 'Cardiology'},
            {'mrn': 'MRN-002', 'first_name': 'Bob', 'last_name': 'Johnson', 'department': 'Neurology'},
            {'mrn': 'MRN-003', 'first_name': 'Carol', 'last_name': 'Williams', 'department': 'Oncology'}
        ]
        
        # Create the run with the array as an input variable
        run_data = RunCreate(
            workflow_id='your-workflow-id',
            machine_id='your-machine-id',
            input_values={
                'patients': patients  # Pass the array directly - SDK handles JSON serialization
            }
        )
        
        response = await client.runs.create(run_data)
        print(f'Run created: {response.data.id}')

    asyncio.run(create_run_with_loop_data())
    ```
  </Tab>
</Tabs>

<Tip>
  **Accessing Nested Fields:** Inside your loop, use dot notation to access object properties:

  * `{{loop_item.mrn}}` → `"MRN-001"`
  * `{{loop_item.first_name}}` → `"Alice"`
  * `{{loop_item.department}}` → `"Cardiology"`

  For deeply nested data, chain the accessors: `{{loop_item.address.city}}` or `{{loop_item.contacts[0].email}}`
</Tip>

## Advanced Patterns

### Nested Data Access

When looping over objects or arrays, access nested fields:

**Example:**

```javascript theme={null}
// Input: [{"name": "Alice", "dept": "Sales"}, {"name": "Bob", "dept": "Engineering"}]

1. Use start_loop with text={employees}
2. Search for employee {{loop_item.name}}
3. Navigate to {{loop_item.dept}} department
4. Use end_loop_iteration with text="Processed {{loop_item.name}} from {{loop_item.dept}}"
```

### Combining With Async Extraction

Extract data asynchronously while looping for maximum efficiency:

**Example:**

```
1. Use start_loop with text=["Form1", "Form2", "Form3"]
2. Navigate to {{loop_item}}
3. Take screenshot with extract_prompt="Extract all form data as JSON" and process_async="run"
4. Use end_loop_iteration with text="Queued extraction for {{loop_item}}"
5. All extractions complete at end of run before generating final output
```

### Error Handling in Loops

Loops exit early if errors occur, returning partial results:

**Example:**

```
1. Use start_loop with text={document_names}
2. Open document {{loop_item}}
3. If document fails to open, use declare_task_failed with message "Cannot open {{loop_item}}"
4. Process the document
5. Use end_loop_iteration with text="Completed {{loop_item}}"
```

**If iteration 2 fails:** Loop returns summary with iterations 0-1 successful, iteration 2 failed, and `early_exit: true`.

## Cache Detection in Loops

By default, recorded loop steps use cache detection.
`end_loop_iteration` and `skip_loop_iteration` are control steps, so they do not use cache detection.
You can disable cache detection for an individual recorded loop step if a step is too noisy or
causes unnecessary cache misses.

### Enabling Loop Cache Detection

In the trajectory editor, cache detection is **on by default** for recorded loop steps that have a reference screenshot.
Toggle **Cache detection** off to force replay for that step without a screenshot comparison.
`end_loop_iteration` and `skip_loop_iteration` are control steps and do not use cache detection.
Steps added later in the UI also cannot use cache detection because they have no reference screenshot.

### Recovery Options

If cache detection fails mid-loop, the recovery agent is invoked with loop context and can:

| Option                | Use When                             | Result                              |
| --------------------- | ------------------------------------ | ----------------------------------- |
| `resume_trajectory`   | Minor issue (popup, timing)          | Retries cache detection             |
| `end_loop_iteration`  | You completed the iteration manually | Loop continues with remaining items |
| `skip_loop_iteration` | This item is problematic             | Skips to next item                  |
| `declare_task_failed` | Workflow is broken                   | Task fails cleanly                  |

If the recovery agent resolves the issue (via `resume_trajectory`, `end_loop_iteration`, or `skip_loop_iteration`), the trajectory is still saved.
If the agent calls `declare_task_failed`, no trajectory is saved.

<Note>
  Loop cache detection is optional per step. Leave it on for steps where visual state matters, and turn it off for steps that are noisy or do not need a screenshot check.
</Note>

## Important Limitations

<Warning>
  **Nested Loops Not Supported**: You cannot call `start_loop` while already in a loop. Complete the current loop first before starting a new one.

  If you attempt nested loops, the system will return an error: `"Cannot start_loop while already in a loop. Call end_loop_iteration first."`
</Warning>

<Tip>
  **Unclosed Loop Reminder**: If you haven't called `end_loop_iteration` after 20 steps, the system will append a reminder to your tool results. This prevents accidentally leaving loops open.
</Tip>

## Complete Examples

### Example 1: Discover and Process Patients (Runtime Variable Loop)

This example demonstrates a common pattern where the loop items are **discovered during the run** rather than passed as input. The agent extracts a list of patients from the screen and then loops over them.

**Workflow Prompt:**

```
1. Navigate to patient management system
2. Login with {username} and {$password}
3. Navigate to "Today's Appointments" page
4. Use focused_action with instruction "Extract all patient IDs visible in the appointments table. Return them as a JSON array of objects with fields: id, name, appointment_time. Save the result as {{todays_patients}}"
5. Use start_loop with text={{todays_patients}}
6. Click on patient {{loop_item.name}} (ID: {{loop_item.id}})
7. Use focused_action to verify the patient record loaded successfully
8. Navigate to "Demographics" tab
9. Use screenshot with extract_prompt="Extract patient demographics as JSON with fields: name, dob, address, phone" and process_async="batch"
10. Navigate to "Insurance" tab
11. Use copy_to_clipboard with text="insurance_id" to copy the insurance ID
12. Use end_loop_iteration with text="Completed {{loop_item.name}} ({{loop_item.id}}) - Appointment: {{loop_item.appointment_time}}, Insurance: {{insurance_id}}"
13. System will automatically process remaining patients
14. Export final report with all patient data
```

**Input Variables:**

```json theme={null}
{
  "username": "admin"
}
```

**What Happens:**

1. Agent logs in and navigates to the appointments page
2. `focused_action` analyzes the screen and extracts patient data, saving it as `{{todays_patients}}`:
   ```json theme={null}
   [
     {"id": "P12345", "name": "Alice Smith", "appointment_time": "9:00 AM"},
     {"id": "P67890", "name": "Bob Johnson", "appointment_time": "10:30 AM"},
     {"id": "P11223", "name": "Carol Williams", "appointment_time": "2:00 PM"}
   ]
   ```
3. `start_loop` begins iterating over this runtime-discovered array
4. Agent completes iteration 0 (Alice Smith) manually
5. System automatically replays iterations 1-2 for Bob and Carol
6. Each iteration accesses nested fields: `{{loop_item.id}}`, `{{loop_item.name}}`, `{{loop_item.appointment_time}}`

**Key Pattern:** The loop items aren't known ahead of time—they're discovered by `focused_action` during execution and stored as a runtime variable. This is powerful for workflows where you need to "find what's on the screen, then process each item."

### Example 2: Batch Download Forms

**Workflow Prompt:**

```
1. Navigate to forms portal
2. Use start_loop with text=10
3. Click on form row {{loop_item}}
4. Click "Download PDF" button
5. Wait 2 seconds for download
6. Use end_loop_iteration with text="Downloaded form {{loop_item}}"
7. Use execute_terminal_command to run "Get-ChildItem $env:USERPROFILE\Downloads\*.pdf | Select-Object -First 10 | ConvertTo-Json" to list downloaded files
```

**Execution Flow:**

* Iteration 0: Agent downloads form 0
* Iterations 1-9: System automatically downloads forms 1-9
* Terminal command lists all downloaded files
* Total time: \~10 seconds (cached) vs \~5 minutes (uncached)

### Example 3: Data Entry From Spreadsheet

**Workflow Prompt:**

```
1. Use execute_terminal_command to run "Import-Csv {csv_file_path} | ConvertTo-Json" to load spreadsheet data
2. Use focused_action to parse the JSON output and save the array as {{records}}
3. Navigate to data entry form
4. Use start_loop with text={{records}}
5. Click "New Record" button
6. Type {{loop_item.name}} in the Name field
7. Type {{loop_item.email}} in the Email field
8. Type {{loop_item.phone}} in the Phone field
9. Click "Save" button
10. Use focused_action to verify "Record saved successfully" message appears
11. Use end_loop_iteration with text="Entered record for {{loop_item.name}}"
12. Click "Close" to exit data entry
```

**Result:** Efficiently processes entire spreadsheet with validation at each step.

## Best Practices

<AccordionGroup>
  <Accordion title="Use focused_action for validation in loops">
    Add focused\_action steps within your loop to verify each iteration succeeded:

    ```
    1. Use start_loop with text={items}
    2. Process {{loop_item}}
    3. Use focused_action to verify processing succeeded
    4. Use end_loop_iteration with text="Verified {{loop_item}}"
    ```

    This ensures errors are caught immediately per iteration.
  </Accordion>

  <Accordion title="Parameterize summaries with runtime variables">
    Make iteration summaries dynamic by using runtime variables:

    ```
    Use end_loop_iteration with text="Processed {{loop_item}}: found {{count}} results, status={{status}}"
    ```

    This creates rich per-iteration results you can analyze later.
  </Accordion>

  <Accordion title="Prefer small, focused loop bodies">
    Keep loop iterations concise (5-15 steps). For complex processing:

    ```
    1. Use start_loop with text={orders}
    2. Use focused_action to process order {{loop_item}} completely
    3. Use end_loop_iteration with text="Completed {{loop_item}}"
    ```

    Let focused\_action handle complex logic per iteration.
  </Accordion>

  <Accordion title="Use runtime variables for conditional logic">
    Set flags during iterations for later decision-making:

    ```
    1. Use start_loop with text={invoices}
    2. Check invoice {{loop_item}} status
    3. If unpaid, save unpaid_count via upsert_runtime_values
    4. Use end_loop_iteration with text="Checked {{loop_item}}"
    5. After loop: If {{unpaid_count}} &gt; 0, send notification email
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Loop doesn't complete all iterations">
    **Cause:** An error occurred in one of the iterations.

    **Solution:** Check the loop summary's `iteration_results` array to see which iteration failed and why. Add focused\_action validation steps to catch errors early.
  </Accordion>

  <Accordion title="Loop is skipped immediately">
    **Cause:** The `start_loop` input resolved to no items, such as `[]`, `null`, an empty string, or `0`.

    **Solution:** Confirm the input or runtime variable really contains a JSON array (or a positive integer for counting loops). If it is intentionally empty, skip the loop body and continue with the steps that come after the loop. Do not call `end_loop_iteration` when no loop was started.
  </Accordion>

  <Accordion title="{{loop_item}} shows as literal text">
    **Cause:** Runtime variable replacement failed or loop wasn't started.

    **Solution:** Ensure `start_loop` was called before using `{{loop_item}}`. Check that the loop items array is valid JSON.
  </Accordion>

  <Accordion title="Nested loop error">
    **Cause:** Attempted to call `start_loop` while already in a loop.

    **Solution:** Complete the current loop with `end_loop_iteration` before starting a new loop. Nested loops are not supported in the current version.
  </Accordion>

  <Accordion title="Loop reminder appears repeatedly">
    **Cause:** You haven't called `end_loop_iteration` after 20+ steps.

    **Solution:** Call `end_loop_iteration` when the iteration is complete. The reminder appears every 20 steps to prevent accidental unclosed loops.
  </Accordion>
</AccordionGroup>

## Related Tools

<CardGroup cols={2}>
  <Card title="Focused Action" icon="crosshairs" href="/workflow-prompting/focused-action">
    Dynamic validation and data extraction within loop iterations
  </Card>

  <Card title="Runtime Variables" icon="brackets-curly" href="/concepts/generating-output-data">
    Set and use runtime variables in loop summaries
  </Card>

  <Card title="Extract Prompt" icon="scanner-image" href="/workflow-prompting/extract-prompt">
    Vision-based extraction with async processing in loops
  </Card>

  <Card title="Trajectories 101" icon="route" href="/concepts/trajectories">
    How caching accelerates loop execution
  </Card>
</CardGroup>
