Skip to content

Commit 70df2b8

Browse files
feat: steer mid-turn prompts by default (#77023)
Summary: - Default active-run queueing to steer while preserving explicit followup/collect modes. - Keep `/steer` fallback behavior and migrate retired queue steering config. - Await Codex app-server steering acceptance so rejected/aborted steering can fall back safely. - Route active subagent announcements through intentional acceptance-aware steering, with legacy queue helpers deprecated for delivery decisions. Verification: - git diff --check - rg -n "^(<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|)" CHANGELOG.md docs src extensions || true - pnpm test src/agents/subagent-announce-dispatch.test.ts src/agents/subagent-announce-delivery.test.ts src/agents/pi-embedded-runner/runs.test.ts src/agents/subagent-announce.format.e2e.test.ts src/agents/subagent-announce.test.ts - pnpm test src/auto-reply/reply/commands-steer.test.ts src/auto-reply/reply/queue/settings.test.ts src/auto-reply/reply/queue-policy.test.ts src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts src/auto-reply/reply/get-reply-run.media-only.test.ts extensions/codex/src/app-server/run-attempt.test.ts -- -t "queued steering|explicit all-mode steering|flushes pending default queued steering|rejects queued steering|resolveActiveRunQueueAction|resolveQueueSettings|handleSteerCommand" Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
1 parent 1c5c72e commit 70df2b8

75 files changed

Lines changed: 1165 additions & 1383 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Docs: https://docs.openclaw.ai
8181
- Telegram: support Mini App `web_app` buttons in generic message presentation payloads, allowing `openclaw message send --presentation` to render Telegram Web App inline buttons for private chats. (#81356) Thanks @jzakirov.
8282
- Scripts: add `OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree` so high-capacity local worktrees can use independent heavy-check locks while shared locks remain the default. Fixes #80729. (#80734) Thanks @samzong.
8383
- Agents/subagents: deliver native `sessions_spawn` tasks in the child session's first visible `[Subagent Task]` message instead of hiding the task in the sub-agent system prompt, keeping delegation auditable without duplicating tokens. Fixes #78592. Thanks @bradestes and @stainlu.
84+
- Messages/queue: make mid-turn prompts steer active runs by default via `/queue steer`, preserve `/queue followup` and `/queue collect` for users who want messages to queue by default, and make `/steer` continue as a normal prompt when steering is unavailable. (#77023) Thanks @fuller-stack-dev.
8485
- Voice Call/Telnyx: add realtime media-streaming call support for conversational voice calls. (#81024) Thanks @dynamite-bud.
8586
- Gateway/OpenAI HTTP: honor `max_completion_tokens` and `max_tokens` on inbound `/v1/chat/completions` requests so client-provided token caps reach the upstream provider via `streamParams.maxTokens`, with `max_completion_tokens` taking precedence when both are sent. Thanks @Lellansin.
8687
- Models/OpenAI CLI auth: make `openclaw models auth login --provider openai` start the ChatGPT/Codex account login by default, while `--method api-key` remains the explicit OpenAI API-key setup path.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
f95819d93e9bec5d059440ab54fb4ccb487425cb91d647c8688cd18ef1d4d848 config-baseline.json
2-
3325af3a6292959bb38166e9136c638dce5d2093d2339076742890848088a972 config-baseline.core.json
1+
bad30fbdd50ecdc6dd0e3dbbea0a1d7ed02a7e3e0cc30d7b1d4459832e4d1bd8 config-baseline.json
2+
932ca6c43b47dc342b6c9999815e5f03c5ff46f6372034a4eb507c629a4e49b1 config-baseline.core.json
33
ad1d3cb596115d66c21e93de95e229c14c585f0dd4799b4ae3cc29b84761adc6 config-baseline.channel.json
44
0dac8944a0d51ae96f97e3809907f8a04d08413434a1a1190240f7e13bb11c4d config-baseline.plugin.json

docs/concepts/agent-loop.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ wired end-to-end.
4646

4747
- Runs are serialized per session key (session lane) and optionally through a global lane.
4848
- This prevents tool/session races and keeps session history consistent.
49-
- Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system.
49+
- Messaging channels can choose queue modes (steer/followup/collect/interrupt) that feed this lane system.
5050
See [Command Queue](/concepts/queue).
5151
- Transcript writes are also protected by a session write lock on the session file. The lock is
5252
process-aware and file-based, so it catches writers that bypass the in-process queue or come from

docs/concepts/agent.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,15 @@ Legacy session folders from other tools are not read.
8484

8585
## Steering while streaming
8686

87-
When queue mode is `steer`, inbound messages are injected into the current run.
88-
Queued steering is delivered **after the current assistant turn finishes
89-
executing its tool calls**, before the next LLM call. Pi drains all pending
90-
steering messages together for `steer`; legacy `queue` drains one message per
91-
model boundary. Steering no longer skips remaining tool calls from the current
92-
assistant message.
93-
94-
When queue mode is `followup` or `collect`, inbound messages are held until the
95-
current turn ends, then a new agent turn starts with the queued payloads. See
96-
[Queue](/concepts/queue) and [Steering queue](/concepts/queue-steering) for mode
97-
and boundary behavior.
87+
Inbound prompts that arrive mid-run are steered into the current run by default.
88+
Steering is delivered **after the current assistant turn finishes executing its
89+
tool calls**, before the next LLM call, and no longer skips remaining tool calls
90+
from the current assistant message.
91+
92+
`/queue steer` is the default active-run behavior. `/queue followup` and
93+
`/queue collect` make messages wait for a later turn instead of steering.
94+
`/queue interrupt` aborts the active run instead. See [Queue](/concepts/queue)
95+
and [Steering queue](/concepts/queue-steering) for queue and boundary behavior.
9896

9997
Block streaming sends completed assistant blocks as soon as they finish; it is
10098
**off by default** (`agents.defaults.blockStreamingDefault: "off"`).

docs/concepts/messages.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,14 @@ default) and per-channel overrides like `channels.slack.historyLimit` or
125125

126126
## Queueing and followups
127127

128-
If a run is already active, inbound messages can be queued, steered into the
129-
current run, or collected for a followup turn.
128+
If a run is already active, inbound messages are steered into the current run by
129+
default. `messages.queue` selects whether active-run messages steer, queue for
130+
later, collect into one later turn, or interrupt the active run.
130131

131132
- Configure via `messages.queue` (and `messages.queue.byChannel`).
132-
- Default mode is `steer`, with a 500ms followup debounce when steering falls
133-
back to queued followup delivery.
134-
- Modes: `steer`, `followup`, `collect`, `steer-backlog`, `interrupt`, and the
135-
legacy one-at-a-time `queue` mode.
133+
- Default mode is `steer`, with a 500ms debounce for Codex steering batches and
134+
followup/collect queues.
135+
- Modes: `steer`, `followup`, `collect`, and `interrupt`.
136136

137137
Details: [Command queue](/concepts/queue) and [Steering queue](/concepts/queue-steering).
138138

docs/concepts/queue-steering.md

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ summary: "How active-run steering queues messages at runtime boundaries"
33
read_when:
44
- Explaining how steer behaves while an agent is using tools
55
- Changing active-run queue behavior or runtime steering integration
6-
- Comparing steer, queue, collect, and followup modes
6+
- Comparing steering with followup, collect, and interrupt queue modes
77
title: "Steering queue"
88
---
99

10-
When a message arrives while a session run is already streaming, OpenClaw can
11-
send that message into the active runtime instead of starting another run for
12-
the same session. The public modes are runtime-neutral; Pi and the native Codex
13-
app-server harness implement the delivery details differently.
10+
When a normal prompt arrives while a session run is already streaming, OpenClaw
11+
tries to send that prompt into the active runtime by default when the queue mode
12+
is `steer`. No config entry and no queue directive are required for that default
13+
behavior. Pi and the native Codex app-server harness implement the delivery
14+
details differently.
1415

1516
## Runtime boundary
1617

@@ -27,44 +28,40 @@ This keeps tool results paired with the assistant message that requested them,
2728
then lets the next model call see the latest user input.
2829

2930
The native Codex app-server harness exposes `turn/steer` instead of Pi's
30-
internal steering queue. OpenClaw adapts the same modes there:
31-
32-
- `steer` batches queued messages for the configured quiet window, then sends a
33-
single `turn/steer` request with all collected user input in arrival order.
34-
- `queue` keeps the legacy serialized shape by sending separate `turn/steer`
35-
requests.
36-
- `followup`, `collect`, `steer-backlog`, and `interrupt` stay OpenClaw-owned
37-
queue behavior around the active Codex turn.
31+
internal steering queue. OpenClaw batches queued prompts for the configured
32+
quiet window, then sends a single `turn/steer` request with all collected user
33+
input in arrival order.
3834

3935
Codex review and manual compaction turns reject same-turn steering. When a
40-
runtime cannot accept steering, OpenClaw falls back to the followup queue where
41-
that mode allows it.
36+
runtime cannot accept steering in `steer` mode, OpenClaw waits for the active
37+
run to finish before starting the prompt.
4238

43-
This page explains queue-mode steering for normal inbound messages. For the
44-
explicit `/steer <message>` command, see [Steer](/tools/steer).
39+
This page explains queue-mode steering for normal inbound messages when the mode
40+
is `steer`. If the mode is `followup` or `collect`, normal messages do not enter
41+
this steering path; they wait until the active run finishes. For the explicit
42+
`/steer <message>` command, see [Steer](/tools/steer).
4543

4644
## Modes
4745

48-
| Mode | Active-run behavior | Later followup behavior |
49-
| --------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
50-
| `steer` | Injects all queued steering messages together at the next runtime boundary. This is the default. | Falls back to followup only when steering is unavailable. |
51-
| `queue` | Legacy one-at-a-time steering. Pi injects one queued message per model boundary; Codex sends separate `turn/steer` requests. | Falls back to followup only when steering is unavailable. |
52-
| `steer-backlog` | Same active-run steering behavior as `steer`. | Also keeps the same message for a later followup turn. |
53-
| `followup` | Does not steer the current run. | Runs queued messages later. |
54-
| `collect` | Does not steer the current run. | Coalesces compatible queued messages into one later turn after the debounce window. |
55-
| `interrupt` | Aborts the active run, then starts the newest message. | None. |
46+
| Mode | Active-run behavior | Later behavior |
47+
| ----------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
48+
| `steer` | Steers the prompt into the active runtime when it can. | Waits for the active run to finish if steering is unavailable. |
49+
| `followup` | Does not steer. | Runs queued messages later after the active run ends. |
50+
| `collect` | Does not steer. | Coalesces compatible queued messages into one later turn after the debounce window. |
51+
| `interrupt` | Aborts the active run instead of steering it. | Starts the newest message after aborting. |
5652

5753
## Burst example
5854

5955
If four users send messages while the agent is executing a tool call:
6056

61-
- `steer`: the active runtime receives all four messages in arrival order before
62-
its next model decision. Pi drains them at the next model boundary; Codex
63-
receives them as one batched `turn/steer`.
64-
- `queue`: legacy serialized steering. Pi injects one queued message at a time;
65-
Codex receives separate `turn/steer` requests.
66-
- `collect`: OpenClaw waits until the active run ends, then creates a followup
67-
turn with compatible queued messages after the debounce window.
57+
- With default behavior, the active runtime receives all four messages in
58+
arrival order before its next model decision. Pi drains them at the next model
59+
boundary; Codex receives them as one batched `turn/steer`.
60+
- With `/queue collect`, OpenClaw does not steer. It waits until the active run
61+
ends, then creates a followup turn with compatible queued messages after the
62+
debounce window.
63+
- With `/queue interrupt`, OpenClaw aborts the active run and starts the newest
64+
message instead of steering.
6865

6966
## Scope
7067

@@ -73,18 +70,17 @@ session, change the active run's tool policy, or split messages by sender. In
7370
multi-user channels, inbound prompts already include sender and route context, so
7471
the next model call can see who sent each message.
7572

76-
Use `collect` when you want OpenClaw to build a later followup turn that can
77-
coalesce compatible messages and preserve followup queue drop policy. Use
78-
`queue` only when you need the older one-at-a-time steering behavior.
73+
Use `followup` or `collect` when you want messages to queue by default instead
74+
of steering the active run. Use `interrupt` when the newest prompt should
75+
replace the active run.
7976

8077
## Debounce
8178

82-
`messages.queue.debounceMs` applies to followup delivery, including `collect`,
83-
`followup`, `steer-backlog`, and `steer` fallback when active-run steering is not
84-
available. For Pi, active `steer` itself does not use the debounce timer because
85-
Pi naturally batches messages until the next model boundary. For the native
86-
Codex harness, OpenClaw uses the same debounce value as the quiet window before
87-
sending the batched `turn/steer`.
79+
`messages.queue.debounceMs` applies to queued `followup` and `collect` delivery.
80+
In `steer` mode with the native Codex harness, it also sets the quiet window
81+
before sending batched `turn/steer`. For Pi, active steering itself does not use
82+
the debounce timer because Pi naturally batches messages until the next model
83+
boundary.
8884

8985
## Related
9086

docs/concepts/queue.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,20 @@ When unset, all inbound channel surfaces use:
3030
- `cap: 20`
3131
- `drop: "summarize"`
3232

33-
`steer` is the default because it keeps the active model turn responsive without
34-
starting a second session run. It drains all steering messages that arrived
35-
before the next model boundary. If the current run cannot accept steering,
36-
OpenClaw falls back to a followup queue entry.
33+
Same-turn steering is the default. A prompt that arrives mid-run is injected
34+
into the active runtime when the run can accept steering, so no second session
35+
run is started. If the active run cannot accept steering, OpenClaw waits for the
36+
active run to finish before starting the prompt.
3737

3838
## Queue modes
3939

40-
Inbound messages can steer the current run, wait for a followup turn, or do both:
40+
`/queue` controls what normal inbound messages do while a session already has
41+
an active run:
4142

42-
- `steer`: queue steering messages into the active runtime. Pi delivers all pending steering messages **after the current assistant turn finishes executing its tool calls**, before the next LLM call; Codex app-server receives one batched `turn/steer`. If the run is not actively streaming or steering is unavailable, OpenClaw falls back to a followup queue entry.
43-
- `queue` (legacy): old one-at-a-time steering. Pi delivers one queued steering message at each model boundary; Codex app-server receives separate `turn/steer` requests. Prefer `steer` unless you need the previous serialized behavior.
44-
- `followup`: enqueue each message for a later agent turn after the current run ends.
45-
- `collect`: coalesce queued messages into a **single** followup turn after the quiet window. If messages target different channels/threads, they drain individually to preserve routing.
46-
- `steer-backlog` (aka `steer+backlog`): steer now **and** preserve the same message for a followup turn.
47-
- `interrupt` (legacy): abort the active run for that session, then run the newest message.
48-
49-
Steer-backlog means you can get a followup response after the steered run, so
50-
streaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want
51-
one response per inbound message.
43+
- `steer`: inject messages into the active runtime. Pi delivers all pending steering messages **after the current assistant turn finishes executing its tool calls**, before the next LLM call; Codex app-server receives one batched `turn/steer`. If the run is not actively streaming or steering is unavailable, OpenClaw waits until the active run ends before starting the prompt.
44+
- `followup`: do not steer. Enqueue each message for a later agent turn after the current run ends.
45+
- `collect`: do not steer. Coalesce queued messages into a **single** followup turn after the quiet window. If messages target different channels/threads, they drain individually to preserve routing.
46+
- `interrupt`: abort the active run for that session, then run the newest message.
5247

5348
For runtime-specific timing and dependency behavior, see
5449
[Steering queue](/concepts/queue-steering). For the explicit `/steer <message>`
@@ -72,9 +67,10 @@ Configure globally or per channel via `messages.queue`:
7267

7368
## Queue options
7469

75-
Options apply to `followup`, `collect`, and `steer-backlog` (and to `steer` or legacy `queue` when steering falls back to followup):
70+
Options apply to queued delivery. `debounceMs` also sets the Codex steering
71+
quiet window in `steer` mode:
7672

77-
- `debounceMs`: quiet window before draining queued followups. Bare numbers are milliseconds; units `ms`, `s`, `m`, `h`, and `d` are accepted by `/queue` options.
73+
- `debounceMs`: quiet window before draining queued followups or collect batches; in Codex `steer` mode, quiet window before sending batched `turn/steer`. Bare numbers are milliseconds; units `ms`, `s`, `m`, `h`, and `d` are accepted by `/queue` options.
7874
- `cap`: max queued messages per session. Values below `1` are ignored.
7975
- `drop: "summarize"`: default. Drop the oldest queued entries as needed, keep compact summaries, and inject them as a synthetic followup prompt.
8076
- `drop: "old"`: drop the oldest queued entries as needed, without preserving summaries.
@@ -99,7 +95,7 @@ keys.
9995

10096
## Per-session overrides
10197

102-
- Send `/queue <mode>` as a standalone command to store the mode for the current session.
98+
- Send `/queue <steer|followup|collect|interrupt>` as a standalone command to store the queue mode for the current session.
10399
- Options can be combined: `/queue collect debounce:0.5s cap:25 drop:summarize`
104100
- `/queue default` or `/queue reset` clears the session override.
105101

docs/gateway/config-agents.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,13 +1280,13 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden
12801280
ackReactionScope: "group-mentions", // group-mentions | group-all | direct | all
12811281
removeAckAfterReply: false,
12821282
queue: {
1283-
mode: "steer", // steer | queue (legacy one-at-a-time) | followup | collect | steer-backlog | steer+backlog | interrupt
1283+
mode: "followup", // steer | followup | collect | interrupt
12841284
debounceMs: 500,
12851285
cap: 20,
12861286
drop: "summarize", // old | new | summarize
12871287
byChannel: {
1288-
whatsapp: "steer",
1289-
telegram: "steer",
1288+
whatsapp: "followup",
1289+
telegram: "followup",
12901290
},
12911291
},
12921292
inbound: {

docs/gateway/configuration-examples.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,18 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
113113
visibleReplies: "message_tool", // normal final replies stay private in groups/channels
114114
},
115115
queue: {
116-
mode: "steer",
116+
mode: "followup",
117117
debounceMs: 500,
118118
cap: 20,
119119
drop: "summarize",
120120
byChannel: {
121-
whatsapp: "steer",
122-
telegram: "steer",
123-
discord: "steer",
124-
slack: "steer",
125-
signal: "steer",
126-
imessage: "steer",
127-
webchat: "steer",
121+
whatsapp: "followup",
122+
telegram: "followup",
123+
discord: "collect",
124+
slack: "collect",
125+
signal: "followup",
126+
imessage: "followup",
127+
webchat: "followup",
128128
},
129129
},
130130
},

0 commit comments

Comments
 (0)