Skip to content

Enforce work claims: task executor and base agent acquire declared scopes #15950

Description

@mrveiss

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.py gains a way for an agent to declare the scopes a
run will touch, and a2a/task_executor.py acquires 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.py exposes scope declaration; at least the file-writing and KB-writing
    agents declare theirs
  • a2a/task_executor.py acquires before execute and releases in a finally — verified by
    a test that raises mid-task and asserts the scope is free afterwards
  • Cancellation (task_manager.cancel_task) releases held claims
  • Claims renew for the life of a running task; a stalled executor's claims expire
  • A refused task reports the holder's identity and intent in its result artifact, not a bare error
  • Guard test fails when a declared write site runs without a held claim
  • Existing a2a tests still pass — the acquire path must not change task semantics when uncontended

Implementation Order

Wave: 3
Depends on: #15946 Tasks 1 and 2

Activity

  1. mrveiss commented on Sep 8, 2026

    @mrveiss
    OwnerAuthor

    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 matcher that never fired reading as a guard in place, a _require_holder called 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.py is retrievable from origin/Dev_new_gui, and that work_claims.py and claim_waitlist.py are 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.

  2. 25 remaining items

  3. added this to the v0.9.0 milestone on Sep 12, 2026
  4. mrveiss commented on Sep 12, 2026

    @mrveiss
    OwnerAuthor

    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.py declared_scopes(request); kb_librarian_agent.py declares kb:<title> on writes; a2a/task_executor.py:93 acquires/releases scopes via hold_scopes(...) inside a finally; claims renew for the run's lifetime; refusal reports the holder's identity/intent as a JSON artifact; a guard test walks BaseAgent subclasses 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.

  5. mrveiss commented on Sep 13, 2026

    @mrveiss
    OwnerAuthor

    Cancellation AC ticked, with evidence from origin/main: autobot-backend/a2a/task_executor.py:392/399/411 stops at three checkpoints via _abort_if_cancelled (before scrub, before orchestration, after orchestration). task_executor_cancellation_test.py:155 test_cancelled_during_orchestration_performs_no_further_work_and_releases cancels through cancel_task and asserts no further work ran and list_claims() == []. Delivered by #16498 (#16174).

  6. mrveiss commented on Sep 17, 2026

    @mrveiss
    OwnerAuthor

    AC verification against origin/main (2026-09-17), all 7 checked

    Ran 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.py exposes scope declaration; file/KB-writing agents declare theirs agents/base_agent.py:193 declared_scopes(); implemented by agents/kb_librarian_agent.py, agents/npu_code_search_agent.py (grep-confirmed, non-test) ✅ (was unticked — done)
    2. task_executor.py acquires before execute, releases in finally, 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.

  7. mrveiss commented on Sep 17, 2026

    @mrveiss
    OwnerAuthor

    All 7 ACs verified against merged code on origin/main (evidence table above). Closing.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions