Per-task briefs a cold subagent can execute without conversation history: objective, deliverables, constraints, done criteria, likely failure modes. Adds orchestrator instructions (dependency graph, model tiers, dispatch loop, stopping conditions), a shared context block, a review gate checklist, and notes for running the plan on a non-Claude model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Ye1KNFMkkUtmzTypHXkoK
22 KiB
pragent Phase 1 — Subagent Dispatch Briefs
Audience: an orchestrating AI that dispatches one fresh subagent per task, plus the subagents themselves.
Companion documents (in this repo):
docs/plans/2026-08-04-pragent-design.md— why the system is shaped this waydocs/plans/2026-08-04-pragent-implementation.md— the authoritative task list, with full code
This file does not restate the code. It supplies what a cold subagent needs that the implementation plan assumes: repo conventions, the exact slice of work, the boundaries it must not cross, and how the orchestrator decides whether the work is done.
Part 1 — Orchestrator instructions
Ground rules
- One subagent per task. Never bundle two tasks into one dispatch. The tasks are sized so a subagent can finish inside a single context window without summarizing.
- Fresh context each time. Do not pass conversation history. Pass the brief. The brief names the files to read; the subagent reads them itself.
- Sequential by default. See the dependency graph below. Parallel execution is possible but requires separate git worktrees — with eleven tasks in one repo, the merge cost usually exceeds the time saved.
- Review between every task. The gate checklist is in Part 3. A subagent reporting "done" is a claim, not evidence. Run the commands yourself.
- A failing task is stopped, not patched. If a subagent's output fails the gate, do not dispatch a second subagent to fix the first one's mess on top of a dirty tree.
git reset --hard HEADback to the last good commit and re-dispatch with the failure described. Layered fixes on unreviewed code are how a plan quietly stops matching the codebase.
Model and effort
| Task | Suggested model tier | Why |
|---|---|---|
| 1, 5, 9 | Mid (Sonnet-class) | Mechanical: scaffold, schema, file append |
| 2, 3, 4, 8 | Mid | Pure functions with clear specs and dense tests |
| 6, 10 | High (Opus-class) | Orchestration logic; the failure modes are subtle |
| 7 | High | The only task with an unverified external API — needs judgment when reality differs from the plan |
| 11 | Mid | Verification and docs |
Dependency graph
T1 scaffold
└─> T2 diff parse
├─> T3 forge port + local adapter ──> T6 analyzer runner ──┬─> T7 pi model client
│ ├─> T8 aggregator
└─> T4 tier engine ──> T5 config │
└────────────────> T9 emitter ─────────┤
└─> T10 CLI wiring ──> T11 close out
Ready-to-run sets, if you do choose worktree parallelism:
- After T2:
{T3, T4}are independent - After T6:
{T7, T8, T9}are independent
Dispatch loop
for task in 1..11:
brief = shared_context + task_brief[task]
result = dispatch_subagent(brief)
if not gate_passes(task):
git reset --hard HEAD # discard the subagent's work
re-dispatch with the specific failure named
else:
continue
Stopping conditions — escalate to the human
- Task 7's live integration test fails in a way that suggests the
piSDK's actual shape differs structurally from the plan (not just field names — e.g. no streaming event API at all). - Two consecutive re-dispatches of the same task fail the gate. The brief is wrong, not the subagent.
- A subagent proposes adding a dependency not listed in this plan. Answer that yourself; do not let a subagent expand the dependency surface unilaterally.
- Measured behaviour contradicts the design doc (e.g. tier rules produce absurd classifications on a real repo). Stop and revise the design, rather than bending the code to match a plan that was wrong.
Part 2 — Shared context block
Prepend this verbatim to every subagent dispatch.
You are implementing one task in the
pragentproject at~/Projects/pragent.What pragent is: an extensible, forge-agnostic pull-request review framework. It runs as a CLI step in CI, classifies each change into an attention tier (
trivial/lite/full/oversized), runs analyzers through a model, and publishes ranked findings. It is a framework other teams extend with their own analyzers and forge adapters — not a closed product.Read before writing any code:
docs/plans/2026-08-04-pragent-implementation.md— find your task number and follow its steps exactly. The code in that document is the specification, not a sketch.docs/plans/2026-08-04-pragent-design.md— read only if you need to understand why a decision was made.Stack: TypeScript 5 strict, ESM (
"type": "module"),NodeNextmodule resolution, Node 22+, Vitest, Zod. Runtime deps arecommander,zod, and (from task 7 onward)@earendil-works/pi-coding-agent+@earendil-works/pi-ai. Nothing else.Non-negotiable conventions:
- Relative imports carry a
.jsextension in.tssource files (import { x } from "./thing.js"). This isNodeNextresolution, not a mistake. Getting this wrong breaks the build in a confusing way.- TDD in the literal order given: write the test → run it and confirm it fails for the expected reason → write the minimum implementation → run it and confirm it passes → commit. Do not write implementation first. A test that passes before the implementation exists is a broken test, and you must fix it rather than celebrate it.
- No network calls in
tests/outsidetests/integration/. Integration tests skip themselves unlessANTHROPIC_API_KEYis set.strictandnoUncheckedIndexedAccessare on. Array indexing yieldsT | undefined. Use the!suffix in tests where the shape is guaranteed by construction; handle it properly insrc/.- Conventional Commits, imperative subject, under 50 characters.
Scope discipline: implement your task and nothing else. Do not refactor code from earlier tasks, do not add error handling for cases that cannot occur, do not add abstractions for hypothetical future needs, and do not start the next task because it seems small. If you believe an earlier task is wrong, say so in your report — do not fix it.
If reality contradicts the plan (a library API differs, a test cannot express what it should), stop and report the mismatch with specifics. Do not silently improvise a different design.
When you finish, report in exactly this format:
TASK: <number and name> STATUS: complete | blocked FILES: <every file created or modified> TESTS: <command run> → <N passed, M failed> TYPECHECK: <pass | fail + first error> COMMIT: <sha and subject> DEVIATIONS: <anything you did differently from the plan, and why — "none" if none> NOTES: <anything the next task's implementer needs to know>
Part 3 — Per-task briefs
Each brief below goes after the shared context block.
Brief — Task 1: Project scaffold
Objective: a TypeScript + Vitest project that builds, typechecks, and runs one passing smoke test.
Deliverables: package.json, tsconfig.json, vitest.config.ts, src/index.ts, tests/smoke.test.ts, package-lock.json.
Constraints:
- Use the exact
tsconfig.jsonfrom the plan.noUncheckedIndexedAccessandNodeNextare load-bearing for later tasks — do not "simplify" them away. - Do not add ESLint, Prettier, husky, CI configuration, or a bundler. Not in scope.
- Do not create
src/cli.tsyet. That is task 10.
Definition of done: npm test → 1 passed. npm run typecheck → clean. npm run build → dist/index.js exists. One commit.
Likely failure mode: installing extra tooling out of habit. The scaffold is deliberately bare.
Brief — Task 2: Diff parsing
Objective: parseUnifiedDiff(text) → ChangedFile[], the input every downstream module reads.
Deliverables: src/diff/types.ts, src/diff/parse.ts, tests/diff/parse.test.ts.
Constraints:
- Do not add a diff-parsing dependency. The subset needed here is small and the plan's implementation covers it.
patchon each file must preserve the raw hunk text verbatim — analyzers send it to the model unmodified.- Handle the four statuses in the tests: added, modified, deleted, renamed.
Definition of done: all four tests in the plan pass, plus npm test overall stays green.
Likely failure modes:
- Counting the
---/+++header lines as added/removed lines. The plan's implementation skips them; keep that. - Losing the last file because the loop pushes only on the next header. The plan handles the trailing file after the loop.
Brief — Task 3: Forge adapter port and local git adapter
Objective: the seam that makes Gitea and GitLab cheap later, plus the local implementation that Phase 1 actually uses.
Deliverables: src/forge/types.ts, src/forge/local.ts, tests/forge/local.test.ts, and a stub src/analyze/types.ts.
Constraints:
- Create
src/analyze/types.tsas the minimal stub the plan gives. Task 6 replaces it with the Zod schema. Do not write the full schema now. - Tests build a real throwaway git repo in
os.tmpdir(). Do not mockgit— the point is that the adapter works against real git output. publish()on the local adapter prints to stdout. That is the whole implementation; do not build formatting infrastructure.- Set
git config user.emailanduser.nameinside the fixture repo. A machine with no global git identity will otherwise failgit commitand you will misdiagnose it as an adapter bug.
Definition of done: both tests pass, including the error-path test asserting a clear message on an invalid range.
Likely failure mode: the error message assertion. git diff failing must produce a message containing the range and the repo path — a bare rethrow of the exec error fails the test, correctly.
Brief — Task 4: Tier engine
Objective: pure, testable rules that decide how much attention a change gets — and record why.
Deliverables: src/tier/types.ts, src/tier/rules.ts, tests/tier/rules.test.ts.
Constraints:
- Rule precedence is not arbitrary and the tests encode it: risk paths beat everything (a three-line auth change is never trivial), then size-based oversized, then generated-only, then size thresholds. Do not reorder to make a test pass — if a test fails, your logic is wrong, not the ordering.
- Every returned
TierDecision.reasonmust name the rule that fired, in the machine-readable form the tests assert (rule:risk_path(**/auth/**),rule:size(lines=42)). This string is the traceability spine of the whole system; a vague reason like"too big"is a defect. - The glob matcher supports
*and**only. Do not addpicomatch. Do not implement brace expansion. - No I/O, no async, no model calls in this module.
Definition of done: all seven tests pass.
Likely failure mode: a glob regex that lets ** match across the wrong boundary, so **/auth/** matches src/oauth/x.ts. Verify the escaping.
Brief — Task 5: Config loading
Objective: load .pragent/config.json, apply defaults, fail loudly on invalid input.
Deliverables: src/config/schema.ts, src/config/load.ts, tests/config/load.test.ts.
Constraints:
- Missing config file is not an error — it yields defaults. Only
ENOENTis swallowed; every other filesystem error propagates. - Invalid config throws with the offending field name in the message. A user with a typo must be told which key is wrong.
- Partial config merges with defaults per-field: overriding
liteMaxLinesmust leavefullMaxLinesat its default. Zod's.default()on each field gives this; a top-level??does not. - Do not implement org-level config layering or locked keys. That is Phase 4.
Definition of done: all three tests pass.
Likely failure mode: wiring defaults so that supplying any tierPolicy key wipes the rest. The second test exists specifically to catch this.
Brief — Task 6: Analyzer runner
Objective: turn a model reply into validated findings, with a ModelClient port so tests never touch the network.
Deliverables: src/analyze/types.ts (replacing the task 3 stub), src/analyze/model.ts, src/analyze/runner.ts, src/analyze/prompts/code-quality.ts, tests/analyze/runner.test.ts.
Constraints:
- The runner fails open. Unparseable model output returns an empty finding list plus an
errorstring. It must never throw — a malformed reply cannot be allowed to break someone's pipeline. - Findings referencing files outside the diff are dropped. A reviewer that invents files loses user trust faster than one that misses bugs. The fourth test enforces this; do not weaken it.
extractJsonmust handle prose wrapped around the JSON and fenced code blocks, via balanced-brace scanning. A greedy/\{.*\}/sregex will pass the happy-path test and fail on trailing prose — the plan's implementation is correct; use it.- Replacing the task 3 stub will change types elsewhere. Run the full suite, not just your own test file, before committing.
- The prompt lives in its own module and returns a string. Do not inline it into the runner, and do not add a template engine.
Definition of done: all four tests pass and npm test is green overall (task 3's forge test must still compile against the real Finding type).
Likely failure modes:
- Throwing on bad JSON instead of returning
{ findings: [], error }. - Forgetting to stamp
analyzeronto each finding — it is the provenance the aggregator and analytics depend on.
Brief — Task 7: pi SDK model client
Objective: the one module that touches the agent SDK, behind the ModelClient port.
Deliverables: src/analyze/pi-model.ts, tests/integration/pi-model.test.ts, updated package.json / lockfile.
Constraints:
- This is the one place the plan is guessing. The SDK usage in the implementation plan comes from published documentation, not a verified local run. If the real API differs — event shape, usage location,
getModelarguments, session lifecycle — fix this file against the real types and record what you found in a comment at the top of the file and in yourDEVIATIONSreport. Do not contort the rest of the codebase to match the plan's guess. tools: []is deliberate: Phase 1 sends the whole diff in the prompt, so the analyzer cannot wander the filesystem and cost stays bounded. Do not enable filesystem tools "to make it better."- The integration test must skip when
ANTHROPIC_API_KEYis unset, not fail. Verify the skip path explicitly — that is the CI behaviour. - Usage accounting may be approximate in Phase 1. If the SDK does not expose token counts where the plan expects, report that rather than fabricating numbers; zeros with a note beat invented figures.
- Do not add retry, backoff, or rate-limit handling. Not this phase.
Definition of done: typecheck clean; integration test skips without a key. If a key is available, it passes against the live API. State clearly in your report which of the two you observed.
Escalate rather than improvise if: the SDK has no streaming/subscription API resembling the plan's snippet, or requires a running agent daemon. That is a structural mismatch and the orchestrator needs to know.
Brief — Task 8: Aggregator
Objective: dedupe, rank, and cap findings before they reach a human.
Deliverables: src/aggregate/aggregate.ts, tests/aggregate/aggregate.test.ts.
Constraints:
- Nothing is silently discarded. Every finding ends in
postedorsuppressed. The counts feed the run record, which is how false-positive rate becomes measurable later. Dropping a finding on the floor breaks the analytics story. - Dedupe key: same file, same category, within a line window (default 5). Cross-analyzer duplicates are the common case — two analyzers finding the same bug should yield one comment.
- Ranking: severity first, confidence as tiebreak.
- The cap applies after ranking, and overflow goes to
suppressed, not away. - Pure function. No I/O.
Definition of done: all four tests pass.
Likely failure mode: capping before ranking, so the highest-severity finding gets cut. The third and fourth tests together catch this.
Brief — Task 9: JSONL emitter
Objective: one structured line per run — the analytics substrate.
Deliverables: src/emit/record.ts, src/emit/jsonl.ts, tests/emit/jsonl.test.ts.
Constraints:
- camelCase in TypeScript, snake_case on the wire.
toWireFormatdoes the translation. Downstream consumers (jq, OTel, any future dashboard) expect conventional field names; the test assertsrun_idandtier_reasonspecifically. - Append, never overwrite. Create parent directories as needed — the test writes to a nested path that does not exist.
- One JSON object per line, newline-terminated. No pretty-printing, no wrapping array.
- Do not implement OTel export. Phase 6.
Definition of done: the test passes, including the nested-directory creation.
Brief — Task 10: CLI wiring
Objective: compose every module into pragent review, with the tier actually controlling spend.
Deliverables: src/review.ts, src/cli.ts, tests/review.test.ts.
Constraints:
- The trivial-tier test is the point of the whole system. A
trivialclassification must return before any model call — the test asserts the model spy was never invoked. If that test passes only because the fake returned quickly, the tiering is decorative and the cost model in the design document is fiction. - A run record is emitted on every path, including trivial and error paths. A run that produced no findings is still data.
- Exit codes:
0clean or advisory,1findings worth blocking on,2internal error. Findings go to stdout; the summary line goes to stderr, sopragent review > findings.txtstill shows the human a summary. review()takes its ports as parameters. It must not constructLocalForgeAdapterorPiModelClientitself — that iscli.ts's job, and it is what makes the orchestration testable without network access.- Do not add a
--dry-run,--format json, or config-override flags. Not this phase.
Definition of done: both tests pass; full suite green; npm run build succeeds; the manual end-to-end run in the plan's step 6 produces findings and a JSONL line.
Likely failure modes:
- Emitting the run record only on the success path.
- Calling the model before checking the tier, making the trivial test pass for the wrong reason.
Brief — Task 11: Close out Phase 1
Objective: verify the whole phase and mark the milestone.
Deliverables: updated README.md, a commit, tag v0.1.0, pushed to origin.
Constraints:
- Run
npm test && npm run typecheck && npm run buildand paste the real output into your report. Do not summarize it as "all green" — the orchestrator wants the counts. - The README's Current state section must describe what genuinely works today, including the local-only limitation. Do not describe Phase 2+ features in the present tense. A README that overstates the state is worse than one that understates it.
- Push with the Gitea token injected at push time only — never write it into
.git/config:TOKEN=$(cat ~/.claude/.gitea-skills-token) git push "http://gitea_admin:${TOKEN}@100.74.17.70:30000/gitea_admin/pragent.git" main:main --tags
Definition of done: tag v0.1.0 exists on origin; README matches reality.
Part 4 — Orchestrator gate checklist
Run this after every task, before dispatching the next. Do not delegate the gate to a subagent.
cd ~/Projects/pragent
npm test # full suite, not just the new file
npm run typecheck
git log --oneline -1 # exactly one new commit, conventional format
git status --porcelain # must be empty — no stray files
Then check by eye:
| Check | Fail signal |
|---|---|
| Scope | Files touched outside the brief's deliverables list |
| TDD | Test and implementation in one commit with no evidence the test ever failed — ask the subagent to quote the failure output |
| Dependencies | package.json gained something the plan did not authorize |
| Test quality | Assertions that restate the implementation instead of the behaviour (expect(result).toEqual(result)-shaped tautologies) |
| Suppressed tests | .skip, .only, commented-out assertions, or a loosened matcher where the plan specified an exact one |
| Report honesty | DEVIATIONS: none on a task where you can see the code diverges from the plan |
The last row matters most. A subagent that quietly deviates and reports "none" is a worse problem than one that fails a test, because it corrupts your model of the codebase. If you catch it once, tighten the next brief's reporting requirement and re-verify the earlier tasks it touched.
Part 5 — Handing off to a different AI
If the executing model is not Claude, three things in the shared context block carry the most risk of being ignored. Restate them in the system prompt of whatever harness you use:
- The
.jsextension on relative TypeScript imports. Nearly every model "corrects" this to.tsor bare paths. It will break the build. - Test-fails-first. Models routinely write implementation and test together, then report TDD. Require the failure output to be quoted in the report.
- Scope discipline. Models improve adjacent code unprompted. In a task-per-subagent flow this silently invalidates your review of earlier tasks.
The task list in docs/plans/2026-08-04-pragent-implementation.md contains complete, runnable code for every task. A model that follows it literally will produce a working system; a model that treats it as inspiration will produce something that looks similar and fails the gate. Bias the prompt toward literal execution, and put the judgment where it belongs — task 7, where the external API is genuinely unverified.