diff --git a/chat/services/regenerate.py b/chat/services/regenerate.py index 6442bb2..bceaf16 100644 --- a/chat/services/regenerate.py +++ b/chat/services/regenerate.py @@ -738,6 +738,12 @@ async def regenerate_assistant_turn( payload={ "event_id": transition.event_id, "started_at": chat.get("time"), + # T114.1: back-reference to the assistant_turn + # that triggered this transition (see turns.py + # for rationale). + "triggered_by_assistant_turn_id": ( + new_assistant_event_id + ), }, ) elif transition.new_status == "completed": @@ -747,6 +753,10 @@ async def regenerate_assistant_turn( payload={ "event_id": transition.event_id, "completed_at": chat.get("time"), + # T114.1: back-reference (see above). + "triggered_by_assistant_turn_id": ( + new_assistant_event_id + ), }, ) promote_completed_event( @@ -762,6 +772,10 @@ async def regenerate_assistant_turn( payload={ "event_id": transition.event_id, "completed_at": chat.get("time"), + # T114.1: back-reference (see above). + "triggered_by_assistant_turn_id": ( + new_assistant_event_id + ), }, ) diff --git a/chat/web/turns.py b/chat/web/turns.py index dfb4b21..623390d 100644 --- a/chat/web/turns.py +++ b/chat/web/turns.py @@ -812,6 +812,14 @@ async def post_turn( payload={ "event_id": transition.event_id, "started_at": chat.get("time"), + # T114.1: back-reference to the assistant_turn that + # triggered this transition. Regenerate uses this + # to roll back lifecycle transitions when the turn + # is superseded. Forward-only — older events + # without this field are skipped by rollback. + "triggered_by_assistant_turn_id": ( + primary_assistant_event_id + ), }, ) elif transition.new_status == "completed": @@ -821,6 +829,10 @@ async def post_turn( payload={ "event_id": transition.event_id, "completed_at": chat.get("time"), + # T114.1: back-reference (see above). + "triggered_by_assistant_turn_id": ( + primary_assistant_event_id + ), }, ) # Run promotion inline so the artifact-emitting events @@ -842,6 +854,10 @@ async def post_turn( payload={ "event_id": transition.event_id, "completed_at": chat.get("time"), + # T114.1: back-reference (see above). + "triggered_by_assistant_turn_id": ( + primary_assistant_event_id + ), }, ) # Any other ``new_status`` value falls through silently — diff --git a/tests/test_turn_flow.py b/tests/test_turn_flow.py index 9d3fd0f..50209cb 100644 --- a/tests/test_turn_flow.py +++ b/tests/test_turn_flow.py @@ -1023,6 +1023,18 @@ def test_turn_with_event_transition_appends_started_event( assert started_payload["event_id"] == "evt_1" assert started_payload["started_at"] == "2026-04-26T20:00:00+00:00" + # T114.1: payload carries the back-reference to the assistant_turn + # that triggered the transition. The assistant_turn lands in + # event_log immediately before the event_started, so its id is + # the largest assistant_turn id in the chat at this point. + at_id = conn.execute( + "SELECT id FROM event_log " + "WHERE kind = 'assistant_turn' " + " AND json_extract(payload_json, '$.chat_id') = 'chat_bot_a' " + "ORDER BY id DESC LIMIT 1" + ).fetchone()[0] + assert started_payload["triggered_by_assistant_turn_id"] == at_id + # The events projection row reflects the active status. ev_row = conn.execute( "SELECT status, started_at FROM events WHERE event_id = ?",