Preserve turn lineage across goal continuations (#41562)

## Why

Automatic goal continuations should remain attributable to the turn that created the goal. External input, hook context, or goal edits can make that attribution ambiguous and must not leave stale lineage metadata behind.

## What changed

- Carry the trusted root and previous parent turn through successive automatic goal continuations.
- Invalidate stored lineage when external context reaches an active turn or when a goal is edited or cleared.
- Preserve lineage for async hook results only when they belong to the receiving turn.

## Testing

- Cover goal continuations across intervening user turns and external objective edits.
- Cover active, buffered, and externally injected hook or response context.

GitOrigin-RevId: 22ccc07961cd0ed8f4b17912b7efa416b342bf50
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-29 18:51:11 +00:00
1 parent 6478a751fd
commit 4210c08def
9 files changed
+410 -10

No files matched your search

+40
View File
@@ -35,6 +35,41 @@ impl Session {
}
}
/// Injects hook context while classifying its actual receiving turn atomically.
#[expect(
clippy::await_holding_invalid_type,
reason = "active turn provenance and turn state updates must remain atomic"
)]
pub(crate) async fn inject_hook_context_if_running(
&self,
input: Vec<ResponseItem>,
source_turn_id: Option<&str>,
) -> Result<(), Vec<ResponseItem>> {
let mut active = self.active_turn.lock().await;
let Some(active_turn) = active.as_mut() else {
return Err(input);
};
let Some(task) = active_turn.task.as_ref() else {
return Err(input);
};
if source_turn_id != Some(task.turn_context.sub_id.as_str()) {
task.turn_context
.turn_metadata_state
.mark_root_turn_ambiguous();
}
self.input_queue
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
active_turn.turn_state.as_ref(),
input
.into_iter()
.map(ResponseItemEnvelope::new)
.map(PendingTurnInput::ResponseItem)
.collect(),
)
.await;
Ok(())
}
/// Preserves trusted client provenance while items wait for an active turn.
#[expect(
clippy::await_holding_invalid_type,
@@ -51,6 +86,11 @@ impl Session {
.collect::<Vec<_>>();
let mut active = self.active_turn.lock().await;
if let Some(active_turn) = active.as_mut() {
if let Some(task) = active_turn.task.as_ref() {
task.turn_context
.turn_metadata_state
.mark_root_turn_ambiguous();
}
self.input_queue
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
active_turn.turn_state.as_ref(),