Skip to content
Back to skills

Paseo Team Lead

ASecurity

Coordinate research, implementation, correction, and independent review through Paseo-managed Pi peers. Use when orchestrating multi-agent work on a repository — scoping, spawning read-only researchers, delegating an engineer to an owned scope, monitoring, and running an independent review on a stable candidate SHA.

  • 14 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentsrustgoshellnoderailsgitapisecuritydocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add Minnyat/paseo-team-orchestration --skill paseo-team-lead --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Paseo Team Lead?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Paseo Team Lead
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/minnyat-paseo-team-lead/badge)](https://www.skillsdirectory.com/skills/minnyat-paseo-team-lead)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: paseo-team-lead
description: Coordinate research, implementation, correction, and independent review through Paseo-managed Pi peers. Use when orchestrating multi-agent work on a repository — scoping, spawning read-only researchers, delegating an engineer to an owned scope, monitoring, and running an independent review on a stable candidate SHA.
---

# Paseo Team Lead Workflow

## Preflight

1. Glance at repository state: `git status` and a few lines of `git log
   --oneline`, nothing bigger. Reconstructing the repo is a Scout's job (see
   Research), not yours.
2. Read `WORKSPACE_PROTOCOL.md` in full — it governs you — and `AGENTS.md` when it
   is short; when it is long, have the Scout extract what binds the work.
3. Identify the outcome — what has to be true afterwards, and for whom —
   separately from any solution the request names, then the success boundary
   and risks. A named solution is evidence about the outcome, not the spec.
4. **Check that this cluster has a Supervisor that decides, and size the watch to
   the job.** `lead_ask_supervisor` reports `NO_SUPERVISOR_SEAT` when it does
   not, and a cluster without one has no delegated decision path — every
   question in it lands on the Human. Seating one is a Lead act (see "Asking
   instead of interrupting" below). A short job needs just that seat. A long one
   — many Peers over several rounds, parallel writers, anything that will
   outlast your own context — also gets watch seats ("Seating supervisors for a
   long job"). Do it now, at intake, not the first time you are stuck.
5. Do not begin implementation yet.

## Research

Research is for understanding the domain — what the code does today, who owns
it, what the outcome actually needs — not for starting the build, and it is a
Peer's work, not yours. You state the questions; a read-only Peer (a Repository
Scout, Documentation Researcher or Solution Challenger) reads and answers; you
read the answer, not the files behind it. Staff it by open question, not by
habit; if you can already state the outcome, the owners and the seams from
reports you hold, go straight to Decision. Scaling ceremony to the task never
scales the invariants: the V3 brief, the lease and the exact-SHA independent
review apply to a one-line change too.

Every Peer works in YOUR workspace (see step 8 of the routing cycle). Send them a
**V3 read-only brief** (`PASEO_TEAM_TASK_V3_BEGIN` … `PASEO_TEAM_TASK_V3_END`
with `MODE: read-only` — see "Task brief template" below). Legacy
`PASEO_TEAM_TASK_V1|V2` headers are parseable for diagnostics only: the
extension ALWAYS resolves them read-only and ignores their MODE and
`*_AUTHORITY` fields, so never use them for new work.

### Delegating reading and doing

Your context is the scarcest in the cluster: you hold decisions, not data. If an
answer needs more than one file or a screen of output, a Peer reads it and reports.

| You want to know… | Ask |
|---|---|
| where X lives, what Y does, how it got that way | `repository-scout` (`FAST_READ`) |
| what a spec, API or doc says; anything outside the repo | `documentation-researcher` (`FAST_READ`) |
| whether an artifact matches a checklist | `acceptance-verifier` (`FAST_READ`; see Review) |
| what a command or test prints, and whether it passes | a scout with `VERIFICATION_PROFILE: focused-test`: it runs it and reports pass/fail, the failing lines and how to reproduce |
| whether a candidate is right | `independent-reviewer` (`REVIEW_HIGH`) |

Seat ONE standing scout per job and keep using it: a follow-up is a
`send_agent_prompt` with a short `MODE: read-only` V3 block and costs no routing
cycle. Never send it bare: with no valid brief a Peer cannot `peer_ask_lead`, and
you would have to read its activity log. Ask for reports that
point at files and run about a screen; when the question needs more, ask for the
top findings and a pointer. What you read yourself: the Workspace Protocol, the
Peers' reports and verdicts, `git status` and a short log, and the one item of a
verdict you doubt.

## Decision

Synthesize evidence. Record:

- chosen approach;
- rejected alternatives;
- owned scope;
- excluded scope;
- the seam contract: for every boundary two tasks share (an API, a schema, a
  behavior, a state another task reads), what is fixed;
- every transitional state, with the task that removes it;
- verification;
- unresolved risks.

### Splitting into tasks

Split along a real dependency or an ownership boundary, never because more
phases look more rigorous. The test for every task and every phase: if it did
not exist, what would go wrong? If the answer is "nothing, the plan would just
be shorter", merge it.

A phase no dependency requires usually stands on a transitional state — a shim,
a flag, a compatibility path, a half-migrated schema — that exists only so the
plan's steps can be committed one at a time. Here that state is more dangerous
than it looks: every task gets a fresh Peer and every candidate a fresh
Reviewer on its own SHA, and none of them knows the shim was meant to go. Tests
get written against it, the next task builds on it, review approves it as
architecture. So when one is unavoidable, name it — what it is, which task
removes it — in three briefs: the one that creates it, the Reviewer's brief for
that intermediate SHA, and the one that removes it. The job is not accepted
while it still exists.

Lock the seams, not the insides. `OWNED_SCOPE` says where a writer may work; a
brief that also dictates how — which helper, which variable, how the logic is
laid out — is pseudo-code in prose: the Peer types it out and loses the room to
tell you the plan is wrong. A brief that leaves the seam open lets two parallel
writers each build a correct half that does not fit the other. Put the seam
contract, word for word the same, in both writers' briefs, and leave everything
behind it to them. The contract holds only what crosses the seam: a fact about
one side — its rollout flag, a helper name the Human asked for — goes outside
it, in that side's brief, attributed to whoever asked. Inside, it reads as
shared and frozen, and the two copies stop being identical.

Writers that commit at the same time cannot share one checkout: it has one
HEAD. Give each its own `git worktree` on `agent/<TASK_ID>` and name the path
(`.worktrees/<TASK_ID>`) in its brief — a git worktree inside your workspace,
like the Reviewer's, never a Paseo workspace. Its shell may start back in your
checkout, so the base gate and the push run as `git -C <path> …`; the push guard
accepts exactly that spelling. Do not tell the writer to leave the tree in
place: review runs from the SHA, and the writer removes it when it has reported
(a correction re-adds it from the branch).

## Accessing Paseo tools

Paseo tools are not separate tools in the prompt — they are reached through the
`mcp` proxy tool (pi-mcp-adapter):

1. `mcp` with `{ "connect": "paseo" }` to connect the Paseo MCP server.
2. `mcp` with `{ "search": "create_agent" }` or `{ "describe": "<tool>" }`
   to discover the exact tool name.
3. `mcp` with `{ "tool": "<name>", "args": { ... } }` to invoke.

The MCP server injected into THIS agent always talks to the **local daemon**
only — there is no `--host` on any MCP tool (`--host` is a Paseo CLI option,
not an MCP argument). Remote daemons are driven through the Paseo CLI via
`remote-paseo.mjs` from the installed support-script directory (see
`REMOTE_CREATE_CYCLE` below). The notation `<PASEO_TEAM_SCRIPTS_DIR>` below
means a resolved filesystem path, never a literal shell token. Resolve it before
running the first support command, without relying on a profile file:

- POSIX/macOS: `SUPPORT_DIR="${PASEO_TEAM_SCRIPTS_DIR:-${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/extensions/paseo-team-scripts}"`
- PowerShell: `$supportDir = if ($env:PASEO_TEAM_SCRIPTS_DIR) { $env:PASEO_TEAM_SCRIPTS_DIR } elseif ($env:PI_CODING_AGENT_DIR) { Join-Path $env:PI_CODING_AGENT_DIR 'extensions\paseo-team-scripts' } else { Join-Path $env:USERPROFILE '.pi\agent\extensions\paseo-team-scripts' }`

Use that resolved directory for every `node .../remote-paseo.mjs`,
`model-routing.mjs`, and `ocr-review.mjs` invocation. Installers place the
scripts at this deterministic default. Source checkouts may set the env
variable to the repository `scripts/` directory. Never resolve support scripts
from the project's current working directory.

## Implementation — model routing cycle (mandatory)

For EVERY `create_agent`, run this exact cycle. Do not skip steps.

0. **Scope lease — writers only.** Before creating any Peer whose brief carries
   `MODE: write` + `EDIT_AUTHORITY: allowed`, take the lease for the scope that
   Peer will own:

   ```text
   team_lease { action: "claim", scope: "<OWNED_SCOPE>", ttlMs: <work window> }
   ```

   Check `granted` in the result, never merely `ok`. A claim that collides
   with a live lease is REFUSED and writes nothing. `<OWNED_SCOPE>` may list
   several paths, comma-separated (`src/api/**` means `src/api`), taken together
   or not at all; `claims` shows each.

   - `granted: true` → continue the cycle.
   - `granted: false` → another Lead owns ground that covers your scope; the
     result names it. Prompt that Lead directly — see "Coordinating with the
     other seats" below. Do NOT create the writer, narrow the scope to sneak
     under the holder, or wait out the TTL as a strategy.
   - Ledger unreadable → `BLOCKED: LEASE_UNVERIFIABLE`. This is a real blocker,
     not a warning.

   Renew with `action: "renew"` when work outlives the TTL, and
   `action: "release"` once the candidate is accepted or abandoned — a scope
   you forget to release blocks other Leads until it expires.

   Read-only dispositions (repository-scout, documentation-researcher,
   solution-architect, acceptance-verifier, independent-reviewer) take no lease:
   they share the tree by design.

   The policy enforces this on both runtimes: a skipped claim is a refused
   `create_agent`, not two engineers quietly editing the same files.

1. Pick `MODEL_CLASS` from task risk + disposition (classes table below).
2. Pick `HOST_ID` from the controller-local `cluster-routing.local.json` in the
   pack's config directory (`PST_TEAM_CONFIG_DIR` → `PASEO_TEAM_HOME` → an
   existing `~/.paseo-pi-team` → `~/.paseo-team-orchestration`; `preflight.mjs`
   prints it as `team-config-dir`). Writers need `git-write`+`focused-test`;
   reviewers `git-read`+`independent-review`. A file you cannot find or read is
   `BLOCKED: HOST_ROUTE_UNAVAILABLE` — check the other directory first, and
   never choose a model from `list_models` instead.
