Skip to content

Commit 4628335

Browse files
committed
fix: prevent duplicate End events in planning mode
When planning mode is enabled, execute_plan calls execute_loop for each step. Previously, execute_loop_inner would emit AgentEvent::End for each step that had no tool calls, causing multiple End events for a single user message. Fix: add emit_end parameter to execute_loop/execute_loop_inner. When called from execute_plan (planning mode), pass emit_end: false to suppress per-step End events. execute_with_planning emits the final End event after execute_plan completes.
1 parent b962bbc commit 4628335

1 file changed

Lines changed: 39 additions & 11 deletions

File tree

core/src/agent.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,7 @@ impl AgentLoop {
15951595
session_id,
15961596
event_tx,
15971597
token,
1598+
true, // emit_end: this is a standalone execution
15981599
)
15991600
.await;
16001601

@@ -1682,7 +1683,7 @@ impl AgentLoop {
16821683
let result = if use_planning {
16831684
self.execute_with_planning(history, prompt, event_tx).await
16841685
} else {
1685-
self.execute_loop(history, prompt, session_id, event_tx, token)
1686+
self.execute_loop(history, prompt, session_id, event_tx, token, true)
16861687
.await
16871688
};
16881689

@@ -1753,17 +1754,28 @@ impl AgentLoop {
17531754
session_id: Option<&str>,
17541755
event_tx: Option<mpsc::Sender<AgentEvent>>,
17551756
cancel_token: &tokio_util::sync::CancellationToken,
1757+
emit_end: bool,
17561758
) -> Result<AgentResult> {
17571759
// When called via execute_loop, the prompt is used for both
17581760
// message-adding and hook/memory/event purposes.
1759-
self.execute_loop_inner(history, prompt, prompt, session_id, event_tx, cancel_token)
1760-
.await
1761+
self.execute_loop_inner(
1762+
history,
1763+
prompt,
1764+
prompt,
1765+
session_id,
1766+
event_tx,
1767+
cancel_token,
1768+
emit_end,
1769+
)
1770+
.await
17611771
}
17621772

17631773
/// Inner execution loop.
17641774
///
17651775
/// `msg_prompt` controls whether a user message is appended (empty = skip).
17661776
/// `effective_prompt` is used for hooks, memory recall, taint tracking, and events.
1777+
/// `emit_end` controls whether to send `AgentEvent::End` when the loop completes
1778+
/// (should be false when called from `execute_plan` to avoid duplicate End events).
17671779
async fn execute_loop_inner(
17681780
&self,
17691781
history: &[Message],
@@ -1772,6 +1784,7 @@ impl AgentLoop {
17721784
session_id: Option<&str>,
17731785
event_tx: Option<mpsc::Sender<AgentEvent>>,
17741786
cancel_token: &tokio_util::sync::CancellationToken,
1787+
emit_end: bool,
17751788
) -> Result<AgentResult> {
17761789
let mut messages = history.to_vec();
17771790
let mut total_usage = TokenUsage::default();
@@ -2202,14 +2215,16 @@ impl AgentLoop {
22022215
"Agent execution completed"
22032216
);
22042217

2205-
if let Some(tx) = &event_tx {
2206-
tx.send(AgentEvent::End {
2207-
text: final_text.clone(),
2208-
usage: total_usage.clone(),
2209-
meta: response.meta.clone(),
2210-
})
2211-
.await
2212-
.ok();
2218+
if emit_end {
2219+
if let Some(tx) = &event_tx {
2220+
tx.send(AgentEvent::End {
2221+
text: final_text.clone(),
2222+
usage: total_usage.clone(),
2223+
meta: response.meta.clone(),
2224+
})
2225+
.await
2226+
.ok();
2227+
}
22132228
}
22142229

22152230
// Notify context providers of turn completion for memory extraction
@@ -2930,6 +2945,17 @@ impl AgentLoop {
29302945
// Execute the plan step by step
29312946
let result = self.execute_plan(history, &plan, event_tx.clone()).await?;
29322947

2948+
// Emit the final End event (execute_loop_inner does not emit End in planning mode)
2949+
if let Some(tx) = &event_tx {
2950+
tx.send(AgentEvent::End {
2951+
text: result.text.clone(),
2952+
usage: result.usage.clone(),
2953+
meta: None,
2954+
})
2955+
.await
2956+
.ok();
2957+
}
2958+
29332959
// Check goal achievement when goal_tracking is enabled
29342960
if self.config.goal_tracking {
29352961
if let Some(ref g) = goal {
@@ -3045,6 +3071,7 @@ impl AgentLoop {
30453071
None,
30463072
event_tx.clone(),
30473073
&tokio_util::sync::CancellationToken::new(),
3074+
false, // emit_end: false — End is emitted by execute_with_planning after execute_plan
30483075
)
30493076
.await
30503077
{
@@ -3139,6 +3166,7 @@ impl AgentLoop {
31393166
None,
31403167
tx,
31413168
&tokio_util::sync::CancellationToken::new(),
3169+
false, // emit_end: false — End is emitted by execute_with_planning after execute_plan
31423170
)
31433171
.await;
31443172
(step_clone.id, sn, result)

0 commit comments

Comments
 (0)