Skip to content

Commit cf3207c

Browse files
authored
Merge pull request #159 from NgoQuocViet2001/NgoQuocViet2001/94-preserve-late-assistant-output
fix(agents): preserve late assistant output after turn completion
2 parents 796bd06 + af42d40 commit cf3207c

1 file changed

Lines changed: 22 additions & 12 deletions

File tree

crates/atlas-agents/src/actor.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,9 @@ pub struct SessionActor {
162162
/// The live turn's epoch, as returned by `mark_turn_started` — the value
163163
/// the backend stamps onto this turn's events. Inbound stamped events
164164
/// whose stamp doesn't match are stragglers from a superseded/cancelled
165-
/// turn and are dropped. `None` = no gating basis (no turn started yet,
166-
/// or `mark_turn_started` failed) → stamped events fail open (applied).
165+
/// turn and are dropped. `None` = no live gating basis (no turn started,
166+
/// a normal turn finished, or `mark_turn_started` failed) → stamped events
167+
/// fail open (applied).
167168
current_epoch: Option<u64>,
168169
/// Sends that arrived while a turn was live. Supersede = cancel-then-send
169170
/// (the Zed pattern): the incoming Send cancels the running turn and
@@ -648,6 +649,12 @@ impl SessionActor {
648649
let (status, delta, sweep_to) = match result {
649650
Ok(stop_reason) => {
650651
let cancelled = stop_reason == "cancelled";
652+
// Normal completion may be followed by output from detached
653+
// work. With no live epoch, stamped session traffic fails open;
654+
// cancelled and failed turns retain their stale-event guard.
655+
if !cancelled {
656+
self.current_epoch = None;
657+
}
651658
(
652659
SessionStatus::Idle,
653660
SessionDelta::TurnFinished { stop_reason, turn_seq },
@@ -1063,9 +1070,10 @@ mod tests {
10631070
}
10641071

10651072
#[tokio::test]
1066-
async fn late_event_from_old_turn_is_dropped() {
1067-
// An event stamped with a finished turn's epoch must not be applied
1068-
// after that turn's terminal.
1073+
async fn late_assistant_event_after_normal_turn_is_applied() {
1074+
// A normal turn may launch detached work that reports back after the
1075+
// prompt future resolves. Its assistant output still belongs in the
1076+
// session transcript even though the actor is already idle.
10691077
let (handle, sink, mut gates, _conn) = setup_gated(1);
10701078
handle.control_tx.send(Control::Send("hi".into())).unwrap();
10711079
settle().await;
@@ -1077,17 +1085,19 @@ mod tests {
10771085
settle().await;
10781086
gates.remove(0).send("end_turn".into()).unwrap();
10791087
settle().await;
1080-
let before = sink.0.lock().len();
1081-
// Straggler stamped with the dead turn's epoch: dropped entirely.
1088+
// A late assistant chunk stamped with the normally completed turn's
1089+
// epoch must still be applied.
10821090
handle
10831091
.stream_tx
1084-
.send(acp_stamped(text_chunk_event("straggler"), 1))
1092+
.send(acp_stamped(text_chunk_event("background result"), 1))
10851093
.unwrap();
10861094
settle().await;
1087-
assert_eq!(
1088-
sink.0.lock().len(),
1089-
before,
1090-
"an event stamped with a dead turn's epoch must produce no deltas"
1095+
assert!(
1096+
sink.0.lock().iter().any(|e| matches!(
1097+
&e.delta,
1098+
SessionDelta::TextChunk { delta, .. } if delta == "background result"
1099+
)),
1100+
"late assistant output after a normal finish must reach the transcript"
10911101
);
10921102
}
10931103

0 commit comments

Comments
 (0)