@signalridge/pi-workflows 1.4.0 → 1.6.0
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/CHANGELOG.md +249 -0
- package/README.md +193 -15
- package/package.json +3 -3
- package/skills/workflow-authoring/SKILL.md +23 -7
- package/skills/workflow-authoring/references/capabilities.md +6 -6
- package/skills/workflow-authoring/references/capability-details.md +16 -6
- package/skills/workflow-authoring/references/debugging.md +2 -2
- package/skills/workflow-authoring/references/quality-helpers.md +2 -2
- package/skills/workflow-authoring/references/registry-ownership.md +6 -4
- package/skills/workflow-authoring/references/review.md +6 -2
- package/skills/workflow-authoring/references/runtime.md +3 -3
- package/skills/workflow-authoring/references/specialized-helpers.md +1 -1
- package/skills/workflow-authoring/references/versions.md +1 -1
- package/skills/workflow-patterns/SKILL.md +1 -1
- package/skills/workflow-review/SKILL.md +1 -1
- package/src/builtins.ts +23 -23
- package/src/capabilities.ts +28 -6
- package/src/engine.ts +248 -58
- package/src/index.ts +457 -173
- package/src/journal.ts +278 -253
- package/src/rpc-client.ts +37 -48
- package/src/runtime.ts +429 -54
- package/src/strengths.ts +93 -0
- package/src/ui.ts +29 -4
- package/src/workflow-settings.ts +95 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,254 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- fabe89a: A workflow now speaks one routing word of its own: **strength**.
|
|
7
|
+
|
|
8
|
+
`agent({ tier })` is gone. A script names `strength: "low" | "medium" | "high"` —
|
|
9
|
+
this package's own word for how much effort a step deserves — and the new
|
|
10
|
+
`strengths` setting is the only thing that binds one to an Agent tier. A strength
|
|
11
|
+
no table defines dispatches with **no tier at all** and takes the agent's ordinary
|
|
12
|
+
default, so nothing is inferred from spelling at dispatch: the binding is always a
|
|
13
|
+
table entry.
|
|
14
|
+
|
|
15
|
+
That inference was the bug. The built-ins ask for `low`, and so does the `Explore`
|
|
16
|
+
agent and everything that names no tier of its own, so pointing `low` at a cheaper
|
|
17
|
+
model to make a 26-agent fan-out affordable dragged every ordinary search spawn
|
|
18
|
+
with it. Pointing a *strength* elsewhere does not.
|
|
19
|
+
|
|
20
|
+
A host that has configured nothing gets a shipped default table rather than no
|
|
21
|
+
mappings: each strength on the catalogue tier of the same name, wherever the host
|
|
22
|
+
defines one — identity on a stock install, empty on a host whose tiers are called
|
|
23
|
+
`cheap`/`deep`. It is computed against the live catalogue, so it is never an
|
|
24
|
+
assertion about someone else's tier names and never complains at run start about
|
|
25
|
+
configuration nobody wrote. Without it every shipped script's `low`/`medium`
|
|
26
|
+
distinction would collapse onto pi-subagents' managed `medium` fallback — the
|
|
27
|
+
*more* expensive rung — and an unconfigured machine would run its fan-outs dearer
|
|
28
|
+
than before this change.
|
|
29
|
+
|
|
30
|
+
The distinction that makes that safe is that it is a **table, not a fallback
|
|
31
|
+
rule**. A rule would be unremovable, and re-pricing a fan-out would again mean
|
|
32
|
+
editing the shared tier. A table is replaced by writing one, a written table that
|
|
33
|
+
omits `low` leaves it unmapped even where a tier called `low` exists, and `{}` is
|
|
34
|
+
a real table meaning "map nothing".
|
|
35
|
+
|
|
36
|
+
This is not the retired `workflow.tiers` key. That one carried its own `model` and
|
|
37
|
+
`thinking`, making it a second model policy behind a second resolver. A value here
|
|
38
|
+
is a key in the host's one catalogue: pi-subagents still owns every model, every
|
|
39
|
+
thinking level, and the only `resolveAgentTier()`, and cannot tell a mapped call
|
|
40
|
+
apart from a spawn that named the key itself. No protocol change.
|
|
41
|
+
|
|
42
|
+
The vocabulary is closed, which buys back what the passthrough used to provide.
|
|
43
|
+
A word outside the three is a typo, not a strength nobody configured, so it fails
|
|
44
|
+
before dispatch. With no way for a script name to reach the catalogue, the
|
|
45
|
+
shipped-vs-user tier rule (`shippedScript`) had nothing left to decide and is
|
|
46
|
+
removed, along with its `run_created` journal field.
|
|
47
|
+
|
|
48
|
+
The same argument applies one level up, so `agent()` and `checkpoint()` now
|
|
49
|
+
reject any option name they do not read instead of ignoring it. `tier`, `model`,
|
|
50
|
+
and `thinking` answer by naming their replacement; anything else lists the legal
|
|
51
|
+
keys, and a non-object options value is refused rather than reaching a bare
|
|
52
|
+
`TypeError`. An unknown key is dropped before the call hash is built, so it moves
|
|
53
|
+
neither the dispatch nor the resume identity: `agent({ strenght: "low" })` would
|
|
54
|
+
have run every step untiered and replayed that way, and
|
|
55
|
+
`checkpoint({ headles: "abort" })` would have auto-approved in headless mode
|
|
56
|
+
instead of aborting. Neither leaves a log line. A script is plain JavaScript
|
|
57
|
+
authored against this contract, so nothing else was going to catch it. Both key
|
|
58
|
+
lists are pinned to their interfaces by a `satisfies Record<keyof …>` clause, in
|
|
59
|
+
both directions.
|
|
60
|
+
|
|
61
|
+
There is deliberately no default strength: a call that names none dispatches with
|
|
62
|
+
no tier, exactly as an unmapped one does, and the host resolves it the way it
|
|
63
|
+
resolves every other spawn. A default was tried and is wrong — a tier a call
|
|
64
|
+
requests outranks the agent type's own, so on a stock install (`medium → medium`)
|
|
65
|
+
every unlabelled dispatch would have pinned `medium` over whatever the agent
|
|
66
|
+
declared, overruling `Explore`'s shipped `tier: low` and re-pricing in the wrong
|
|
67
|
+
direction the one agent this indirection exists to leave alone. Nothing is lost:
|
|
68
|
+
a script that named no strength stated no opinion, and pi-subagents' ordinary
|
|
69
|
+
precedence is still user-owned policy.
|
|
70
|
+
|
|
71
|
+
What the table must reach is every call a script did label, and the shipped
|
|
72
|
+
scripts label all of theirs. The three helpers that dispatch on the script's
|
|
73
|
+
behalf — `verify`, `judgePanel`, `completenessCheck` — took no strength at all
|
|
74
|
+
and so could never be re-routed; they now accept one, defaulting to `low`, `low`,
|
|
75
|
+
and `medium`.
|
|
76
|
+
|
|
77
|
+
Those defaults are an opinion where there was none, and an opinion outranks the
|
|
78
|
+
host's: a tier a call requests beats `agentTiers.defaultTier`, so on a machine
|
|
79
|
+
whose default is `high` a `completenessCheck` now runs at whatever `medium` maps
|
|
80
|
+
to rather than at `high`. That is the price of making the helpers steerable at
|
|
81
|
+
all — a helper that names no tier can never be pointed anywhere — and the new
|
|
82
|
+
option is how a script takes it back.
|
|
83
|
+
|
|
84
|
+
Fixes a re-spend bug: `strengths` is deliberately never frozen onto a run, but the
|
|
85
|
+
resumes the engine starts itself (`/workflows resume`, the provider-limit retry)
|
|
86
|
+
passed no table, and since a call's identity keys on the tier it requested, every
|
|
87
|
+
finished call missed its journal entry and re-ran untabled. The engine now reads
|
|
88
|
+
the current table for those.
|
|
89
|
+
|
|
90
|
+
`/workflows-models` is gone; the table is edited with `/workflows strength`
|
|
91
|
+
(`[<low|medium|high> <tier>|off]`), which also prints the effective table and the
|
|
92
|
+
tiers this host defines — the one place both halves are visible. It validates the
|
|
93
|
+
strength and the target before writing and reports the resolved result rather than
|
|
94
|
+
echoing the write, so it can no longer claim a change the settings layer dropped.
|
|
95
|
+
`off` yields to a real host tier of that name, and says so when it does, since a
|
|
96
|
+
user who typed the documented keyword and got a mapping has to be told which
|
|
97
|
+
reading won. The view also names where a machine-wide table lives, and — while
|
|
98
|
+
the shipped default is in force — that the default is identity, so workflow work
|
|
99
|
+
is still sharing the tiers ordinary spawns use until a tier is defined for it.
|
|
100
|
+
|
|
101
|
+
Settings commands now write project scope instead of the merged view. Previously
|
|
102
|
+
the first `/effort` or `/workflows-progress` in a project copied every global value
|
|
103
|
+
and default into the project file, after which that project stopped tracking the
|
|
104
|
+
global one.
|
|
105
|
+
|
|
106
|
+
`strengths` needs the opposite treatment, because it replaces rather than merges at
|
|
107
|
+
each level, so `/workflows strength` seeds its write from the table a run would
|
|
108
|
+
actually use — project, else global, else the shipped default. Built on project
|
|
109
|
+
scope alone, setting one entry would have discarded an inherited global table, and
|
|
110
|
+
`off` against an entry the project never wrote would have written nothing and left
|
|
111
|
+
the mapping standing.
|
|
112
|
+
|
|
113
|
+
One caution the docs now carry: the provider-limit retry resumes on a timer with
|
|
114
|
+
nobody watching, so a table edited while a run is rate-limit-paused is charged the
|
|
115
|
+
re-spend above without being asked. Stop the run first if that is not worth it.
|
|
116
|
+
|
|
117
|
+
## 1.5.0
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- c1b1741: Protocol v4: workflows use the Agent-tier catalogue directly.
|
|
121
|
+
|
|
122
|
+
The wire contract changes, so the three packages move together: `pi-workflows`
|
|
123
|
+
now declares `"@signalridge/pi-subagents": ">=1.9.0"`, and a peer outside that
|
|
124
|
+
range fails the startup handshake with a diagnostic naming both packages rather
|
|
125
|
+
than failing mid-run.
|
|
126
|
+
|
|
127
|
+
The `small | medium | large` workflow-tier vocabulary and its mapping layer are
|
|
128
|
+
gone. A managed request names an Agent tier — a key in the host's own
|
|
129
|
+
`agentTiers` catalogue — and `resolveAgentTier()` is the single resolver for
|
|
130
|
+
every spawn path. Removed with it: the wire's mapped `agentTier` field, the
|
|
131
|
+
`workflow` settings key (retired with a warning naming it; `agentTiers.defaultTier`
|
|
132
|
+
replaces `workflow.defaultTier`), and the per-call `model`/`thinking` selectors,
|
|
133
|
+
which the validator now rejects rather than accepting and ignoring.
|
|
134
|
+
|
|
135
|
+
Every v4 capability is required and every ping carries the host's tier catalogue,
|
|
136
|
+
so an incomplete peer fails one check instead of five optional ones. Fresh
|
|
137
|
+
installs ship `low`/`medium`/`high` profiles, all inheriting their model, and a
|
|
138
|
+
managed call that names no tier falls back to `medium`, so a workflow runs on an
|
|
139
|
+
unconfigured machine without this package choosing a vendor. Because `medium`
|
|
140
|
+
inherits, that fallback lands on the parent session's model — it commits to an
|
|
141
|
+
effort level, not to a vendor, so it does change the thinking level a managed
|
|
142
|
+
call runs at when the parent session is set higher, and it does not move the
|
|
143
|
+
work anywhere cheaper. What it buys is a named policy with a durable snapshot
|
|
144
|
+
and a scope check; a workspace that wants cheaper managed work names a
|
|
145
|
+
`defaultTier` whose profile pins a model. The fallback is scoped to calls that
|
|
146
|
+
cannot inherit a parent model rather than being installed as
|
|
147
|
+
`agentTiers.defaultTier`: a shipped catalogue default would have applied to
|
|
148
|
+
every ordinary `Agent` spawn as well, silencing `defaultModel` and pinning a
|
|
149
|
+
thinking level on machines that configured neither. The catalogue no longer
|
|
150
|
+
ships a `fast` profile; it was the same (model, thinking) pair as `low`, and the
|
|
151
|
+
one agent that named it (Explore) now names `low`. A user agent file that still
|
|
152
|
+
says `agentTier: fast` is reported by the existing unknown-reference check when
|
|
153
|
+
settings and agents load, rather than failing at its first spawn.
|
|
154
|
+
|
|
155
|
+
`agentTiers.defaultTier` therefore has three states, and `/agents → Settings`
|
|
156
|
+
offers all three rather than rendering two of them as one word: a named tier,
|
|
157
|
+
`unset` (managed calls reach the shipped fallback), and `none` (recorded as
|
|
158
|
+
`noDefaultTier`, which withdraws the fallback so managed calls fail closed).
|
|
159
|
+
`setDefaultAgentTier` takes that choice as a tagged value, so a caller cannot
|
|
160
|
+
express "no default" without saying which of the two it means.
|
|
161
|
+
|
|
162
|
+
`resolveAgentTier` gained `requireTier` for that fallback, and the two things
|
|
163
|
+
pre-resolution callers need are both answered by the resolver rather than
|
|
164
|
+
restated beside it: `agentTierApplies()` for "will a tier own this spawn's
|
|
165
|
+
model?", and `selectAgentTier()` for the tier key itself, which the managed
|
|
166
|
+
path needs to label a tombstone and the lifecycle events before the runner has
|
|
167
|
+
resolved anything.
|
|
168
|
+
|
|
169
|
+
Because a tier now owns model resolution outright, the spawn paths stop
|
|
170
|
+
pre-resolving one — and with it they stopped producing the model name the agent
|
|
171
|
+
UI shows. The resolution callback carries that label back instead, so a tier
|
|
172
|
+
that pins a model still names it in the viewer and the agent list, and a profile
|
|
173
|
+
that inherits correctly shows none. The label rides beside the snapshot rather
|
|
174
|
+
than inside it: the snapshot is a durable policy record a managed tombstone
|
|
175
|
+
persists and revalidates, and a cosmetic string does not belong in it.
|
|
176
|
+
|
|
177
|
+
Workflow resume now keys each cached call on the policy for that call's own tier
|
|
178
|
+
rather than on a whole-catalogue fingerprint, so defining or editing an unrelated
|
|
179
|
+
tier no longer forces a full re-execution — but only for a call that names its
|
|
180
|
+
tier. A call that names none keeps the whole-catalogue key, because the host
|
|
181
|
+
resolves those as `call > agent frontmatter > defaultTier` and frontmatter is
|
|
182
|
+
not on this wire: an agent reaches a managed caller as a name. Keyed on
|
|
183
|
+
`defaultTier`, such a call would be wrong twice over — replaying stale work
|
|
184
|
+
after an edit to the tier its agent actually declares, and re-executing after an
|
|
185
|
+
edit to a default it never reached. `agentTierPolicyIdentity()` therefore
|
|
186
|
+
requires a tier rather than accepting `undefined` and folding the default in, so
|
|
187
|
+
the wrong call cannot be written. Journal schema v4 and managed tombstone schema
|
|
188
|
+
v2 are quarantined from older facts rather than migrated.
|
|
189
|
+
|
|
190
|
+
The tier catalogue belongs to the user, names included. The built-in workflows
|
|
191
|
+
and the ad-hoc script ship with this package, so a tier name they use that the
|
|
192
|
+
host does not define is dropped in favour of the host's default rather than
|
|
193
|
+
failing the run — they cannot assert which names exist on someone else's
|
|
194
|
+
machine. A script the user wrote still fails closed on an undefined tier, since
|
|
195
|
+
that is a typo in a catalogue they own. Shipped-ness travels with the script
|
|
196
|
+
rather than with the frame that called it, so a user script that reaches a
|
|
197
|
+
built-in through `workflow(name)` applies the built-in's rule to the built-in;
|
|
198
|
+
the authoring skill now says outright that `low`/`medium`/`high` are the shipped
|
|
199
|
+
profiles rather than names an authored script may assume.
|
|
200
|
+
|
|
201
|
+
The managed routing policy published on the wire reports the default a managed
|
|
202
|
+
call will actually get, fallback included, so a peer's replay identity cannot
|
|
203
|
+
disagree with the host's selection; its sorts are code-unit rather than
|
|
204
|
+
locale-dependent, so the same catalogue fingerprints identically everywhere. The
|
|
205
|
+
workflow peer re-pings per start and resume instead of pinning the catalogue it
|
|
206
|
+
saw at session start, and reports the host-selected tier back through an
|
|
207
|
+
`onTierResolved` callback rather than by mutating the dispatch options.
|
|
208
|
+
|
|
209
|
+
Fixes: a nested spawn no longer relabels an agent's frontmatter tier as
|
|
210
|
+
caller-requested, which had turned a `scopeModels` warning into a refusal and
|
|
211
|
+
misattributed the choice; the scheduler no longer freezes a frontmatter tier into
|
|
212
|
+
a job, so editing the agent file takes effect at fire time; retired managed spawn
|
|
213
|
+
keys are now bounded instead of growing for the life of a session; a
|
|
214
|
+
scope refusal for a tier whose profile inherits its model now names the model
|
|
215
|
+
that would have run instead of the literal `undefined`; and the retired
|
|
216
|
+
`workflow` key warns once per process rather than on every settings read and
|
|
217
|
+
every project write.
|
|
218
|
+
|
|
219
|
+
The tier-key predicate and its length bound now have a single definition, in
|
|
220
|
+
`pi-subagents-protocol`, which `pi-subagents` re-exports. The wire is the
|
|
221
|
+
narrower of the two gates — a key one accepted and the other rejected could
|
|
222
|
+
never reach a peer — so a second copy could only ever be a way for them to
|
|
223
|
+
disagree. That one definition now also governs `blockedProfiles`, which was
|
|
224
|
+
being validated by a helper that trims first: `" low"` became `"low"` there
|
|
225
|
+
while the same value in `profiles` or `defaultTier` was refused outright. All
|
|
226
|
+
three reject.
|
|
227
|
+
|
|
228
|
+
`/agents → Settings` no longer names the fallback tier in the `unset` row's
|
|
229
|
+
label. Which tier `unset` reaches depends on the catalogue, and a workspace that
|
|
230
|
+
edits or deletes the shipped `medium` profile leaves it reaching nothing — at
|
|
231
|
+
which point `unset` and `none` behave identically. The row's description asks
|
|
232
|
+
the resolver what `unset` would currently resolve to and says that, including
|
|
233
|
+
when the answer is "nothing".
|
|
234
|
+
|
|
235
|
+
`pi-workflows` also gained the session-lifecycle handling this routing work
|
|
236
|
+
needed to sit on. The engine has an explicit dispose lifecycle, so a protocol
|
|
237
|
+
probe or a start that is in flight when a session ends rejects with a disposed
|
|
238
|
+
error instead of resolving against an engine nobody owns. The tool, command and
|
|
239
|
+
widget surfaces register exactly once and resolve the *current* engine on each
|
|
240
|
+
use, rather than closing over the one that existed when they were registered —
|
|
241
|
+
a session replacement previously left them pointing at a disposed engine. Tool
|
|
242
|
+
and command handlers report failures as results rather than throwing out of the
|
|
243
|
+
host's dispatch, and the lifecycle handler accepts `subagents:created` and
|
|
244
|
+
`subagents:started` so a spawn's identity is recorded from the first event that
|
|
245
|
+
carries it rather than only at completion.
|
|
246
|
+
|
|
247
|
+
### Patch Changes
|
|
248
|
+
|
|
249
|
+
- Updated dependencies [c1b1741]
|
|
250
|
+
- @signalridge/pi-subagents-protocol@1.4.0
|
|
251
|
+
|
|
3
252
|
## 1.4.0
|
|
4
253
|
### Minor Changes
|
|
5
254
|
|
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ isolation, retry, and session policy.
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
`@signalridge/pi-workflows` requires `@signalridge/pi-subagents` `>=1.
|
|
16
|
-
with protocol
|
|
15
|
+
`@signalridge/pi-workflows` requires `@signalridge/pi-subagents` `>=1.9.0`
|
|
16
|
+
with protocol v4 managed-spawn, Agent-tier, and managed-policy support. They are separate Pi packages: the peer
|
|
17
17
|
dependency documents the requirement but intentionally does not auto-load or
|
|
18
18
|
duplicate the subagent extension.
|
|
19
19
|
|
|
@@ -54,9 +54,9 @@ export const meta = {
|
|
|
54
54
|
|
|
55
55
|
phase("scan");
|
|
56
56
|
const findings = await parallel([
|
|
57
|
-
() => agent("Check for cross-package imports", { label: "imports",
|
|
58
|
-
() => agent("Check for secrets", { label: "secrets",
|
|
59
|
-
() => agent("Check for dead code", { label: "dead-code",
|
|
57
|
+
() => agent("Check for cross-package imports", { label: "imports", strength: "low" }),
|
|
58
|
+
() => agent("Check for secrets", { label: "secrets", strength: "low" }),
|
|
59
|
+
() => agent("Check for dead code", { label: "dead-code", strength: "low" }),
|
|
60
60
|
]);
|
|
61
61
|
|
|
62
62
|
phase("synthesize");
|
|
@@ -77,15 +77,17 @@ user's own LLM and run on the user's own machine.
|
|
|
77
77
|
|
|
78
78
|
### Runtime globals
|
|
79
79
|
|
|
80
|
-
- `agent(prompt, { label, phase, schema,
|
|
80
|
+
- `agent(prompt, { label, phase, schema, strength, isolation, thread, agentType, toolset, excludeTools, timeoutMs, retries })`
|
|
81
81
|
— dispatch one subagent through the managed spawn protocol. Returns text, a
|
|
82
82
|
schema-validated value, or recoverable `null` after retries. Replayed journal
|
|
83
83
|
calls consume no real-dispatch cap or token/phase budget. `schema` is a
|
|
84
84
|
plain JSON Schema; the reply is parsed and validated client-side with bounded
|
|
85
85
|
repair across attempts, and exhaustion throws `SCHEMA_NONCOMPLIANCE`.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
`strength` is the provider-neutral workflow route; `thread` chains sequential turns
|
|
87
|
+
within a named conversation thread. A legacy programmatic `model` option may
|
|
88
|
+
still be accepted by non-managed callers, but workflow meta/phase model or
|
|
89
|
+
thinking fields are rejected and are never a workflow policy source. Pre-schema-v4
|
|
90
|
+
journal runs are quarantined and cannot be resumed.
|
|
89
91
|
- `parallel(thunks)` — run thunks concurrently, preserve input order. Recoverable
|
|
90
92
|
thunk failures become `null`; non-recoverable failures (token budget, agent
|
|
91
93
|
limit) halt the run after the batch barrier settles. Fan-out that breaches
|
|
@@ -148,9 +150,12 @@ workflow, and the review skill before accepting a topology or recovery change.
|
|
|
148
150
|
- `workflow_control`: `list`, `get`, `pause`, `resume`, `stop`, or `rm` a run; `rm` writes a durable removal tombstone.
|
|
149
151
|
- `pi.events` emits `pi-workflows:runtime` snapshots (`{ runId, event }`) for phase, task, quality, retry, and nested-workflow progress; observer failures are contained.
|
|
150
152
|
- `/workflows`: TUI navigator plus `run`, `status`, `watch`, `stop`, `pause`,
|
|
151
|
-
`resume`, `rm`,
|
|
152
|
-
- `/workflows
|
|
153
|
-
|
|
153
|
+
`resume`, `rm`, `save <name> [runId]`, and `strength` subcommands.
|
|
154
|
+
- `/workflows strength`: `[<low|medium|high> <tier>|off]` — which Agent tier each
|
|
155
|
+
workflow strength runs on, and the tiers this host defines. Unconfigured hosts
|
|
156
|
+
start on a shipped default table (identity, where the host defines the name).
|
|
157
|
+
The tiers themselves are still configured only in pi-subagents'
|
|
158
|
+
`/agents → Model tiers`; there is deliberately no second tier configuration file.
|
|
154
159
|
- `/effort` and `/ultracode`: standing `off|high|ultra` effort guidance that auto-arms substantive messages.
|
|
155
160
|
- `/workflows-progress`: `compact|detailed|status|max <N>` controls the persisted live panel.
|
|
156
161
|
- `/deep-research`, `/adversarial-review`, `/code-review`,
|
|
@@ -184,12 +189,185 @@ written atomically with a backup.
|
|
|
184
189
|
|
|
185
190
|
## Model routing
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
A workflow speaks one word about cost: **strength**, this package's own name for
|
|
193
|
+
how much effort a step deserves. `low`, `medium`, `high` — that is the whole
|
|
194
|
+
vocabulary, and a script may name nothing else.
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
await agent("summarize this diff", { strength: "low" })
|
|
198
|
+
await agent("design the migration", { strength: "high" })
|
|
199
|
+
await agent("the ordinary case") // no strength: see below
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
There is no default strength. A call that names none dispatches with **no tier**,
|
|
203
|
+
exactly as an unmapped strength does, and pi-subagents resolves it the way it
|
|
204
|
+
resolves any other spawn: the agent type's own frontmatter tier, then
|
|
205
|
+
`agentTiers.defaultTier`. That matters because a tier a call requests outranks
|
|
206
|
+
the agent type's own — so a default strength would have pinned every unlabelled
|
|
207
|
+
call to whatever it mapped to, overruling `Explore`'s shipped `tier: low` and
|
|
208
|
+
re-pricing the one agent this indirection exists to leave alone. Label the calls
|
|
209
|
+
whose cost you mean to steer; leave the rest to the host.
|
|
210
|
+
|
|
211
|
+
A strength is not an Agent tier. A `strengths` table is the only thing that
|
|
212
|
+
binds one to a tier:
|
|
213
|
+
|
|
214
|
+
```jsonc
|
|
215
|
+
// <agent dir>/workflows/settings.json, or the per-project settings file
|
|
216
|
+
{ "strengths": { "low": "cheap-search", "high": "deep" } }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**An unmapped strength dispatches with no tier at all** and takes the agent's
|
|
220
|
+
ordinary default — the agent's own frontmatter tier, then the host's configured
|
|
221
|
+
default. `{}` is a real table meaning exactly that for every strength.
|
|
222
|
+
|
|
223
|
+
### The default table
|
|
224
|
+
|
|
225
|
+
A host that has configured nothing does not get "no mappings". It gets a table
|
|
226
|
+
this package ships: each strength on the catalogue tier of the same name,
|
|
227
|
+
**wherever the host defines one**. On a stock install that is
|
|
228
|
+
`low → low, medium → medium, high → high`; on a host whose tiers are called
|
|
229
|
+
`cheap` and `deep` it is empty, and every strength is unmapped. It is computed
|
|
230
|
+
against the live catalogue rather than hardcoded, so it is never an assertion
|
|
231
|
+
about someone else's tier names and never produces a start-up complaint about
|
|
232
|
+
configuration you did not write.
|
|
233
|
+
|
|
234
|
+
Without it, every shipped script's `low`/`medium` distinction would collapse
|
|
235
|
+
onto pi-subagents' managed default — which is the *more* expensive rung, so an
|
|
236
|
+
unconfigured machine would run its fan-outs dearer, not cheaper.
|
|
237
|
+
|
|
238
|
+
That it is a **table and not a fallback rule** is the whole design. A rule
|
|
239
|
+
saying "an unmapped strength runs on the tier of the same name" would be
|
|
240
|
+
unremovable, so making a 26-agent fan-out cheaper would still mean editing tier
|
|
241
|
+
`low` itself — dragging the `Explore` agent and everything else that names it. A
|
|
242
|
+
table is replaced by writing one, and a written table that omits `low` leaves it
|
|
243
|
+
unmapped even on a host that defines a tier called `low`. Nothing is ever
|
|
244
|
+
inferred from spelling at dispatch: the binding is always a table entry.
|
|
245
|
+
|
|
246
|
+
So `low → low` in a written table is a real entry and not a no-op — a table you
|
|
247
|
+
write replaces the default outright, so the entries you leave out are unmapped,
|
|
248
|
+
not inherited.
|
|
249
|
+
|
|
250
|
+
### Editing it
|
|
251
|
+
|
|
252
|
+
`/workflows strength` edits the table and also prints the tiers this host
|
|
253
|
+
defines — the one place both halves are visible, since the strengths come from
|
|
254
|
+
this package and the tiers from pi-subagents:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
/workflows strength # show the effective table and the host's tiers
|
|
258
|
+
/workflows strength low cheap # run workflow strength `low` on Agent tier `cheap`
|
|
259
|
+
/workflows strength low off # unmapped; back to the agent's own default
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Edits land in **this project's** settings, like every other setting this package
|
|
263
|
+
owns. Because the table replaces rather than merges at each level, the command
|
|
264
|
+
seeds the write from the table a run would actually use — the project file, else
|
|
265
|
+
global, else the shipped default — so setting one entry cannot silently discard
|
|
266
|
+
the rest, and `off` clears a mapping this project never wrote. The write is
|
|
267
|
+
therefore a full copy of the current table into the project; edit
|
|
268
|
+
`<agent dir>/workflows/settings.json` by hand for a machine-wide table.
|
|
269
|
+
|
|
270
|
+
Any key the host's catalogue defines is a legal target — an existing tier, or a
|
|
271
|
+
profile added for exactly this purpose. There is no shortlist and no naming
|
|
272
|
+
convention: the target is checked against the live catalogue at run start, so a
|
|
273
|
+
profile added to `subagents.json` is usable the moment it exists. One hop, never
|
|
274
|
+
chained. An entry whose tier the host does not define is reported once at run
|
|
275
|
+
start and ignored, leaving that strength unmapped — a stale entry costs the
|
|
276
|
+
redirect, not every workflow on the machine.
|
|
277
|
+
|
|
278
|
+
This is not the retired `workflow.tiers` key and must not become it. That one
|
|
279
|
+
carried its own `model` and `thinking` values, which made it a second model
|
|
280
|
+
policy resolved by a second resolver. A value here is a **key in the host's one
|
|
281
|
+
catalogue**: pi-subagents still owns every model, every thinking level, and the
|
|
282
|
+
only `resolveAgentTier()`, and cannot tell a mapped call apart from a spawn that
|
|
283
|
+
named the key itself.
|
|
284
|
+
|
|
285
|
+
There is no per-call `model`, `thinking`, or `tier`, and `meta`/phase metadata
|
|
286
|
+
may not carry them either. A strength is the only model policy a workflow can
|
|
287
|
+
express, which is what keeps a script from pinning a vendor the host did not
|
|
288
|
+
choose.
|
|
289
|
+
|
|
290
|
+
The vocabulary is closed so that a word outside it is a *typo* rather than a
|
|
291
|
+
strength nobody configured: `agent({ strength: "lowe" })` fails before any
|
|
292
|
+
dispatch instead of silently running at the default.
|
|
293
|
+
|
|
294
|
+
The same argument applies one level up, to the option *name*. `agent()` and
|
|
295
|
+
`checkpoint()` reject any key they do not read, rather than ignoring it:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
await agent("x", { tier: "low" }) // → name a strength instead: one of low, medium, high
|
|
299
|
+
await agent("x", { model: "…" }) // → a strength is the only model policy a workflow can express
|
|
300
|
+
await agent("x", { strenght: "low" }) // → valid options are: label, phase, schema, strength, …
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
An unknown key is dropped before the call hash is built, so it changes neither
|
|
304
|
+
the dispatch nor the resume identity — the run would simply spend at a policy
|
|
305
|
+
nobody chose, and a resume would replay it without noticing. A script is plain
|
|
306
|
+
JavaScript authored against this contract, so there is no other gate: a
|
|
307
|
+
misspelled option would run every step untiered, and a misspelled
|
|
308
|
+
`checkpoint({ headles: "abort" })` would auto-approve in headless mode instead
|
|
309
|
+
of aborting.
|
|
310
|
+
|
|
311
|
+
### Helpers that dispatch for you
|
|
312
|
+
|
|
313
|
+
`verify()`, `judgePanel()`, and `completenessCheck()` spawn agents on the
|
|
314
|
+
script's behalf, so the script cannot label those calls. Each carries a
|
|
315
|
+
documented default and takes an override, which keeps every dispatch inside the
|
|
316
|
+
vocabulary:
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
await verify(finding) // reviewers run at "low"
|
|
320
|
+
await verify(finding, { strength: "high" }) // unless you say otherwise
|
|
321
|
+
await completenessCheck(args, results) // one open-ended read: "medium"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
These defaults are an opinion, and an opinion outranks the host's: a tier a call
|
|
325
|
+
requests beats `agentTiers.defaultTier`, so on a machine whose default is `high`
|
|
326
|
+
a `completenessCheck` now runs at whatever `medium` maps to rather than at
|
|
327
|
+
`high`. That is the trade for making the helpers re-routable at all — they
|
|
328
|
+
previously named no tier and could never be steered — and the override above is
|
|
329
|
+
how a script takes it back.
|
|
330
|
+
|
|
331
|
+
### Resume
|
|
332
|
+
|
|
333
|
+
The table is read fresh at each start and resume — including the resumes the
|
|
334
|
+
engine starts itself, `/workflows resume` and the provider-limit retry — never
|
|
335
|
+
frozen onto a run.
|
|
336
|
+
|
|
337
|
+
Resume identity keys a call on the tier it will actually request, so editing the
|
|
338
|
+
table and resuming re-routes the calls that have not run — and **also re-runs
|
|
339
|
+
the finished calls whose strength moved**, plus everything after the first such
|
|
340
|
+
miss, because their cached answers came from a different tier. Re-routing
|
|
341
|
+
mid-run is therefore a re-spend; if the point of the edit was to spend less,
|
|
342
|
+
start a new run instead of resuming one that is mostly done. The provider-limit
|
|
343
|
+
retry resumes on a timer with no one watching, so a table edited while a run is
|
|
344
|
+
rate-limit-paused is charged the same way without being asked — stop that run
|
|
345
|
+
before editing if the re-spend is not worth it. A nested
|
|
346
|
+
`workflow()` boundary folds the table into its own key for the same reason: the
|
|
347
|
+
value it caches for the whole child frame would otherwise survive a change that
|
|
348
|
+
re-routes the child.
|
|
349
|
+
|
|
350
|
+
Fresh installs of pi-subagents ship `low`/`medium`/`high` tier profiles, all
|
|
351
|
+
inheriting their model — which is what the default table above resolves against,
|
|
352
|
+
so the built-ins run on a new machine, at their own strengths, with no
|
|
353
|
+
configuration. A managed call that still names no tier falls back to `medium`;
|
|
354
|
+
that is where an unmapped strength lands. The fallback is scoped to managed
|
|
355
|
+
calls: it is not the catalogue's `defaultTier`, so an ordinary `Agent` spawn on
|
|
356
|
+
the same machine keeps falling through to `defaultModel` and the parent session.
|
|
357
|
+
A host whose default has been cleared (`noDefaultTier`) rejects an untiered
|
|
358
|
+
managed call rather than quietly inheriting the parent session's model — which
|
|
359
|
+
an unmapped strength can reach.
|
|
360
|
+
|
|
361
|
+
The managed wire, invocation record, journal, tombstone, and resume state all
|
|
362
|
+
carry the one resolved tier — including one the host defaulted to, so a run's
|
|
363
|
+
record says what actually ran. Resume keys each cached call on the policy for
|
|
364
|
+
*that call's* tier: editing an unrelated tier does not invalidate work that never
|
|
365
|
+
used it.
|
|
188
366
|
|
|
189
367
|
## Intentional adaptations from upstream
|
|
190
368
|
|
|
191
369
|
- **No host-side web tools.** Agents reach the web through configured pi-web-access/MCP tools or an agent type's tool policy.
|
|
192
|
-
- **Agent registry and tier catalogue ownership.** `agentType` and
|
|
370
|
+
- **Agent registry and tier catalogue ownership.** `agentType` and the tier catalogue resolve in pi-subagents; the workflow package supplies only a tier key its own `strengths` table chose, avoiding a second model/thinking policy.
|
|
193
371
|
- **Schema validation is client-side.** The managed request does not assume a structured-output tool; the runtime asks for JSON, parses, validates (including `additionalProperties`), and repairs across bounded attempts.
|
|
194
372
|
|
|
195
|
-
State is persisted as `pi.appendEntry("pi-workflows:journal", ...)` custom entries (schema
|
|
373
|
+
State is persisted as `pi.appendEntry("pi-workflows:journal", ...)` custom entries (schema v4), including frozen invocation args, resolved tier identity, terminal result previews, script revisions, named nested-workflow result boundaries, and durable removals. Interrupted/provider-limited runs replay from the journal; pre-schema-v4 journals are quarantined rather than replayed. Foreground `AbortSignal`s stop owned children through explicit owner-scoped stop/quiescence, and dispose/branch replacement reject stale waiters.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalridge/pi-workflows",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Deterministic JavaScript orchestration workflows backed by pi-subagents managed spawning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
19
19
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
20
|
-
"@signalridge/pi-subagents": ">=1.
|
|
20
|
+
"@signalridge/pi-subagents": ">=1.9.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@signalridge/pi-subagents-protocol": "^1.
|
|
23
|
+
"@signalridge/pi-subagents-protocol": "^1.4.0",
|
|
24
24
|
"@sinclair/typebox": "^0.34.50",
|
|
25
25
|
"acorn": "^8.16.0"
|
|
26
26
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: workflow-authoring
|
|
3
3
|
description: Design, write, review, and debug Pi JavaScript workflows that coordinate multiple subagents. Use when a request needs named dependencies, bounded fan-out, per-item pipelines, quality gates, saved or nested workflows, pause/resume, or workflow progress; do not use it merely to run an existing named workflow.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "1.
|
|
5
|
+
version: "1.6.0"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Workflow authoring
|
|
@@ -28,10 +28,22 @@ JavaScript.
|
|
|
28
28
|
- Bound graph tasks, fan-out items, loops, retries, agents, concurrency, and
|
|
29
29
|
evidence size. Invocation-level token and time caps are user constraints, not
|
|
30
30
|
substitutes for algorithmic bounds.
|
|
31
|
-
- Route an agent call with `
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
- Route an agent call with `strength: "low" | "medium" | "high"` — this
|
|
32
|
+
package's own word for how much effort a step deserves, and the only routing
|
|
33
|
+
word a workflow has. It is **not** an agent-tier key: a `strengths` table
|
|
34
|
+
alone binds a strength to a tier — the user's, or the shipped default on a
|
|
35
|
+
host that configured none — and a strength no table defines dispatches with no
|
|
36
|
+
tier and takes the agent's own default. So name the effort the step deserves
|
|
37
|
+
and never reach for a catalogue name; a word outside the three is rejected
|
|
38
|
+
before dispatch, as is `tier:` itself. There is no default: omitting
|
|
39
|
+
`strength` dispatches with no tier and lets the agent type's own tier stand,
|
|
40
|
+
so label the calls whose cost you mean to steer and leave the rest alone.
|
|
41
|
+
There is no per-call `model`/`thinking`/`tier`, and none may
|
|
42
|
+
appear in workflow meta or phase profiles. `agent()` and `checkpoint()` reject
|
|
43
|
+
any option name they do not read rather than ignoring it, so pass only the
|
|
44
|
+
documented keys. Never guess an agent-type name—use
|
|
45
|
+
names supplied by the current environment and follow
|
|
46
|
+
[registry ownership](references/registry-ownership.md).
|
|
35
47
|
- Workflows have no imports, filesystem/network APIs, timers, or unrestricted
|
|
36
48
|
Node APIs. Pass timestamps, randomness, and external decisions through `args`.
|
|
37
49
|
|
|
@@ -53,7 +65,9 @@ JavaScript.
|
|
|
53
65
|
the child can see the parent conversation.
|
|
54
66
|
6. **Add quality only where it changes a decision.** Use a schema before reading
|
|
55
67
|
structured fields, then use `verify`, `judgePanel`, `gate`, or
|
|
56
|
-
`completenessCheck` with explicit bounds.
|
|
68
|
+
`completenessCheck` with explicit bounds. These dispatch on your behalf, so
|
|
69
|
+
pass `strength` when the default (`low`, or `medium` for
|
|
70
|
+
`completenessCheck`) is not the effort that gate deserves.
|
|
57
71
|
7. **Aggregate and report.** Return a bounded JSON result containing the answer,
|
|
58
72
|
coverage, and important failures. If the workflow runs in the background,
|
|
59
73
|
the run ID is the handle for later control/resume.
|
|
@@ -173,6 +187,8 @@ Before shipping a workflow change:
|
|
|
173
187
|
- read [debugging](references/debugging.md) when reproducing a failure;
|
|
174
188
|
- read [the review checklist](references/review.md) before accepting topology;
|
|
175
189
|
- read [the generated capability index](references/capabilities.md) when a
|
|
176
|
-
signature, default, or support boundary is disputed;
|
|
190
|
+
signature, default, or support boundary is disputed; remember that a strength
|
|
191
|
+
is this package's own vocabulary, bound to a host tier only by the user's
|
|
192
|
+
`strengths` table, while model/thinking resolution stays in pi-subagents;
|
|
177
193
|
- ensure every package-relative example/reference link remains inside the
|
|
178
194
|
publishable package.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!-- GENERATED from WORKFLOW_CAPABILITY_CONTRACT; do not edit by hand. -->
|
|
2
2
|
# Workflow capability index
|
|
3
3
|
|
|
4
|
-
Contract format: `
|
|
5
|
-
Contract content / skill / extension: `1.
|
|
4
|
+
Contract format: `2.0.0`<br>
|
|
5
|
+
Contract content / skill / extension: `1.6.0`
|
|
6
6
|
|
|
7
7
|
This compact generated index covers supported runtime globals and workflow-tool inputs. For constraints, compatibility behavior, internal boundaries, and dynamic-reference ownership, follow the [exhaustive generated facts](capability-details.md).
|
|
8
8
|
|
|
@@ -11,15 +11,15 @@ This compact generated index covers supported runtime globals and workflow-tool
|
|
|
11
11
|
<!-- BEGIN GENERATED SUPPORTED WORKFLOW CAPABILITIES -->
|
|
12
12
|
| Name | Classification | Signature | Options and defaults |
|
|
13
13
|
| --- | --- | --- | --- |
|
|
14
|
-
| agent | runtime-global | `agent(prompt, options?) => Promise<string \| structured value \| null>` | `label`: string (optional); default: derived from phase and call count<br>`phase`: string (optional); default: current phase<br>`schema`: plain JSON Schema (optional)<br>`
|
|
14
|
+
| agent | runtime-global | `agent(prompt, options?) => Promise<string \| structured value \| null>` | `label`: string (optional); default: derived from phase and call count<br>`phase`: string (optional); default: current phase<br>`schema`: plain JSON Schema (optional)<br>`strength`: "low" \| "medium" \| "high" (optional); default: no tier is sent; the agent's own tier, else agentTiers.defaultTier<br>`isolation`: "worktree" (optional); default: pi-subagents worktree policy<br>`thread`: string (optional); default: fresh session per call<br>`toolset`: string (optional); default: configured host/agent toolset hint<br>`excludeTools`: string[] (optional)<br>`agentType`: string (optional); default: general-purpose<br>`timeoutMs`: number \| null (optional); default: 300000 (5min); null disables<br>`retries`: number (optional); default: run retry count |
|
|
15
15
|
| parallel | runtime-global | `parallel(thunks) => Promise<Array<unknown \| null>>` | — |
|
|
16
16
|
| pipeline | runtime-global | `pipeline(items, ...stages) => Promise<Array<unknown \| null>>` | — |
|
|
17
17
|
| orchestrate | runtime-global | `orchestrate(tasks, options?) => Promise<{ results, tasks }>` | `onError`: "skip-dependents"\|"continue"\|"fail-fast" (optional); default: "skip-dependents" |
|
|
18
18
|
| workflow | runtime-global | `workflow(savedName, childArgs?) => Promise<unknown>` | — |
|
|
19
|
-
| verify | runtime-global | `verify(item, options?) => Promise<{ real, realCount, total, votes }>` | `reviewers`: number (optional); default: 2<br>`threshold`: number (optional); default: 0.5<br>`lens`: string \| string[] (optional) |
|
|
20
|
-
| judgePanel | runtime-global | `judgePanel(attempts, options?) => Promise<{ index, attempt, score, judgments } \| undefined>` | `judges`: number (optional); default: 3<br>`rubric`: string (optional); default: overall quality and correctness |
|
|
19
|
+
| verify | runtime-global | `verify(item, options?) => Promise<{ real, realCount, total, votes }>` | `reviewers`: number (optional); default: 2<br>`threshold`: number (optional); default: 0.5<br>`lens`: string \| string[] (optional)<br>`strength`: "low" \| "medium" \| "high" (optional); default: "low" |
|
|
20
|
+
| judgePanel | runtime-global | `judgePanel(attempts, options?) => Promise<{ index, attempt, score, judgments } \| undefined>` | `judges`: number (optional); default: 3<br>`rubric`: string (optional); default: overall quality and correctness<br>`strength`: "low" \| "medium" \| "high" (optional); default: "low" |
|
|
21
21
|
| loopUntilDry | runtime-global | `loopUntilDry({ round, key?, consecutiveEmpty?, maxRounds? }) => Promise<unknown[]>` | `round`: (roundIndex) => unknown[]; default: required<br>`key`: (item) => string (optional); default: JSON.stringify<br>`consecutiveEmpty`: number (optional); default: 2<br>`maxRounds`: number (optional); default: 50 |
|
|
22
|
-
| completenessCheck | runtime-global | `completenessCheck(taskArgs, results) => Promise<{ complete, missing? } \| null>` |
|
|
22
|
+
| completenessCheck | runtime-global | `completenessCheck(taskArgs, results, options?) => Promise<{ complete, missing? } \| null>` | `strength`: "low" \| "medium" \| "high" (optional); default: "medium" |
|
|
23
23
|
| retry | runtime-global | `retry(thunk, options?) => Promise<unknown>` | `attempts`: number (optional); default: 3<br>`until`: (result) => boolean (optional); default: accept first result |
|
|
24
24
|
| gate | runtime-global | `gate(thunk, validator, options?) => Promise<{ ok, value, attempts }>` | `attempts`: number (optional); default: 3 |
|
|
25
25
|
| checkpoint | runtime-global | `checkpoint(prompt, options?) => Promise<unknown>` | `default`: unknown (optional); default: true when no UI and omitted<br>`headless`: "default" \| "abort" (optional); default: "default"<br>`kind`: "confirm" \| "input" \| "select" (optional); default: "confirm"<br>`choices`: string[] (optional)<br>`timeoutMs`: number (optional) |
|