Agent SwarmAgent Swarm
Guides

Steer a Running Task

Add context to a running agent, request an interrupt, and understand degradation and delivery states

Task steering sends new instructions to work that has already started. Use it to correct an assumption, add missing context, or redirect an agent without cancelling the task and starting over.

Choose a delivery mode

ModeMeaningUse when
queueDeliver the message at the next turn boundary.The current turn can finish safely and you want to add context without discarding work.
steerInterrupt the current turn and deliver the message immediately.Letting the current turn continue would be wrong or unsafe.

Not every harness can interrupt. Task responses include supportedSteerModes, and the dashboard disables modes the assigned harness does not advertise.

ProviderSupported modesA steer request normally becomes
pi-monosteer, queuesteered
Claude Managedsteer, queuesteered
opencodequeuequeued
Devinqueuequeued
Claude Codequeuequeued
Codexqueuequeued (delivered by Codex lifecycle hooks at the next tool-call boundary)

Claude Code queue delivery additionally depends on a supported stock CLI invocation. See Harness Providers: Claude queue-steering gate. Codex delivery is harness-side: the worker image registers managed Codex hooks that inject queued messages into the running session; there is no mid-turn interrupt. See Harness Providers: Codex harness-side delivery.

Enable steering

Steering is disabled by default. Set STEERING_ENABLED=true (or 1) on the API server and worker containers to turn it on. While disabled, new steering requests are rejected across HTTP, MCP, scripts, and Slack; the dashboard hides its steering controls and the server does not register steer-task or accept-steer. Existing message history stays readable, and delivery callbacks plus terminal-status promotion remain enabled so messages already in flight can finish safely if the flag is turned off again.

The flag can also be set as a global swarm_config entry — global config rows are injected into the server environment at startup and on config reload.

Degradation and onUnsupported

The server uses a durable fallback ladder:

requested steer
  -> deliver as steer when supported
  -> otherwise queue at a turn boundary
  -> otherwise create a follow-up task

onUnsupported controls whether the fallback is allowed:

  • degrade (default) preserves the message by moving down the ladder. Read outcome, effectiveMode, degradedFrom, and promotedTaskId to learn what happened.
  • fail rejects an unsupported mode. HTTP callers receive 422, MCP/script callers receive an error, and no steering-message row is created.

Choose fail when true interrupt semantics are required. For example, an instruction to stop a destructive operation should not silently wait until the operation finishes.

Tasks that haven't started yet (unassigned, offered, pending) also accept steering when the provider supports live delivery: the message queues as pending and the worker delivers it once the session is live. A steer request on a not-yet-started task degrades to queue — there is no turn to interrupt yet. If the task reaches a terminal state without ever starting, pending messages are promoted to follow-up tasks as usual.

Send steering

Dashboard

Open an in-progress task and use the steering composer. The mode selector follows supportedSteerModes, and the result reports whether the message interrupted, queued, or became a follow-up. The task page's Steering section refreshes the message lifecycle from the API.

MCP

steer-task is available on both the agent and user MCP surfaces:

{
  "taskId": "00000000-0000-0000-0000-000000000000",
  "message": "Stop changing the schema; keep the fix application-only.",
  "mode": "steer",
  "onUnsupported": "fail"
}

Agents may steer tasks they created, and the lead may steer any task. User calls are limited to tasks allowed by task.steer.own.

After incorporating a delivered message, the assigned agent calls accept-steer:

{
  "steeringMessageId": "00000000-0000-0000-0000-000000000000",
  "note": "Kept the existing schema and moved validation into the service."
}

Script SDK

Scripts use the same MCP handler through task_steer:

const result = await swarm.task_steer({
  taskId,
  message: "Use the cached result; do not repeat the external request.",
  mode: "queue",
  onUnsupported: "degrade",
});

HTTP

curl -X POST "http://localhost:3013/api/tasks/$TASK_ID/steer" \
  -H "Authorization: Bearer $AGENT_SWARM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Stop and verify the migration against an existing database.",
    "mode": "steer",
    "onUnsupported": "degrade"
  }'

Inspect the audit trail with GET /api/tasks/{id}/steering-messages. Worker delivery callbacks live under /api/steering-messages/{id}/{delivered,undeliverable,handled} and require the assigned worker's X-Agent-ID; normal clients should not call them.

Slack

Slack thread steering is opt-in:

SLACK_THREAD_STEERING=lead       # or all
SLACK_THREAD_STEERING_MODE=queue # or steer

lead targets the latest in-progress lead task in the thread. all targets the latest active task regardless of role. When the flag is unset, existing thread follow-up routing is unchanged. See Slack Integration.

Message lifecycle

Every accepted message has its own audit record:

pending -> delivered -> handled
   |
   +-> promoted
   |
   +-> cancelled
  • pending: stored and waiting for the worker to deliver it.
  • delivered: the provider accepted it. deliveredMode records what the adapter actually used.
  • handled: the agent acknowledged the message with accept-steer after incorporating it.
  • promoted: live delivery was unavailable, the task stopped before delivery, or the provider rejected the message. promotedTaskId links to the durable follow-up task.
  • cancelled: pending delivery was cancelled before a provider accepted it.

If the parent task reaches a terminal state while messages are still pending, the server promotes each one exactly once rather than dropping it.

On this page