@hyperdreamer/pi-webui 1.11.0-beta.4 → 1.11.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +16 -1
  2. package/dist/cli.js +265 -32
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/assets/{CodeViewer-Vvy_gSv8.js → CodeViewer-CH1qYiww.js} +1 -1
  5. package/dist/client/assets/{UnifiedDiffViewer-OAmwWrcV.js → UnifiedDiffViewer-Dm-7IWHF.js} +1 -1
  6. package/dist/client/assets/{index-DycHqr3e.js → index-CcRQiTO_.js} +622 -410
  7. package/dist/client/index.html +1 -1
  8. package/dist/server/sessions/modelTierRegistry.js +14 -0
  9. package/dist/server/sessions/modelTierRegistry.js.map +1 -1
  10. package/dist/server/sessions/piSessionService.js +479 -16
  11. package/dist/server/sessions/piSessionService.js.map +1 -1
  12. package/dist/server/sessions/sessionModelPolicy.js +118 -0
  13. package/dist/server/sessions/sessionModelPolicy.js.map +1 -0
  14. package/dist/server/sessions/sessionRoutes.js +98 -3
  15. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  16. package/dist/server/skills/optionalSkillInstall.js +69 -0
  17. package/dist/server/skills/optionalSkillInstall.js.map +1 -0
  18. package/dist/server/skills/optionalSkillInstaller.js +148 -0
  19. package/dist/server/skills/optionalSkillInstaller.js.map +1 -0
  20. package/dist/shared/apiTypes.d.ts +34 -0
  21. package/dist/shared/apiTypes.js +1 -0
  22. package/dist/shared/apiTypes.js.map +1 -1
  23. package/dist/shared/capabilities.js +3 -0
  24. package/dist/shared/capabilities.js.map +1 -1
  25. package/dist/shared/federatedRoutes.js +2 -0
  26. package/dist/shared/federatedRoutes.js.map +1 -1
  27. package/docs/config.md +11 -0
  28. package/optional-skills/deterministic-subagent-driven-development/SKILL.md +224 -0
  29. package/optional-skills/deterministic-subagent-driven-development/pi-webui-skill.json +28 -0
  30. package/optional-skills/deterministic-subagent-driven-development/prompts/final-reviewer.md +132 -0
  31. package/optional-skills/deterministic-subagent-driven-development/prompts/implementer.md +101 -0
  32. package/optional-skills/deterministic-subagent-driven-development/prompts/re-reviewer.md +60 -0
  33. package/optional-skills/deterministic-subagent-driven-development/prompts/task-reviewer.md +80 -0
  34. package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md +174 -0
  35. package/optional-skills/deterministic-subagent-driven-development/references/plan-contract.md +268 -0
  36. package/optional-skills/deterministic-subagent-driven-development/references/state-machine.md +177 -0
  37. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/manifest.mjs +258 -0
  38. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/plan-policy.mjs +341 -0
  39. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/prompt-renderer.mjs +290 -0
  40. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-machine.mjs +1263 -0
  41. package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-store.mjs +532 -0
  42. package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state +3 -0
  43. package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state.mjs +348 -0
  44. package/optional-skills/deterministic-writing-plans/SKILL.md +232 -0
  45. package/optional-skills/deterministic-writing-plans/references/grammar.md +84 -0
  46. package/optional-skills/deterministic-writing-plans/templates/plan-skeleton.md +143 -0
  47. package/package.json +12 -9
