# The run loop

An agent drives a Workbench run the same way over `mcp.workbenches.dev`, over local `wb mcp`, and with the CLI: start it, wait for it to need you, reply, and wait again.

```text
run  ->  wait  ->  send or answer  ->  wait  ->  ...  ->  completed, failed, or cancelled
```

## 1. Find and inspect

```json
{ "tool": "search", "arguments": { "query": "code review" } }
{ "tool": "view", "arguments": { "workbench": "acme/code-review" } }
```

Check `required_env` in the `view` result: a cloud run needs an organization secret for each name, plus `OPENROUTER_API_KEY`.

## 2. Track it in a project

Call `projects` and reuse a close match. Call `create_project` only when none exists, then pass its name or slug to `run`.

## 3. Start the run

```json
{
    "tool": "run",
    "arguments": {
        "workbench": "acme/code-review",
        "task": "Review the open migration and open a draft pull request with fixes.",
        "repository": "acme/api",
        "ref": "main",
        "branch": "migration-review",
        "project": "api",
        "max_minutes": 90
    }
}
```

`run` returns at once with a `run_id` and `session_id`. The run keeps going whether or not you are calling tools. Name the branch with `branch`, not in the task: the agent can push only its own branch, `workbenches/<branch>`.

## 4. Wait

```json
{ "tool": "wait", "arguments": { "run_id": "wb_...", "after": 0 } }
```

`wait` blocks until the run asks a question or requests a permission, ends, or `timeout_s` passes (900 seconds by default, 3600 at most). Progress alone never returns it, so make one call per thing that needs you. A timeout with nothing new is normal: call `wait` again.

Every result carries `next`, the cursor of the last event you saw. Pass it back as `after` so nothing is repeated or missed.

## 5. Reply

When `wait` reports a pending request, answer it with its request ID:

```json
{
    "tool": "answer",
    "arguments": {
        "run_id": "wb_...",
        "request_id": "...",
        "response": "allow"
    }
}
```

To add direction, use `send`:

| `mode`  | Use when                                                                 |
| ------- | ------------------------------------------------------------------------ |
| `send`  | The runner is idle and you want a new turn.                              |
| `steer` | A turn is in progress and you want to change it.                         |
| `queue` | You want the message to run as the next turn. It runs at once when idle. |

`send`, `answer`, and `cancel` return `after`. Pass it to the next `wait`.

## 6. Finish

Repeat until the status is `completed`, `failed`, or `cancelled`. The final `wait` result carries the final response, the pull request the agent opened or updated, if any, a summary of changed files, and `outcome_url` when the run returned files. Call `changes` for the diff. Use `cancel` to stop a run early.

## Supervising many runs

Wait on a whole project, or up to 50 runs, at once:

```json
{ "tool": "wait", "arguments": { "project": "api" } }
```

The result's `attention` lists every run waiting on an answer and every run that ended since the previous call. Answer what is listed, then call `wait` again with the returned `since`. It returns at once when `attention` is not empty or nothing is running.

## Run states

A cloud run moves through `queued`, `provisioning`, `starting`, `running`, `needs_input`, `completing`, and one of `completed`, `failed`, or `cancelled`. A failed run carries the reason as its `terminal_reason`.

## With the CLI

The same loop on your own machine:

```sh
wb run project-core --task-file ./brief.md --detach --json
wb wait wb_... --timeout 120 --json
wb answer wb_... <request-id> allow --json
wb send wb_... "Check the rollback too" --json
wb wait wb_... --run --after 42 --timeout 120 --json
```

`wb wait` exits 2 when the run needs input and 124 on timeout. See [Sessions](/docs/cli/sessions).
