Skip to content

Commit 6cd5399

Browse files
committed
fix: cap gateway turns and stream text to Telegram incrementally
Two fixes for multi-turn tool-calling sessions feeling unresponsive: 1. Set max_turns to 5 for gateway sessions. Previously None (default 1000), so the agent could do dozens of tool-call round-trips before responding. Now after 5 LLM→tool turns it stops and replies with what it has, asking the user if they want to continue. 2. Send assistant text to Telegram as it arrives instead of buffering everything until the stream ends. When a ToolRequest follows text in the same message, the text is flushed immediately — the user sees 'Let me check...' right away, then the typing indicator while tools run, then the next response as a separate message. Only the final trailing text (after the last tool round-trip) waits for stream end. Before: user waits 30+ seconds seeing only 'typing', gets one giant message at the end (or nothing if it times out). After: user sees incremental messages as the agent works through each step, with clear activity indicators between them.
1 parent 6afcf67 commit 6cd5399

File tree

1 file changed

+57
-17
lines changed

1 file changed

+57
-17
lines changed

crates/goose-server/src/gateway/handler.rs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,16 @@ impl GatewayHandler {
303303
let cancel = CancellationToken::new();
304304
let user_message = Message::user().with_text(&message.text);
305305

306+
// Cap tool-calling loops so the agent doesn't run away doing
307+
// dozens of tool calls before responding. After this many
308+
// LLM→tool round-trips the agent will stop and reply with
309+
// whatever it has.
310+
const GATEWAY_MAX_TURNS: u32 = 5;
311+
306312
let session_config = SessionConfig {
307313
id: session_id.to_string(),
308314
schedule_id: None,
309-
max_turns: None,
315+
max_turns: Some(GATEWAY_MAX_TURNS),
310316
retry_config: None,
311317
};
312318

@@ -356,7 +362,13 @@ impl GatewayHandler {
356362
}
357363
});
358364

359-
let mut response_text = String::new();
365+
// Buffer text within a single assistant message so we send one
366+
// Telegram message per LLM turn rather than per-chunk. When a
367+
// ToolRequest appears in the same message we flush the buffer
368+
// first — the user sees "Let me check…" immediately, then the
369+
// typing indicator while the tool runs, then the next response.
370+
let mut pending_text = String::new();
371+
let mut sent_any = false;
360372
let mut event_count: u64 = 0;
361373

362374
while let Some(event) = stream.next().await {
@@ -373,12 +385,29 @@ impl GatewayHandler {
373385
for content in &msg.content {
374386
match content {
375387
MessageContent::Text(t) => {
376-
if !response_text.is_empty() {
377-
response_text.push('\n');
388+
if !t.text.is_empty() {
389+
if !pending_text.is_empty() {
390+
pending_text.push('\n');
391+
}
392+
pending_text.push_str(&t.text);
378393
}
379-
response_text.push_str(&t.text);
380394
}
381395
MessageContent::ToolRequest(req) => {
396+
// Flush any accumulated text before
397+
// the tool runs — the user sees the
398+
// assistant's intent immediately.
399+
if !pending_text.is_empty() {
400+
let _ = self
401+
.gateway
402+
.send_message(
403+
&message.user,
404+
OutgoingMessage::Text {
405+
body: std::mem::take(&mut pending_text),
406+
},
407+
)
408+
.await;
409+
sent_any = true;
410+
}
382411
if let Ok(call) = &req.tool_call {
383412
tracing::debug!(
384413
session_id,
@@ -462,23 +491,34 @@ impl GatewayHandler {
462491
tracing::debug!(
463492
session_id,
464493
event_count,
465-
response_len = response_text.len(),
494+
pending_text_len = pending_text.len(),
495+
sent_any,
466496
"gateway stream: finished"
467497
);
468498

469-
if response_text.is_empty() {
470-
response_text = "(No response)".to_string();
499+
// Send any remaining buffered text (this is typically the final
500+
// assistant response after the last tool round-trip).
501+
if !pending_text.is_empty() {
502+
self.gateway
503+
.send_message(
504+
&message.user,
505+
OutgoingMessage::Text {
506+
body: pending_text,
507+
},
508+
)
509+
.await?;
510+
} else if !sent_any {
511+
// Nothing was ever sent — let the user know.
512+
self.gateway
513+
.send_message(
514+
&message.user,
515+
OutgoingMessage::Text {
516+
body: "(No response)".to_string(),
517+
},
518+
)
519+
.await?;
471520
}
472521

473-
self.gateway
474-
.send_message(
475-
&message.user,
476-
OutgoingMessage::Text {
477-
body: response_text,
478-
},
479-
)
480-
.await?;
481-
482522
Ok(())
483523
}
484524
}

0 commit comments

Comments
 (0)