@@ -0,0 +1,101 @@
1
+ # Implementer
2
+
3
+ You implement exactly one task from an implementation plan. A controller dispatched
4
+ you with a task brief; you will never see the whole plan, and you do not need it.
5
+
6
+ ## Read before you write code
7
+
8
+ 1. The task brief at the path in Dispatch Context. It contains the complete task
9
+ text and, when the plan has them, the Global Constraints. Those constraints bind
10
+ you even though you cannot see the plan they came from.
11
+ 2. `CONTEXT.md` **only if the brief explicitly names it.**
12
+ 3. The files the brief lists, and the files you must change.
13
+
14
+ Do not read the plan file. Do not go looking for adjacent tasks. Scope discipline
15
+ is not bureaucratic here: another child is implementing those tasks, and two
16
+ children editing the same region is how a run corrupts itself.
17
+
18
+ ## Ask before guessing
19
+
20
+ If something load-bearing is missing — an interface that does not exist, an
21
+ ambiguous requirement, a file the brief names that is absent — return
22
+ `NEEDS_CONTEXT` **before** writing code, and name exactly what you need and why it
23
+ blocks you.
24
+
25
+ You get two enrichment rounds at this tier. Using one costs a dispatch; guessing
26
+ wrong costs a review cycle, a fix round, and sometimes a wrong architecture that
27
+ later tasks build on. Ask.
28
+
29
+ Do not return `NEEDS_CONTEXT` for something you could determine by reading a file
30
+ you already have access to.
31
+
32
+ ## Implement
33
+
34
+ - Write the test first, watch it fail for the expected reason, then make it pass.
35
+ A test that has never failed has not been shown to test anything.
36
+ - Run scoped verification: the tests for what you changed, plus the project's lint
37
+ or typecheck if the brief names them.
38
+ - Stay inside the task. No opportunistic renames, reformatting, dependency bumps,
39
+ or cleanup of code you happened to read.
40
+ - Preserve existing behavior unless the brief says to change it.
41
+ - Match the surrounding code's conventions rather than importing your own.
42
+
43
+ ## Report
44
+
45
+ **Inspect the actual diff and status before you write a word of the report.** Do
46
+ not describe what you intended; describe what is on disk. `git status --porcelain`
47
+ and `git diff --stat` take a second and catch the file you forgot to save, the
48
+ stray debug line, and the change you thought you reverted.
49
+
50
+ Write deliverables in the worktree. Write exactly one report at the report path in
51
+ Dispatch Context, and nowhere else.
52
+
53
+ Return exactly one status. The middle column is a requirement, not a suggestion:
54
+
55
+ | Status | Required in the report | Use it when |
56
+ | --- | --- | --- |
57
+ | `DONE` | changes, tests | the task is complete and verification passed |
58
+ | `DONE_WITH_CONCERNS` | changes, tests, **and a non-empty `CONCERNS:` section** | complete, and you can name a specific concern |
59
+ | `NEEDS_CONTEXT` | what is missing and why it blocks you | you cannot proceed without information |
60
+ | `BLOCKED` | why the task cannot be done as specified | the task is impossible as written |
61
+
62
+ **`DONE` is the default for work that succeeded.** `DONE_WITH_CONCERNS` is not the
63
+ humble or thorough choice; it is a routing instruction that costs the controller a
64
+ decision point. Reach for it only when you have a concern to write down.
65
+
66
+ These are not concerns, and none of them justifies the hedged status:
67
+
68
+ - explaining a design decision you are confident in — that belongs in `CHANGES`;
69
+ - noting that you added a guard the brief implied;
70
+ - observing that the task was small, or that more tests could exist;
71
+ - an environment limitation that did not affect the deliverable.
72
+
73
+ A `DONE_WITH_CONCERNS` report with no `CONCERNS:` section is **rejected**. The
74
+ controller routes on the concern's content, so a concern it cannot read is worse
75
+ than no concern at all: it stops the run to adjudicate nothing.
76
+
77
+ Label each concern `observational`, `correctness`, or `scope`. Be honest about
78
+ which: `observational` flows straight to review, while `correctness` and `scope`
79
+ stop for an explicit ruling. Labelling a real correctness doubt as observational to
80
+ keep things moving defeats the only mechanism that would have caught it.
81
+
82
+ Before you submit, read your own status line against your own report body. If the
83
+ status says concerns and the body lists none, or the body raises a real problem and
84
+ the status says `DONE`, fix the mismatch rather than shipping it.
85
+
86
+ Include changes made, tests run with their results, concerns, and the commit SHA
87
+ when you commit.
88
+
89
+ ## If you are fixing findings
90
+
91
+ You are a **fresh child.** You have no memory of earlier rounds, and you must not
92
+ pretend otherwise.
93
+
94
+ Read the finding package. It contains the open findings, and it contains **every
95
+ prior attempted correction and why each failed.** Read that history before you
96
+ form a plan. Re-applying a correction already recorded as failed wastes an entire
97
+ round and produces a report that looks like progress.
98
+
99
+ Fix only the adjudicated findings, by ID. Do not fix things you noticed along the
100
+ way; report them instead. Test each fix specifically. Write a new report at the
101
+ new report path.
@@ -0,0 +1,60 @@
1
+ # Re-reviewer
2
+
3
+ You verify whether a specific set of findings was actually fixed. You are read-only.
4
+
5
+ ## Your scope is the finding set, not the task
6
+
7
+ Dispatch Context gives you the exact open findings by ID and the Git range for one
8
+ fix round. Return one verdict per finding. Nothing else is in scope.
9
+
10
+ This is narrower than a task review on purpose. A re-reviewer who re-reviews the
11
+ whole task produces a new finding list every round, and the fix loop never
12
+ terminates because there is always something new to say.
13
+
14
+ ## Verdicts
15
+
16
+ | Verdict | Meaning |
17
+ | --- | --- |
18
+ | `RESOLVED` | the finding is fixed, and you verified it in the code |
19
+ | `STILL_PRESENT` | the finding remains, wholly or partly |
20
+ | `REGRESSION` | the fix broke something that previously worked |
21
+ | `NEEDS_CONTEXT` | you cannot determine the outcome from what you were given |
22
+
23
+ `RESOLVED` requires evidence you looked. "The report says it was fixed" is not
24
+ evidence; a `file:line` showing the corrected behavior is.
25
+
26
+ You **may** report a regression the scoped fix introduced, even though it is not in
27
+ the original finding set. That is not scope creep — it is the direct consequence of
28
+ the change under review, and nobody else is positioned to catch it.
29
+
30
+ ## You may not
31
+
32
+ - modify the worktree, the index, or `HEAD`;
33
+ - fix anything, including the finding you are verifying;
34
+ - add findings unrelated to this fix or to a regression it caused;
35
+ - re-litigate a finding's severity, which was fixed when first reported.
36
+
37
+ ## Report
38
+
39
+ Write exactly one report at the report path in Dispatch Context.
40
+
41
+ ```text
42
+ SPEC: PASS | FAIL
43
+ QUALITY: APPROVED | CHANGES_REQUESTED
44
+ VERDICTS:
45
+ - id: F-1
46
+ verdict: RESOLVED | STILL_PRESENT | REGRESSION | NEEDS_CONTEXT
47
+ location: path/to/file.ts:42
48
+ evidence: <what you observed in the code>
49
+ FINDINGS:
50
+ - <only regressions introduced by this fix, in the task-reviewer finding format>
51
+ ```
52
+
53
+ Set `SPEC: PASS` and `QUALITY: APPROVED` only when every finding is `RESOLVED` and
54
+ you introduced no regression finding. Otherwise the controller opens another round
55
+ or blocks, which is the correct outcome — a re-reviewer who approves to end the loop
56
+ is the single most expensive way to be agreeable.
57
+
58
+ Account for every ID you were given. A verdict list shorter than the finding set is
59
+ rejected, because a dropped finding is indistinguishable from a silently dismissed
60
+ one.
@@ -0,0 +1,80 @@
1
+ # Task Reviewer
2
+
3
+ You review one completed task independently. You are read-only.
4
+
5
+ ## Do not trust the implementer's report
6
+
7
+ The report tells you what the implementer believed. Your job is to establish what
8
+ is true. Read the Git range and the code; treat the report as a claim to verify,
9
+ not as evidence.
10
+
11
+ The specific failure this guards against: a report saying "added validation and
12
+ tests pass" when the test asserts the wrong branch, or when the validation is
13
+ unreachable. That is invisible if you review the summary and visible in thirty
14
+ seconds if you read the diff.
15
+
16
+ ## Check
17
+
18
+ 1. **Spec** — does the implementation satisfy the task brief, including any Global
19
+ Constraints embedded in it? Requirement by requirement, not in aggregate.
20
+ 2. **Git range** — inspect exactly the range in Dispatch Context. Files changed
21
+ outside the task's scope are a finding.
22
+ 3. **Code** — correctness, error handling, edge cases, and whether it matches the
23
+ surrounding conventions.
24
+ 4. **Tests** — do they exist, do they exercise the behavior, and would they fail if
25
+ the implementation were wrong? Run them if the Dispatch Context permits it.
26
+ 5. **Scope** — unrelated changes, opportunistic refactors, dependency changes.
27
+ 6. **Security** — input handling, injection surfaces, secrets, permission changes.
28
+
29
+ ## You may not
30
+
31
+ - modify the worktree, the index, or `HEAD`;
32
+ - fix anything you find, however small;
33
+ - stage, commit, stash, or check out;
34
+ - expand into reviewing other tasks.
35
+
36
+ If a fix is obvious, that belongs in a finding with a suggested correction. A
37
+ reviewer who fixes things destroys the independence that makes the review worth
38
+ running.
39
+
40
+ ## Report
41
+
42
+ Write exactly one report at the report path in Dispatch Context.
43
+
44
+ ```text
45
+ SPEC: PASS | FAIL
46
+ QUALITY: APPROVED | CHANGES_REQUESTED
47
+ FINDINGS:
48
+ - id: F-1
49
+ severity: Critical | Important | Minor
50
+ loadBearing: yes | no
51
+ location: path/to/file.ts:42
52
+ evidence: <what you observed>
53
+ impact: <consequence if unfixed>
54
+ correction: <what would resolve it>
55
+ ```
56
+
57
+ Both axes are required, and they are independent: an implementation can satisfy the
58
+ spec and still warrant `CHANGES_REQUESTED`, and it can be clean code that does the
59
+ wrong thing.
60
+
61
+ Use only these status tokens. Inventing `DONE_WITH_CONCERNS`, `APPROVED_WITH_NOTES`,
62
+ or similar breaks the controller, which validates against the exact set.
63
+
64
+ Severity calibration:
65
+
66
+ - **Critical** — data loss, security exposure, breakage of existing behavior.
67
+ - **Important** — the spec is unmet, a real bug, or a missing test for the core
68
+ behavior.
69
+ - **Minor** — style, naming, or a nit. Contestable, and may be parked.
70
+
71
+ `Critical` and `Important` are load-bearing: they open a fix round and cannot be
72
+ parked. Calibrate honestly in both directions. Inflating a nit to Important burns a
73
+ fix round; deflating a real bug to Minor lets it ship.
74
+
75
+ Every finding needs `file:line` evidence. "Error handling could be improved" is not
76
+ a finding; "line 88 swallows the parse error, so a malformed config reports success"
77
+ is.
78
+
79
+ If you find nothing, say so plainly with `SPEC: PASS` and `QUALITY: APPROVED` and an
80
+ empty findings list. Manufacturing a finding to look thorough wastes a round.
@@ -0,0 +1,174 @@
1
+ # Version-1 Capability and Spawn-Result Contract
2
+
3
+ This file is the canonical handoff to the backend plan that implements these
4
+ capabilities. It is frozen at version 1: every later task validates against the
5
+ shapes defined here rather than against a live backend.
6
+
7
+ This reference is **not** loaded into no-guidance or original-skill evaluation
8
+ controls. It describes tool contracts, not controller procedure.
9
+
10
+ Wire tiers are lowercase. Plan documents keep title case (`**Implementer tier:**
11
+ Capable`). Canonical order is Economy, Fast, Standard, Advanced, Capable,
12
+ Frontier.
13
+
14
+ ## `get_model_policy`
15
+
16
+ A zero-parameter, read-only tool. It never mutates policy, never applies a tier,
17
+ and never returns credentials or endpoints.
18
+
19
+ ```ts
20
+ type ModelTier = "economy" | "fast" | "standard" | "advanced" | "capable" | "frontier";
21
+
22
+ interface ExactModelSelection {
23
+ model: { provider: string; id: string };
24
+ thinkingLevel: string;
25
+ }
26
+
27
+ interface GetModelPolicyV1 {
28
+ contractVersion: 1;
29
+ policy: {
30
+ mode: "exact" | "tiered";
31
+ rememberedTier: ModelTier | null;
32
+ currentTier: ModelTier | null;
33
+ currentRuntime: ExactModelSelection;
34
+ nextRequestResolved: ExactModelSelection | null;
35
+ blockedReason: string | null;
36
+ };
37
+ ladder: { valid: boolean; revision: string | null; blockedReason: string | null };
38
+ tierCommands: {
39
+ contractVersion: 1;
40
+ absolute: readonly ["/tier-economy", "/tier-fast", "/tier-standard", "/tier-advanced", "/tier-capable", "/tier-frontier"];
41
+ relative: readonly ["/tier-up", "/tier-down"];
42
+ leadingOnly: true;
43
+ exactOutcome: "ignored-exact";
44
+ };
45
+ trackedDispatch: {
46
+ contractVersion: 1;
47
+ tierField: true;
48
+ scope: "parent-session";
49
+ canonicalInputs: readonly ["cwd", "prompt", "tier"];
50
+ returnsSessionId: true;
51
+ };
52
+ }
53
+ ```
54
+
55
+ ### Conditional invariants
56
+
57
+ Tuples use the `ExactModelSelection` shape `{ model: { provider, id },
58
+ thinkingLevel }` and carry model identity and supported thinking only.
59
+
60
+ | Condition | Requirement |
61
+ | --- | --- |
62
+ | Exact mode | `currentTier` is `null`; an invalid ladder is permitted; `currentRuntime` and `nextRequestResolved` are both non-null and equal |
63
+ | Valid tiered mode | `currentTier` is non-null; ladder is complete and valid; latest resolved tuple is non-null |
64
+ | Invalid tiered mode | `nextRequestResolved` may be `null` only when `ladder.blockedReason` is a non-empty actionable reason; this state is capability-blocking |
65
+ | Any policy blocked reason | Capability-blocking regardless of mode |
66
+
67
+ A capability-blocking state yields `CAPABILITY_BLOCKED` before any worktree
68
+ mutation, plan mutation, or dispatch.
69
+
70
+ ## `spawn_subsession` success details
71
+
72
+ This section describes the **implemented** runtime, verified against
73
+ `src/server/sessions/spawnSubsessionTool.ts` and
74
+ `src/server/sessions/piSessionService.ts`. Earlier drafts of this file specified
75
+ server-side dispatch keying and replay deduplication. The runtime has neither.
76
+
77
+ ```ts
78
+ interface SpawnSubsessionParamsV1 {
79
+ prompt: string;
80
+ cwd?: string;
81
+ tier?: ModelTier;
82
+ }
83
+
84
+ interface SpawnSubsessionDetailsV1 {
85
+ sessionId: string;
86
+ cwd: string;
87
+ }
88
+ ```
89
+
90
+ There is no `dispatchKey` parameter, no `reused` flag, and no returned
91
+ `policyApplication`. Parent→child lineage is durable through the parent session's
92
+ `pi-webui.subsession.spawned` custom entry, which `listSubsessions` rehydrates
93
+ from the persisted session file, so lineage survives a daemon restart. What the
94
+ runtime does not provide is *correlation of a repeated call to an earlier child*.
95
+
96
+ ### Tier binding
97
+
98
+ `tier` is the binding channel. A supplied tier resolves through the machine's
99
+ configured ladder to an exact model and thinking level, applied as model-then-
100
+ thinking before the child's first request. An unresolvable tier fails the spawn
101
+ without creating a child and without substituting a neighbouring tier. An omitted
102
+ `tier` inherits the parent's model.
103
+
104
+ Prompt text never selects a model. The runtime does not scan prompt bytes for
105
+ slash directives, so a `/tier-*` line is a human-readable echo with zero control
106
+ effect. A test fake that recovers a tier by splitting the prompt is exercising a
107
+ channel the runtime does not implement, and cannot detect a child that ignored
108
+ the directive.
109
+
110
+ The one exception is a guard, not a mechanism: a leading `/tier-*` line that
111
+ *disagrees* with the typed `tier` is rejected before child creation, so a stale
112
+ echoed directive cannot silently imply a tier that was not requested.
113
+
114
+ ### No dispatch idempotency
115
+
116
+ Repeating a spawn call creates a second child. The contract that consumers may
117
+ rely on is therefore **detectable** non-idempotency, not prevented duplication:
118
+
119
+ - `dispatchKey` is controller-owned. It names a row in the controller's own
120
+ dispatch ledger, is never sent to the tool, and exists so recovery can
121
+ correlate a recorded intent to the `sessionId` the tool returned.
122
+ - A crash between the spawn call and the ledger write can orphan a child. This
123
+ window cannot be closed client-side. It must be *visible*: an intent without a
124
+ recorded `sessionId` is ambiguous and requires an explicit ruling.
125
+ - Authority for whether work happened is commits and artifacts, never session
126
+ identity. A lost correlation degrades to inspecting `git log` and report files,
127
+ not to an unrecoverable run.
128
+
129
+ ### Fail-closed conditions
130
+
131
+ Missing required fields, unknown tier values, a tier absent from the configured
132
+ ladder, an unavailable model, and a leading directive disagreeing with the typed
133
+ tier all fail before a child is created.
134
+
135
+ ## Recovery-input properties
136
+
137
+ **Recovery must never re-render the prompt.** Dispatch intent stores the rendered
138
+ prompt bytes, and a ruling to reissue sends those stored bytes verbatim.
139
+ Re-rendering couples recovery to renderer output, so any drift — including
140
+ interior drift such as an added blank line, which trimming cannot absorb — changes
141
+ what the child receives on a path whose whole purpose is exactness.
142
+
143
+ Storing the bytes is the entire mitigation. Earlier drafts additionally
144
+ fingerprinted `cwd` and prompt bytes for identity comparison and specified
145
+ normalization of byte-order marks, CRLF, and outer whitespace. With no
146
+ server-side deduplication there is nothing to compare against, so both the
147
+ fingerprint and its normalization rules are removed rather than kept as unused
148
+ ceremony.
149
+
150
+ ## Resolved divergence: the eval fake once modelled the withdrawn dedup contract
151
+
152
+ `evals/fake-sdd-tools.mjs` originally implemented an earlier draft of this file: a
153
+ `dispatchKey` parameter, a `reused` flag, conflicting-reuse rejection, and a
154
+ returned `policyApplication`. None of that exists in the runtime, so a fake
155
+ asserting it could only manufacture confidence in a channel the runtime never had.
156
+
157
+ That divergence is **closed**. The fake was realigned to the real contract before
158
+ the controller GREEN run: `spawn_subsession` takes `{ prompt, cwd, tier }`, returns
159
+ `{ sessionId, cwd }`, and creates a new child on every call with no deduplication.
160
+ The two controller scenarios that referenced the withdrawn fields
161
+ (`missing-capability-contract`, `dispatch-intent-crash-recovery`) were regenerated
162
+ against the real contract at the same time.
163
+
164
+ The prerequisite this section once stated -- realign the fake and regenerate those
165
+ scenarios before certifying controller behavior -- was therefore satisfied, not
166
+ waived. It is kept here as history because the reasoning still governs: a fake that
167
+ validates a channel the runtime does not implement must never be used to certify
168
+ controller behavior, and the version-1 field list above remains the authority on
169
+ what the fake is allowed to model.
170
+
171
+ The consequence for evidence is that controller results predating the realignment
172
+ are not comparable to results after it. The baseline reported in
173
+ `evals/baseline-report.md` was recorded against the older fake; `evals/green-report.md`
174
+ notes that limitation rather than presenting the two as a matched pair.
@@ -0,0 +1,268 @@
1
+ # Plan and Artifact Contract
2
+
3
+ What a plan must contain for the deterministic controller to run it, and what each
4
+ artifact may hold. The grammar here is **copied from** `scripts/lib/plan-policy.mjs`
5
+ and never widened; that module is the authority. Transitions live in
6
+ [`state-machine.md`](state-machine.md) and are not restated here.
7
+
8
+ ## Contents
9
+
10
+ - [Plan grammar](#plan-grammar)
11
+ - [Tiers and role formulas](#tiers-and-role-formulas)
12
+ - [Choosing the implementer tier](#choosing-the-implementer-tier)
13
+ - [Identity pinning](#identity-pinning)
14
+ - [Preflight](#preflight)
15
+ - [Report schemas](#report-schemas)
16
+ - [The fix package](#the-fix-package)
17
+ - [Artifact separation](#artifact-separation)
18
+ - [Bounds](#bounds)
19
+ - [Blocked-state recovery](#blocked-state-recovery)
20
+
21
+ ## Plan grammar
22
+
23
+ A task heading is exactly:
24
+
25
+ ```text
26
+ ## Task <N>: <Title>
27
+ ```
28
+
29
+ `<N>` starts at 1 and increases by 1 with no gaps. Any other task-like ATX heading
30
+ outside a code fence is an error, including `### Task 1:`. This matters in practice
31
+ because the `writing-plans` skill currently emits `### Task N:` with no tier field,
32
+ so a plan from that skill must be converted before this controller will accept it.
33
+ `validate-plan` names the depth found, the depth required, and the repair.
34
+
35
+ Each task carries exactly one tier field, outside any code fence:
36
+
37
+ ```text
38
+ **Implementer tier:** Advanced
39
+ ```
40
+
41
+ Title case in the plan document, lowercase on the wire. The parser normalizes at
42
+ that boundary so no dispatch site has to remember to.
43
+
44
+ An optional `## Global Constraints` section precedes the first task and appears at
45
+ most once. When present it is included in **every** task brief, because a child
46
+ that never sees the plan cannot infer a constraint stated only there.
47
+
48
+ Fence handling follows the parser exactly: fenced content is inert, so a fenced
49
+ `## Task 1:` example is not a task. Indented four-space blocks are ordinary
50
+ content.
51
+
52
+ **A tier-annotated plan is a precondition, not an inference.** A plan missing a
53
+ tier enters `PLAN_INVALID` with a diagnostic naming the repair. The controller
54
+ never guesses a tier: guessing is precisely what the typed `tier` parameter exists
55
+ to eliminate.
56
+
57
+ ## Tiers and role formulas
58
+
59
+ Six tiers, ascending: `economy`, `fast`, `standard`, `advanced`, `capable`,
60
+ `frontier`.
61
+
62
+ | Role | Tier |
63
+ | --- | --- |
64
+ | Implementer | the plan's `**Implementer tier:**` for that task |
65
+ | Task reviewer | implementer + 1, floored at `standard`, capped at `frontier` |
66
+ | Re-reviewer | same formula as the task reviewer |
67
+ | Fixer | implementer, + 1 rung at fix round 4, + 2 rungs at round 5 |
68
+ | Final reviewer, final fixer, final re-reviewer | always `frontier` |
69
+
70
+ ### Choosing the implementer tier
71
+
72
+ The controller never picks this value; a human does, while writing the plan and
73
+ seeing every task at once. Only the implementer is annotated, because the five
74
+ other roles derive from it. A plan that also names reviewer or fixer tiers invites
75
+ disagreement with `role-tier`, which is authoritative.
76
+
77
+ | Task shape | Tier |
78
+ | --- | --- |
79
+ | Plan text contains the complete code; the work is transcription plus running tests | `economy` |
80
+ | Single-file mechanical change against an exact spec | `fast` |
81
+ | One or two files, complete spec, no integration concerns | `standard` |
82
+ | Several files with integration concerns, or behavior described in prose rather than code | `advanced` |
83
+ | Requires design judgement or broad codebase understanding | `capable` |
84
+ | Not chosen by hand; final review always runs here | `frontier` |
85
+
86
+ Two rules override the table.
87
+
88
+ **`standard` is the floor whenever the implementer must decide anything.** Wall-clock
89
+ and context cost scale with turns taken, and the cheapest models routinely take two
90
+ to three times the turns on multi-step work, costing more in total than the tier
91
+ saved. `economy` is correct only when the plan hands over literal code to transcribe.
92
+
93
+ **A wrong tier is a cost and latency defect, not a correctness one.** Review gates,
94
+ not tier, are what catch bad implementations. Choose for throughput and spend, and
95
+ do not treat the annotation as a quality lever.
96
+
97
+ The escalation map is `{1:0, 2:0, 3:0, 4:1, 5:2}`. A consequence worth stating:
98
+ at round 5 the fixer can sit one rung **above** the re-reviewer, because the
99
+ re-reviewer formula takes no round. That is pinned in tests as a deliberate
100
+ decision; changing it requires changing this contract.
101
+
102
+ `tier` is the binding channel. The rendered prompt also opens with
103
+ `/tier-<lowercase>` as a human-readable echo. The echo carries no control effect —
104
+ the runtime never parses prompt text to select a model — but PI WEBUI does reject
105
+ a leading directive that *disagrees* with the typed tier, so the echo works as a
106
+ cross-check. An absent echo is not an error; a disagreeing one is.
107
+
108
+ ## Identity pinning
109
+
110
+ At init the run pins: the plan's SHA-256 digest, repo root, worktree, run root,
111
+ branch, base ref, and merge base. Then
112
+
113
+ ```text
114
+ runId = sha256(planDigest ⁰ worktree ⁰ branch ⁰ mergeBase ⁰ createdAt)
115
+ dispatchKey = <runId>:task-<n>:<role>:attempt-<n>[:round-<n>]
116
+ ```
117
+
118
+ (`⁰` is a NUL byte; no component may contain one.)
119
+
120
+ `dispatchKey` is **controller-owned**. It is never passed to `spawn_subsession`,
121
+ which accepts only `{ prompt, cwd, tier }` and returns `{ sessionId, cwd }`. The
122
+ key names a row in this run's own ledger so recovery can correlate an intent to
123
+ the session the tool returned. It buys correlation, not idempotency.
124
+
125
+ Every mutation recomputes the plan digest. Drift fails closed with exit 4: a plan
126
+ edited mid-run invalidates every tier already dispatched, so the run stops for a
127
+ human decision rather than continuing against a plan nobody reviewed.
128
+
129
+ **Ground truth for "was this work done" is Git commits and artifacts on disk**,
130
+ not session identity. This is inherited from the original SDD skill, and it is why
131
+ a lost correlation degrades to reading `git log` and report files rather than to an
132
+ unrecoverable run.
133
+
134
+ ## Preflight
135
+
136
+ The run wants a fresh worktree at a known merge base. If the tree is dirty or the
137
+ branch has unexpected commits, preflight reports a conflict and the run enters
138
+ `PREFLIGHT_DECISION_REQUIRED`. Leaving it needs a persisted ruling naming a
139
+ decision and a reason. Untracked build output is usually fine to proceed past;
140
+ uncommitted source changes usually are not. The controller does not decide that
141
+ silently.
142
+
143
+ ## Report schemas
144
+
145
+ Every child writes exactly one bounded report at the report path it was given, and
146
+ returns exactly one status.
147
+
148
+ **Implementer and fixer:**
149
+
150
+ ```text
151
+ STATUS: DONE | DONE_WITH_CONCERNS | NEEDS_CONTEXT | BLOCKED
152
+
153
+ CHANGES:
154
+ - <file>: <what changed and why>
155
+
156
+ TESTS:
157
+ - <command>: <result, with counts>
158
+
159
+ CONCERNS: (required when DONE_WITH_CONCERNS; the reducer rejects an
160
+ empty list, so a hedged status must name something)
161
+ - kind: observational | correctness | scope
162
+ note: <one line>
163
+
164
+ COMMIT: <sha> (when the role commits)
165
+ ```
166
+
167
+ `observational` concerns pass to review. A `correctness` or `scope` concern routes
168
+ to `CONCERN_DECISION_REQUIRED` and needs a ruling, because those two are findings
169
+ wearing a softer word.
170
+
171
+ `NEEDS_CONTEXT` must name what is missing and why it is load-bearing. Two
172
+ enrichments are allowed at the planned tier; a third blocks. Enrichment is not a
173
+ fix round and never touches `fixRound`.
174
+
175
+ **Task reviewer and re-reviewer:**
176
+
177
+ ```text
178
+ SPEC: PASS | FAIL
179
+ QUALITY: APPROVED | CHANGES_REQUESTED
180
+ FINDINGS:
181
+ - id: F-<n>
182
+ severity: Critical | Important | Minor
183
+ loadBearing: yes | no
184
+ location: <file>:<line>
185
+ evidence: <what was observed, not what was assumed>
186
+ impact: <consequence>
187
+ correction: <what would resolve it>
188
+ ```
189
+
190
+ Both axes are required and independent. Task completion needs `PASS` **and**
191
+ `APPROVED` with no open load-bearing finding.
192
+
193
+ `Critical` and `Important` are load-bearing: they open a fix round and can never be
194
+ parked. `Minor` is contestable and may be parked with a ruling that names evidence.
195
+ Severity is fixed when first reported and cannot be re-reported lower, which closes
196
+ the obvious route to dismissing a finding.
197
+
198
+ A re-reviewer returns one verdict per open finding — `RESOLVED`, `STILL_PRESENT`,
199
+ `REGRESSION`, or `NEEDS_CONTEXT` — scoped to that fix's Git range. It may report a
200
+ regression the fix introduced. It may not expand into a fresh whole-task review.
201
+
202
+ ## The fix package
203
+
204
+ Every fix round dispatches a **fresh child with no memory of prior rounds**. The
205
+ package must therefore carry:
206
+
207
+ 1. the task brief, including Global Constraints;
208
+ 2. the persistent implementer report;
209
+ 3. the exact open findings, by ID, with evidence;
210
+ 4. **each prior attempted correction and why it failed**;
211
+ 5. the relevant tests;
212
+ 6. the scoped diff for the range under repair.
213
+
214
+ Item 4 is the one that is easy to omit and expensive to omit. Without it, round 3
215
+ can re-apply the fix that failed in round 2, spend a full review cycle, and arrive
216
+ back at the same finding. The child must read that history and must not repeat a
217
+ correction already recorded as failed.
218
+
219
+ ## Artifact separation
220
+
221
+ | Artifact | Location | Writer |
222
+ | --- | --- | --- |
223
+ | Deliverables | the worktree | implementer, fixer |
224
+ | Reports | run root | each child, one file each |
225
+ | Task briefs | run root | `task-brief` |
226
+ | Review packages | run root | `review-package` |
227
+ | Rendered prompts | run root | `render-prompt` |
228
+ | `state.json` | run root | the store, under lock |
229
+ | `progress.md` | run root | the store, append-only |
230
+
231
+ `state.json` is canonical; `progress.md` is derived. Never hand-edit either. A
232
+ hand-edited state is indistinguishable from a corrupted one, and the reducer is
233
+ built to refuse exactly the plausible-looking repair a stuck controller would
234
+ otherwise invent.
235
+
236
+ ## Bounds
237
+
238
+ Each tested at the limit and one byte past it.
239
+
240
+ | Thing | Bound |
241
+ | --- | --- |
242
+ | Task brief | 256 KiB |
243
+ | Rendered prompt | 384 KiB |
244
+ | Child or reviewer report | 64 KiB |
245
+ | `state.json` | 1 MiB |
246
+ | One audit line | 8 KiB |
247
+ | Finding records | 256 |
248
+ | Any single path | 4096 UTF-8 bytes |
249
+ | Any recorded human string | 256 characters, single line |
250
+
251
+ Recorded strings reject control characters and the audit marker outright rather
252
+ than escaping them, so no reason text can forge a transition record.
253
+
254
+ ## Blocked-state recovery
255
+
256
+ | State | What it means | What clears it |
257
+ | --- | --- | --- |
258
+ | `CAPABILITY_BLOCKED` | tier resolution or the policy tool is unavailable | fix the environment; re-init |
259
+ | `PLAN_INVALID` | grammar, tier, or digest failure | repair the plan; re-init, since the digest changed |
260
+ | `PREFLIGHT_DECISION_REQUIRED` | the tree is not in the expected shape | a persisted preflight ruling |
261
+ | `DISPATCH_AMBIGUOUS` | the spawn/correlate window was crossed | a ruling: adopt an observed session, or reissue stored bytes |
262
+ | `DISPATCH_MISMATCH_BLOCKED` | a dispatch did not match its intent | human inspection; no automatic path |
263
+ | `TASK_BLOCKED` / `FINAL_BLOCKED` | terminal | a human decision outside the run |
264
+
265
+ `DISPATCH_AMBIGUOUS` deserves emphasis: the runtime offers no dispatch
266
+ idempotency, so a crash between the spawn call and the ledger write can orphan a
267
+ child. That window cannot be closed from the controller. The guarantee is that it
268
+ is always **visible** and never silently resolved.