Skip to content

Mission Steering

Mission Steering lets you guide an already-running agent without cancelling it. Instead of waiting for the current turn to finish (or pressing Ctrl+C), you inject a typed steer signal that the agent picks up at the next tool-result boundary.


The ReAct loop can run for many iterations and many seconds. During that time you may realize the agent is:

  • heading in a direction that is technically correct but not what you wanted (Hint — subtle correction, no hard pivot)
  • going down a wrong path entirely (Redirect — explicit new direction)
  • doing something you want stopped immediately (Stop — clean exit after the current tool finishes)

Steering lets you communicate those corrections inline without destroying the conversation context or losing the work already done.


Press Ctrl+S while the agent is processing. A compact floating panel appears at the bottom of the screen:

┌─────────────── ⛵ Mission Steer ───────────────────────┐
│ kind: HINT REDIRECT STOP (Tab=cycle) │
│ │
│ > type your steer here… │
│ │
│ Enter send Tab kind Esc cancel │
└─────────────────────────────────────────────────────────┘

Press Tab to cycle between the three kinds:

KindColourEffect
HINTgreenGentle nudge; agent stays on course but considers the hint
REDIRECTamberExplicit new direction; agent pivots
STOPredRequest clean exit after the current tool finishes

Press Enter to send. The overlay closes and a confirmation appears in the output stream:

⛵ Steer queued (HINT/focus only on the auth layer)

The status bar shows the pending count in amber:

⛵ 1 pending

When the agent picks it up the status bar flashes green:

⛵ applied

If the agent is not currently processing, pressing Ctrl+S opens the overlay but sends the steer as a new user message prefixed with [⛵ STEER/<kind>] when you press Enter. The agent receives it as an ordinary turn.

Press Esc or Ctrl+S again to close the overlay without sending.


The status bar reflects steering state:

StateDisplay
Steer sent, not yet picked up⛵ N pending (amber background)
Steer picked up by agent⛵ applied (green, fades after 4 s)
No pending steers(nothing)

The right-side hint area shows ^S=steer while the agent is running.


Steers can be sent programmatically via the steering channel exposed by Agent::steer_sender():

use edgecrab_core::{SteeringEvent, SteeringKind};
// Obtain the sender once after building the agent.
let steer_tx = agent.steer_sender();
// Send a hint (clone the sender for each send).
steer_tx.send(SteeringEvent::new(
SteeringKind::Hint,
"focus on the auth module only",
))?;
// Redirect mid-turn.
steer_tx.send(SteeringEvent::new(
SteeringKind::Redirect,
"use an async approach instead",
))?;
// Ask the agent to stop cleanly.
steer_tx.send(SteeringEvent::new(
SteeringKind::Stop,
"stop after this tool call",
))?;

SteeringSender is a cheap clone (UnboundedSender) — store one per session and share clones across threads.

Steers are injected after each tool result

Section titled “Steers are injected after each tool result”
while api_call_count < max_iterations {
response = provider.chat(model, messages, tools).await?;
if response.has_tool_calls() {
for call in response.tool_calls {
result = registry.dispatch(call).await;
messages.push(tool_result(call.id, result));
}
// ← Steers injected HERE as [⛵ STEER] user messages ←
drain_pending_steers(&steer_rx, &mut messages);
} else {
return final_response;
}
}

STOP steers also cancel any long-running tool via the shared cancellation token.


In messaging platform deployments (Telegram, Slack, etc.) you can configure how a second message that arrives while the agent is already running is handled. Set second_message_mode in gateway: in config.yaml:

gateway:
second_message_mode: steer # queue | steer | interrupt
ModeBehaviour
queueThe new message is queued and replayed after the current turn finishes. (default)
steerThe new message is injected as a Redirect steer into the running agent. The user receives ⛵ Steering... as an acknowledgement.
interruptThe running agent is cancelled immediately and a new turn starts with the new message.
User: summarise the last 100 commits
Bot: [agent starts reading git log...]
User: actually just focus on the last 7 days
Bot: ⛵ Steering the agent with your new message...
[agent pivots without the user needing to wait]

ScenarioOutcome
Steer channel closed (session ended)CLI: falls back to sending as new message. Gateway: falls back to queue.
STOP steerAgent exits cleanly after the current tool returns. Active background processes receive a cancellation signal.
Multiple steers in flightAll are injected in order at the next tool boundary. The pending count in the status bar reflects the queue depth.
Steer arrives when no tool calls are pendingInjected before the next LLM call as context.

Ctrl+S (TUI)
┌─────────────────┐
│ SteeringOverlay │ (render_steering_overlay)
│ (steering_kind, │
│ textarea) │
└────────┬────────┘
│ send_steer_from_overlay()
┌─────────────────┐
│ SteeringSender │ agent.steer_sender()
│ (unbounded tx) │
└────────┬────────┘
│ SteeringEvent { kind, message }
┌────────────────────────────┐
│ ReAct Loop │
│ (conversation.rs) │
│ │
│ …tool result returned… │
│ ↓ │
│ drain_pending_steers() │
│ ↓ │
│ inject as [⛵ STEER] │
│ user message │
└────────────────────────────┘

  • React Loop — detailed walkthrough of execute_loop()
  • TUI Interface — all keyboard shortcuts and status bar details
  • Memory — persistent agent memory, distinct from transient steers