Use agentrc defaults for chief and pr-babysit, document config-free bench locks (#3936)

- Drop TinyUSB's pr-babysit reviewer overrides in favour of agentrc's defaults
- Document the human's yes that authorizes a headless chief to drive a PR
- Allow HIL remote selection and lock forcing only when the task scope names them
- Document that a named-board lock needs no config, while hil_test.py and
  hil_pool_check.py still need the host's config
diff --git a/.claude/agents/hil-operator.md b/.claude/agents/hil-operator.md
index 58a0a8e..0fe0371 100644
--- a/.claude/agents/hil-operator.md
+++ b/.claude/agents/hil-operator.md
@@ -32,8 +32,8 @@
   `13-1.6` creates a lock file for a board that does not exist and reserves nothing while
   reporting success. If `--all` cannot be taken, wait: a partial hold is worse than none,
   because it reads as protection.
-- If a lock is already held by someone else: report holder/reason (`hil_lock.py status`) — never force, never kill the holder. If the holder's reason is `hil_test.py`, that is a concurrent CI job mid-test on the board: waiting a few minutes and retrying once is appropriate when your task allows; otherwise return the holder info so the orchestrator can ask the user.
-- You cannot ask the user anything. Bypassing a lock (`HIL_NO_BOARD_LOCK=1`, or proceeding with manual hardware work despite a held lock) is allowed ONLY when your prompt explicitly states the user authorized forcing.
+- If a lock is already held by someone else: report holder/reason (`hil_lock.py status`) — force only as the next bullet allows, never kill the holder. If the holder's reason is `hil_test.py`, that is a concurrent CI job mid-test on the board: waiting a few minutes and retrying once is appropriate when your task allows; otherwise return the holder info to the orchestrator.
+- You cannot ask the user anything. Bypassing a lock (`HIL_NO_BOARD_LOCK=1`, or proceeding with manual hardware work despite a held lock) is allowed ONLY when your prompt's scope explicitly names forcing those boards.
 
 ## Hard rules
 
diff --git a/.claude/skills/hil/SKILL.md b/.claude/skills/hil/SKILL.md
index 1d65f66..14a02ff 100644
--- a/.claude/skills/hil/SKILL.md
+++ b/.claude/skills/hil/SKILL.md
@@ -13,7 +13,7 @@
 | `tusb` (hifiphile's external rig) | `test/hil/hfp.json`                  | no outbound SSH to dev PCs/ci; SSH-reachable FROM both |
 | anything else (a dev PC)          | `test/hil/local.json`                | yes (large pool, `test/hil/tinyusb.json`)              |
 
-Default to **local**. Use **remote** only when on a dev PC and the user says `remote`/`ci.lan`. Never attempt remote on `ci`.
+Default to **local**. Use **remote** only when on a dev PC and the request or task scope names `remote`/`ci.lan`. Never attempt remote on `ci`.
 
 `tusb` (ssh alias `hifiphile`) is an external rig (hosted by maintainer hifiphile), exercised by the
 GitHub CI `hil-tinyusb (hfp.json)` matrix job — **never run HIL against it unless the user explicitly asks.**
@@ -31,10 +31,11 @@
 python3 test/hil/helper/hil_lock.py release BOARD [BOARD...]
 ```
 
+- A manual session on a dev-bench board with no entry in this host's HIL config locks it by an agreed board name, with no config: `hil_lock.py hold BOARD --reason "..."` only reserves that name, so verify the probe serial and board identity yourself. A named-board lock needs no config; config-driven tools (`hil_test.py`, `hil_pool_check.py` without an explicit config, `hil_lock.py hold --all`) still need this host's config.
 - Never pre-hold boards you are about to run `hil_test.py` on — it self-locks and would treat your own hold as a conflict.
 - Rig-wide operations (uhubctl power cycling, `usb_recover.sh root-cycle`, pci-rebind, controller resets — bus renumbering) affect every board: `hil_lock.py hold --all --config <this host's config> --reason "..."` first — `--all` defaults to `tinyusb.json`, so on `tusb` it would reserve 27 boards that do not exist there and none of the three that do. Even a single root-port bounce needs `--all`: nothing maps a sysfs busport to a board name, and `hil_lock.py hold` accepts any string, so a "just the siblings" hold reserves nothing while reporting success.
 - `hil_lock.py status` lists holders. Locks auto-release when the holder process dies (kernel flock); `/tmp` clears on reboot.
-- Forcing past a lock: `HIL_NO_BOARD_LOCK=1 python3 test/hil/hil_test.py ...` bypasses the guard without killing the holder. Only with the user's explicit go-ahead — they accept the risk of colliding with whatever holds the board.
+- Forcing past a lock: `HIL_NO_BOARD_LOCK=1 python3 test/hil/hil_test.py ...` bypasses the guard without killing the holder. Only when the request or task scope explicitly names forcing that board — it risks colliding with whatever holds it; a refused hold alone never adds that scope.
 
 ## Pool check (board/probe health)
 
@@ -90,7 +91,7 @@
 - **Board:** `-b BOARD_NAME`, repeatable for a subset (`-b a -b b`); omit to run all boards in the config. Give a whole set to ONE run rather than one run per board: it schedules the boards across host controllers and budgets concurrent flashes and usbtest batteries per controller (`hil_lock.py` `FLASH_PARALLEL`/`USBTEST_PARALLEL`). Those permits are in-process semaphores — a second `hil_test.py` running alongside does not share them, it multiplies the load on the same xHCI cards.
 - **Pass-through:** `-v`, `-r N`, etc. forwarded unchanged.
 
-If `local.json` is missing on a dev PC, ask the user to supply one (only fall back to `tinyusb.json` if told to).
+If `local.json` is missing on a dev PC, ask the user to supply one before a `hil_test.py` or `hil_pool_check.py` run. An agent that cannot ask (`hil-operator`) does not run `hil_test.py`: it returns one `ran: false` row per requested board whose `detail` names the missing `test/hil/local.json`. Fall back to `tinyusb.json` only when the request or task scope says so; a manual session locks by board name as Board locks says.
 
 ## Local execution
 
diff --git a/.claude/skills/pre-pr/SKILL.md b/.claude/skills/pre-pr/SKILL.md
index 81c2330..1b00090 100644
--- a/.claude/skills/pre-pr/SKILL.md
+++ b/.claude/skills/pre-pr/SKILL.md
@@ -43,6 +43,6 @@
 ## 4. Summarize
 
 - Per-stage table: unit / build:<board> / size / pvs, then HIL per board — pass/fail with the first error for each failure.
-- If the hardware result has non-empty `locked` (a CI job held those boards): ask the user to choose **Force now** (re-invoke `hil-validate` with `force: true` for those boards; user accepts the risk of colliding with a mid-test CI job), **Keep waiting** (re-invoke `hil-validate` for them after a few minutes; ask again if still locked), or **Accept** the partial verdict. Never force without the user's answer.
+- If the hardware result has non-empty `locked` (a CI job held those boards): when the task scope names forcing those boards, re-invoke `hil-validate` with `force: true` for them (it risks colliding with a mid-test CI job); otherwise re-invoke it after a few minutes, and if they stay locked return the holders and the scope gap to the caller. Accepting the partial verdict is the caller's decision; in an interactive session, ask.
 - Wedged boards: point at `.claude/skills/usb-kernel-recover/SKILL.md`.
 - End with a clear ship / no-ship verdict and what to fix first.
diff --git a/.claude/workflows/hil-validate.js b/.claude/workflows/hil-validate.js
index 81d455a..f77ed10 100644
--- a/.claude/workflows/hil-validate.js
+++ b/.claude/workflows/hil-validate.js
@@ -1,7 +1,7 @@
 export const meta = {
   name: 'hil-validate',
   description: 'Hardware-in-the-loop run: one hil-operator flashes and tests every board in a single hil_test.py run; per-board flock locks arbitrate with concurrent CI (the actions-runner keeps running)',
-  whenToUse: 'After validate passes, to exercise built firmware on the physical rig. Requires the boards to be built (cmake-build/cmake-build-<board>, plus a dir per declared variant). If the result has non-empty `locked`, ask the user: force (re-invoke with force: true), continue waiting (re-invoke later), or accept the partial result. Pass force: true ONLY with explicit user authorization.',
+  whenToUse: 'After validate passes, to exercise built firmware on the physical rig. Requires the boards to be built (cmake-build/cmake-build-<board>, plus a dir per declared variant). If the result has non-empty `locked`, act on the task scope: force (re-invoke with force: true) when it names forcing those boards, otherwise wait and re-invoke, or return the holders and the scope gap to the caller; accepting a partial result is the caller decision. Pass force: true ONLY when the caller scope names forcing.',
   phases: [{ title: 'HIL', detail: 'one hil-operator, every board in one hil_test.py run' }],
 }
 
@@ -53,7 +53,7 @@
     : '') +
   'Do NOT touch the actions-runner service and do NOT pre-hold the board locks — hil_test.py self-locks each board for its flash+test. ' +
   (args.force
-    ? 'THE USER HAS EXPLICITLY AUTHORIZED FORCING: run hil_test.py with HIL_NO_BOARD_LOCK=1 in the environment (bypasses the board lock check; do NOT release or kill the existing holder). '
+    ? 'THE CALLER SCOPE NAMES FORCING THESE BOARDS: run hil_test.py with HIL_NO_BOARD_LOCK=1 in the environment (bypasses the board lock check; do NOT release or kill the existing holder). '
     : 'A board whose lock is held (a dev session or concurrent CI job) fails fast inside the run without blocking the others — never force the lock. ') +
   'If hil_test.py refuses the run with "board(s) not in <config>", re-run it WITHOUT the unknown names but keep the FULL board list on the hil_report call below — it emits a ran:false entry for every board you name, so the unknown ones surface as "no report row" instead of costing the whole batch. ' +
   'Use the config for this host (hostname first). Run hil_test.py as a BACKGROUND Bash task and wait for it (a stuck fleet runs to its pool guard, 60 min by default — beyond any foreground timeout); never cancel it early. ' +
@@ -141,7 +141,7 @@
 
 const { pass, wedged, locked } = summarize(results, args.force, runCaveat)
 if (wedged.length) log(`WEDGED boards needing usb-kernel-recover: ${wedged.join(', ')}`)
-// Workers cannot prompt the user — surface still-locked boards for the main
-// session to ask: force (re-invoke with force: true), wait, or accept.
-if (locked.length) log(`still locked after retry: ${locked.join(', ')} — ask the user: force / keep waiting / accept`)
+// Workers cannot decide scope — surface still-locked boards to the caller, whose
+// scope decides: force (re-invoke with force: true), wait, or accept.
+if (locked.length) log(`still locked after retry: ${locked.join(', ')} — caller's scope decides: force / keep waiting / accept`)
 return { pass, results, wedged, locked, caveat: runCaveat }
diff --git a/.claude/workflows/test/test-hil-validate.mjs b/.claude/workflows/test/test-hil-validate.mjs
index 467c700..840d3de 100644
--- a/.claude/workflows/test/test-hil-validate.mjs
+++ b/.claude/workflows/test/test-hil-validate.mjs
@@ -92,5 +92,9 @@
 check('an omitted caveat cannot silently disable the gate (schema requires it)',
   HIL_REQUIRED.includes('caveat'), true)
 
+check('force forwards the caller\'s scope and never claims human approval',
+  [/THE CALLER SCOPE NAMES FORCING THESE BOARDS/.test(src), /USER HAS EXPLICITLY AUTHORIZED|explicit user authorization/i.test(src)],
+  [true, false])
+
 console.log(failed ? `\n${failed} FAILED` : '\nall checks passed')
 process.exit(failed ? 1 : 0)
diff --git a/CLAUDE.md b/CLAUDE.md
index 9babce1..cf02a74 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -13,16 +13,12 @@
 
 ## Skills
 
+- Agents, skills and workflows beyond this repo's `.claude/` come from [agentrc](https://github.com/hathach/agentrc); install it before agent work on TinyUSB.
 - Skill script tests live in `.claude/test/test_*.py`.
-- From [agentrc](https://github.com/hathach/agentrc): skills `read-doc`, `simplify-gate`, `usb-sniffer`, `usb-kernel-debug`, `rtt`, `etm-trace`, `target-debug`, `esp-target-debug`; agents `chief`, `pvs-studio`, `code-writer`, `code-verifier`, `finding-verifier`, `pr-ci-watcher`, `pr-review-validator`, `hw-debugger`, `hw-validator`; workflows `code-audit`, `pr-babysit`. When one is unavailable, skip the step that needs it.
 - Hardware diagnosis and repair use agentrc's `hw-debugger`; independent hardware claims and committed-fix validation use `hw-validator`, orchestrated by `chief`.
 - Driver audit: the `code-audit` workflow with `dirs` (e.g. `src/portable/<vendor>/<driver>`) and `dimensions`: `correctness: transfer state machines, endpoint bookkeeping, completion and error paths`; `ISR safety: work deferred to task context, shared-state races, register access ordering`; `register use vs datasheet and MCU errata: cross-check the reference manual AND errata sheets via the read-doc skill; if the skill is unavailable treat the document as absent (low confidence, never a web/filesystem substitute); a missing erratum workaround is a finding`; `style: repo conventions (TU_ASSERT, no dynamic allocation, include order, naming)`.
 - Static analysis: the `pvs-studio` agent with rules `.PVS-Studio/.pvsconfig` (never add suppressions) on a board's examples build; `raspberry_pi_pico` mirrors CI, `stm32f407disco` is fastest.
 
-## Claude and Codex Collaboration
-
-- Keep project workflows in `.claude/workflows/`, nested one level at most.
-
 ## Build and Validate
 
 - Build contract: `.claude/skills/build/SKILL.md`. Its script resolves a change to boards and builds them; `--shared` writes `cmake-build/cmake-build-<board>`, the dir HIL flashes from, so preserve it. Flash with `ninja -C cmake-build/cmake-build-<board> <example>-jlink` or `-openocd`.
@@ -36,7 +32,7 @@
 ## PRs and Follow-ups
 
 - Before opening or updating a PR, follow Build and Validate; use `pre-pr` when workflows are available.
-- After opening a PR, use `chief` to drive reviews and CI to green. TinyUSB `pr-babysit` args:
-  `{"pr": <num>, "reviewers": ["codex","copilot","coderabbit"], "autoRun": ["codex","copilot","coderabbit"], "protected": "^test/hil/[^/]+\\.json$"}`
+- After opening a PR, ask the human to authorize one headless `chief` invocation to drive reviews and CI to green under agentrc chief's Authorization exception. Name the PR URL, head repository and branch, and actions: push commits to that branch, post replies to the PR's review feedback, and resolve review threads through `pr-babysit` with `autoPush: true`. On an affirmative answer, launch `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 claude -p --agent chief "$task"` in the PR worktree. The task names the PR, head repository and branch, expected HEAD and scope, and includes the question and answer verbatim. Leave the checkout to chief until it exits; a new chief invocation requires a fresh exchange. TinyUSB `pr-babysit` args:
+  `{"pr": <num>, "protected": "^test/hil/[^/]+\\.json$"}`; reviewers are agentrc's default.
   (`protected` excludes the HIL rig rosters from automated fixes).
 - The follow-up label is `followup`.