3. Read that host's route from the SAME file (single source of truth for the
   whole cluster — never infer a remote host's route from local memory), or
   run the resolver when the role pack repo is available:
   `node <PASEO_TEAM_SCRIPTS_DIR>/model-routing.mjs resolve --class <CLASS>` for the local
   `model-routing.local.json` (legacy single-host form).
4. Verify the target daemon is reachable before routing:
   - local: `paseo status` (daemon up);
   - remote: the endpoint env var named by `connection.endpointEnv` must be
     SET (never print or invent its value) AND
     `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs health --host-id <id>` must return
     `ok: true` → else `BLOCKED: HOST_ROUTE_UNAVAILABLE` (no silent fallback
     to another host; switching hosts is a recorded routing decision).

### The hard rule — local MCP vs remote CLI

The injected MCP server is LOCAL-ONLY. `--host` is a Paseo CLI option, not an
MCP argument. Therefore the target host decides the mechanism:

```text
IF connection.type == local:
    use MCP operations (through the mcp proxy)

IF connection.type == remote:
    do NOT use MCP operations for that host
    use `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs` (Paseo CLI with
    `--host` under the hood)
```

Resolving a remote host and then calling `list_providers`/`create_agent`/…
via MCP is a routing ERROR: the call lands on the LOCAL daemon, so you get
local inventory and a local agent while believing you are on the remote host.
This is the exact failure mode the cluster config exists to prevent.

### LOCAL_CREATE_CYCLE — target is `connection.type: local` (MCP)

1. Call `list_providers` (mcp) on the local daemon; verify the answer comes
   from the intended daemon.
2. Verify the route's role provider exists, is enabled AND reports a
   healthy status (an enabled provider with a bad status is NOT routable) →
   else `BLOCKED: ROLE_PROVIDER_UNAVAILABLE`.
3. Call `list_models` for that role provider.
4. Verify the exact model ID exists (check BOTH segments are non-empty in
   `<pi-provider>/<model-id>`) → else `BLOCKED: MODEL_UNAVAILABLE`.
5. Verify the configured thinking level, and read the model's answer as THREE
   states, not two — "we do not know" and "we know it has none" are different
   facts and only the first one is unverifiable:
   - the model publishes options and yours is among them → pass;
   - the model publishes options and yours is NOT among them →
     `BLOCKED: THINKING_OPTION_UNAVAILABLE`;
   - the model says it has no extended thinking — `thinkingSupported: false`,
     or (the same statement written as data) an option list that is present
     and EMPTY, which is exactly how `claude-peer/claude-haiku-4-5` reports
     itself → route it at `thinking: off` and nothing else. That is a verified
     pass, not a tolerated one. Any other level is
     `BLOCKED: THINKING_OPTION_UNAVAILABLE`.
   - the model says NOTHING about thinking → genuinely UNVERIFIABLE. Refuse
     the route (strict policy: unverifiable is not a pass) — UNLESS you asked
     for `thinking: off`, which an empty inventory satisfies in every case an
     inventory could have.
   Reading the third case as the fourth is what took the cheapest seat on the
   host out of service for work that never needed thinking. `resolveRoute` in
   `scripts/model-routing.mjs` implements exactly this and reports which case
   it hit in `thinkingValidated` (`exact` | `off` | `unsupported` |
   `unverifiable`).
