Repository navigation
Conversation
🧪 Code Coverage (vs
|
| Base | PR | Delta | |
|---|---|---|---|
| src/a2a/server/agent_execution/active_task.py | 96.10% | 96.11% | 🟢 +0.01% |
| src/a2a/server/cluster/database_event_stream.py | 93.64% | 97.27% | 🟢 +3.64% |
| src/a2a/server/request_handlers/default_request_handler_v2.py | 88.98% | 89.01% | 🟢 +0.03% |
| src/a2a/server/tasks/task_manager.py | 98.95% | 98.98% | 🟢 +0.04% |
| Total | 93.42% | 93.46% | 🟢 +0.04% |
Generated by coverage-comment.yml
c34c53f to
6be10db
Compare
|
Friendly ping — this is ready for review whenever anyone has bandwidth (both branches just rebased onto latest |
mykytanetipa
left a comment
There was a problem hiding this comment.
Why only stamp terminal states in TaskManager?
-
If an agent sends an unstamped WORKING or INPUT_REQUIRED update (for example via new_text_status_update_event, which doesn't set a timestamp), task.status.CopyFrom(event.status) still wipes out the existing timestamp and writes last_updated = NULL to the DB.
-
New SUBMITTED tasks created via new_task_from_user_message or TaskManager._init_task_obj (which ensure_task_id saves directly through _save_task) also start without a timestamp, so tasks created with return_immediately=True or artifact-first flows end up with last_updated = NULL too.
I think we should stamp any status that lacks a timestamp (in save_task_event and _init_task_obj / _save_task), not just terminal ones
Most task-lifecycle defects in this SDK are not wrong values: they are a correct value that reached one surface and not the others (persisted store, streaming subscribers, push notifications, the cluster task_events table, and the two separate v0.3 compat mappers). The existing rule layer routes mistakes to mistake-reflection and code style to docs/ai, but nothing tells a change agent which surfaces a status write must reach, so the same class keeps recurring: a2aproject#1276 (status.timestamp mapped for JSON-RPC/REST compat but not gRPC), a2aproject#1188 (handler resumes from a stale snapshot), a2aproject#1206/a2aproject#1411 (contextId inference disagrees between events and the stored task), and the review findings on a2aproject#1346 (stamping the copy instead of the event, and missing the CANCELED paths). Adds .agents/skills/task-state-invariants/SKILL.md with the invariant set (each claim anchored to current main), the sibling-surface checklist, the matching test files per surface, and the evidence expected in a PR, plus a one-line router pointer in AGENTS.md so agents actually load it. Signed-off-by: andy <linux2011@qq.com>
Framework failure paths could persist a FAILED task without status.timestamp: the consumer failure path built a TaskStatusUpdateEvent with an unstamped TaskStatus (which TaskManager.save_task_event then CopyFrom'd over the task, clearing any prior timestamp), and the producer failure path mutated task.status.state directly before saving. DatabaseTaskStore derives last_updated from status.timestamp, so the task was persisted with last_updated = NULL, breaking ListTasks ordering and time-based filters (issue a2aproject#1331). Per the approach endorsed on the issue, this lands both layers: - failure sites in ActiveTask (_write_failed_status, _mark_task_as_failed, and the producer failure path) stamp the current time explicitly; - TaskManager.save_task_event gains a defensive backstop that auto-populates the timestamp whenever a terminal state arrives unstamped, covering raw Task and status-event paths alike. Non-terminal states are left untouched, and explicitly provided timestamps are never overwritten. Signed-off-by: andy <linux2011@qq.com>
6be10db to
c923c37
Compare
|
All review threads here are addressed and resolved (rebased + fixes pushed as c923c37). Thanks for the detailed review — the Summary of what changed after your review:
Local: |
Description
Framework failure paths could persist a FAILED task without
status.timestamp, andDatabaseTaskStorederiveslast_updatedfromstatus.timestamp(database_task_store.py:152-156) — so the task was persisted withlast_updated = NULL, breakingListTasksordering andstatus_timestamp_afterfiltering. Root-cause analysis and a runtime reproduction are in #1331.Per the two-layer approach endorsed on the issue:
ActiveTask):_write_failed_status,_mark_task_as_failed, and the producer failure path now stamp the current time on the FAILED status.TaskManager.save_task_event: a terminal state arriving without a timestamp (rawTaskpath or status-event path) is auto-stamped with the current time, so future call sites cannot reintroduce the NULL-last_updatedhole.Semantics: non-terminal states are never touched, and explicitly provided timestamps are never overwritten.
fix:→ SemVer patch)Fixes #1331 🦕
Validation
TASK_STATE_FAILEDwithstatus.timestampset; non-terminal statuses and explicitly provided timestamps are untouched.state=TASK_STATE_FAILED, timestamp set=False(repro in [Bug]: Framework-written FAILED status drops status.timestamp, persisting last_updated as NULL #1331). AFTER:timestamp set=True.tests/server/tasks/test_task_manager.py(terminal-unstamped auto-stamp, provided-timestamp preserved, non-terminal untouched, raw-Task path guarded) + 1 end-to-end intest_default_request_handler_v2.py.2302 passed, coverage93%(gate 88%).Prepared with AI assistance (WorkBuddy); reviewed and verified by Linux2010