@signalridge/pi-subagents 1.8.0 → 1.9.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 +170 -0
- package/README.md +85 -55
- package/package.json +2 -2
- package/src/agent-manager.ts +235 -120
- package/src/agent-runner.ts +90 -72
- package/src/agent-tiers.ts +236 -70
- package/src/cross-extension-rpc.ts +6 -0
- package/src/default-agents.ts +3 -3
- package/src/index.ts +149 -139
- package/src/internal-run.ts +14 -0
- package/src/invocation-config.ts +24 -13
- package/src/model-resolver.ts +12 -0
- package/src/model-scope.ts +5 -2
- package/src/nested-tools.ts +35 -22
- package/src/schedule.ts +3 -0
- package/src/settings.ts +68 -247
- package/src/types.ts +10 -13
- package/src/ui/agent-display.ts +1 -1
- package/src/workflow-tiers.ts +0 -202
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,175 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- c1b1741: Protocol v4: workflows use the Agent-tier catalogue directly.
|
|
7
|
+
|
|
8
|
+
The wire contract changes, so the three packages move together: `pi-workflows`
|
|
9
|
+
now declares `"@signalridge/pi-subagents": ">=1.9.0"`, and a peer outside that
|
|
10
|
+
range fails the startup handshake with a diagnostic naming both packages rather
|
|
11
|
+
than failing mid-run.
|
|
12
|
+
|
|
13
|
+
The `small | medium | large` workflow-tier vocabulary and its mapping layer are
|
|
14
|
+
gone. A managed request names an Agent tier — a key in the host's own
|
|
15
|
+
`agentTiers` catalogue — and `resolveAgentTier()` is the single resolver for
|
|
16
|
+
every spawn path. Removed with it: the wire's mapped `agentTier` field, the
|
|
17
|
+
`workflow` settings key (retired with a warning naming it; `agentTiers.defaultTier`
|
|
18
|
+
replaces `workflow.defaultTier`), and the per-call `model`/`thinking` selectors,
|
|
19
|
+
which the validator now rejects rather than accepting and ignoring.
|
|
20
|
+
|
|
21
|
+
Every v4 capability is required and every ping carries the host's tier catalogue,
|
|
22
|
+
so an incomplete peer fails one check instead of five optional ones. Fresh
|
|
23
|
+
installs ship `low`/`medium`/`high` profiles, all inheriting their model, and a
|
|
24
|
+
managed call that names no tier falls back to `medium`, so a workflow runs on an
|
|
25
|
+
unconfigured machine without this package choosing a vendor. Because `medium`
|
|
26
|
+
inherits, that fallback lands on the parent session's model — it commits to an
|
|
27
|
+
effort level, not to a vendor, so it does change the thinking level a managed
|
|
28
|
+
call runs at when the parent session is set higher, and it does not move the
|
|
29
|
+
work anywhere cheaper. What it buys is a named policy with a durable snapshot
|
|
30
|
+
and a scope check; a workspace that wants cheaper managed work names a
|
|
31
|
+
`defaultTier` whose profile pins a model. The fallback is scoped to calls that
|
|
32
|
+
cannot inherit a parent model rather than being installed as
|
|
33
|
+
`agentTiers.defaultTier`: a shipped catalogue default would have applied to
|
|
34
|
+
every ordinary `Agent` spawn as well, silencing `defaultModel` and pinning a
|
|
35
|
+
thinking level on machines that configured neither. The catalogue no longer
|
|
36
|
+
ships a `fast` profile; it was the same (model, thinking) pair as `low`, and the
|
|
37
|
+
one agent that named it (Explore) now names `low`. A user agent file that still
|
|
38
|
+
says `agentTier: fast` is reported by the existing unknown-reference check when
|
|
39
|
+
settings and agents load, rather than failing at its first spawn.
|
|
40
|
+
|
|
41
|
+
`agentTiers.defaultTier` therefore has three states, and `/agents → Settings`
|
|
42
|
+
offers all three rather than rendering two of them as one word: a named tier,
|
|
43
|
+
`unset` (managed calls reach the shipped fallback), and `none` (recorded as
|
|
44
|
+
`noDefaultTier`, which withdraws the fallback so managed calls fail closed).
|
|
45
|
+
`setDefaultAgentTier` takes that choice as a tagged value, so a caller cannot
|
|
46
|
+
express "no default" without saying which of the two it means.
|
|
47
|
+
|
|
48
|
+
`resolveAgentTier` gained `requireTier` for that fallback, and the two things
|
|
49
|
+
pre-resolution callers need are both answered by the resolver rather than
|
|
50
|
+
restated beside it: `agentTierApplies()` for "will a tier own this spawn's
|
|
51
|
+
model?", and `selectAgentTier()` for the tier key itself, which the managed
|
|
52
|
+
path needs to label a tombstone and the lifecycle events before the runner has
|
|
53
|
+
resolved anything.
|
|
54
|
+
|
|
55
|
+
Because a tier now owns model resolution outright, the spawn paths stop
|
|
56
|
+
pre-resolving one — and with it they stopped producing the model name the agent
|
|
57
|
+
UI shows. The resolution callback carries that label back instead, so a tier
|
|
58
|
+
that pins a model still names it in the viewer and the agent list, and a profile
|
|
59
|
+
that inherits correctly shows none. The label rides beside the snapshot rather
|
|
60
|
+
than inside it: the snapshot is a durable policy record a managed tombstone
|
|
61
|
+
persists and revalidates, and a cosmetic string does not belong in it.
|
|
62
|
+
|
|
63
|
+
Workflow resume now keys each cached call on the policy for that call's own tier
|
|
64
|
+
rather than on a whole-catalogue fingerprint, so defining or editing an unrelated
|
|
65
|
+
tier no longer forces a full re-execution — but only for a call that names its
|
|
66
|
+
tier. A call that names none keeps the whole-catalogue key, because the host
|
|
67
|
+
resolves those as `call > agent frontmatter > defaultTier` and frontmatter is
|
|
68
|
+
not on this wire: an agent reaches a managed caller as a name. Keyed on
|
|
69
|
+
`defaultTier`, such a call would be wrong twice over — replaying stale work
|
|
70
|
+
after an edit to the tier its agent actually declares, and re-executing after an
|
|
71
|
+
edit to a default it never reached. `agentTierPolicyIdentity()` therefore
|
|
72
|
+
requires a tier rather than accepting `undefined` and folding the default in, so
|
|
73
|
+
the wrong call cannot be written. Journal schema v4 and managed tombstone schema
|
|
74
|
+
v2 are quarantined from older facts rather than migrated.
|
|
75
|
+
|
|
76
|
+
The tier catalogue belongs to the user, names included. The built-in workflows
|
|
77
|
+
and the ad-hoc script ship with this package, so a tier name they use that the
|
|
78
|
+
host does not define is dropped in favour of the host's default rather than
|
|
79
|
+
failing the run — they cannot assert which names exist on someone else's
|
|
80
|
+
machine. A script the user wrote still fails closed on an undefined tier, since
|
|
81
|
+
that is a typo in a catalogue they own. Shipped-ness travels with the script
|
|
82
|
+
rather than with the frame that called it, so a user script that reaches a
|
|
83
|
+
built-in through `workflow(name)` applies the built-in's rule to the built-in;
|
|
84
|
+
the authoring skill now says outright that `low`/`medium`/`high` are the shipped
|
|
85
|
+
profiles rather than names an authored script may assume.
|
|
86
|
+
|
|
87
|
+
The managed routing policy published on the wire reports the default a managed
|
|
88
|
+
call will actually get, fallback included, so a peer's replay identity cannot
|
|
89
|
+
disagree with the host's selection; its sorts are code-unit rather than
|
|
90
|
+
locale-dependent, so the same catalogue fingerprints identically everywhere. The
|
|
91
|
+
workflow peer re-pings per start and resume instead of pinning the catalogue it
|
|
92
|
+
saw at session start, and reports the host-selected tier back through an
|
|
93
|
+
`onTierResolved` callback rather than by mutating the dispatch options.
|
|
94
|
+
|
|
95
|
+
Fixes: a nested spawn no longer relabels an agent's frontmatter tier as
|
|
96
|
+
caller-requested, which had turned a `scopeModels` warning into a refusal and
|
|
97
|
+
misattributed the choice; the scheduler no longer freezes a frontmatter tier into
|
|
98
|
+
a job, so editing the agent file takes effect at fire time; retired managed spawn
|
|
99
|
+
keys are now bounded instead of growing for the life of a session; a
|
|
100
|
+
scope refusal for a tier whose profile inherits its model now names the model
|
|
101
|
+
that would have run instead of the literal `undefined`; and the retired
|
|
102
|
+
`workflow` key warns once per process rather than on every settings read and
|
|
103
|
+
every project write.
|
|
104
|
+
|
|
105
|
+
The tier-key predicate and its length bound now have a single definition, in
|
|
106
|
+
`pi-subagents-protocol`, which `pi-subagents` re-exports. The wire is the
|
|
107
|
+
narrower of the two gates — a key one accepted and the other rejected could
|
|
108
|
+
never reach a peer — so a second copy could only ever be a way for them to
|
|
109
|
+
disagree. That one definition now also governs `blockedProfiles`, which was
|
|
110
|
+
being validated by a helper that trims first: `" low"` became `"low"` there
|
|
111
|
+
while the same value in `profiles` or `defaultTier` was refused outright. All
|
|
112
|
+
three reject.
|
|
113
|
+
|
|
114
|
+
`/agents → Settings` no longer names the fallback tier in the `unset` row's
|
|
115
|
+
label. Which tier `unset` reaches depends on the catalogue, and a workspace that
|
|
116
|
+
edits or deletes the shipped `medium` profile leaves it reaching nothing — at
|
|
117
|
+
which point `unset` and `none` behave identically. The row's description asks
|
|
118
|
+
the resolver what `unset` would currently resolve to and says that, including
|
|
119
|
+
when the answer is "nothing".
|
|
120
|
+
|
|
121
|
+
`pi-workflows` also gained the session-lifecycle handling this routing work
|
|
122
|
+
needed to sit on. The engine has an explicit dispose lifecycle, so a protocol
|
|
123
|
+
probe or a start that is in flight when a session ends rejects with a disposed
|
|
124
|
+
error instead of resolving against an engine nobody owns. The tool, command and
|
|
125
|
+
widget surfaces register exactly once and resolve the *current* engine on each
|
|
126
|
+
use, rather than closing over the one that existed when they were registered —
|
|
127
|
+
a session replacement previously left them pointing at a disposed engine. Tool
|
|
128
|
+
and command handlers report failures as results rather than throwing out of the
|
|
129
|
+
host's dispatch, and the lifecycle handler accepts `subagents:created` and
|
|
130
|
+
`subagents:started` so a spawn's identity is recorded from the first event that
|
|
131
|
+
carries it rather than only at completion.
|
|
132
|
+
|
|
133
|
+
### Patch Changes
|
|
134
|
+
|
|
135
|
+
- c1b1741: Durability and containment fixes found in a repository-wide scan.
|
|
136
|
+
|
|
137
|
+
`pi-gpt-fast` rewrote pi's own global `settings.json` with a plain
|
|
138
|
+
`writeFileSync`. That file is shared with pi and every other extension, and this
|
|
139
|
+
one is the only writer of it; a torn write would have left the user with no pi
|
|
140
|
+
configuration at all rather than just no gpt-fast setting. It now writes a temp
|
|
141
|
+
file and renames, matching every other settings writer in the repository.
|
|
142
|
+
`pi-goal` gets the same treatment for its cross-project goal state file, which
|
|
143
|
+
its own settings module already did.
|
|
144
|
+
|
|
145
|
+
`pi-ralph-wiggum` resolved `/ralph start <path>` straight against the session
|
|
146
|
+
cwd, so `../../notes.md` would create directories and a file outside the project
|
|
147
|
+
the user opened — silently, before the loop started. This extension drives long
|
|
148
|
+
unattended loops, so that command is as likely to come from a model as from a
|
|
149
|
+
person. Task-file paths are now required to stay inside the workspace, on the
|
|
150
|
+
command, on the `ralph_start` tool, and on the paths read back out of persisted
|
|
151
|
+
state, so a state file written earlier or edited by hand cannot pull a file in
|
|
152
|
+
from outside either.
|
|
153
|
+
|
|
154
|
+
The containment test canonicalizes both sides before comparing, because a purely
|
|
155
|
+
lexical one is defeated by a symlink: every segment of `linked/plan.md` reads as
|
|
156
|
+
inside the workspace when `linked` is a door out of it. `realpathSync` throws on
|
|
157
|
+
a path that does not exist yet — the ordinary case, since `start` is usually
|
|
158
|
+
creating the file — so it resolves the deepest existing ancestor and re-attaches
|
|
159
|
+
the not-yet-created suffix, which cannot itself be a link. The traversal test
|
|
160
|
+
matches a `..` segment rather than a `..` prefix; the prefix form also rejected
|
|
161
|
+
`..notes.md`, an ordinary filename sitting in the workspace.
|
|
162
|
+
|
|
163
|
+
Every remaining bare `catch {}` now says why the error is safe to drop, matching
|
|
164
|
+
the convention the rest of the repository already follows.
|
|
165
|
+
- Updated dependencies [c1b1741]
|
|
166
|
+
- @signalridge/pi-subagents-protocol@1.4.0
|
|
167
|
+
|
|
168
|
+
## 1.8.1
|
|
169
|
+
### Patch Changes
|
|
170
|
+
|
|
171
|
+
- 28c8aa1: Remove non-functional references to external product names from package descriptions, examples, and comments. Provider identifiers required for runtime compatibility remain unchanged.
|
|
172
|
+
|
|
3
173
|
## 1.8.0
|
|
4
174
|
### Minor Changes
|
|
5
175
|
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-
|
|
|
26
26
|
- **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
|
|
27
27
|
- **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
|
|
28
28
|
- **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
|
|
29
|
-
- **Cross-extension RPC** — other Pi extensions can spawn and stop subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`). Protocol
|
|
29
|
+
- **Cross-extension RPC** — other Pi extensions can spawn and stop subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`). Protocol v4 adds managed spawning, a request-level Agent `tier`, toolset/denylist/thread/worktree hints, owner-scoped stop/quiescence, a published Agent-tier routing policy, and standardized reply envelopes; pi-subagents remains the final policy owner. Pre-schema-v2 managed tombstones are quarantined rather than replayed. Emits `subagents:ready` on session start
|
|
30
30
|
- **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
|
|
31
31
|
- **Model tiers** — name a (model, thinking) pair once and let the orchestrator pick it by name; the `Agent` tool exposes `tier` and never `model`/`thinking`, so which model runs stays a config decision. Manage the catalogue in `/agents → Model tiers`, pick the default in `/agents → Settings → Default tier`, or set a plain `defaultModel` when one line beats a catalogue
|
|
32
32
|
- **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
|
|
@@ -414,23 +414,24 @@ When background agents complete, they notify the main agent. The **join mode** c
|
|
|
414
414
|
|
|
415
415
|
## Model tiers
|
|
416
416
|
|
|
417
|
-
|
|
418
|
-
by name and nothing else: the `Agent` tool exposes `tier` and does **not**
|
|
419
|
-
`model` or `thinking`, so which model runs is decided by
|
|
420
|
-
|
|
417
|
+
An **Agent tier** is one name for a (model, thinking) pair. The host agent picks a
|
|
418
|
+
tier by name and nothing else: the `Agent` tool exposes `tier` and does **not**
|
|
419
|
+
expose `model` or `thinking`, so which model runs is decided by the Agent-tier
|
|
420
|
+
catalogue rather than by the orchestrator improvising per call.
|
|
421
421
|
|
|
422
|
-
Names are yours.
|
|
423
|
-
`cheap`, `nightly` are equally valid keys.
|
|
422
|
+
Names are yours. The names below are only an example — `research`,
|
|
423
|
+
`cheap`, `nightly` are equally valid keys. Replace the illustrative provider/model
|
|
424
|
+
values with models available in your environment.
|
|
424
425
|
|
|
425
426
|
```json
|
|
426
427
|
{
|
|
427
428
|
"agentTiers": {
|
|
428
429
|
"defaultTier": "medium",
|
|
429
430
|
"profiles": {
|
|
430
|
-
"
|
|
431
|
-
"medium": { "description": "Ordinary planning and review",
|
|
432
|
-
"
|
|
433
|
-
"research": { "description": "Long-context research",
|
|
431
|
+
"low": { "description": "Fast, cheap exploration", "model": "provider/fast-model", "thinking": "max" },
|
|
432
|
+
"medium": { "description": "Ordinary planning and review", "model": "provider/reasoning-model", "thinking": "max" },
|
|
433
|
+
"high": { "description": "Architecture and risky review", "model": "provider/architecture-model", "thinking": "xhigh" },
|
|
434
|
+
"research": { "description": "Long-context research", "model": "provider/long-context-model", "thinking": "max" }
|
|
434
435
|
}
|
|
435
436
|
}
|
|
436
437
|
}
|
|
@@ -438,7 +439,56 @@ Names are yours. `small`/`medium`/`large` below are only an example — `researc
|
|
|
438
439
|
|
|
439
440
|
A profile is all-or-nothing: both `model` and `thinking` are required, and either
|
|
440
441
|
may be the literal `"inherit"` to keep the parent's. `description` is optional and
|
|
441
|
-
defaults to the key; it is what the host reads when choosing between tiers.
|
|
442
|
+
defaults to the key; it is what the host reads when choosing between Agent tiers.
|
|
443
|
+
|
|
444
|
+
### One catalogue, including for workflows
|
|
445
|
+
|
|
446
|
+
A managed `pi-workflows` call names a key from this same `agentTiers` catalogue.
|
|
447
|
+
There is no second workflow-tier vocabulary and no mapping layer: a workflow that
|
|
448
|
+
wants cheap work asks for the tier you defined for cheap work.
|
|
449
|
+
|
|
450
|
+
```js
|
|
451
|
+
// in a workflow script
|
|
452
|
+
await agent("summarize this diff", { tier: "low" })
|
|
453
|
+
await agent("design the migration", { tier: "high" })
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
The tier is resolved by the same `resolveAgentTier()` path an ordinary Agent
|
|
457
|
+
spawn uses — same precedence, same model lookup, same thinking clamping, same
|
|
458
|
+
availability checks, same immutable resolution snapshot. A tier the host does not
|
|
459
|
+
define is rejected before dispatch, naming the tiers it does define.
|
|
460
|
+
|
|
461
|
+
Model and thinking are deliberately absent from the managed request. A tier is
|
|
462
|
+
the only model policy a workflow can express, so there is no second selector that
|
|
463
|
+
could silently win or be silently ignored.
|
|
464
|
+
|
|
465
|
+
Fresh installs ship an effort ladder: `low`, `medium`, `high`. Every shipped
|
|
466
|
+
profile inherits its model, so a new machine gets a working vocabulary without
|
|
467
|
+
this package ever choosing a vendor for you. A managed call that names no tier
|
|
468
|
+
uses the agent's own tier, then `agentTiers.defaultTier`, and finally falls back
|
|
469
|
+
to `medium` so a workflow runs on an unconfigured machine.
|
|
470
|
+
|
|
471
|
+
`medium` inherits its model, so on an unconfigured machine that fallback runs on
|
|
472
|
+
the parent session's model. What it buys is a call with a *named* policy, a
|
|
473
|
+
durable resolution snapshot and a scope check — not cheaper work. If you want
|
|
474
|
+
managed work to run somewhere cheaper, set a `defaultTier` whose profile pins a
|
|
475
|
+
model.
|
|
476
|
+
|
|
477
|
+
That last fallback is scoped to managed calls. It is deliberately **not** the
|
|
478
|
+
catalogue's `defaultTier`: a shipped default that applied to every ordinary spawn
|
|
479
|
+
would silence [`defaultModel`](#defaultmodel) and pin a thinking level on machines
|
|
480
|
+
that configured neither.
|
|
481
|
+
|
|
482
|
+
So `Default tier` has three settings, not two, and the menu offers all three:
|
|
483
|
+
|
|
484
|
+
| Setting | Ordinary spawn | Managed workflow call |
|
|
485
|
+
| --- | --- | --- |
|
|
486
|
+
| a tier name | that tier | that tier |
|
|
487
|
+
| `unset` | `defaultModel`, then the parent session | the shipped `medium` |
|
|
488
|
+
| `none` | `defaultModel`, then the parent session | rejected |
|
|
489
|
+
|
|
490
|
+
`none` is a policy statement, recorded as `noDefaultTier`; `unset` is the absence
|
|
491
|
+
of one. Deleting the profiles has the same effect on managed calls as `none`.
|
|
442
492
|
|
|
443
493
|
### How the host discovers tiers
|
|
444
494
|
|
|
@@ -449,8 +499,8 @@ remember. It sees:
|
|
|
449
499
|
```
|
|
450
500
|
Available agent tiers:
|
|
451
501
|
|
|
452
|
-
-
|
|
453
|
-
model:
|
|
502
|
+
- low: Fast, cheap exploration
|
|
503
|
+
model: provider/fast-model
|
|
454
504
|
thinking: max
|
|
455
505
|
...
|
|
456
506
|
Default tier: medium
|
|
@@ -476,6 +526,11 @@ frontmatter are read only to warn that they are stale, and the built-in agents
|
|
|
476
526
|
pin nothing either. With nothing configured at all, a subagent runs on the
|
|
477
527
|
parent session's model.
|
|
478
528
|
|
|
529
|
+
A **managed workflow call** cannot take steps 4 and 5 — it has no parent session
|
|
530
|
+
to inherit from — so it gets one extra step between 3 and the end: the shipped
|
|
531
|
+
`medium` fallback. That step exists only for callers that would otherwise fail
|
|
532
|
+
closed, which is why it does not displace `defaultModel` for everyone else.
|
|
533
|
+
|
|
479
534
|
### `defaultModel`
|
|
480
535
|
|
|
481
536
|
Steps 1–3 are a catalogue; step 4 is one line. Set it when the whole point is
|
|
@@ -548,22 +603,13 @@ level nobody chose for that pair. A project profile that fails validation blocks
|
|
|
548
603
|
its global namesake rather than reviving it, and `defaultTier` is a simple
|
|
549
604
|
project-over-global override.
|
|
550
605
|
|
|
551
|
-
### Not the same as `workflow.tiers`
|
|
552
|
-
|
|
553
|
-
`pi-workflows` has its own tiers, and they stay fixed at `small | medium | large`.
|
|
554
|
-
That vocabulary is part of the cross-package protocol: it lets a workflow
|
|
555
|
-
definition be validated at parse time and stay portable between machines, neither
|
|
556
|
-
of which survives arbitrary names. The two systems share no fields — a spawn
|
|
557
|
-
records `agentTier`/`agentTierSnapshot` or `tier`/`tierSnapshot`, never one
|
|
558
|
-
standing in for the other.
|
|
559
|
-
|
|
560
606
|
### Migrating from `model:`/`thinking:`
|
|
561
607
|
|
|
562
|
-
Define the profiles once, then replace each agent's `model:`/`thinking:`
|
|
563
|
-
`tier: <name>`. Files that still carry the old fields load and run — the
|
|
564
|
-
are ignored, with a warning naming the file — so the migration can be done
|
|
565
|
-
agent at a time. Until an agent names a tier it uses `defaultTier`,
|
|
566
|
-
parent's model when none is set.
|
|
608
|
+
Define the Agent-tier profiles once, then replace each agent's `model:`/`thinking:`
|
|
609
|
+
with `tier: <name>`. Files that still carry the old fields load and run — the
|
|
610
|
+
fields are ignored, with a warning naming the file — so the migration can be done
|
|
611
|
+
one agent at a time. Until an agent names a tier it uses `agentTiers.defaultTier`,
|
|
612
|
+
then `defaultModel`, or the parent's model when none is set.
|
|
567
613
|
|
|
568
614
|
Programmatic callers and the legacy RPC may still pass `model`/`thinking`
|
|
569
615
|
directly. That is the escape hatch for code, not a way to configure an agent.
|
|
@@ -578,11 +624,11 @@ When on, each subagent spawn's effective model is validated against pi's own `en
|
|
|
578
624
|
|
|
579
625
|
| Model source | Out-of-scope behavior |
|
|
580
626
|
|---|---|
|
|
581
|
-
| Caller-supplied
|
|
627
|
+
| Caller-supplied programmatic `model` (only when no Agent tier applies) | Hard error returned to the orchestrator, listing allowed models |
|
|
582
628
|
| Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
|
|
583
629
|
| Parent-inherited (neither set) | Warning toast + parent's model runs |
|
|
584
630
|
|
|
585
|
-
**Design:** `scopeModels` is a guardrail against
|
|
631
|
+
**Design:** `scopeModels` is a guardrail against unexpected runtime model choices, not a hard policy against user-level config. An applicable Agent tier is checked after its single final resolution; compatibility model inputs are checked only on the no-tier path and cannot override a tier.
|
|
586
632
|
|
|
587
633
|
**Nested spawns** ([nested subagents](#nested-subagents)) apply the same table against the parent's config root. The hard-error case is identical; the warning cases proceed silently, since a subagent session has no UI to toast to.
|
|
588
634
|
|
|
@@ -599,24 +645,9 @@ Runtime tuning values set via `/agents` → Settings (max concurrency, default m
|
|
|
599
645
|
|
|
600
646
|
**Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `4`, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, defaults enabled).
|
|
601
647
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
```json
|
|
605
|
-
{
|
|
606
|
-
"workflow": {
|
|
607
|
-
"defaultTier": "medium",
|
|
608
|
-
"tiers": {
|
|
609
|
-
"small": { "model": "inherit", "thinking": "low" },
|
|
610
|
-
"medium": { "model": "inherit", "thinking": "medium" },
|
|
611
|
-
"large": { "model": "openai-codex/gpt-5.6-luna", "thinking": "max" }
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
Profiles are complete `model` + `thinking` tuples. Each field may use `inherit`; a project profile replaces the whole matching global tier entry. Malformed or incomplete entries are retained as durable blocked-tier tombstones and fail closed rather than falling back to a built-in profile or merging field-by-field. An explicit `defaultTier` applies when a workflow task omits its tier. Without a task tier or configured default, the parent model and thinking level are inherited. Agent frontmatter remains authoritative for its explicit `model` and `thinking`; thinking is clamped to the selected model's native supported levels.
|
|
648
|
+
The `workflow` settings key is **retired**. Managed `pi-workflows` calls name an `agentTiers` key directly, so there is no separate workflow routing table; a file that still has one is ignored with a warning naming the key. `agentTiers.defaultTier` replaces what `workflow.defaultTier` used to do. See [One catalogue, including for workflows](#one-catalogue-including-for-workflows).
|
|
618
649
|
|
|
619
|
-
**Default model** (`defaultModel`, unset): the model a subagent runs
|
|
650
|
+
**Default model** (`defaultModel`, unset): the model a non-tiered ordinary subagent runs — see [`defaultModel`](#defaultmodel) for where it sits in precedence, why an unresolvable value falls back instead of failing, and how `"inherit"` lets a project cancel a global default. **Default tier** (`agentTiers.defaultTier`, unset) is the tier applied when neither the caller nor the agent names one; the profiles it selects from live under [`agentTiers`](#model-tiers). It has three settings — a tier name, `unset`, and `none` — which the menu offers separately because the last two behave differently for managed workflow calls; see [Model tiers](#model-tiers) for the table.
|
|
620
651
|
|
|
621
652
|
**Strict agent files** (`strictAgentFiles`, default `false`): normal startup skips unreadable or malformed agent definitions with a warning that includes the file path. Enable it to fail closed during the first `session_start`, using that session's `ctx.cwd`, with the path in the error instead of silently running a surviving lower-priority override. A failed validation leaves no root manager or RPC responder behind. Reloads after startup remain lenient, so an accidental edit cannot terminate an active session; the setting applies on the next pi session.
|
|
622
653
|
|
|
@@ -673,7 +704,7 @@ Agent lifecycle events are emitted via `pi.events.emit()` so other extensions ca
|
|
|
673
704
|
| `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount`, optional `owner` |
|
|
674
705
|
| `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
|
|
675
706
|
| `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
|
|
676
|
-
| `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `version:
|
|
707
|
+
| `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `version: 4`, `capabilities` (`managedSpawn`, `lifecycleOwner`, `ownedStop`, `ownedQuiescence`, `childContext`, `agentTiers`, `managedPolicy` — all required), `routingPolicy` (Agent-tier catalogue + fingerprint) |
|
|
677
708
|
| `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
|
|
678
709
|
| `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
|
|
679
710
|
|
|
@@ -699,20 +730,20 @@ pi.events.on("subagents:ready", () => {
|
|
|
699
730
|
|
|
700
731
|
### Ping
|
|
701
732
|
|
|
702
|
-
Check if the subagents extension is loaded and get the protocol version:
|
|
733
|
+
Check if the subagents extension is loaded and get the protocol version and current routing-policy fingerprint:
|
|
703
734
|
|
|
704
735
|
```typescript
|
|
705
736
|
const requestId = crypto.randomUUID();
|
|
706
737
|
const unsub = pi.events.on(`subagents:rpc:ping:reply:${requestId}`, (reply) => {
|
|
707
738
|
unsub();
|
|
708
|
-
if (reply.success) console.log("Protocol version:", reply.data.version);
|
|
739
|
+
if (reply.success) console.log("Protocol version:", reply.data.version, "routing policy:", reply.data.routingPolicy.fingerprint);
|
|
709
740
|
});
|
|
710
741
|
pi.events.emit("subagents:rpc:ping", { requestId });
|
|
711
742
|
```
|
|
712
743
|
|
|
713
|
-
### Managed spawn (protocol
|
|
744
|
+
### Managed spawn (protocol v4)
|
|
714
745
|
|
|
715
|
-
Workflow-owned orchestration uses the
|
|
746
|
+
Workflow-owned orchestration uses the `subagents:rpc:spawn-managed` channel. Its request may include the core identity fields plus an optional Agent `tier`, `toolset`, `excludeTools`, `thread`, and `isolation: "worktree"`. There is no per-call `model` or `thinking` — the wire validator rejects them:
|
|
716
747
|
|
|
717
748
|
```json
|
|
718
749
|
{
|
|
@@ -721,15 +752,14 @@ Workflow-owned orchestration uses the additive `subagents:rpc:spawn-managed` cha
|
|
|
721
752
|
"type": "Explore",
|
|
722
753
|
"prompt": "Find the relevant files",
|
|
723
754
|
"description": "Find relevant files",
|
|
724
|
-
"tier": "
|
|
725
|
-
"model": "provider/model:medium",
|
|
755
|
+
"tier": "low",
|
|
726
756
|
"excludeTools": ["workflow", "workflow_control"],
|
|
727
757
|
"isolation": "worktree",
|
|
728
758
|
"owner": { "extension": "pi-workflows", "runId": "run-id", "nodeId": "node-id", "attemptId": "run-id/node-id/attempt-1" }
|
|
729
759
|
}
|
|
730
760
|
```
|
|
731
761
|
|
|
732
|
-
The manager validates and resolves
|
|
762
|
+
The manager validates and resolves the tier, agent configuration, queue, tool, session, and worktree policy against its own Agent-tier catalogue and model scope. The resolved tier and its snapshot are retained on the managed invocation/tombstone. `spawnKey` is idempotent within a root manager; the same normalized request returns the existing agent id and a conflicting request is rejected. A named managed `thread` re-enters one sequential session only while its effective model, thinking, toolset, denylist, isolation, and agent policy fingerprint remain unchanged — including the model and thinking its tier currently resolves to, so switching the session model interrupts a thread whose tier inherits it; a policy change or concurrent call is rejected rather than silently reusing the old session. Managed agents use the normal Agent execution path, queue, FleetView, activity, transcript, compaction, and lifecycle events. Only the automatic main-session completion nudge is suppressed for an owner-scoped record. Managed requests must carry an attempt-scoped owner, and `stop-owned`/`quiesce-owned` fail closed when exact node/generation metadata is missing. During branch replacement, timed-out records are detached and late callbacks are suppressed.
|
|
733
763
|
|
|
734
764
|
### Spawn
|
|
735
765
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalridge/pi-subagents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Signalridge's managed subagent runtime with workflow-owned orchestration RPC.",
|
|
5
5
|
"author": "tintinweb and signalridge contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@sinclair/typebox": "^0.34.50",
|
|
34
34
|
"croner": "^10.0.1",
|
|
35
|
-
"@signalridge/pi-subagents-protocol": "^1.
|
|
35
|
+
"@signalridge/pi-subagents-protocol": "^1.4.0",
|
|
36
36
|
"@signalridge/pi-ui": "^1.3.0",
|
|
37
37
|
"nanoid": "^5.0.0"
|
|
38
38
|
},
|