6. Verify against `~/.pi/agent/models.json` `thinkingLevelMap` on the target
   host: a level mapped to `null` is silently clamped by pi → pick another
   level/model instead of accepting the clamp.
7. Compute the exact create_agent provider string:
   `<role-provider>/<pi-provider>/<model-id>` (Paseo splits at the FIRST
   slash only, so multi-slash model IDs like `openrouter/vendor/name` work).
   Thinking goes in `settings.thinkingOptionId` — never inside the model string.
8. **Leave the workspace alone — one job, one workspace.** Every agent of the
   job lives in the workspace you were started in, and `create_agent` puts it
   there when you pass no placement at all (Paseo nests the new seat under you).
   There is no workspace to decide, create or archive: the policy refuses
   `create_workspace`/`archive_workspace` for a Lead, and a `create_agent` that
   carries `workspaceId` (unless your own), `workspace`, `relationship`, `cwd`,
   `worktreeName`, `branchName`, `baseBranch`, `refName` or `githubPrNumber` —
   each mints a NEW workspace or detaches the agent.
   - A **Writer** is kept apart by `OWNED_SCOPE` and the scope lease (step 0):
     one writer per scope, enforced before it exists. Writers committing in
     parallel each add a git worktree under `.worktrees/` ("Splitting into
     tasks").
   - The **independent Reviewer** reviews a detached checkout of the exact
     candidate SHA and never touches the Engineer's tree: it makes that checkout
     itself with `git worktree add --detach <path> <candidate-sha>` inside your
     workspace (`.worktrees/review-<TASK_ID>`) and runs the wrapper against it.
     The wrapper checks the git fact (`REVIEW_WORKSPACE_NOT_WORKTREE`), not a
     Paseo workspace. If the worktree cannot be made it reports
     `BLOCKED: REVIEW_WORKTREE_UNAVAILABLE` — no fallback to the primary checkout.
9. Call `create_agent` with exactly the parameters Paseo reads, and nothing
   else — the policy refuses the rest, and says which parameter it dislikes:

   ```jsonc
   create_agent({
     title: "<short label, at most 60 chars>",
     provider: "<role-provider>/<model-ref>",      // the model lives HERE, exact
     initialPrompt: "<the brief, step 'Task brief template'>",
     settings: { thinkingOptionId: "<routed level>",   // or "off"
                 modeId: "auto" },                     // claude-* routes only
     labels: { "team.cluster": "<your own cluster>",
              "team.model-class": "<MODEL_CLASS from step 1>" }
     // no workspaceId, workspace, relationship, cwd, worktree*, mode, model, thinking
   })
   ```

   `team.model-class` is REQUIRED: the policy refuses the call unless provider,
   model and `thinkingOptionId` equal that class's route on this host (the refusal
   names the expected values — copy them). A class routed to a `*-supervisor`
   provider (`MONITOR_ECONOMY`) cannot seat a Peer.

   Routing is a parameter of this call and nowhere else. The model, thinking
   level, mode and workspace are NOT repeated in the message the Peer reads:
   they have one source of truth, the daemon, and `get_agent_status` is how you
   read them back. `settings.modeId` is REQUIRED on every `claude-*` route (see
   "Every `claude-*` agent you create needs `settings.modeId`"). NEVER omit the
   model to inherit a daemon default.
   The `team.cluster` label is REQUIRED and checked against YOUR OWN cluster:
   omit it and `create_agent` is refused, the message naming the exact value to
   pass (`labels["team.cluster"] is required and must be "<value>"`). Your own
   value is the `cluster` field of any `team_lease` result (`status` changes
   nothing) or of any Peer you already created; never guess it from the project
   name. A DIFFERENT cluster is refused too — that would stamp a new seat into
   another project's authority, not a typo to correct.
10. Call `get_agent_status` and bounded-poll `snapshot.runtimeInfo.model` and
    `runtimeInfo.thinkingOptionId` until startup identity is populated. Missing
    identity during the bounded startup window is
    `BLOCKED: STARTUP_IDENTITY_UNAVAILABLE`; do **not** archive because this is
    not a confirmed mismatch. If both identity fields appear and either differs
    from the request, classify `BLOCKED: MODEL_RESOLUTION_MISMATCH` and archive
    the wrongly-resolved agent.
11. Only then deliver/continue the initial task.

### REMOTE_CREATE_CYCLE — target is `connection.type: remote` (remote-paseo.mjs)

Every operation goes through `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs` (it drives the
Paseo CLI with `--host` and returns one JSON envelope per call). Never
hand-build `paseo ... --host` shell commands — the wrapper validates
provider/model/thinking, keeps the endpoint value out of every message, and
returns host-tagged JSON so a remote answer can never be confused with a
local one. In the commands below, `<id>` is the HOST_ID from
`cluster-routing.local.json`.

1. Reachability is already proven (step 4 of the shared cycle).
2. List the REMOTE daemon's role providers:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs providers --host-id <id>`
3. Verify the route's role provider exists, is enabled AND healthy **on the
   remote daemon** → else `BLOCKED: ROLE_PROVIDER_UNAVAILABLE`.
4. List the REMOTE model inventory (the inventory is per-daemon — cache per
   hostId, never by provider name):
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs models --host-id <id> --provider <role-provider>`
   ⚠️ `list_models` via MCP would return the LOCAL inventory — only the
   wrapper's answer counts for a remote host.
5. Verify the exact model ID + thinking level against the REMOTE list (same
   `BLOCKED: MODEL_UNAVAILABLE` / `THINKING_OPTION_UNAVAILABLE` rules, and the
   same three-state reading of thinking as local step 5: unverifiable is not a
   pass, but a model that reports no extended thinking is routable at
   `thinking: off`).
6. **One workspace for the whole job, ON THE REMOTE host.** A local workspace
   ID means nothing there, and a `run` without `--workspace` would run in the
   CONTROLLER's cwd, so the wrapper needs one workspace id, the same for every
   agent of the job, Writer and Reviewer included:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs workspaces --host-id <id>`
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs workspace-create --host-id <id> --path <path-on-remote> --title <t>`
   `workspace-create` is "ensure": it lists what is open and REUSES the workspace
   on that path (`reused: true`), creating one only when there is none, so a
   second call never leaves a second one. It creates only local workspaces
   (`--isolation worktree` is refused: `WORKSPACE_ISOLATION_REFUSED`; the old
   `--disposition` flag is gone). The Reviewer makes its own detached
   `git worktree add` inside this workspace, as on the local host.
7. Create the agent on the remote daemon (background by default; add
   `--wait-timeout <dur>` to wait for completion). The route is flags, not
   message text — provider/model, thinking, mode, workspace and cluster all go
   on the command, and none of them is repeated in the brief:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs run --host-id <id> --provider <role-provider>/<pi-provider>/<model-id> --thinking <level> --workspace <wks> --title <t> --brief <brief-file>`
   The envelope returns `agentRef: <host-id>/<agent-id>` — record it.
   `run` requires a `team.cluster` label the same way the local `create_agent`
   does, and the wrapper fills it in from your own cluster (`selfCluster()`) when
   `--label` does not set one. Pass it explicitly only for a remote seat in a
   DIFFERENT cluster (rare; note why in the `ROUTING_DECISION`); a run with no
   value even after the auto-fill is refused, naming `--label team.cluster=`.
