Skip to content

fix(subagents): tear down dispatched child before batch retry - #5253

Open
Zsanz3 wants to merge 20 commits into
bytedance:mainfrom
Zsanz3:cursor/fix-batch-bookkeeping-overlap-adc2
Open

Zsanz3 wants to merge 20 commits into
bytedance:mainfrom
Zsanz3:cursor/fix-batch-bookkeeping-overlap-adc2

Conversation

@Zsanz3

@Zsanz3 Zsanz3 commented Sep 7, 2026

Copy link
Copy Markdown

Fixes #5240

Problem

After execute_async() starts a child, a bookkeeping failure (mark_item_running / lease renew) could finalize and requeue the durable item without cancelling that child. The service then dropped ownership while the previous execution kept running, so a retry could overlap it.

Fix

On supervisor/bookkeeping failure after a child was dispatched: retain ownership, request cancellation, await teardown, then release for retry. Lease fencing and user-cancel paths are unchanged beyond this local supervision order.

Tests

Added a bookkeeping-failure overlap regression in test_subagent_batch_service.py (schema-valid batch config bounds).

Scope

Only batch_service.py, its AGENTS note, and the regression test. Does not change #5116.

@CLAassistant

CLAassistant commented Sep 7, 2026 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ shoyann
✅ Zsanz3
❌ cursoragent
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added area:agents Agents, subagents, graph wiring, prompts, langgraph.json area:docs Documentation and Markdown only needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/M PR changes 100-300 lines labels Sep 7, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at c9851204b (refs/auto-review/5253-c9851204). The supervision order is the right shape — retain ownership, request cancellation, wait, then finalize — and the new regression test passes (5 passed). Two real gaps remain on this head: the teardown helper can only ever return True, so the refusal branch is dead and the wait is an unbounded, lease-renewing loop that can wedge an item forever (reproduced locally), and on current main execution_done_event does not exist while terminal status is set before _aexecute's finally teardown, so "teardown confirmed" can still release the item during the previous child's cleanup. Two smaller items cover the lost-lease path and the unprotected renewal inside the wait loop.

Comment thread backend/packages/harness/deerflow/subagents/batch_service.py
Comment thread backend/packages/harness/deerflow/subagents/batch_service.py Outdated
Comment thread backend/packages/harness/deerflow/subagents/batch_service.py Outdated
Comment thread backend/packages/harness/deerflow/subagents/batch_service.py Outdated
@github-actions github-actions Bot added size/L PR changes 300-700 lines and removed size/M PR changes 100-300 lines labels Sep 8, 2026
@cursor
cursor Bot force-pushed the cursor/fix-batch-bookkeeping-overlap-adc2 branch from 51dc49a to 909a53d Compare September 8, 2026 14:03
@Zsanz3

Zsanz3 commented Sep 8, 2026

Copy link
Copy Markdown
Author

Thanks for the review at c9851204.

Kept the retain → cancel → wait → finalize order and closed the gaps:

  • _await_dispatched_execution_teardown() can now return False. The wait is bounded to a few lease periods; unconfirmed teardown no longer takes the ordinary immediate-retry path.
  • Teardown is confirmed only by SubagentResult.execution_teardown_event (set after run_with_timeout() returns) or a vanished registry entry — not by CANCELLED/FAILED/completed_at.
  • valid: False / cancel_requested stops the renew/poll loop and returns False instead of finalizing without a held lease.
  • Renewal exceptions are caught, logged with item_id, and treated as the lease-expire path.

Regressions cover lease-lost, unconfirmed teardown, and renewal failure. HEAD is 909a53d547f81f24640d7cf3741ceeffb6ef80e7.

