Task Pipeline
This page documents the committed source. Treat revision-sensitive RuneLite UI, packet, or in-game outcomes as pending live-client verification unless the page records direct evidence.
TaskPipeline is a small tick-driven workflow runner. Each named step returns a StepResult, and the pipeline advances only on SUCCESS.
Step statuses:
SUCCESS: advance to the next step.WAIT: start a tick delay before running the same step again.WAIT_EVENT: pause pipeline polling and resume instantly when the specified event fires on the SdkEventTracker.RETRY: keep the same step without adding a delay.RESET: return to the first step and clear delay.FAILED: stop advancement until caller resets or handles failure.
Interaction result bridges:
StepResult.fromInteraction(result): preserve legacy behavior;SUCCESSbecomesSUCCESS, every other status becomesFAILED.StepResult.fromInteractionPaced(result): retry onlyPACED.StepResult.fromInteractionTransient(result): retry the SDK default transient set for paced actions, temporarily missing/hidden targets or widgets, and bank/deposit-box/production interfaces that are not open yet.StepResult.fromInteractionRetrying(result, statuses...): retry the exact caller-provided statuses.
Diagnostics:
currentStepTicks()counts ticks spent on the current step, including delay ticks.currentStepAttempts()counts executions of the current step body.- Both reset when the pipeline resets, advances to a new step, or completes.
Example:
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());
}
if (pipeline.currentStepTicks() > 20) {
log.debug("Pipeline has been on {} for {} ticks",
pipeline.currentStepName(), pipeline.currentStepTicks());
}
Workflow Builders
TaskPipeline remains the fixed-order step runner. Branching domain builders such as CombatWorkflowBuilder and ProductionWorkflowBuilder instead return TypesafeCarouselStateMachine instances configured by typed plan objects. See combat-and-prayer.md, production-workflow.md, and banking.md.
StepContext is the shared mutable context passed through builder-produced steps. It stores label positions and caller-defined values:
getLabels()returns the label map used bylabel(...)andjump(...).put(key, value),get(key),contains(key), andremove(key)manage step-local state.clearValues()removes caller-defined values while leaving labels intact.
Keep keys stable and narrow to the workflow that owns them. Prefer typed service callbacks for domain data; use StepContext for cross-step scratch state such as a selected target, timeout flag, or last observed count.
Timing helpers:
TickDelaytracks a single countdown.Cooldownstracks named countdowns.
Both are deterministic and covered by unit tests.
TaskPipeline Execution Model
The TaskPipeline runs sequential steps, managing delays, retries, and errors on each tick. It can also yield execution via WAIT_EVENT, halting its polling completely until an external event wakes it.
Polling vs Yielding
When an action takes time (like walking to a bank or opening an interface), you should prefer yielding:
// Anti-pattern: Polling every tick for the UI to load
return StepResult.fromInteractionTransient(Api.actions.widget().interact(bankBooth, "Bank"));
// Optimal: Issue the click once, then yield the pipeline until the UI event fires
Api.actions.widget().interact(bankBooth, "Bank");
return StepResult.waitForEvent(WidgetLoaded.class, event -> event.getGroupId() == 12);
Yielding guarantees sub-tick precision (your pipeline advances exactly when the widget loads) and saves CPU by skipping redundant tick() evaluations while waiting.