8. Verify the OBSERVED runtime identity on the remote daemon. The wrapper's
   `run` command performs a bounded startup poll; use `--startup-timeout <dur>`
   when the host needs a longer (still bounded) initialization window:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs status --agent-ref <host-id>/<agent-id>`
   Missing `data.Model`/`data.Thinking` until the startup deadline is
   `BLOCKED: STARTUP_IDENTITY_UNAVAILABLE`; do not archive. Only after both
   fields appear, compare them with the request: a confirmed mismatch is
   `BLOCKED: MODEL_RESOLUTION_MISMATCH`, then archive the wrongly-resolved agent
   on that host
   (`node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs archive --agent-ref <host-id>/<agent-id>`).
9. Follow-ups / corrections:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs send --agent-ref <host-id>/<agent-id> --prompt <text>`
   (or `--prompt-file <file>` for long briefs). send is fire-and-forget by
   default; `status` confirms completion. To interrupt a stuck agent:
   `node <PASEO_TEAM_SCRIPTS_DIR>/remote-paseo.mjs cancel --agent-ref <host-id>/<agent-id>`.
10. Only then deliver/continue the initial task.

Never: omit the model field, silently change models, fall back to another
model or host without recording a routing decision, launch first and "hope",
trust a model name written in a prompt instead of runtime config, or call MCP
for a remote host.

### Runtime family (mixed fleet)

Every role runs on two runtimes. The Paseo role provider names the family:
`pi-<role>` or `claude-<role>`. Both carry the SAME role contract — the same
prompt, the same V3 brief, the same authority gates — so the choice is a
capacity/capability decision, never a policy one:

| Pick | When |
|---|---|
| `pi-*` | the host's routing file points the class at a pi model; work that needs a pi-only model id (`<pi-provider>/<model-id>`) |
| `claude-*` | Claude-only capabilities are needed (`ultracode` thinking, Claude model ids); or the pi provider is unavailable/disabled on that host |

Hard rules for a mixed fleet:

- The model reference SHAPE differs per family and is validated:
  `pi-peer` → `<pi-provider>/<model-id>`; `claude-peer` → a bare id such as
  `claude-opus-5`. A pi-shaped model on a Claude route is a config error, not
  something to normalise.
- Thinking vocabularies differ: `minimal` exists only on pi, `ultracode` only
  on Claude. Take the value from the route, never from habit.
- Keep ONE Lead per project, on ONE family, for the life of that project.
  Peers may be mixed freely; the Lead is the deterministic part.
- The family you route to is the `provider` you pass to `create_agent`, and
  `get_agent_status` reports what actually came up. They must match; a seat on a
  different family than you asked for is `BLOCKED: MODEL_RESOLUTION_MISMATCH`,
  not a detail.
- Claude Peers cannot spawn Claude subagents (the `Task` tool is denied for
  every role). Fan-out is always yours, through Paseo.
- **Every `claude-*` agent you create needs `settings.modeId`.** A permission
  mode is never inherited across providers — not even `claude-lead` →
  `claude-peer`, where a value legal for the target is still refused: `cannot
  inherit mode 'auto' from caller … Pass an explicit mode.` pi declares NO modes,
  which is the only reason `pi-lead` → `pi-peer` works. Rule: pi route → nothing
  to pass, Claude route → always pass it. The field is `settings.modeId`, NOT a
  top-level `mode` (Paseo ignores that one, leaving the error above and no clue):
  `create_agent({ provider: "claude-peer/claude-opus-5", …, settings: { modeId:
  "auto", thinkingOptionId: "high" } })`. The CLI and `remote-paseo.mjs run` spell
  it `--mode`; the wrapper refuses a `claude-*` route without one. The provider
  supplies nothing: `defaultMode=auto` in `paseo provider ls` only preselects a
  picker, and at create time the daemon uses `modeId` if given, else `"default"`
  (measured 2026-09-07) — the `PreToolUse` gate refuses a `claude-*` create with
  no `settings.modeId` and names the value to pass.

  Use `"auto"` unless you have a reason not to. A Peer is bounded by its role
  policy plus its V3 brief, both enforced in the `PreToolUse` hook before Paseo's
  permission queue sees the call; on `"default"` every call parks in that queue
  and the Peer looks hung while you triage. Narrow it deliberately — `"plan"` to
  propose before acting, `"default"` to watch call by call, `"acceptEdits"` for a
  write Peer whose brief grants `EDIT_AUTHORITY` — and NEVER `bypassPermissions`:
  Paseo's own guardrails outside the role policy go too.

  A fork needs nothing extra: `paseo import` carries no mode, so `team_fork`
  moves the fork onto `auto` after the import and deletes it if that fails. Pass
  `modeId` to narrow it (`"plan"`), and whenever `auto` is unavailable
  (Bedrock/Vertex, "auto mode unavailable for this model") — otherwise
  `FORK_MODE_UNSET`: pick another explicit mode the seat supports, never
  `bypassPermissions`. Seats already on the wrong mode show in `pteam watchdog`
  under `parked`, each row carrying the `paseo agent mode <id> auto` fix (or a
  `fixNote` where `auto` does not exist).

Model classes (decided by task risk + disposition, not by role name):

| MODEL_CLASS | Use for |
|---|---|
| MONITOR_ECONOMY | supervisor heartbeat, structured observation |
| FAST_READ | scout, researcher, inventory, factual summary, acceptance-verifier, the mechanical pass of a review |
| CODING_MEDIUM | bounded implementation, clear-ownership bugfix, tests |
| REASONING_HIGH | architect, lifecycle/ownership/concurrency, migration, security design |
| REVIEW_HIGH | independent reviewer, proof auditor, exact-SHA acceptance |
| SUPERVISOR_GOVERNANCE | *optional* — ONLY for seating your Supervisor (route names a `*-supervisor` provider) |
| LEAD_RECOVERY | *optional* — ONLY for seating a Lead: a Supervisor's recovery, a successor Lead, a fork of a Lead (route names a `*-lead` provider) |

The two optional classes may be absent from a host's route file; the flow that
needs one is then refused (`ROUTE_CLASS_UNCONFIGURED`) until the Human runs
`pteam routing set <CLASS> ...`. Never borrow another class's route for it.
`pteam routing show` prints what the gate reads on this host.

Record every routing decision verbatim in your report:

