You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/steering.md
+34-1Lines changed: 34 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,18 @@ Agent Loop ▼
21
21
└─ new LLM turn with steering message
22
22
```
23
23
24
+
## Scoped queues
25
+
26
+
Steering is now isolated per resolved session scope, not stored in a single
27
+
global queue.
28
+
29
+
- The active turn writes and reads from its own scope key (usually the routed session key such as `agent:<agent_id>:...`)
30
+
-`Steer()` still works outside an active turn through a legacy fallback queue
31
+
-`Continue()` first dequeues messages for the requested session scope, then falls back to the legacy queue for backwards compatibility
32
+
33
+
This prevents a message arriving from another chat, DM peer, or routed agent
34
+
session from being injected into the wrong conversation.
35
+
24
36
## Configuration
25
37
26
38
In `config.json`, under `agents.defaults`:
@@ -86,12 +98,18 @@ if response == "" {
86
98
87
99
`Continue` internally uses `SkipInitialSteeringPoll: true` to avoid double-dequeuing the same messages (since it already extracted them and passes them directly as input).
88
100
101
+
`Continue` also resolves the target agent from the provided session key, so
102
+
agent-scoped sessions continue on the correct agent instead of always using
103
+
the default one.
104
+
89
105
## Polling points in the loop
90
106
91
-
Steering is checked at **two points** in the agent cycle:
107
+
Steering is checked at the following points in the agent cycle:
92
108
93
109
1.**At loop start** — before the first LLM call, to catch messages enqueued during setup
94
110
2.**After every tool completes** — including the first and the last. If steering is found and there are remaining tools, they are all skipped immediately
111
+
3.**After a direct LLM response** — if a new steering message arrived while the model was generating a non-tool response, the loop continues instead of returning a stale answer
112
+
4.**Right before the turn is finalized** — if steering arrived at the very end of the turn, the agent immediately starts a continuation turn instead of leaving the message orphaned in the queue
95
113
96
114
## Why remaining tools are skipped
97
115
@@ -156,11 +174,26 @@ When the agent loop (`Run()`) starts processing a message, it spawns a backgroun
156
174
157
175
- Users on any channel (Telegram, Discord, etc.) don't need to do anything special — their messages are automatically captured as steering when the agent is busy
158
176
- Audio messages are transcribed before being steered, so the agent receives text. If transcription fails, the original (non-transcribed) message is steered as-is
177
+
- Only messages that resolve to the **same steering scope** as the active turn are redirected. Messages for other chats/sessions are requeued onto the inbound bus so they can be processed normally
178
+
-`system` inbound messages are not treated as steering input
159
179
- When `processMessage` finishes, the drain goroutine is canceled and normal message consumption resumes
160
180
181
+
## Steering with media
182
+
183
+
Steering messages can include `Media` refs, just like normal inbound user
184
+
messages.
185
+
186
+
- The original `media://` refs are preserved in session history via `AddFullMessage`
187
+
- Before the next provider call, steering messages go through the normal media resolution pipeline
188
+
- Image refs are converted to data URLs for multimodal providers; non-image refs are resolved the same way as standard inbound media
189
+
190
+
This applies both to in-turn steering and to idle-session continuation through
191
+
`Continue()`.
192
+
161
193
## Notes
162
194
163
195
- Steering **does not interrupt** a tool that is currently executing. It waits for the current tool to finish, then checks the queue.
164
196
- With `one-at-a-time` mode, if multiple messages are enqueued rapidly, they will be processed one per iteration. This gives the model the opportunity to react to each message individually.
165
197
- With `all` mode, all pending messages are combined into a single injection. Useful when you want the agent to receive all the context at once.
166
198
- The steering queue has a maximum capacity of 10 messages (`MaxQueueSize`). `Steer()` returns an error when the queue is full. In the bus drain path, the error is logged as a warning and the message is effectively dropped.
199
+
- Manual `Steer()` calls made outside an active turn still go to the legacy fallback queue, so older integrations keep working.
0 commit comments