fix(btw): compensate a work submission that is never handed over - #171
Open
YUZHEthefool wants to merge 1 commit into
Open
fix(btw): compensate a work submission that is never handed over#171YUZHEthefool wants to merge 1 commit into
YUZHEthefool wants to merge 1 commit into
Conversation
10 tasks
submit() creates the queued session, yields the acknowledgement, and only registers the background run once the generator resumes. A later stage that stops the event drops the generator instead, so nothing cancelled the session: /work status kept reporting a queued task that no run owned, and pending sessions are exempt from retention, so it survived until a reload. Reclaim the session from both ends. The generator cancels it in a finally unless the background run accepted it, and the scheduler -- the only place that runs even when the generator is never resumed -- cancels it as the event finishes. cancel_if_pending makes the transition itself decide which caller performs it, so the two paths cannot cancel or clean up twice. config_id_of moves to runtime_registry, which is already how the pipeline reaches work-loop state. Fixes #155 AI-Generated: true Generated-At: 2026-09-11T14:02:36Z
YUZHEthefool
added this pull request to stack #174
September 11, 2026 15:57
YUZHEthefool
force-pushed
the
fix/btw-submit-handoff-compensation-155
branch
from
September 11, 2026 16:00
e890c8a to
c729ccd
Compare
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.
Summary
A work submission interrupted between its acknowledgement and the background
hand-off left a queued task no run owned. Expected the session to end
cancelled/failedand be cleaned up once; actual/work statuskept reportinga
pendingtask, with zero background tasks registered and the event alreadycleaned up. Pending sessions are exempt from retention, so the record survived
until a reload or restart.
Related issue
Fixes #155
Root cause
submit()creates the queued session, yields the acknowledgement, and registersthe background run only once the generator resumes. When a later stage stops the
event, the scheduler breaks out of the generator and never resumes it — an
async forthat breaks does not close its generator — so no code path cancelledthe session.
close()only walks registered_tasks, and the retention sweepskips
PENDING, so nothing reaped it either.Reproduction
Build a real
PipelineSchedulerandWorkLoop, add a stage after theacknowledgement that calls
event.stop_event(), then runscheduler.executeandread the
WorkSession. The production equivalents are a plugin that stops theevent while decorating the acknowledgement, or a cancelled request.
Implementation notes
Reclaim the session from both ends, with the transition itself deciding who did
it:
submit()cancels the session in afinallyunless the background runaccepted it. This covers a generator closed by
aclose()/GeneratorExit.PipelineScheduler.execute's existingfinallyis the one place that runs evenwhen the generator is never resumed, so it asks
runtime_registryto cancel awork session whose background run never took over. This reuses the same hook
point the scheduler already has for BTW (
btw_detached_work,finalize_detached_event), so the coupling is not new.WorkSessionManager.cancel_if_pendingonly movesPENDINGtoCANCELLED, sothe two paths cannot cancel twice or clean up twice. It cannot touch a
legitimate background run that is merely queued behind the semaphore, because
the hand-off marks the event before the task starts.
config_id_ofmoves toruntime_registry(the module the pipeline already usesto reach work-loop state);
submissionimports it from there.Validation
The new test was run against the unmodified tree first, where it fails with
WorkSessionStatus.PENDINGand zero background tasks — the exact reportedsymptom.
make check,make quality, and the coverage-gated--test-profile allrun werenot run locally on this Windows checkout; this PR relies on CI for those, as #168
did.
After #168 was rebased onto the updated
masterhead, this branch was rebased ontothe new head as well and the whole stack was re-validated at its tip:
pytest tests/unit -q5233 passed / 6 skipped andpytest --test-profile blocking -q5354 passed / 6 skipped / 1 deselected.The count above was taken on this branch before that rebase.
Compatibility and risk
PipelineScheduler.execute'sfinallynow makes one extra call per event(a registry lookup plus a mark check). It is a no-op for every event that does
not carry a BTW work session.
config_id_ofis still importable fromastrbot.core.agent.btw.submission.Checklist
docs/zh/anddocs/en/when needed. (No user-visible text changed.)docs/public/openapi.json, and tests change together when routes or schemas change. (No route or schema change.)pyproject.toml,requirements.txt, anduv.locktogether. (No dependency change.)!and aBREAKING CHANGE:footer. (None.)Agent note
Goal: resolve #155 by compensating between the acknowledgement
and the background hand-off, keeping BTW off by default and preserving
authorization and request identity.
Paths touched:
astrbot/core/agent/btw/work_loop.py,astrbot/core/agent/btw/work_sessions.py,astrbot/core/agent/btw/runtime_registry.py,astrbot/core/agent/btw/submission.py,astrbot/core/pipeline/scheduler.py, andtests/unit/test_btw_delivery.py,tests/unit/test_work_tools.py.Checks run: the commands under Validation, plus the new test against the
unmodified tree.
Residual risk: the compensation depends on the scheduler's
finallyrunning. Anisolated
WorkLoopused withsubmit()and no scheduler has neither thegenerator resumption nor that backstop, so a submission abandoned there could
still linger; the
finallyinsubmit()covers the close paths that do occur,and no such caller exists in the tree.
Tools used: Claude Code (Opus 5) with the repository's AGENTS.md and AI_POLICY.md.
This PR is stacked on #170; merge that first.