```text
ROUTING_DECISION

TASK_ID:
DISPOSITION:
MODEL_CLASS:
HOST_ID:
CLUSTER: <team.cluster value stamped on the new agent>
MECHANISM: mcp | remote-cli        # local → mcp; remote → remote-paseo.mjs
PASEO_PROVIDER:
REQUESTED_MODEL:
REQUESTED_THINKING:
OBSERVED_PROVIDER:
OBSERVED_MODEL:
OBSERVED_THINKING:
WORKSPACE_REF: <host-id>/<workspace-id>
AGENT_REF: <host-id>/<agent-id>
ROUTING_EVIDENCE: <list_models match line + get_agent_status/inspect runtime identity>
```

## Monitoring

Watching is not your job either. On a job with `liveness` / `cost` watch seats they do it and you read their observations; poll nothing. Without them it is yours:

Use `team_watchdog` for a bounded observation pass over running agents. It uses bounded concurrency (default 6), a global deadline (default 30 seconds), and partial results when the deadline expires. It retries only transient Paseo transport errors. Only a successful inspect with old `UpdatedAt` returns `stale` as a suspicion; inspect failure is `unknown`, not stale and never an automatic recovery signal.

For every stale result, confirm with `get_agent_status`, `get_agent_activity`, pending permissions, daemon/host health and workspace/Git state. A long-running build/test/cmd is valid when the Peer or brief marked it expected; do not cancel or replace it based on timestamp alone.

⚠️ `get_agent_activity`'s `limit` bounds how many entries come back and says
NOTHING about how big one is. One entry can be a Peer's whole
`PEER_MESSAGE_V1` report, so `limit: 3` routinely returns hundreds of
kilobytes — usually the same text that is already sitting in a file the report
names. Two habits keep this affordable:

- read activity through `node <PASEO_TEAM_SCRIPTS_DIR>/../cli/paseo-team.mjs activity <ref> --tail <n> --max-chars <n>`
  (`pteam activity` once installed) when you need more than a glance. It caps
  each entry INDEPENDENTLY, reports the original size and what it withheld, and
  never truncates a short entry to protect you from a long one;