@cursor
cursor Bot force-pushed the cursor/fix-batch-bookkeeping-overlap-adc2 branch 2 times, most recently from 5c0cfe2 to 0ca802a Compare September 9, 2026 13:55

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 0ca802a1 (refs/local-review/5253-0ca802a1). All four findings from the c9851204 round are addressed, and I checked each against the head code rather than the diff: the supervised wait is bounded to three lease periods and _await_dispatched_execution_teardown() can now return False; teardown is confirmed only by SubagentResult.execution_teardown_event (or a vanished registry entry), never by terminal status/completed_at; valid: False and cancel_requested stop the loop and refuse the ordinary retry; and a renewal exception is caught, logged with item_id, and takes the lease-expiry path instead of escaping. I ran both suites at this head in a pinned worktree (150 passed) and confirmed the four new regressions fail against c9851204's batch_service.py/executor.py, so they have real teeth. Nothing blocking; one suggestion below on pinning the ordering the fix depends on.

Comment thread backend/tests/test_subagent_executor.py
@cursor
cursor Bot force-pushed the cursor/fix-batch-bookkeeping-overlap-adc2 branch 2 times, most recently from 0e7d9a8 to 20e9b0d Compare September 10, 2026 14:32
@WillemJiang

Copy link
Copy Markdown
Collaborator

@Zsanz3 please click the CLA Assiatant button to sign the CLA before we merge you code.

@cursor
cursor Bot force-pushed the cursor/fix-batch-bookkeeping-overlap-adc2 branch from 20e9b0d to 56d0f67 Compare September 11, 2026 13:40
@github-actions github-actions Bot removed the area:docs Documentation and Markdown only label Sep 11, 2026
@WillemJiang

Copy link
Copy Markdown
Collaborator

@Zsanz3 please click the CLA assistant button to sign the CLA and resolve the conflict with the main branch before we start the merge process.

cursoragent and others added 5 commits September 15, 2026 19:30
A bookkeeping exception after execute_async() could finalize/requeue the
durable item while the previous child was still running and no longer
supervised. Retain ownership, cancel, wait for teardown, then release.

Fixes bytedance#5240

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
SubagentBatchesConfig rejects poll_interval_seconds < 0.1 and
lease_seconds < 10. Keep the bookkeeping-failure overlap test inside
those schema bounds.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
Bound the supervised wait, wait for the executor teardown signal
instead of terminal status, and stop without immediate retry when the
lease is lost or renewal fails.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
Record lease/holder release in delayed_aexecute's finally and assert
["release", "teardown_event"] so the event cannot fire before that
cleanup. Terminal status remains published earlier and is not
confirmation.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
The teardown Flow sentence pushed the local file over LOCAL_SOFT (40 KiB)
and failed test_repository_guidance_stays_below_soft_budgets. Restore the
file to match upstream main; the batch teardown contract remains covered
by tests.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/fix-batch-bookkeeping-overlap-adc2 branch from 56d0f67 to 2419618 Compare September 15, 2026 19:30
Upstream now revalidates the durable lease after tool assembly and
before execute_async(). Keep the first renew succeeding so these
cases still dispatch a child, then apply the lost-lease / renewal
failure on the teardown-wait path.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at the rewritten head d209757c (refs/auto-review/5253-d209757c; the branch was rebased, so I re-checked at head rather than relying on the 0ca802a1/20e9b0d rounds). All five prior findings remain fixed there: the supervised wait is bounded to three lease periods and _await_dispatched_execution_teardown() returns a real bool (the refusal branch is reachable); teardown is confirmed only by execution_teardown_event or a vanished registry entry, never terminal status/completed_at; valid: False/cancel_requested stops the wait and refuses the ordinary retry; a renewal exception is caught and logged with item_id, taking the lease-expiry path; and the executor regression now pins order == ["release", "teardown_event"] with a production-shaped finally in the patched _aexecute stand-in — that closes the round-3 test-strength thread. I also reviewed the delta since 20e9b0d — the rebase onto current main (pre-launch lease revalidation and off-loop tool assembly in _execute_item), the AGENTS.md restore, and the test-only adaptation making the first renewal the pre-launch revalidation (both regressions now assert lease_renewals >= 2, correctly distinguishing the wait-loop renew from the pre-launch one) — and found no regressions: run_coroutine_threadsafe guarantees the future (and forget_future) cannot resolve before run_with_timeout's finally, so the sticky event cannot fire early. Ran both suites at the pinned head: test_subagent_batch_service.py 10 passed, executor teardown tests 2 passed. Nothing outstanding from my side.

