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

# Automation loop

> Choose and drive the right workflow primitive: TaskPipeline, TypesafeCarouselStateMachine, and the observable runtime.

Pick the workflow primitive from the shape of the work, not from habit:

| Work shape                                                              | Primitive                                                  | Advance method              |
| ----------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------- |
| Fixed, ordered sub-steps                                                | `TaskPipeline`                                             | `tick()`                    |
| Branching, looping, or recovery states                                  | `TypesafeCarouselStateMachine<E>`                          | `pulse(clientTick)`         |
| Standard bank, combat, production, enchanting, pouch, or spellbook flow | The matching [workflow builder](/guides/workflow-builders) | `pulse(clientTick)`         |
| A single observed action                                                | The relevant `Api.actions` method                          | Re-observe on the next tick |

```mermaid theme={null}
flowchart TD
    Start([Automation task]) --> Q1{Workflow complexity?}
    Q1 -->|Single click| Direct[Direct Api.actions call]
    Q1 -->|Fixed step sequence| Pipeline[TaskPipeline]
    Q1 -->|Branching state logic| Q2{Standard domain flow?}
    Q2 -->|Yes| Domain[Domain workflow builder]
    Q2 -->|No| Carousel[TypesafeCarouselStateMachine]
```

## TaskPipeline

`TaskPipeline` is a tick-driven runner for fixed sequences. Each named step returns a `StepResult`, and the pipeline advances only on `SUCCESS`.

| Status       | Behavior                                                           |
| ------------ | ------------------------------------------------------------------ |
| `SUCCESS`    | Advance to the next step                                           |
| `WAIT`       | Start a tick delay, then re-run the same step                      |
| `WAIT_EVENT` | Pause polling until the specified event fires on the event tracker |
| `RETRY`      | Re-run the same step next tick without a delay                     |
| `RESET`      | Return to the first step and clear delay                           |
| `FAILED`     | Stop advancement until the caller resets or handles the failure    |

```java theme={null}
TaskPipeline pipeline = TaskPipeline.create()
    .step("open-bank", () -> StepResult.fromInteraction(BankActions.openNearestAccessible()))
    .step("withdraw-runes", () -> StepResult.fromInteraction(BankActions.withdraw(561, 100)));

StepResult result = pipeline.tick();
if (result.getStatus() == StepStatus.FAILED) {
    log.debug("Pipeline failed at {}: {}", pipeline.currentStepName(), result.getMessage());
}
```

### Interaction bridges

Bridge `InteractionResult` into step results instead of translating statuses by hand:

* `StepResult.fromInteraction(result)` turns `SUCCESS` into step success and everything else into `FAILED`.
* `StepResult.fromInteractionPaced(result)` retries only `PACED`.
* `StepResult.fromInteractionTransient(result)` retries the SDK default transient set: paced actions, temporarily missing or hidden targets and widgets, and bank, deposit-box, and production interfaces that have not opened yet.
* `StepResult.fromInteractionRetrying(result, statuses...)` retries exactly the caller-provided statuses.

### Yield instead of poll

When an action takes time to land (a bank booth click that loads an interface), dispatch once and yield until the event fires. This advances the pipeline exactly when the widget loads and skips redundant evaluations while waiting:

```java theme={null}
// Polling every tick for the interface:
return StepResult.fromInteractionTransient(Api.actions.widget().interact(bankBooth, "Bank"));

// Dispatch once, then yield:
Api.actions.widget().interact(bankBooth, "Bank");
return StepResult.waitForEvent(WidgetLoaded.class, event -> event.getGroupId() == 12);
```

### Diagnostics and shared context

* `currentStepTicks()` counts ticks spent on the current step, including delay ticks.
* `currentStepAttempts()` counts executions of the current step body.
* Both reset when the pipeline advances, resets, or completes.