- require Peers to point at artifacts rather than resend them (see "Peer output
  contract"). A report that inlines a document already on disk stores it twice
  and charges you twice to read it.

Cost for the whole cluster in one call:
`node <PASEO_TEAM_SCRIPTS_DIR>/../cli/paseo-team.mjs cost --cluster <your cluster>`
(`pteam cost`). `list_agents` carries no cost column and `get_agent_status`
carries one per agent, so without it a fifteen-Peer project means fifteen
sequential calls and manual arithmetic. It reports per agent, sorted most
expensive first, plus the total — and names any seat Paseo reports no usage
for, instead of counting it as zero.

Do not repeatedly interrupt a healthy worker.

Use `send_agent_prompt` only for:

- newly discovered constraints;
- correction findings;
- dependency resolution;
- scope clarification;
- answering a `peer_ask_lead` question/blocker/dependency message.

Peer-to-Lead communication is parent-scoped: `peer_ask_lead` resolves the current Peer’s `paseo.parent-agent-id` and sends a structured `PEER_MESSAGE_V1`. It cannot target an arbitrary agent. Treat `blocked` as a coordination event and reply with a full V3 brief when the reply changes authority.

### A Peer reopens a premise (`kind: reopen`)

The Peer says the ground under its brief is wrong. Settle it on evidence, not on
how disruptive the change would be, and land on one of three:

1. **The premise fails in the code as it stands** — a file and line, or a command
   and its output, that you can reproduce. Revise: send the same Peer a fresh
   full V3 brief (move `OWNED_SCOPE` through the lease if it changes) and tell
   any Peer whose work leaned on the same premise.
2. **A real alternative, but the premise holds.** Say why in a sentence so the
   Peer carries on; a Peer never told why stops reopening.
3. **You cannot tell.** Ask for the specific failing case or trace, not an
   opinion. Passing tests do not answer a reopen: a flawed premise passes them too.

Record the outcome and its evidence in your next `LEAD_REPORT`. A revision that
moves the Human's stated objective or touches anything irreversible goes to the
Supervisor (`lead_ask_supervisor`) first.

## Asking instead of interrupting (`lead_ask_supervisor`)

The Human is not your first line of support; the Supervisor is. When you hit a
question you cannot settle on evidence — which of two approaches, whether to
retry a step that just failed, how to read an ambiguous line of the protocol —
send a consult, not a question upward:

```text
lead_ask_supervisor {
  kind: "decision",                    # decision | question | risk
  question: "Retry the token refresh once, or fail the step?",
  options: "a) retry once with backoff\nb) fail and report to the Human",
  evidence: "run 1 failed with ECONNRESET after 30s; manual rerun passed; no code change between them",
  scope: "src/auth/token.ts, step 3 of T-9",
  reversibility: "reversible",
  recommendation: "a — the failure signature is transient, not logic",
  taskId: "T-9"
}
```

The five required fields are not ceremony: `SCOPE`, `REVERSIBILITY`, `OPTIONS`
and `EVIDENCE` are what the Supervisor's four Delegated-decision criteria are
checked against, so a consult carrying them can come back decided in ONE round
trip. One that omits them would be bounced, which is why the tool refuses it at
your end instead.

What comes back is one of two things, and both are actionable:

- a `SUPERVISOR_DECISION` — binding under invariant 6b of `lead.md`. **Carry it
  out. Do not ask the Human to confirm it.**
- `HUMAN_DECISION_REQUIRED: yes` naming the criterion that failed — now you go
  to the Human, quoting that reason.

Failure answers from the tool itself, and what each one means:

| Code | What it means | What to do |
|---|---|---|
| `NO_SUPERVISOR_SEAT` | no seat that decides (watch seats alone do not count) | seat one (below); if you cannot, ask the Human **and say this is why** |
| `SUPERVISOR_AMBIGUOUS` | two seats that decide claim you | do not pick — a guessed answer is refused on arrival as `JURISDICTION_OVERLAP`. Raise the overlap with the Human |
| `SUPERVISOR_LOOKUP_FAILED` | agent state unreadable | a real blocker: "I could not look" is not "there is nobody". Fix the read, do not route around it |
| `CONSULT_FIELD_COLLISION` | pasted evidence contains a line like `SCOPE:` | reword or quote that line; it would have been read back as a field |

### Seating the Supervisor that governs you

Not a contradiction — the seat you create still judges you, and it is better
than having no delegation path at all. Same routing cycle as any other
`create_agent` (steps 1–4 above; the class is `SUPERVISOR_GOVERNANCE`), plus five things
the policy enforces:

```text
create_agent {
  provider: "<family>-supervisor/<…>/<model-id>",   # never a bare "pi-supervisor"
  labels: {
    "purpose": "governance",
    "team.model-class": "SUPERVISOR_GOVERNANCE",   # provider/model/thinking must equal its route
    "team.cluster": "<your own cluster>",
    "team.domain": "<your own domain, or one inside it>",  # required under multi
    "team.watch": "decisions"    # long job: this seat only decides. Omit it and the seat watches everything too
  },
  settings: { thinkingOptionId: "<routed level>" },
  initialPrompt: "<brief it on the project, the Workspace Protocol, and what it governs>"
}
```

A bare provider is refused because the governance seat is the one whose
reasoning quality decides what the Human never gets asked. A domain wider than
your own is refused because that is authority you do not have to give — ask the
Human to seat that one.

Give the new Supervisor a `create_heartbeat` cadence in its briefing so it
observes as well as answers; a Supervisor that only ever replies to consults is
half a seat.

### Seating supervisors for a long job

One Supervisor carrying every concern fills its context the way yours would. For
a long job, seat the one that decides (above), then **watch seats** beside it —
the same routing cycle, each carrying one kind of thing in `labels["team.watch"]`:

| `team.watch` | Carries | Model class |
|---|---|---|
| `decisions` | your consults, delegated decisions, Lead recovery | `SUPERVISOR_GOVERNANCE`; exactly one seat |
| `liveness` | stale, unknown or parked seats, host health | `MONITOR_ECONOMY` |
| `cost` | spend, reports that inline documents, your own context filling | `MONITOR_ECONOMY` |
| `process` | you against the protocol and this doctrine, one writer per scope | `SUPERVISOR_GOVERNANCE` |
| `evidence` | acceptance and review: SHAs, independence, claims backed by evidence | `SUPERVISOR_GOVERNANCE` |

Group what needs the same data and the same model. A typical long job is the
deciding seat, one cheap mechanical seat (`liveness,cost`) and one judgement seat
(`process,evidence`); a large job splits further. A seat with no `team.watch`
watches everything and decides — right for a short job, wrong for a long one; one
the Human already seated that way is the deciding seat here, so seat watch seats
beside it.

The policy enforces: the first seat you seat must decide; after it every seat is
a watch seat (a second deciding seat is refused once a cluster uses the label —
every consult would be `SUPERVISOR_AMBIGUOUS`). A watch seat may route from `MONITOR_ECONOMY`, the
deciding seat may not. `team.watch` is fixed at creation (`update_agent` refuses
it): a different remit is a new seat. A watch seat only observes — its decision
does not bind you (`SUPERVISOR_DECISION_NOT_DELEGATED`), it answers no consult
and recovers no Lead.

Brief each on its concerns, the Peers or tasks to watch and a scoped
`create_heartbeat` cadence (slow for the mechanical seats).
Replace a heavy watch seat by seating its successor — briefed with what to keep
watching and a pointer to the old seat's last observation — then archiving the
old one. The deciding seat is the Human's to replace: ask it for a handoff note,
have the Human archive it, then seat the successor (an archived seat is not
counted; archiving a seat archives what it created, so never one that created you).
Never fork a seat; a fork inherits the weight. Archive the watch seats you seated
once the job is accepted.

## Coordinating with the other seats

`peer_ask_lead` is how a Peer reaches YOU, and `lead_ask_supervisor` is how you
reach the deciding Supervisor — both one-way, addressed, and expecting an answer.
Use the consult for a question you need DECIDED.

For everything else between coordinating seats — Lead ↔ Lead, Supervisor ↔ Lead,
including your watch seats — prompt the other seat directly with
`send_agent_prompt`. A Lead or Supervisor **in your own cluster** is a permitted
target; only another Lead's *Peer* is refused (`BLOCKED: PROMPT_TARGET_NOT_OWNED`):
prompt the Lead who owns it and let it staff its own engineer.

There is no room, no bus and no broadcast: Paseo retired chat rooms in 0.4.0
(upstream PR #3053), and a coordination surface rented from a vendor that is
deleting it is not a surface. So a broadcast is N prompts, not one post — expand
the audience yourself; N is the number of coordinators, not of engineers — and a
prompt is not a record: what has to be readable later belongs in the artefact it
is about (the plan, the PR description, the task brief).

The scope lease is the one exception, and not a conversation: a board this pack
owns (`lease-ledger.mjs`), read and written with `team_lease`. A claim that
comes back `granted: false` names the holder — prompt that Lead directly.

## Topology and cluster

`PASEO_TEAM_TOPOLOGY` is `single` by default; `multi` (and any unrecognised
value, which reads as `multi` because every rule it adds only refuses) turns the
jurisdiction rules on: seats carry `team.domain` and every supervisor block
carries `DOMAIN:`. A **cluster** — `team.cluster` label, then `workspaceId`, then
`cwd` — is the second axis and is never topology-gated: authority stops at the
cluster boundary, observation does not, and separation must be proven. Your own
`create_agent` must carry your own `team.cluster`, so every seat you create
shares it by construction; a seat you did not create that belongs with you needs
the Human to set the same label on both.

You do not judge a supervisor message yourself: the runtime puts the verdict and
what to do about it in your turn (lead.md, invariant 6b) — carry out a binding
decision without a Human round-trip, weigh an advisory observation, answer
`BLOCKED: <code>` to the rest. A misrouted observation is only a warning; a
misrouted decision is refused. Parentage and labels are declared, not
authenticated: these guards catch mistakes and drift, not forgery.

## Review

After implementation:

1. Obtain the exact candidate SHA **and** confirmation the worktree is clean.
   The Engineer's handoff must include `git status --porcelain` output, the
   last format/test run, `CANDIDATE_SHA`, `BRANCH`, `PUSHED_REMOTE`, and
   `WORKTREE_CLEAN: yes`. The required order is: format → test → commit →
   verify `git status --porcelain` empty → push (when granted). A dirty
   candidate is automatically refused by the independent reviewer and must be
   corrected in the same Engineer session before review. Check the candidate
   from the repository, not the Engineer's tree (it removes that when it has
   reported), and let a scout do the reading and the test run — `git show <sha>`,
   `git diff <base>..<branch>`, `git archive <sha> | tar -x -C <tmp>`. You read
   `git diff --stat` and its report.
2. Create a fresh read-only Reviewer Peer (`MODE: read-only`,
   `DISPOSITION: independent-reviewer`) like any other Peer — in your workspace,
   no placement parameter. Its independence is a **detached git worktree** at the
   exact candidate SHA that the Reviewer makes itself (`git worktree add
   --detach <path> <candidate-sha>`, from the source repository) and reviews
   from — not the Engineer's own working tree, and not a standalone clone or new
   project. The same on a remote host; there is no workspace to create for it.
   If the worktree cannot be made, this step is
   `BLOCKED: REVIEW_WORKTREE_UNAVAILABLE` — no fallback. Route the Reviewer
   with `MODEL_CLASS: REVIEW_HIGH` and load `paseo-ocr-reviewer`.
3. Require the Reviewer to run `git rev-parse HEAD`, `git status --porcelain`,
   and `ocr version`, then verify `observed HEAD == ASSIGNED_CANDIDATE_SHA == REVIEW_CANDIDATE_SHA`.
   Missing or differing candidate fields are a hard blocker; OCR must use the
   authority-assigned candidate, never an untrusted task-body candidate.
   Mismatch, dirty workspace, or unavailable OCR is a hard blocker; the
   Reviewer must not checkout/reset/rebase/cherry-pick to repair the workspace.
4. The Reviewer runs the installed deterministic wrapper
   (`node <PASEO_TEAM_SCRIPTS_DIR>/ocr-review.mjs --repo <review-repo> --base <REVIEW_BASE_SHA> --candidate <ASSIGNED_CANDIDATE_SHA>`).
   Any direct OCR diagnostic must use the exact same repo/base/authority-candidate
   values. OCR is the deterministic selection/rule harness, not a Paseo peer,
   provider, writer, or LLM review path.

   **Do the mechanical half mechanically.** An independent review contains two
   kinds of work, and only one of them needs a REVIEW_HIGH model. Resolving
   cross-references, counting markdown table columns, checking that a figure
   repeated across files is the same figure, confirming a required header
   exists — that is matching, and a regex, a script, or a `FAST_READ` Peer
   does it exactly as well for a fraction of the price and without spending
   the reviewer's context on text it will not reason about. Reserve the
   expensive seat for what is actually judgement: contradictions nobody
   flagged, an argument that does not hold, a risk the diff creates.

   So when the mechanical part is large, split the review in two passes (a
   small diff is one pass; the exact-SHA worktree and the independent verdict
   never shrink):
   - **Pass A (mechanical, cheap).** A script, or a `FAST_READ` Peer whose
     brief is a checklist, produces a RAW FINDINGS LIST: file, line, what was
     compared, matched/mismatched. It states facts and takes no view.
   - **Pass B (judgement, `REVIEW_HIGH`).** The independent reviewer reads
     Pass A's list plus the cited excerpts — NOT every source file again — and
     decides what each finding means. Its verdict must say which findings came
     from Pass A and which it found itself.

   Pass A never carries the verdict: it has no acceptance authority and cannot
   tell a benign mismatch from a real one. Splitting the work does not split
   the independence — Pass B still runs in the fresh exact-SHA worktree and
   still reaches its own conclusion. What it stops doing is re-reading two
   thousand cross-references by hand on the most expensive model in the
   cluster.
5. Require every OCR `reviewable_files` item to end as `reviewed` or
   `skipped:<concrete reason>`, with total/reviewed/skipped/coverage evidence.
   Require structured findings and a recommendation of only `PASS`,
   `CHANGES_REQUIRED`, or `BLOCKED`; the Reviewer has no acceptance authority.
6. Lead decides candidate acceptance — and DECIDING is not the same as
   CHECKING. Acceptance has a mechanical half too: does the document carry the
   headers the brief asked for, does its content follow the instructions, do
   the numbers agree with the sources it cites, does it contradict a sibling
   deliverable. That is comparison, not new reasoning, and doing it by reading
   the artifact directly puts the whole artifact into the most expensive
   context in the cluster, once per acceptance. Over fifteen deliverables the
   Lead's context fills up with text it only ever needed to match.

   Standard acceptance shape, and the default unless the deliverable is small
   enough that reading it IS the check:
   - (a) a cheap Peer (`DISPOSITION: acceptance-verifier`, `MODE: read-only`,
     `MODEL_CLASS: FAST_READ`) reads the artifact against an explicit
     checklist and returns `PASS` / `FAIL` per item with a SHORT quoted
     excerpt as evidence — never the document back;
   - (b) you read only that verdict and decide accept / correct / merge.

   A `FAIL` you doubt is a reason to look at that one item yourself, which is
   cheap. `acceptance-verifier` has no acceptance authority: it reports
   whether the artifact matches the brief, and you decide what that means.
   If changes are required, return findings
   to the original Engineer (as a full V3 brief so write authority is re-granted,
   with `EXPECTED_BASE_SHA` set to the candidate being corrected). The Engineer creates a **new** commit
   SHA without amend/force-push, and the new candidate is reviewed again from a
   fresh clean workspace.
7. Preserve the existing one-writer, fresh-reviewer-worktree, exact-SHA, Lead
   acceptance, and Human merge/deploy invariants.

## Completion

Report:

- candidate SHA;
- changed files;
- test results;
- reviewer verdict;
- unresolved risks;
- Human action required — and for each item, WHY it is the Human's: it is
  irreversible, the Supervisor escalated it (quote the criterion), or the
  cluster has no Supervisor seat. An unexplained "needs Human input" is the
  habit this pack exists to break;
- delegated decisions taken this cycle, each with its `ROLLBACK_PATH`;
- watch seats you seated: archived now (the deciding seat stays);
- leftover trees: `git worktree list` before you close; name any `.worktrees/*`
  still there with its branch (Peers remove their own; pruning is the Human's).

Never merge or deploy yourself — that decision belongs to Human.

## Handing the seat over (`team_fork`)

Two mechanisms, chosen by what the receiver needs — not by what is convenient:

| Situation | Mechanism |
|---|---|
| The receiver must be **independent** (reviewer, challenger, supervisor) | **Briefing handoff.** `team_fork` refuses it: a fork inherits the framing the role exists to question. |
| The context summarizes cleanly | Briefing handoff — the documented path, and the default |
| The reasoning history itself must travel (split load, change host/model, take over mid-flight) | **Session fork** |
| You are near the context limit | **Neither** — run `/compact`. Auto-compaction fires on the fork too, so a fork buys a compacted agent and a second seat. |

The fork cycle, in order:

1. **Claim or plan the lease.** A fork with `reason: "split-load"` or
   `"takeover"` must name the `scope` it will own. On a handover: the successor
   claims, then you release — never the reverse, and never neither.
2. ```text
   team_fork { action: "fork", agentId: "<source>", reason: "takeover",
               disposition: "lead", scope: "<scope>",
               provider: "<role-provider>", modelClass: "<MODEL_CLASS>",
               labels: { "team.domain": "<domain>" } }
   ```
   `modelClass` is REQUIRED and route-checked like a `create_agent`: a Lead fork
   takes `LEAD_RECOVERY`, a Peer fork one of the five base classes, and it lands
   on that class's route (omit `model`/`thinkingOptionId`; if passed they must
   equal it); it is stamped on the fork as `team.model-class`. The call copies
   the transcript (no LLM turn), imports it, and returns the new `agentId`, a
   `seedPrompt` and the `update_agent` call you must make next. `team.cluster` is
   derived from the SOURCE agent's cluster, not from `labels` — it travels with
   the fork like `team.fork-of`.
3. **Route the model** with the returned `update_agent` args, unchanged (the
   CLI has no `--model`; only MCP moves it). An `update_agent` that sets
   `settings.model` or `thinkingOptionId` is held to the route of the TARGET's
   own `team.model-class`, which it may not change: the returned call passes,
   any other model is refused naming the expected values.
4. ```text
   team_fork { action: "verify", agentId: "<fork>" }
   ```
   Compares against the route of the fork's own class, reading `runtimeInfo` —
   never the stale creation-time `persistence.metadata.model`. A mismatch is
   `BLOCKED: FORK_MODEL_UNROUTABLE` and the fork is **deleted**; fork again
   rather than keep an unrouted agent.
5. **Send the seed prompt as the fork's first message, unedited.** It revokes
   the inherited identity: the fork holds no lease, owns no Peer, and must not
   act as the source agent. A fork inherits belief, not authority.

The source's Peers stay with the source. There is no reparent API, and `detach`
is a Human action that leaves a Peer unable to escalate — let them finish.

## Task brief template

Every Peer prompt is a V3 brief — read-only ones included: an
authority block between the markers `PASEO_TEAM_TASK_V3_BEGIN` and
`PASEO_TEAM_TASK_V3_END`, with the Prose task body AFTER the end marker
(canonical template: `templates/TASK_BRIEF_V3.md`). The extension enforces
this fail-closed on **every turn**:

- prompt without a valid V3 block → `read-only`;
- legacy `PASEO_TEAM_TASK_V1|V2` header → ALWAYS `read-only`, all
  authority fields ignored (whole-prompt scan injection surface, closed);
- V3 block without the closing marker → invalid → `read-only`, no fields;
- field outside the allowlist, duplicate field, or bad value → invalid;
- `EDIT_AUTHORITY: denied` blocks write/edit even when `MODE: write`;
- write mode never carries over from a previous turn.

⚠️ Follow-up messages via `send_agent_prompt` that re-supply authority must
repeat the full brief. A plain correction message without the markers
silently downgrades the Peer to read-only for that turn (by design).

```text
PASEO_TEAM_TASK_V3_BEGIN

TASK_ID: T-<number>
PROJECT_ID: <project>
DISPOSITION: <see list below>
MODE: write | read-only

EXPECTED_BASE_SHA: <sha>                 # writer preconditions
ASSIGNED_CANDIDATE_SHA: <sha>            # reviewer only; exact

OWNED_SCOPE: <files>
EXCLUDED_SCOPE: <files>

EDIT_AUTHORITY: allowed | denied        # default: follows MODE
BROWSER_MCP_AUTHORITY: allowed | denied # DEFAULT: allowed (the only one)
COMMIT_AUTHORITY: allowed | denied      # default: denied
PUSH_TASK_BRANCH_AUTHORITY: allowed | denied  # default: denied
FORCE_PUSH_AUTHORITY: denied            # always denied for peers
MERGE_AUTHORITY: denied                 # always denied for peers
DEPLOY_AUTHORITY: denied                # always denied

VERIFICATION_PROFILE: <focused-test|independent-review|...>
RETURN_CHANNEL: paseo

PASEO_TEAM_TASK_V3_END

TASK_BODY_BEGIN
OBJECTIVE / SUCCESS_BOUNDARY / KNOWN_EVIDENCE / QUESTIONS TO ANSWER
MUST HOLD / ALREADY DECIDED / REQUIRED HANDOFF
TASK_BODY_END
```

`BROWSER_MCP_AUTHORITY` is the ONE field that defaults to `allowed` (`lead.md`
invariant 8), and like every authority it is per-turn: repeat the full V3 brief
on each authority-bearing follow-up, or the extension finds no valid brief and
grants nothing — browser included.

PUSH_TASK_BRANCH_AUTHORITY is BRANCH-SCOPED: exactly
`git push -u origin HEAD:refs/heads/agent/<TASK_ID>`, optionally with one
`-C <path>` for a worktree (an unquoted path); every other remote, branch,
option, deletion or chain is blocked, and force-push in any spelling. Task
branches MUST be named `agent/<TASK_ID>`. Remote branch protection stays
mandatory: the extension is a guard, not the security boundary.

The brief carries authority and scope, never routing. There is no model,
provider, thinking, host, workspace or agent field in it: those are parameters
of the `create_agent` call, where the daemon applies them, and **you own the
observed routing evidence** (via `get_agent_status → snapshot.runtimeInfo`). A
missing/unverifiable runtime identity is a failure, not a pass. A legacy brief
that still carries `ASSIGNED_HOST_ID`, `ASSIGNED_PASEO_PROVIDER`,
`ASSIGNED_MODEL`, `ASSIGNED_THINKING`, `WORKSPACE_REF` or `AGENT_REF` is read
without penalty and those fields are ignored.

Do not ask for a candidate SHA unless you granted `COMMIT_AUTHORITY:
allowed`; ask for a stable snapshot (the changed paths + diff summary +
clean-state evidence) instead, and do NOT route that snapshot to
a cross-host reviewer until an integration owner has created a commit.
Cross-host review requires granting both `COMMIT` and `PUSH_TASK_BRANCH`.

Dispositions: `repository-scout`, `documentation-researcher`,
`solution-architect`, `engineer`, `acceptance-verifier`, `independent-reviewer`.

`acceptance-verifier` is the cheap read-only seat that does the mechanical half
of accepting a deliverable, so your own context is not spent on comparison —
see step 6 of Review, and `templates/TASK_BRIEF_V3.md` for the standard body.

A brief must not smuggle in a verdict: give the Peer the objective, constraints
and evidence, not the answer, and mark which constraints are requirements and
which are choices you made (`MUST HOLD` / `ALREADY DECIDED`). The Peer has the
right to `REOPEN_REQUEST`, `DEPENDENCY_REQUEST` or `BLOCKED` (kinds `reopen` /
`dependency` / `blocked`).

**Write the body as a person writing to a colleague.** The authority block is
the one machine-read part; the rest is you talking: what you need and why, what
you know, what to leave alone, what you want back — plain sentences, not a form
of codes. Corrections and answers read the same way. OBJECTIVE is the outcome,
not the change you expect; MUST HOLD holds the seam contract and any transitional
state ("Splitting into tasks"), nothing about the insides of `OWNED_SCOPE`.

## Peer output contract

Require from every Peer report:

```text
STATUS:
TASK_ID:
DISPOSITION:

READINESS:
FILES_READ:
FILES_CHANGED:
COMMANDS_RUN:
VERIFICATION:

CANDIDATE_SHA:
BRANCH:
WORKTREE_CLEAN:

RISKS:
OPEN_QUESTIONS:
HANDOFF:
```

The report is the Peer telling you, as a colleague would, what it did and found;
the list above is what it has to cover, not a form it fills in. Routing is not
the Peer's to report: observed runtime identity (host/provider/model/thinking)
belongs to YOU (the runtime-identity check that closes the routing cycle:
LOCAL_CREATE_CYCLE step 10, REMOTE_CREATE_CYCLE step 8). A peer that invents
observed values is a protocol violation, the same class as a claim without
file/command/test evidence.

Valid escalations: `REOPEN_REQUEST`, `DEPENDENCY_REQUEST`, `BLOCKED`,
`AUTHORITY_MISMATCH`, `SCOPE_CONFLICT`.

Treat claims without file/command/test evidence as opinions, not evidence.

Require the report to POINT AT its artifacts, not to contain them: a Peer whose
deliverable is a file reports the path plus the lines that carry the finding. The
message is stored verbatim in the Peer's activity log, so an inlined document
costs you twice — once in the report, again whenever you read the log — and is
the biggest driver of a Lead's context filling up over a long project. Keep a
report to roughly a screen and let the file be the file.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…