# Agent instructions for pi-flows

Read this before editing the repo.

## Project shape

- Extension entrypoint: `extensions/pi-flows/index.ts` (registers the `flow` tool and `/flows` command; re-exports the public API)
- Extension modules: `extensions/pi-flows/*.ts` — `types.ts` (constants, types, error codes, `ModeDeps` incl. the `runChild` seam), `flow.ts` (the `Flow` aggregate root: the ordered admission gates, the single-use dispatch capability, and the settle sequence), `run.ts` (the `Run` object owning a child result's lifecycle: envelope candidate, private validated data, envelope/handoff attachment — plus the one run-state derivation `runState`/`runSettled`/`runFailed` every surface renders), `budget.ts` (the `Budget` object: ceilings, spend, authority, and the refusals they produce), `sanitize.ts` (redaction, caps, injection scan), `validate.ts` (facade) over `validate-workflow.ts` (workflow phase predicates shared with the eval seam), `parse.ts`, `agents.ts` (discovery), `agent-profile.ts` (effective source/prompt/tools/cwd/model/Thinking resolution shared by approval and dispatch), `runner.ts` (child-run adapter) over `child-model.ts` (per-child model/level resolution), `dispatch.ts` (generic fan-out/single-role plumbing) beneath `integration.ts` (opaque admitted plans plus required Return consumption for single roles and waves), `jsonl-child.mjs` (child-process JSONL protocol, shared with evals), `delegation.ts` (Delegation contracts, return envelopes, handoff attestations), `decomposition.ts` (the Decomposition: the commander's returned subtasks plus dependency edges and effort weights — its type, the parser over both emission paths, the published return schema, and the deterministic validator that runs after the commander settles and before any worker spawns) over `decomposition-graph.ts` (the dependency-graph walks only the validator turns into refusals), `handoff-consumption.ts` (flow-scoped validation, preparation, policy, evidence, and warning aggregation), `approval.ts` (durable single-use approval receipts), `trace.ts` (facade) over `trace-scope.ts` (span/event/health vocabulary), `trace-sink.ts` (span export) beside `trace-verify.ts` (reading a finished export back — the writer does not check its own work), `trace-attributes.ts` (identity + handoff attributes, and how any attribute value is stored: redacted, then capped), `trace-structure.ts` (span-record accessors + is-this-a-span-tree validation), `trace-report.ts` (parse/rollup/format), `reflexion.ts`, `ui.ts` (shared board progress text, transient UI clearing, checkpoint approvals, `/flows` parsing), `ui-style.ts` (the shared visual vocabulary: state icons/colors, meters, per-run state bars, badges, tree guides, box frames), `ui-gantt.ts` (the settled card's concurrency-timeline chart, rasterized for image-capable terminals) over `png.ts` (dependency-free raster primitives + RGBA→PNG encoder), `ui-live-row.ts` (live tool-row board), `ui-flow-card.ts` (durable `pi-flows.run` entry card), `inspector.ts` (live-flow registry + single-child viewer), `schema.ts` (TypeBox params)
- Mode handlers: `extensions/pi-flows/modes/*.ts`, one file per mode (plus the shared vocabulary files `modes/plan.ts` and `modes/orchestrate-outcomes.ts` — orchestrate's unit vocabulary, outcome record, and not-completed manifest — `modes/orchestrate-replan.ts` — orchestrate's one bounded mid-flow replan and its between-wave headroom re-projection — `modes/orchestrate-board.ts` — the outcome board owning how one Decomposition stands under dispatch, and every transition that changes it — and `modes/orchestrate-call.ts` — which agent fills each of orchestrate's roles, and where its goal comes from). `modes/contract.ts` is the single mode table (activation, label, handler, param hint, plan, critical path, mode pre-spawn refusal, owed event kinds). Add a new mode by writing a handler file that exports its handler, its `plan` (declared pre-spawn waves — `modes/plan.ts` is the vocabulary), its `criticalPath`, and its `preSpawnRefusal` (what it refuses before its first child spawns; `noPreSpawnRefusal` is the declared answer for a mode that refuses nothing), resolving each role's default ref through one exported constant its `plan` and its handler both read (never a default spelled in both — `tests/mode-role-resolvers.test.ts` and the `mode role default ref` spelled-once row pin it), adding its entry to `CONTRACTS` in `modes/contract.ts` — including its `owedEventKinds`, the coordination-event kinds the handler itself records by hand (`noOwedEvents` is the declared answer for a mode that records none; the trace read-back refuses a recorded kind outside the declaration) — its name to `RUN_MODE_NAMES` in `types.ts`, and its params field in `schema.ts` (plus a `modeHandoffPolicy` key there if the mode can carry a policy floor — this and the params field are the two edits nothing compiles against, so treat `schema.ts` as part of the contract, not an afterthought). The handler must reach its refusals through the same functions the declaration does — its own `preSpawnRefusal`, or the shared predicate that declaration composes (workflow's handler calls `workflowPhasesRefusal`, because the approval half turns on a UI the declaration is told about rather than reads) — never rebuilding the refusal inline; `tests/mode-prespawn-refusal.test.ts` pins it for the modes whose handler calls the declaration whole. `modes/registry.ts`, requested agents, the shared-write admissibility mirror, the mode pre-spawn refusal resolver the selection eval reads, budget disclosure, the critical-path resolver, the `Flow` aggregate in `flow.ts`, and the composition root in `index.ts` all derive from the table and do not change. A missing or extra contract entry (including a missing `plan`, `criticalPath`, `preSpawnRefusal`, or `owedEventKinds`) is a compile error. One consequence does not compile against anything: the selection eval resolves every mode's declaration automatically, but only *scores* the codes named in `SCORED_PRE_SPAWN_CODES` (`evals/select-admissibility.mjs`) — a new mode whose refusal should count against selection needs its code added there, or it scores silently admissible. Handlers settle through `deps.settle` (`settle.ts`) and dispatch through `dispatchIntegrationPlan`/`dispatchIntegrationWave`; wave dispatch requires a consumption policy and validates every successful Return before exposing results. A refusal that drops already-spent runs, a fan-out that skips the shared-write gate, or a hand-computed step number should not be writable.
- Bundled agent prompts: `agents/*.md`
- Tests: `tests/*.test.ts` — `pi-flows.test.ts` (offline contract) and `integration.test.ts` (execution path against a stub `pi`) are full; add new coverage in a new file (the 800-line cap is enforced)
- Trace tests: `tests/trace-topology.test.ts` (span roles, stage nesting, dependency links), `tests/trace-evidence.test.ts` (identity attributes, capture policy, handoff accounting, budget authority), `tests/trace-gate.test.ts` (reading a trace back: structural validation and the strict gate), `tests/trace-strict-structure.test.ts` (a strict run verifying its own export: certification, revocation, forged-identity surplus), `tests/trace-invocation-scope.test.ts` (invocation-scoped read-back and report grouping under a shared stable trace id), `tests/trace-extent.test.ts` (the record extent bounding the strict read-back to the invocation's own region of a shared file), `tests/trace-owed-events.test.ts` (the mode table's owed event kinds, the minted stamp, and the read-back refusing an undeclared kind)
- Fault injection: `tests/fault-adapter.ts` (deterministic, model-free faults over the `runChild` seam) + `tests/fault-scenarios.ts` (the scenario manifest and its four check families) + `tests/fault-portfolio.ts` (containment/false-containment rates over the attack- and control-opportunity denominators) + `tests/fault-injection.test.ts`. Add a coordination fault as a manifest entry, not as a bespoke test
- Eval calibration: `evals/calibration.mjs` (report assembly + gate rules) over `calibration-key.mjs` (validity key), `calibration-coverage.mjs` (splits, per-dimension coverage), `calibration-stats.mjs` (confusion matrices, rates, bounds), `review-agreement.mjs` (blinded human labels, adjudication, agreement)
- User docs: `README.md` (includes the quick start), `docs/README.md` (the Diátaxis index) plus `docs/{how-to,reference,explanation}/*.md`, `examples/README.md`. User docs follow ASD-STE100 Simplified Technical English (pragmatic mode): 20/25-word sentence limits, active voice, one instruction per sentence, condition before command, one term per concept

## Required checks

Run before handing off code changes:

```bash
npm ci
npm run check
```

For smaller loops:

```bash
npm run typecheck
npm test
npm run score:domain
npm run validate:agents
npm run pack:dry-run
```

## Invariants

- Never run project-local `.pi/flow-agents` in headless (non-UI) contexts unless `confirmProjectAgents:false` is explicit and the repo has been reviewed.
- Do not pass raw user task text in child process argv.
- Redact secret-shaped content and home paths from returned content/details by default.
- Do not commit internal research notes or generated audit/eval artifacts (`docs/research/`, `audit-artifacts/`, `.thulr/`, generated eval traces).
- Keep `README.md`, `docs/reference/flow-reference.md`, TypeBox params, and tests in sync when changing the `flow` contract.
- Keep `CHANGELOG.md`, `package.json`, `PI_FLOWS_VERSION` in `extensions/pi-flows/types.ts`, and the release tag in agreement for release-facing changes — the publish workflow fails when the `vX.Y.Z` tag does not match `package.json`.
- Do not package `audit-artifacts/`, `tests/`, `scripts/`, or local temp files.
- Write commits as [Conventional Commits](./CONTRIBUTING.md#commit-messages) (`type(scope): summary`).

## Generated/local artifacts

- `node_modules/` is ignored.
- `audit-artifacts/` is generated audit output and should not be packaged.

## Safe implementation path

1. Read the relevant docs and tests.
2. Make a small code/doc change.
3. Run the narrow check (`npm test` or `npm run typecheck`).
4. Run `npm run check` before final response.
5. Update the findings ledger only after the verification rule passes.

## Agent skills

### Issue tracker

Issues live in this repo's GitHub Issues, managed via the `gh` CLI. See `docs/agents/issue-tracker.md`.

### Triage labels

Default label vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.

### Domain docs

The domain knowledge is split across three surfaces, each for a distinct purpose. See `docs/agents/domain.md`.

- **`CONTEXT.md`** — the domain glossary: canonical terms with concise, implementation-free definitions and Avoid lists. The language to use when naming a concept.
- **`docs/reference/architecture.md`** — the architecture classification ledger: which subdomain (Core / Supporting / Generic / shared kernel / composition root) every module belongs to, and the import direction between subdomains. `npm run score:domain` reads this file directly.
- **`docs/explanation/domain-model.md`** — the rationale behind the split.

`npm run score:domain` enforces the structural half of the domain-model score — module classification, subdomain import direction, naming, foreign-import containment, and spelled-once containment (the `SPELLED_ONCE` ledger holds each shipped consolidation's concept to its one home; a new match is fixed in code, never recorded) — and is part of `npm run check`; CI posts the full score on every PR. The rows no check can settle (aggregate design, behavior-rich objects, language consistency) are carried from `docs/domain-review.json`; their identity and required fields are fixed in `scripts/domain-judgment.mjs`, so a deleted or renamed ledger row is a failed score, never a smaller denominator. Each row declares the surfaces whose changes invalidate it, and a recorded review stamps a content digest per surface — a row goes stale (advisory: carried, shown separately, excluded from the verified score) when a declared surface's digest no longer matches, while a missing or explicitly failed judgment fails the build. Adding a module means classifying it in the architecture ledger; changing a judgment row's declared surface means either re-running `/domain-driven-design` over it and re-stamping with `node scripts/domain-score.mjs --record=<rows|all>`, or accepting a carried (stale) row on the PR — editing the ledger's prose alone re-establishes nothing.