Drive the executor regression through _aexecute_admitted's sandbox
lease release instead of a patched _aexecute body, so moving
mark_execution_teardown_complete() before that release fails.

Co-authored-by: Zsanz3 <Zsanz3@users.noreply.github.com>
@Zsanz3

Zsanz3 commented Sep 16, 2026

Copy link
Copy Markdown
Author

Follow-up on 078d0da6: the teardown regression now pins the event after the production finally path (sandbox/holder release), instead of only while a patched _aexecute body is still sleeping. Happy to adjust further if anything still looks off.

@WillemJiang

WillemJiang commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

@Zsanz3 Please make sure the git commit author email is added in your GitHub profile email settings.

@Zsanz3 please click the CLA Assiatant button to sign the CLA before we merge you code.

@shoyann

shoyann commented Sep 21, 2026

Copy link
Copy Markdown

I found a cancellation-specific gap on d42a748b: request_cancel_background_task() calls cancel() on the cross-thread submission Future, which invokes forget_future() before the isolated-loop task has finished draining. That callback currently sets execution_teardown_event, so the teardown check can pass while resource cleanup is still blocked.

I opened a focused follow-up against your branch: Zsanz3#4 (one commit, implementation + two regressions + the lifecycle note).

The new tests use the real submission/cancellation path and cover both cancellation during blocked cleanup and cancellation before loop dispatch. They fail on this head and pass with the follow-up; the executor/batch/capacity/runtime selection passes all 185 tests. The PR also reports the full Windows baseline comparison, including outstanding failures — it does not claim an all-green full suite.

The change leaves teardown confirmation in the execution wrapper's finally and keeps the Future callback responsible only for forgetting the submission handle. It does not change the lease-expiry recovery policy or ordinary FAILED-result retries.

…xecution

Merging shoyann's teardown-confirmation follow-up into the bytedance#5253 head branch.
@github-actions github-actions Bot added area:docs Documentation and Markdown only size/XL PR changes 700+ lines and removed size/L PR changes 300-700 lines labels Sep 21, 2026
@Zsanz3

Zsanz3 commented Sep 21, 2026

Copy link
Copy Markdown
Author

Thanks @shoyann — I reviewed the follow-up and merged it into this branch (f86b7662, via Zsanz3#4).

Teardown confirmation now stays in the execution wrapper's finally after cleanup drains; the Future callback only forgets the submission handle. The two new cancellation regressions are on this head.

Happy to adjust further if anything still looks off.

@WillemJiang

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts in this pull request

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read-only static review of pinned current revision; verified introduced findings below. No PR code or tests executed.

"Lease renewal failed while waiting for dispatched child teardown (item_id=%s)",
item_id,
)
lease_held = False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Exclude retained teardown items from lease-expiry claims

After a bookkeeping failure, one transient renewal exception stops all subsequent renewals but leaves this item in _executions until teardown or the three-lease-period deadline. With a spare service slot, the same worker's run_once keeps calling claim_items: when this lease expires, the repository requeues and commits a new claim with attempt += 1, then run_once discards that claim because the old item task is still mapped. For example, with legal 10-second leases and max_attempts=3, cleanup lasting 25 seconds allows claims at roughly 10 and 20 seconds to consume attempts 2 and 3 without dispatching either retry; the next expiry terminalizes the item as failed after only one actual execution. Prevent atomic recovery/claim from consuming attempts for locally retained teardown items, or return to the lease-expiry path immediately once renewal fails instead of retaining a nonrenewing item task. Cover this with the real repository and polling during blocked teardown.

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

Labels

area:agents Agents, subagents, graph wiring, prompts, langgraph.json area:docs Documentation and Markdown only needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/XL PR changes 700+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Batch retry can overlap a still-running subagent after a bookkeeping error

6 participants