Repository navigation
Enforce work claims: task executor and base agent acquire declared scopes #15950
Description
Activity
Do not start this until #15987 is merged, not merely approved
Recording the reasoning before someone picks this up, because "approved" would look sufficient and is not.
Enforcement makes claims binding. It is built on the lock defined in #15987 (PR #16067, in review) and the negotiation in #15948. If review changes the lock's semantics — the overlap rule, what an interest does on acquire, whether a handoff can be refused — then enforcement written against the old semantics is not merely stale. It is authoritatively wrong: it will refuse writes it should allow, or allow writes it should refuse, and do so with the confidence of a guard.
That is worse than no enforcement, and the asymmetry is the point. A guard that is absent gets noticed. Someone eventually writes the check, or the collision it would have caught happens loudly. A guard that is confidently enforcing the wrong rule does not get noticed — it produces correct-looking refusals, and every one of them is evidence that the system is working.
This repository has produced that shape repeatedly: a
matcherthat never fired reading as a guard in place, a_require_holdercalled at three sites and defined at none, and four assertions on #15992 that would have passed without the behaviour they named. All of those were checks that looked present. Enforcement built on unmerged semantics is the same family, one level up — the check is present, runs, and enforces something that is no longer true.So the gate is base, not approval. Verify that
branch_stewardship.pyis retrievable fromorigin/Dev_new_gui, and thatwork_claims.pyandclaim_waitlist.pyare unchanged since — against merged base, the way #15947, #15948 and #15952 were closed, not against a PR that is expected to merge.Reasoning contributed by the session running the review queue; posted here so whoever implements meets the reason rather than the instruction.
Correction: the previous revision of this comment lost a sentence to shell command substitution — backticks inside a double-quoted argument were executed rather than quoted. Restored above. Worth noting because the mangled version still read as coherent prose: "the gate is base, not approval: returning the file, and / unchanged since" is broken, but broken in a way a skim does not catch.
- added 6 commits that reference this issue
on Sep 9, 2026 25 remaining items
- added a commit that references this issue
on Sep 11, 2026 - added a commit that references this issue
on Sep 12, 2026 Stale-issue sweep (never auto-closed — Dev_new_gui wasn't GitHub's default branch at merge time). Verified against current
origin/main; leaving open.MET:
agents/base_agent.pydeclared_scopes(request);kb_librarian_agent.pydeclareskb:<title>on writes;a2a/task_executor.py:93acquires/releases scopes viahold_scopes(...)inside afinally; claims renew for the run's lifetime; refusal reports the holder's identity/intent as a JSON artifact; a guard test walksBaseAgentsubclasses transitively and fails on an undeclared writing agent.NOT MET (both already tracked, not dropped): the second write-capable agent,
NPUCodeSearchAgent, can't declare yet — blocked by a file-size ceiling, tracked at open #16173. Cancellation doesn't release claims (only TTL-lapse bounds it) — tracked at open #16174. The delivering PR (#16175) lists both as explicitly deferred.- added 6 commits that reference this issue
on Sep 12, 2026 Cancellation AC ticked, with evidence from
origin/main:autobot-backend/a2a/task_executor.py:392/399/411stops at three checkpoints via_abort_if_cancelled(before scrub, before orchestration, after orchestration).task_executor_cancellation_test.py:155test_cancelled_during_orchestration_performs_no_further_work_and_releasescancels throughcancel_taskand asserts no further work ran andlist_claims() == []. Delivered by #16498 (#16174).AC verification against
origin/main(2026-09-17), all 7 checkedRan the control (
git log origin/main -E --grep="#15950([^0-9]|$)") first — 8 commits, matches expectation. Then checked every AC against current code, not against the issue's own checklist state (three were still shown unticked but are implemented):AC Evidence Status 1. base_agent.pyexposes scope declaration; file/KB-writing agents declare theirsagents/base_agent.py:193 declared_scopes(); implemented byagents/kb_librarian_agent.py,agents/npu_code_search_agent.py(grep-confirmed, non-test)✅ (was unticked — done) 2. task_executor.pyacquires before execute, releases infinally, tested(already ticked) ✅ 3. Cancellation releases held claims (already ticked) ✅ 4. Claims renew for the run's life; a stalled executor's claims expire agents/scope_enforcement.py:_renew_forever/_renew_round/_run_is_over, explicitly citing "#15950 AC4, owner ruling" in its own comments — renewal is progress-tied (stall window), not timer-tied, and a lapsed run's claims expire at TTL✅ (was unticked — done) 5. A refused task reports the holder's identity and intent (already ticked) ✅ 6. Guard test fails when a declared write site runs without a held claim tests/agents/test_declared_scopes_guard.py::test_every_write_capable_agent_declares_its_scopes— "the rule with teeth", plus two tripwire tests proving the detector can both fire (test_the_detector_fires_on_a_writing_class_that_does_not_declare) and pass (test_the_detector_does_not_fire_on_a_read_only_agent)✅ (was unticked — done) 7. Existing a2a tests still pass, uncontended semantics unchanged Ran locally: pytest a2a/→ 196 passed;pytest tests/coordination/test_work_claims.py tests/agents/test_declared_scopes_guard.py tests/agents/test_scope_enforcement.py→ 73 passed. Environment is below CI's declared dependency floors (same caveat as every local run this session), so this confirms no local regression, not CI parity — the real gate already ran this in CI when each of the 8 commits merged.✅ (was unticked — local re-confirmation) All 7 acceptance criteria are met on
main. The issue's checklist was stale relative to its own linked commits — AC1/4/6 landed but were never ticked.All 7 ACs verified against merged code on
origin/main(evidence table above). Closing.
Parent: #15946
Problem
A claim registry nobody calls is dead code. This task makes claiming the path agents
actually take, so "two agents overwrite each other" becomes impossible rather than
discouraged.
Scope
Declared scopes.
agents/base_agent.pygains a way for an agent to declare the scopes arun will touch, and
a2a/task_executor.pyacquires them before executing and releases after— including on failure and cancellation. A task whose scopes are refused does not run; it
reports the conflict (holder identity + intent) as its result, so the operator sees why.
Heartbeat. A long-running task renews its claims while it works; the renew is tied to the
task's lifetime, so a hung executor stops renewing and the scope frees itself.
Guard. A test that fails if a declared write site executes without a held claim — the
rule needs teeth, otherwise the next agent added skips it silently.
Acceptance criteria
base_agent.pyexposes scope declaration; at least the file-writing and KB-writingagents declare theirs
a2a/task_executor.pyacquires before execute and releases in afinally— verified bya test that raises mid-task and asserts the scope is free afterwards
task_manager.cancel_task) releases held claimsImplementation Order
Wave: 3
Depends on: #15946 Tasks 1 and 2