Skip to content

fix(server): save a resumed turn's message with its first event - #1392

Draft
mykytanetipa wants to merge 1 commit into
multiserver/fix-terminal-state-finalfrom
multiserver/fix-resumed-turn-single-save
Draft

mykytanetipa wants to merge 1 commit into
multiserver/fix-terminal-state-finalfrom
multiserver/fix-resumed-turn-single-save

Conversation

@mykytanetipa

Copy link
Copy Markdown
Collaborator

Resuming a task that waits for input wrote the task once with its old state, which a subscriber on another replica took as the end of the stream. Found while working on #1324.

Example:

sequenceDiagram
    participant C as Client
    participant A as Replica A (runs agent)
    participant S as Shared store
    participant B as Replica B
    Note over S: T is INPUT_REQUIRED
    C->>A: SendMessage(T, follow-up)
    C->>B: SubscribeToTask(T)
    A->>S: save T + message, still INPUT_REQUIRED
    S-->>B: event log row Task(INPUT_REQUIRED)
    B-->>C: stream closed (interrupted state)
    A->>S: WORKING, artifact, COMPLETED
    Note over C: never sees the rest of the turn
Loading

Fix: the follow-up message is added to the task in memory and persisted by the save of the agent's first event, so a resumed turn no longer writes its previous state. If that save conflicts with another replica, the message is added again when the event is retried on the reloaded task, so it is not lost. In the example B follows the turn to completion, and the message is recorded once.

Tested: a remote subscriber follows a resumed turn to completion, and the message is recorded once, including after a conflict; full suite and Postgres/MySQL tests pass.

When a task waiting for input is resumed, the consumer added the user's
message to the history with a separate save before applying the agent's
first event. That save wrote the task with its previous state, and in
cluster mode it also wrote a Task(INPUT_REQUIRED) row to the shared event
log. A SubscribeToTask on another replica treated that row as the end of
the stream and missed the rest of the turn.

The message is now added in memory and persisted by the save of the
event itself, so a resumed turn writes no stale state. If that save
conflicts, the message is added again when the event is retried on the
reloaded task.
@mykytanetipa
mykytanetipa added this pull request to stack #1393 October 27, 2026 12:55
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage (vs multiserver/fix-terminal-state-final)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/agent_execution/active_task.py 95.92% 95.91% 🔴 -0.01%
src/a2a/server/cluster/database_event_stream.py 96.94% 92.86% 🔴 -4.08%
src/a2a/server/cluster/database_task_store.py 96.55% 99.31% 🟢 +2.76%
src/a2a/server/events/event_queue_v2.py 91.79% 92.27% 🟢 +0.48%
src/a2a/server/request_handlers/default_request_handler_v2.py 87.68% 88.54% 🟢 +0.86%
src/a2a/utils/telemetry.py 90.70% 91.47% 🟢 +0.78%
Total 93.15% 93.20% 🟢 +0.05%

Generated by coverage-comment.yml

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant