# Sub-agent dispatch (RD)

> Body of `## Sub-agent dispatch`. When this skill is launched as a sub-agent via `peaks sub-agent dispatch <role>` (then the LLM executes the returned toolCall) from `peaks-code`, the following sections of THIS skill are **suspended** for the sub-agent run:

## Superpowers chain refusal (L1 worktree governance — locked 2026-07-29)

You MUST NOT follow the superpowers chain for worktree decisions:

- superpowers:brainstorming → superpowers:writing-plans →
  superpowers:subagent-driven-development → superpowers:using-git-worktrees

This chain ends with `git worktree add` (superpowers:using-git-worktrees SKILL.md line 96). It bypasses peaks-loop's L2 hook gate and L3 IDE `permissions.deny`. Even when L3 denies the terminal Skill, the chain has already taught you to use raw `git worktree add`, so L3 is not sufficient.

For worktree operations, use ONLY:

- `peaks worktree spawn --rid <rid> --role <role> --purpose <text>` (after rid-L2-extended ships)
- until then: `peaks worktree auth grant --operation <op> --reason <text>` (`<op>`, e.g. `git-worktree` | `agent-isolation-worktree`)

The superpowers skills remain available as REFERENCE material for brainstorming / planning techniques, but MUST NOT be used as a workflow. peaks-rd's own PRD/RD artifacts (`.peaks/_runtime/<sessionId>/rd/requests/<rid>.md`) are authoritative.

If the upstream superpowers chain suggests raw `git worktree add`:

1. STOP
2. Re-author the plan as a peaks-rd artifact
3. Continue with `peaks worktree spawn` (or the auth-grant fallback)

## Default `--from-dag` is mandatory (slice 2026-06-24-efficiency-4p-bundle / G2 / P0.3)

> **Hard constraint**: when the swarm plan DAG has ≥ 2 leaves at the
> same topological level, the **only** allowed dispatch shape is
>
> ```
> peaks sub-agent dispatch --from-dag <dag-file> --batch-id <id>
> ```
>
> — i.e. one batch with `dispatchCount === N` parallel `buildToolCall`
> envelopes. Do NOT fan out by issuing N separate
> `peaks sub-agent dispatch <role> --prompt ...` calls in sequence
> (or as a non-DAG multi-call in one message); that serialises the
> wait and turns wall-time into `sum`, not `max`. The CLI rejects
> hand-rolled serial fan-out for ≥ 2-leaf DAGs at the SKILL.md /
> orchestrator level — there is no preference, env-var, or CLI flag
> that overrides this constraint.
>
> Single-leaf (≥ 1 leaf, exactly 1) DAGs may still use the legacy
> single-dispatch shape `peaks sub-agent dispatch <role> --prompt ...`
> because no parallelism is on the table. `config | docs | chore`
> request types still skip Swarm (no DAG emitted) and remain on the
> single-dispatch path.
>
> This constraint is **text-locked** in this reference (so the LLM
> runner sees the rule every time it reads the dispatch contract). It is
> no longer test-locked: the unit test that carried the ≥ 8 cases
> (1-leaf, 2-leaf, 3+-leaf, config/docs/chore type-bypass, and the
> preferences-with-serial-default fan-out escape hatch) was deleted in
> `f17aa377` and has not been replaced. See also
> `skills/peaks-code/references/fanout-mandatory.md` for the
> orchestrator-side rationale; the two files share the same wording by
> design — if either changes, update the other.

- **Session id** — use the parent's sid (read `.peaks/_runtime/session.json` or pass `--session-id <parent-sid>` to any session-creating CLI). Do NOT spawn your own session. The new `peaks session info --active` reads the canonical binding for you.
- **Skill presence (MANDATORY first action)** — do NOT call `peaks skill presence:set peaks-rd`. The sub-agent must not overwrite the main presence lease; the main Code loop owns it. If you need to mark your own state, write a marker file at `.peaks/_runtime/<sessionId>/system/sub-agent-rd.json` and only that.
- **Workspace initialization** — Code has already run `peaks workspace init` before fan-out. Do not re-run it.
- **Mode selection** — Code has already chosen the mode. Read it from the prompt arguments (or from `peaks skill presence --json` if you can, but do not write it).
- **Statusline install** — already done by Code at session startup; do not re-run.

What the sub-agent **MUST** still do, from this skill's contract:

0. **Do NOT call `peaks request init`** — Code has already initialised the request artefact slot in the main loop before fan-out (the runbook has the exact `peaks request init --role rd --id <rid> --project <repo> --apply --type <type> --json` call). The sub-agent reads the slot via `peaks request show <rid> --role rd --project <repo> --json` if it needs to. Note: `peaks request init` is **dry-run by default**. Pass `--apply` to actually create the artifact.
2. `peaks request show <rid> --role prd --project <repo> --json` (and `--role ui` if UI is in the swarm plan).
3. Standards preflight (dry-run only; Code owns the apply step).
4. Project-scan read; create `rd/project-scan.md` only if Code flagged it missing in the dispatch prompt.
5. Write the planning artefact: `rd/requests/<rid>.md` (feature/refactor, per the `artifact-per-request.md` contract) or `rd/bug-analysis-<rid>.md` (bugfix). If `--type` is `config|docs|chore`, **no planning artefact is required** — return immediately with `{"role":"rd-planning","status":"skipped","reason":"type=<type>"}`.

   > **v2.11.0 change (Group A):** `rd/tech-doc.md` is removed. The per-slice planning record is the per-request artefact at `rd/requests/<rid>.md`; the slice's source-of-truth architecture is the immutable peaks-prd handoff at `prd/handoff.md` (verify handoff hash = `<dispatched value>` before reading).
6. Return only a compact JSON envelope — Code will run the convergence gate (`ls` checks):

```json
{
  "role": "rd-planning",
  "rid": "<rid>",
  "status": "ok" | "blocked" | "skipped",
  "artefacts": [".peaks/_runtime/<sessionId>/rd/requests/<rid>.md"],
  "warnings": [],
  "blockedReason": null
}
```

**Hard prohibitions** (sub-agent context, in addition to general red lines):

- Do NOT call `Skill(skill="...")` from inside the sub-agent — that defeats the fan-out.
- Do NOT call `peaks skill presence:set` — Code owns the presence lease.
- Do NOT commit, push, install hooks, or apply settings.json mutations.
- Do NOT ask the user interactive questions. If you need clarification, return `{"status":"blocked","blockedReason":"<text>"}` and let Code handle the user message.
- Do NOT modify code (the Swarm phase is planning only; code edits happen in the RD implementation phase, which is a separate sub-agent or inline run after Gate B).

After returning, Code re-checks Gate B (`ls .peaks/_runtime/<sessionId>/rd/requests/<rid>.md` etc.) and proceeds to RD implementation, which is a different sub-agent or inline run.

## G11.5 visibility contract (mandatory, slice 2026-07-28-sub-agent-visibility)

Any RD/QA/SC sub-agent dispatched by `peaks sub-agent dispatch --from-dag` (or by the single-shot `dispatch <role> --prompt ...` path) MUST register an artifact path via `--write-artifact <path>` BEFORE spawning, and the orchestrator MUST surface a 1-line `⏳ Spawning sub-agent via Task tool: <description>` prose to the user BEFORE each `Agent` tool call.

**Hard rules (text-locked, no skip):**

1. **Artifact registration is mandatory for non-trivial RD/QA/SC sub-agents.** The dispatch CLI now exposes two new envelope fields — `artifactsPublicPaths: string[]` and `orchestratorVisibleHint: string` — that the orchestrator reads back to the user. The RD/QA/SC sub-agent MUST register the artifact path it intends to write via `--write-artifact <absolute-path>` so the envelope surfaces the path to the user before the agent actually starts. Skipping `--write-artifact` is a visibility regression: the user cannot tell what file the sub-agent will create.
2. **Orchestrator surfaces the prose BEFORE spawning.** For every `Agent` tool call that materializes a `peaks sub-agent dispatch` envelope, the orchestrator MUST emit one visible line of prose to the user of the form `⏳ Spawning sub-agent via Task tool: <description>` where `<description>` is ≥ 6 chars and includes the rid. The line lives in the orchestrator's reply text, not in the tool result.
3. **Fan-out keeps the line singular.** When the envelope returns `dispatchCount > 1` (i.e. `--from-dag` with N ≥ 2 leaves), the orchestrator emits the prose once for the whole batch, not N times. Per-leaf prose defeats the wall-time benefit of fan-out.
4. **No auto-commit disclaimer survives this contract.** The same prompt template that today forbids `git commit / git push / install hooks / apply settings.json mutations` (slice 2026-06-28 incident) is unchanged. G11.5 layers ABOVE that — visibility first, hard prohibitions second.

**Where the orchestrator reads the values from**: `data.orchestratorVisibleHint` (one line, copy verbatim) and `data.artifactsPublicPaths` (the public list of artifact paths the sub-agent will own). The CLI fills these in; the orchestrator just transcribes.

**Regression guard**: `tests/unit/skills/g11-5-title-lock.test.ts` — it pins this file's G11.5 heading and the `⏳ Spawning sub-agent via Task tool: <description>` form. The original guard (the unit test that parsed `rd-sub-agent-dispatch.md` and asserted the G11.5 heading is present) was deleted in `f17aa377`; the pin was restored in slice D2 (2026-09-18), which found that the AC-3 title-lock had covered only the peaks-code sibling of this file. Do not delete the heading.

---

## Final report cap (mandatory, slice 2026-09-10-context-audit-and-discipline)

Your FINAL report to the parent MUST be **≤ 40 lines and ≤ 2 KB**. Write any longer detail into the artifact file you already own (registered via `--write-artifact`); the parent can `Read` that file for the full detail, so nothing is lost. The report itself MUST still carry:

- changed files (one line each)
- the exact commands you ran
- pass/fail counts
- tsc status
- any blocker

Do NOT paste file contents, full tool output, or logs into the report. **Rationale (measured, session 2026-09-07-session-245530):** 20 sub-agent final reports ≈ 60 KB ≈ 15K tokens of the orchestrator's window — the report is an index into the artifact, not a copy of it. This rule is also machine-injected into every dispatch prompt (`REPORT_CAP_BLOCK` in `src/services/context/build-dispatch-system-prompt.ts`); this doc and that constant MUST stay in lockstep.

---

## Test Tool Detection (mandatory)

The dispatch CLI (`peaks sub-agent dispatch`) automatically prepends a Test Tool Detection block to every sub-agent prompt — telling the sub-agent to read `package.json#scripts.test` first and use the project-local runner (`./node_modules/.bin/<runner>` or `pnpm test -- <file>`). NEVER use `npx <runner>`. This rule is machine-injected, not a prompt ritual — every sub-agent gets it including rd/qa/ui/txt/sc.

If the framework is not obvious from `package.json#scripts.test`, the sub-agent should run `peaks test --json` to introspect the resolved framework + argv before picking a runner.

See the block constant at `src/services/dispatch/test-tool-detection.ts` for the verbatim text.

## Karpathy-guidelines context (Slice 1/6 — karpathy prompt-injection-lift)

When the dispatch primitive constructs the sub-agent prompt, the following context block MUST be appended to the sub-agent prompt verbatim. The block is the canonical injection source for any RD-spawned sub-agent (including 4-way fanout, including inline main-loop RD).

**The dispatch CLI now does this itself (2026-10-13).** `peaks sub-agent dispatch` runs `ensureKarpathyBlock` over the assembled prompt (`src/services/dispatch/karpathy-block.ts`, applied in `src/cli/commands/dispatch-prompt-composition.ts`), so the block reaches the child whether or not the caller remembers to append it. A caller that already appended it is not appended twice. The constant is the machine copy of the block below, and a unit test asserts the two are byte-equal, so neither can drift. What the obligation still covers is the path that does NOT go through this CLI: `--from-dag` (`src/services/code/dag-orchestrator.ts`) and `--mode detached` inject no Karpathy block, so a caller using either must append it itself.

```
Karpathy-guidelines context (REQUIRED — read before any code action)

The 4 Karpathy guidelines are mandatory for every planning, implementation, refactor, and review action you take. If 200 lines could be 50, rewrite. Touch only what the request requires. Surface assumptions, do not hide them. Define verifiable success criteria.

1. Think Before Coding

Don't assume. Don't hide confusion. Surface tradeoffs. Before implementing: state assumptions explicitly; if multiple interpretations exist, present them; if a simpler approach exists, say so; if unclear, stop and ask.

2. Simplicity First

Minimum code that solves the problem. Nothing speculative. No features beyond what was asked. No abstractions for single-use code. No "flexibility" or "configurability" that wasn't requested. No error handling for impossible scenarios. If you write 200 lines and it could be 50, rewrite it. Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

3. Surgical Changes

Touch only what you must. Clean up only your own mess. When editing existing code: don't "improve" adjacent code, comments, or formatting; don't refactor things that aren't broken; match existing style. When your changes create orphans: remove imports/variables/functions that YOUR changes made unused. The test: every changed line should trace directly to the user's request.

4. Goal-Driven Execution

Define success criteria. Loop until verified. "Add validation" → write tests for invalid inputs, then make them pass. "Fix the bug" → write a test that reproduces it, then make it pass. For multi-step tasks, state a brief plan with verify checkpoints. Strong success criteria let you loop independently. Weak criteria require constant clarification.
```

Sub-agents MUST NOT silently drop this block. The canonical skill id for the full guidelines text is `andrej-karpathy-skills:karpathy-guidelines`.

Corrected 2026-10-13, kept visible rather than edited away: this paragraph used to read *"Nothing asserts its presence automatically — the unit test that did was deleted in `f17aa377`, so a dropped block fails silently."* That was true when written and it was the defect. The composer now injects the block and `tests/unit/services/dispatch/karpathy-block.test.ts` + `tests/unit/skills/karpathy-injection.test.ts` assert both the delivery and the byte-equality of this file's block with the constant, so a dropped block no longer fails silently on the CLI path. Measured 2026-10-12, before the executor existed: four RD dispatch prompts in one session carried zero occurrences of `Karpathy`.

## BDD Test Style Contract (effective rid-2026-08-05-bdd-test-style, v4.0.11+)

When you (the LLM sub-agent) write new or modified unit tests in `tests/unit/**`, every `it()` / `test()` block MUST follow the given-when-then contract:

1. The first string-literal argument of `it()` / `test()` MUST describe business behavior and MUST include either the word "when" (state / pre-condition) or "should" (observable outcome). The idiomatic form `when X, should Y` names both, so prefer it — but it is a preference, not a requirement: a description carrying only "when" or only "should" is compliant.
2. The body callback (the second argument) MUST start with exactly 3 leading comments:
   ```typescript
   // given: <precondition — system / user state>
   // when: <action — what is invoked>
   // then: <expected outcome — what is asserted>
   ```
3. Legacy `// arrange:` / `// act:` / `// assert:` AAA markers MUST NOT appear in tests you write.

Run `node scripts/migrate-to-bdd.mjs --dry-run <file>` before writing new tests to inspect the contract, or use the Slice B verifier's rule directly:

- description: must contain `/(\bwhen\b|\bshould\b)/`
- first 3 body comments: must match `/^\s*\/\/\s*given\s*:/`, `/^\s*\/\/\s*when\s*:/`, `/^\s*\/\/\s*then\s*:/`

If your tests fail peaks-qa's `bdd-test-style-verifier` (see Slice B), the slice will be returned-to-rd. Fix the violations, do NOT bypass the check.

---

## Scratch path (absolute — never a cwd-relative redirect)

The dispatch CLI machine-injects a scratch-path block into every sub-agent prompt, naming the ABSOLUTE scratch directory for that dispatch: `<projectRoot>/.peaks/_runtime/<sessionId>/<role>/scratch/`. The rule it carries is one line: **never redirect to a cwd-relative path**. The two paths that do NOT pass through that composer — `--from-dag` and `--mode detached` — carry no scratch block, so a caller using either must state the rule itself.

**Why it is a block and not a line in a brief.** The rule used to live only in the orchestrator's brief, which is exactly as strong as the orchestrator's memory at 3am. Measured 2026-10-02, five zero-byte `*.err` captures — the `2> name.err` of five per-file test runs — landed at the repository root and were committed; `git status` stayed clean because the files were tracked, and neither the file-size census nor eslint reads a `.err`.

**Two copies, one pin.** The block below is the canonical text; `src/cli/commands/dispatch-scratch-path-block.ts` holds its machine copy (`SCRATCH_PATH_BLOCK_TEMPLATE`) and `tests/unit/cli/commands/dispatch-scratch-path-block.test.ts` asserts the two are byte-equal, so neither can drift. The path differs for every dispatch, so the placeholder is what this file carries.

```
Scratch path (REQUIRED — absolute path; never a cwd-relative redirect)

Write every temporary file for this task under this ABSOLUTE path:
{{scratchDir}}

Never redirect to a path that depends on the working directory — `2> out.err`, `> run.log`, `./scratch/`. A cwd-relative redirect writes wherever the task happens to `cd`: measured 2026-10-02, five zero-byte `*.err` captures landed at the repository root and were committed, and `git status` stayed clean because the files were tracked. Use the absolute path above; it lives under the session's runtime dir, which is gitignored, so nothing written there can be committed by accident.
```