Repository navigation
Conversation
TaskManager.save_task_event applied status and artifact events to the cached task in place and only then called the task store. When the store raised, the cache was ahead of the store. ActiveTask's failure handler reads that cache to decide whether to record FAILED, saw the unsaved COMPLETED, and wrote nothing, so the task stayed WORKING in the store. Apply the event to a copy instead. _save_task already caches the task only after the store accepts it, so a failed save leaves the cache as last persisted, and a VersionedTaskStore now receives the task as previously read in `prev`, as its contract documents.
🧪 Code Coverage (vs
|
| Base | PR | Delta | |
|---|---|---|---|
| src/a2a/server/cluster/database_event_stream.py | 92.86% | 96.94% | 🟢 +4.08% |
| src/a2a/server/tasks/task_manager.py | 98.94% | 98.95% | 🟢 +0.01% |
| Total | 93.37% | 93.41% | 🟢 +0.04% |
Generated by coverage-comment.yml
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When the task store rejects the write of a task's terminal status (for example a dropped database connection during the COMPLETED save),
DefaultRequestHandlernever records FAILED: the task staysTASK_STATE_WORKINGin the store, and clients pollingtasks/getnever see a terminal state.TaskManager.save_task_eventapplies a status or artifact event to the cached_current_taskin place and only then calls the store. Whensave()raises, the cache already says COMPLETED. The consumer's failure handler,_write_failed_statusinactive_task.py, reads that cache throughtask_manager.get_task()and returns early for a terminal state, so the FAILED write it documents for this case never happens.Reproducer: an agent emits
Task,start_work(),complete(); the store raisesConnectionErroron the first COMPLETED write; thentasks/get.flowchart TD E[status or artifact event] --> M{TaskManager.save_task_event} M -->|main| A[mutate cached _current_task in place] A --> S1[store.save raises] S1 --> C1[cache says COMPLETED, store says WORKING] C1 --> W1[_write_failed_status: cache is terminal, skip] W1 --> R1[task stays WORKING] M -->|this PR| B[apply event to a copy] B --> S2[store.save raises] S2 --> C2[cache still says WORKING, as persisted] C2 --> W2[_write_failed_status writes FAILED] W2 --> R2[task is FAILED]Change, in
src/a2a/server/tasks/task_manager.py:save_task_eventcopies the cached task and applies the event to the copy._save_taskalready assigns_current_taskonly aftersave()returns, so a failed save leaves the cache as last persisted. The same change fixesprevfor aVersionedTaskStore: it was the updated task itself rather than "the task as previously read" thatVersionedTaskStore.savedocuments.Also:
ActiveTask.cancelthat said ordinary status writes still update the shared object in place is updated; Task events andupdate_with_messagestill do.test_default_request_handler.py(legacy handler) relied on the in-place updates:test_on_message_send_non_blockingcomparedlen(result.history)with the stored task after polling.resultwas the cached object, so it kept growing after it was returned. It is now the task as returned, and the test checks that its history is a prefix of the stored history.test_on_message_send_error_does_not_hangused anAsyncMock(spec=TaskStore)whosegetreturned anAsyncMockin place of aTask; it now returnsNoneso the task is created and the firstsaveraises as before.Behaviour notes:
RequestContext.current_taskis no longer updated in place by later status and artifact events during a request; it is the task as read when the request started. Those in-place updates raced with the consumer anyway (enqueue_eventreturns before the event is applied).VersionedDatabaseTaskStorethe version check rejects the stale FAILED write.Task.CopyFromper status or artifact event. The default path already copies the whole task per event (_enqueue_to_subscribers,CopyingTaskStoreAdapter.save,DatabaseTaskStoreserialisation).Not in this PR:
update_with_messagestill appends the user message to the cached task before its save (#1392 rewrites those lines inactive_task.py).Tests:
tests/server/tasks/test_task_manager.py:test_save_task_event_failed_save_keeps_cached_task(status and artifact events) andtest_save_task_event_passes_previous_task_as_prev.tests/server/request_handlers/test_default_request_handler_v2.py:test_failed_terminal_save_records_failed_task, end to end throughon_message_sendwith anInMemoryTaskStorethat fails the first COMPLETED write.main(assert 3 == 1, artifact left in the cache,assert 2 == 1,assert 2 == 4for WORKING vs FAILED) and pass with the change.Checks, run on Windows 11 (CPU, Python 3.10.19); Linux CI will confirm:
bash ./scripts/lint.sh: ruff check, ruff format andtypass (three existing unusedtype: ignorewarnings in files this PR does not touch).uv run pytest tests/server/tasks/test_task_manager.py tests/server/request_handlers/test_default_request_handler.py tests/server/request_handlers/test_default_request_handler_v2.py tests/server/agent_execution: 297 passed.uv run pytest --ignore=tests/integration/cross_version --ignore=tests/integration/test_scenarios.py --cov=src: 2364 passed, 203 skipped, 4 xfailed; total coverage 93%. The 4 setup errors (tests/integration/push_notifications,Can't pickle local objectunder Windows spawn) happen onmainhere too.tests/integration/cross_version(usesos.killpg) andtests/integration/test_scenarios.py(hangs on Windows onmain) were not run locally, so the Linux CI run is the check for those.Follow the
CONTRIBUTINGGuide.Make your Pull Request title in the https://www.conventionalcommits.org/ specification.
Ensure the tests and linter pass
Appropriate docs were updated (if necessary)