Repository navigation
Conversation
…yable 4xx Align _post_execution_status with the Go SDK's postExecutionStatus: transport errors, 408, 429, and 5xx retry up to five times with 1+2+4+8s backoff and a 30s per-attempt timeout; every other HTTP status returns immediately. 404 is permanent now that Agent-Field#1086 persists the execution row. Refs Agent-Field#1088 Co-authored-by: Steven Huang <remote-controlled-man@users.noreply.github.com>
Match Go postExecutionStatus: resend httpx.TransportError (including timeouts), the requests connection and timeout errors from the sync fallback, and HTTP 408, 429, and 5xx. Fail fast on request-construction and programming errors such as InvalidURL and TypeError. CancelledError still leaves the loop immediately. A permanent HTTP status is logged once, and the stale no-cover pragma on the handler is gone. Refs Agent-Field#1088 Co-authored-by: Steven Huang <remote-controlled-man@users.noreply.github.com>
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Performance
✓ No regressions detected |
|
The |
AbirAbbas
left a comment
There was a problem hiding this comment.
Ran this against a live control plane built from current main: async execution completes normally, an unknown execution id gets exactly one 404 attempt, and a rotated key gets one 401 attempt. The new tests also fail against main's _post_execution_status the way they should (permanent cases go five rounds there).
One thing to fix before merging: the requests fallback in _is_retryable_status_callback_error only retries ConnectionError and Timeout, but requests reads the body eagerly and raises requests.exceptions.ChunkedEncodingError when a response is cut off mid-body. That's a transport failure, not a programming error. A real server that sends a truncated 503 and then a clean 200 gets one request from this branch, which then logs "not retrying" and drops the status, while main gets there on the second try. Adding ChunkedEncodingError (and arguably ContentDecodingError) to the retryable tuple, with a test that goes through the actual fallback against a server that truncates once, would cover it.
A heads-up rather than a blocker: the legacy reasoner and skill handlers in reasoners.go only log when createLegacyExecutionRecord fails and still dispatch, and a later workflow event can insert the row afterwards. In that narrow window, the first 404 here is actually transient. I think the right fix is on the control plane side (fail closed like execute_prepare.go does), so I'll track that separately.
Also, merging main will clear the red Python CI: that was the FastAPI 0.148 enable_mcp breakage, fixed in #1120.
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
|
Thanks for live-testing this! I merged main (which picks up #1120, so Python CI is green again) and added |
Return a structured 500 before reasoner or skill dispatch when the execution row cannot be persisted, including serverless nodes. Previously the insert failure was only logged and the call was dispatched anyway, so the agent's status callback could get a 404 that this PR now treats as permanent while a later workflow event created the row, leaving it running with no result. Cover insert failures and keep the persist-before-dispatch success path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks for the quick turnaround. The fallback fix checks out: a truncated 503 and an invalid-gzip 503 both deliver on the second request now, and your new test fails against the previous head. Permanent 4xx still stop after one attempt, and 408, 429 and 5xx still retry. I pushed the control-plane side on top (8a4b07f) so this can go in as one piece. The legacy |
Summary
Stop retrying permanent execution-status callback failures in the Python SDK. Currently a 400/401/403/404/409 response or a request-construction/programming error consumes the normal five callback attempts and the 1/2/4/8-second backoff; this change returns after the first permanent failure and logs it once.
Transport errors, HTTP 408/429 and 5xx responses still retry. The existing default of five outer callback attempts, exponential backoff, timeout arguments (30 seconds normally, 5 seconds during the single shutdown attempt), successful-response handling and cancellation propagation are preserved. HTTPX interprets those scalar timeout arguments per phase; this PR does not add a total request deadline.
This addresses the Python portion of #1088. The TypeScript portion remains outside this PR, and #1099 has already fixed the execution-row race that previously made 404 retryable temporarily.
Type of change
Test plan
Executed locally on Windows, Python 3.11.7, against base
2f5583e5and reviewed headbc8149d8:Regression RED: restore the unpatched base implementation and run the new callback behavior cases. Eleven cases fail on actual retry-count assertions; collection succeeds. Restore the reviewed files exactly afterward.
python -m pytest --no-cov tests/test_async_execution.py: 47 passed. Cases cover status classification, HTTPX/requests and built-in transport exceptions, permanent errors, backoff/exhaustion, shutdown and cancellation.python -m ruff check .with CI-pinned Ruff 0.15.22: passed.python -m mypy --config-file mypy.ini agentfield/: passed on 83 source files.Broader SDK tests with
--no-cov,--ignore=tests/test_agent_mesh.pyand the eight baseline exclusions below: 2537 passed, 6 skipped, 46 deselected, zero failures. Git Bash is used for CLI subprocess tests.Eight failures were reproduced on the exact unpatched base and explicitly deselected: seven Windows
test_log_writer.pypipe/CRLF cases and onetest_tool_calling.pycase. The 46 deselections include 38 already configured by the repository. An earlier coverage-enabled run had 2536 passed and one failure in the unchanged connection-manager health-check reconnection timing assertion; that node was retained and passed in the final run. Three isolated base repetitions also passed, so this is not claimed as a reproduced base failure. A later coverage-enabled run stopped with Windows resource exhaustion after 703 passes; the final run disables coverage collection, without adding exclusions. Both failures remain recorded.tests/test_agent_mesh.pywas not run in this continuation because of the previously observed Windows hang. Linux, other Python versions, alternate websockets versions, live control-plane tests,scripts/test-all.shandscripts/coverage-summary.shwere not run locally; Docker is unavailable. No full unfiltered suite or hosted CI pass is claimed.Test coverage
The local configured coverage selection does not measure
agentfield.agent; the callback assertions above execute it directly. Coverage baselines are unchanged. Hosted coverage results still need to be checked.Checklist
docs/CONTRIBUTING.md,docs/DEVELOPMENT.mdand the applicable repository instructions.The two source commits retain their original
Cursor Agentauthorship and are unsigned after rebasing. This contribution used AI assistance; the tests listed above were executed during preparation, independently of the original source PR's claims.Related issues / PRs
Refs #1088. Follows the Go callback classification in #1081 and the legacy dispatch fix in #1099.