Files
roboco/docs/rag/workflows/task-claiming.md
879afc14a4 Board Program LEARN context, ruff 0.16, and verb-rejection observability (#700)
* fix(board): LEARN decisions name the item, not its per-cycle index

A cycle's reject reasons are rendered into the NEXT cycle's exploration
prompt, but the ref recorded alongside each reason was the item's stored
id (item-0/item-1) — a per-cycle index that means something different
every cycle and appears nowhere the explorer can resolve. The reason
survived the loop; what it was about did not.

Record the item's title instead, via a shared learn_ref() helper (falls
back to the id when title-less, and reads target_task_title for Scales,
whose items name the live task they mutate).

* chore(lint): satisfy ruff 0.16 — keyword-only signatures and markdown formatting

The dev toolchain resolved ruff 0.16.0, which stabilises PLR0917 (too many
positional arguments) and formats python code blocks inside markdown. Both
fired repo-wide and neither had anything to do with the code they flagged.

- 36 signatures gain a `*` so their tail arguments are keyword-only, and
  the 104 call sites that passed them positionally are converted. mypy was
  the safety net for the static ones; the full suite caught nine more that
  only bind at runtime (the MCP tool functions, whose real callers already
  pass named JSON arguments).
- 28 markdown files reformatted by 0.16's code-block formatter.
- One RUF036 (`None` mid-union) autofixed in the GitLab provider.

* fix(gateway): log the reason when a verb rejects

A rejected envelope rides an HTTP 200, its body is never logged, and there
is no trace table — so in the access log a verb an agent could not satisfy
looks identical to one that worked. On 2026-07-25 four Board Programs
(Periscope, Sentinel, Scales, Barfly) each POSTed their propose verb three
or four times, persisted nothing, and left their exploration tasks PENDING;
the reason was unrecoverable afterwards, from the logs or from the agents'
own transcripts.

Log error/message/remediate/missing plus the calling agent at
envelope_to_response — the one chokepoint every v1 flow and do route
returns through. Success envelopes stay silent.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-26 15:07:28 +02:00

7.5 KiB

Task Claiming Workflow

Who Can Claim What

Role Claim verb Can Claim From Status
Developer i_will_work_on pending, needs_revision
QA claim_review awaiting_qa
Documenter claim_doc_task awaiting_documentation, pending
PM triage / give_me_work pending

Claiming a Task

# 1. Get a task assigned to you (returns a pending/awaiting task)
give_me_work()

# 2. Claim it. The claim verb is role-specific:
i_will_work_on(task_id)  # Developer — claims + auto-creates the branch
claim_review(task_id)  # QA — claims + auto-checks-out the dev's branch
claim_doc_task(task_id)  # Documenter

# Result:
# - status: claimed (then in_progress)
# - assigned_to: your agent ID

The claim verb both claims and starts the task — there is no separate start call. For developers, i_will_work_on also creates the feature/{team}/{task-hierarchy} branch and adds a dedicated per-task worktree at {clone_root}/.worktrees/{task-id-first-8}/, checking out the branch there. Your container is started with that worktree as its cwd, and the clone root's HEAD is never moved by the claim — so a second claim (or a coordinator PM's many parallel roots) never overwrites your first task's uncommitted work. See docs/rag/architecture/workspaces.md for the worktree model.

Exactly one active WorkSession exists per task at a time (enforced in the service layer and by a DB unique index). A re-claim — pool release, reaper unclaim, escalation redirect, or your own i_will_work_on/i_will_plan re-entry — supersedes any prior agent's stale active session for that task and re-points the worktree at the new claim.

If the branch you're reclaiming was cut a while ago, the base it branched from may have advanced since (a sibling merged into the parent, or a cell PR landed on root). The claim automatically folds that advance into your existing branch: an already-ancestor base is a no-op, a clean merge just happens silently, and a real conflict leaves your branch exactly where it was and appends a note plus a dev_notes line telling you to run sync_branch(task_id) to finish it by hand — the claim itself never fails over this. This only runs on developer/cell_pm/main_pm claims of a pending/needs_revision task; a QA/documenter claim or a PM gate claim reviews the branch exactly as pushed and never touches it.

