@hyperdreamer/pi-webui 1.10.7 → 1.11.0-beta.10
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.
- package/README.md +17 -2
- package/dist/cli.js +265 -32
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-BIb2Wx8W.js → CodeViewer-CAf_j27u.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-DpWiGlQo.js → UnifiedDiffViewer-wBjEJWa1.js} +1 -1
- package/dist/client/assets/{index-DX583DEv.js → index-5EtDb9nj.js} +1197 -476
- package/dist/client/index.html +1 -1
- package/dist/config.js +52 -11
- package/dist/config.js.map +1 -1
- package/dist/pi-webui-plugins/workspace-memory/pi-webui-plugin.js +3 -2
- package/dist/pi-webui-plugins/workspace-tasks/pi-webui-plugin.js +1 -0
- package/dist/plugin-api.d.ts +30 -0
- package/dist/server/app.js +6 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +40 -5
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/realtime/sessionStatusCoalescer.js +137 -0
- package/dist/server/realtime/sessionStatusCoalescer.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +57 -5
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/modelPolicyCapability.js +88 -0
- package/dist/server/sessions/modelPolicyCapability.js.map +1 -0
- package/dist/server/sessions/modelPolicyTool.js +31 -0
- package/dist/server/sessions/modelPolicyTool.js.map +1 -0
- package/dist/server/sessions/modelTierRegistry.js +14 -0
- package/dist/server/sessions/modelTierRegistry.js.map +1 -1
- package/dist/server/sessions/modelTierSettingsRoutes.js +36 -0
- package/dist/server/sessions/modelTierSettingsRoutes.js.map +1 -0
- package/dist/server/sessions/modelTierSettingsService.js +100 -0
- package/dist/server/sessions/modelTierSettingsService.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +1643 -433
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionDefaultsRoutes.js +39 -0
- package/dist/server/sessions/sessionDefaultsRoutes.js.map +1 -1
- package/dist/server/sessions/sessionDefaultsService.js +36 -6
- package/dist/server/sessions/sessionDefaultsService.js.map +1 -1
- package/dist/server/sessions/sessionMetadataStore.js +107 -13
- package/dist/server/sessions/sessionMetadataStore.js.map +1 -1
- package/dist/server/sessions/sessionModelPolicy.js +118 -0
- package/dist/server/sessions/sessionModelPolicy.js.map +1 -0
- package/dist/server/sessions/sessionReorder.js +117 -0
- package/dist/server/sessions/sessionReorder.js.map +1 -0
- package/dist/server/sessions/sessionRouteFastifyOptions.js +8 -0
- package/dist/server/sessions/sessionRouteFastifyOptions.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +208 -3
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/starterModelPolicyPreferenceStore.js +164 -0
- package/dist/server/sessions/starterModelPolicyPreferenceStore.js.map +1 -0
- package/dist/server/sessions/utilityModelExtension.js +199 -0
- package/dist/server/sessions/utilityModelExtension.js.map +1 -0
- package/dist/server/sessions/utilityModelResolver.js +77 -0
- package/dist/server/sessions/utilityModelResolver.js.map +1 -0
- package/dist/server/sessions/utilityModelSettingsRoutes.js +62 -0
- package/dist/server/sessions/utilityModelSettingsRoutes.js.map +1 -0
- package/dist/server/sessions/utilityModelSettingsService.js +101 -0
- package/dist/server/sessions/utilityModelSettingsService.js.map +1 -0
- package/dist/server/skills/optionalSkillInstall.js +69 -0
- package/dist/server/skills/optionalSkillInstall.js.map +1 -0
- package/dist/server/skills/optionalSkillInstaller.js +148 -0
- package/dist/server/skills/optionalSkillInstaller.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +129 -3
- package/dist/shared/apiTypes.js +10 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +15 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +7 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/assets/pi-webui-desktop.png +0 -0
- package/docs/config.md +61 -3
- package/docs/plugins.md +93 -16
- package/optional-skills/deterministic-subagent-driven-development/SKILL.md +224 -0
- package/optional-skills/deterministic-subagent-driven-development/pi-webui-skill.json +28 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/final-reviewer.md +132 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/implementer.md +101 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/re-reviewer.md +60 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/task-reviewer.md +80 -0
- package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md +174 -0
- package/optional-skills/deterministic-subagent-driven-development/references/plan-contract.md +268 -0
- package/optional-skills/deterministic-subagent-driven-development/references/state-machine.md +177 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/manifest.mjs +258 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/plan-policy.mjs +350 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/prompt-renderer.mjs +290 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-machine.mjs +1264 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-store.mjs +532 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state +3 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state.mjs +349 -0
- package/optional-skills/deterministic-writing-plans/SKILL.md +232 -0
- package/optional-skills/deterministic-writing-plans/references/grammar.md +84 -0
- package/optional-skills/deterministic-writing-plans/templates/plan-skeleton.md +143 -0
- package/package.json +13 -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.
|
package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md
ADDED
|
@@ -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
|
+
It advertises no tier slash commands. An earlier draft of this file listed
|
|
20
|
+
`/tier-economy`...`/tier-frontier` plus `/tier-up` and `/tier-down`. No such
|
|
21
|
+
command is registered in the runtime: tier selection is the typed `tier` field on
|
|
22
|
+
`spawn_subsession` and nothing else. The rendered `Model tier: <tier>` line is a
|
|
23
|
+
human-readable consistency label, not a command.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
type ModelTier = "economy" | "fast" | "standard" | "advanced" | "capable" | "frontier";
|
|
27
|
+
|
|
28
|
+
interface ExactModelSelection {
|
|
29
|
+
model: { provider: string; id: string };
|
|
30
|
+
thinkingLevel: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface GetModelPolicyV1 {
|
|
34
|
+
contractVersion: 1;
|
|
35
|
+
policy: {
|
|
36
|
+
mode: "exact" | "tiered";
|
|
37
|
+
rememberedTier: ModelTier | null;
|
|
38
|
+
currentTier: ModelTier | null;
|
|
39
|
+
currentRuntime: ExactModelSelection;
|
|
40
|
+
nextRequestResolved: ExactModelSelection | null;
|
|
41
|
+
blockedReason: string | null;
|
|
42
|
+
};
|
|
43
|
+
ladder: { valid: boolean; revision: string | null; blockedReason: string | null };
|
|
44
|
+
trackedDispatch: {
|
|
45
|
+
contractVersion: 1;
|
|
46
|
+
tierField: true;
|
|
47
|
+
scope: "parent-session";
|
|
48
|
+
canonicalInputs: readonly ["cwd", "prompt", "tier"];
|
|
49
|
+
returnsSessionId: true;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Conditional invariants
|
|
55
|
+
|
|
56
|
+
Tuples use the `ExactModelSelection` shape `{ model: { provider, id },
|
|
57
|
+
thinkingLevel }` and carry model identity and supported thinking only.
|
|
58
|
+
|
|
59
|
+
| Condition | Requirement |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Exact mode | `currentTier` is `null`; `currentRuntime` and `nextRequestResolved` are both non-null and equal |
|
|
62
|
+
| Valid tiered mode | `currentTier` is non-null; ladder is complete and valid; latest resolved tuple is non-null |
|
|
63
|
+
| Invalid tiered mode | `nextRequestResolved` may be `null` only when `ladder.blockedReason` is a non-empty actionable reason; this state is capability-blocking |
|
|
64
|
+
| Any policy blocked reason | Capability-blocking regardless of mode |
|
|
65
|
+
| Invalid ladder, either mode | Capability-blocking. Children are dispatched *by tier* whatever the parent's mode, and reviewer/fixer tiers are derived by formula, so any unresolvable rung can fail a later round |
|
|
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
|
+
model-selection commands, so a `Model tier: <tier>` line is a human-readable echo
|
|
106
|
+
with zero control effect. A test fake that recovers a tier from this label is
|
|
107
|
+
exercising a channel the runtime does not implement, and cannot detect a child
|
|
108
|
+
that ignored the typed field.
|
|
109
|
+
|
|
110
|
+
The one exception is a guard, not a mechanism: a leading label that *disagrees*
|
|
111
|
+
with the typed `tier` is rejected before child creation, so a stale rendered
|
|
112
|
+
prompt 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 tier label 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
|
+
`Model tier: <lowercase>` as a human-readable echo. The echo carries no control
|
|
104
|
+
effect — the runtime never parses prompt text to select a model — but PI WEBUI
|
|
105
|
+
rejects a leading label that *disagrees* with the typed tier, so the echo works as
|
|
106
|
+
a 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.
|