`StepContext` is the mutable context passed through builder-produced steps. Use `put`/`get`/`contains`/`remove` for cross-step scratch state such as a selected target or last observed count, and `getLabels()` with `label(...)`/`jump(...)` for flow control. Keep keys narrow to the workflow that owns them. `TickDelay` and `Cooldowns` provide deterministic named countdowns.

## TypesafeCarouselStateMachine

`TypesafeCarouselStateMachine<E>` is the suite's state-machine engine for branching and looping workflows. The builder requires a workflow ID, a lifecycle owner, an initial state, and exactly one handler per enum constant. Missing or duplicate handlers, a missing owner, or a missing initial state fail at `build()`.

```java theme={null}
enum State { IDLE, WALK, CHOP }

TypesafeCarouselStateMachine<State> workflow =
    TypesafeCarouselStateMachine.builder(State.class, "woodcutting.main")
        .owner(this)
        .initial(State.IDLE)
        .on(State.IDLE, ctx -> CarouselResult.transitionTo(
            State.WALK, "inventory_ready", "Walking to trees"))
        .on(State.WALK, ctx -> atTrees()
            ? CarouselResult.transitionTo(State.CHOP, "arrived", "At trees")
            : CarouselResult.stay("walking", "Waiting for arrival"))
        .on(State.CHOP, ctx -> inventoryFull()
            ? CarouselResult.complete("logs_cut", "Done")
            : CarouselResult.stay("chopping", "Chopping"))
        .build();
```

Call `pulse(clientTick)` at most once per game tick. A duplicate-tick pulse returns a `stay` result with reason `duplicate_tick` without executing the handler. Every handler returns one of:

* `CarouselResult.stay(reason, detail)` keeps the current state.
* `transitionTo(target, reason, detail)` moves to another state.
* `complete(reason, detail)` records a successful terminal state.
* `fail(reason, detail)` records a failed terminal state.

Use `machine.getContext()` for workflow-owned scratch values and tick deadlines, `snapshot()` for immutable status and transition history, `cancel(reason, detail)` for cooperative cancellation, and `reset()` before reusing a terminal machine.

```mermaid theme={null}
flowchart TD
    Tick[Game tick] --> Pulse[machine.pulse clientTick]
    Pulse --> Guard{Duplicate tick?}
    Guard -->|Yes| Dup[stay - duplicate_tick]
    Guard -->|No| Term{Current state terminal?}
    Term -->|Yes| Terminal[Return terminal status]
    Term -->|No| Handler[Run handler for current state]
    Handler --> Result{Result}
    Result -->|stay| Stay[Keep state, record reason]
    Result -->|transitionTo| Move[Set new state]
    Result -->|complete| Done[Set terminal SUCCESS]
    Result -->|fail| Failed[Set terminal FAILED]
```

To hand a machine to a managed runtime, wrap it with `AutomationLoop.fromStateMachine(config, machine)`. `AutomationLoop` keeps its own break handler gate and accepts a `WorkflowSupervisor` for cooperative guards that return `RUN`, `SUSPEND`, `INTERRUPT`, or `CANCEL` between pulses.

## Observable runtime and telemetry

`ObservableWorkflow` is the read-only telemetry boundary. Active workflows publish immutable `WorkflowSnapshot` values through `WorkflowRegistry`, and terminal or failed workflows stay registered until you call `reset()` or `stop()` so the final decision remains visible. History is bounded to 64 decisions per workflow. The Agent Server exposes registered snapshots at `GET /api/v1/workflow/status` and through the `n3_get_workflow_status` MCP tool.

## Runtime rules

1. Observe live state before choosing a transition.
2. Perform at most one meaningful action per tick.
3. Inspect `InteractionResult`. An accepted or paced dispatch is not confirmation.
4. Re-fetch entities and widgets after state-changing actions.
5. Reset transient workflow state on logout, disable, or scene invalidation.
6. Leave suite-wide pacing and walker ticking to Packet Utils.