If your task has dependency_ids in the same repo, the fresh branch cut also backfills each dependency's already-landed work when it sits outside your branch's own ancestor chain (a same-parent sibling or a same-project wave usually doesn't need this — the shared base already has it; a cross-subtree or cross-cell dependency edge can). This is a content assist, not a gate: a clean merge is silent, and a real conflict aborts the merge (your branch is left exactly at its cut point) and appends a note to the task naming the conflicting branch and files — resolve it by hand (merge the named branch into yours) before assembling your PR. A cross-repo dependency has no shared git history and is skipped entirely.

Before Claiming

  1. Check you have capacity (developers / QA / documenters work one task at a time; PM coordinators are exempt — a Main / Cell PM may hold many roots at once, gated only by sequence dependencies)
  2. Verify dependencies are completed AND no lower-sequence sibling is still open (see Claiming Rules below)
  3. Read task description and acceptance criteria

After Claiming

  1. Get proactive context: roboco_get_proactive_context(task_id)
  2. Search the KB for similar work: roboco_kb_search(query="...")

Claiming Rules

  • One at a time (workers only): Developers, QA, and documenters can't hold multiple in-progress tasks at once. A blocked task still counts as active — a blocked dev cannot claim a second task; unblock or unclaim first. PM coordinators are exempt — a Main / Cell PM plans and delegates many roots in parallel, so it may hold several at once; only a real upstream sequence dependency (an unfinished task it depends on) holds one of its roots back.
  • Team match: cell-scoped roles (developer, QA, documenter, cell PM) are rejected not_authorized on another team's tasks — claim, resume, unblock, and activate are all team-matched. The remediate hint says it: call give_me_work() to find a task in your own team. Org-wide roles (Main PM, Board, CEO, PR reviewer) are exempt.
  • Self-review prevention: QA cannot claim_review tasks they developed
  • Self-documentation prevention: Documenter cannot claim tasks they developed
  • Branch requirement: Branch auto-created on i_will_work_on
  • Sequence order (assignee-blind, reachability-aware outside MegaTask batches): if a task has a parent and a sequence number, a lower-sequence sibling can hold it — but whether ANY lower-sequence sibling blocks, or only a REAL one, depends on context. Inside a MegaTask batch (a root-subtask under the umbrella), the rule is strict and edge-agnostic exactly as before: held while ANY same-parent sibling with a strictly lower sequence is non-terminal, regardless of whether the two tasks are actually connected (the batch's sequence is a one-shot, globally-computed staged-release wave). Everywhere else, a lower-sequence sibling only blocks when it's a real (transitive) predecessor via dependency_ids — a task with no dependency edge onto any same-parent sibling still falls back to the strict raw bar. Siblings on the SAME sequence always run in parallel; cancelled siblings never block. This is independent of, and stricter than (in the batch case), dependency_ids: a claim attempt on a sequence-held task fails even with no unmet dependency. A held claim now returns a dedicated sequence_held error naming the blocking sibling — unclaim/wait is the only remedy, there is no override verb. The dispatcher pre-filters sequence-held (and dependency-held) tasks before attempting a claim (including on needs_revision reclaims), so you should rarely see this in practice — but a claim you make directly (rather than via give_me_work) can still hit it.
  • Project budget cap (when task budgets are armed): i_will_work_on / i_will_plan are refused once the project's monthly_budget_usd has been reached this calendar month — a WORK-STARTING claim only, so a QA/doc/PR-review/PM-merge claim on already-in-flight work is never blocked by this. There is no override; wait for the next month or ask the CEO to raise the cap.

Releasing a Claimed Task

If you claimed a task but realize you shouldn't work on it, use unclaim:

# Release back to pool
unclaim(task_id)

# Result:
# - status: pending
# - assigned_to: None
# - You can now claim new work

unclaim takes only the task_id — it returns the task to the pool for re-pickup. To hand a specific task to a specific agent, escalate to your PM (escalate_up) and let the PM re-delegate or reassign it.

When to use unclaim:

  • Task is out of your team's scope
  • Task requires a different role
  • You need to prioritize other work
  • Better suited for another agent

Status After Claim

pending → claimed (Developer via i_will_work_on / PM)
needs_revision → claimed (Developer via i_will_work_on)
awaiting_qa → awaiting_qa (QA via claim_review — claim recorded, status stays put so pass/fail match)
awaiting_documentation → claimed (Documenter via claim_doc_task)

Cannot Claim

  • completed or cancelled (terminal states)
  • Tasks assigned to others
  • Tasks you cannot work on (wrong role/team)