@sublang/playbook 17.0.0 → 17.2.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/docs/cli.md +7 -6
- package/docs/configuration.md +45 -31
- package/docs/embedding.md +2 -2
- package/package.json +4 -4
- package/reference/sdlc/code.playbook/bin/launch-config.js +55 -10
- package/reference/sdlc/code.playbook/bin/playbook.js +3 -2
- package/reference/sdlc/code.playbook/bin/run.js +37 -19
- package/reference/sdlc/code.playbook/bin/session-store.js +18 -4
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +6 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +36 -5
- package/reference/sdlc/code.playbook/playbook-captain.ts +58 -12
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +9 -8
package/docs/cli.md
CHANGED
|
@@ -202,8 +202,8 @@ The former positional `<from>`, `resume`, `--player`, `--captain`,
|
|
|
202
202
|
`--option`, `--cwd`, `--last`, run-only `--config`, and top-level `run:`
|
|
203
203
|
config are removed from `playbook run`. Enable a registry under `playbooks`,
|
|
204
204
|
declare provider agents once under top-level `players`, bind every local role
|
|
205
|
-
under `playbooks.<id>.roles`, tune compatible model, effort,
|
|
206
|
-
overlay, invoke the effective `/command`, and run from the working directory
|
|
205
|
+
under `playbooks.<id>.roles`, tune compatible model, effort, fast mode, and
|
|
206
|
+
subagent model in a `--with` overlay, invoke the effective `/command`, and run from the working directory
|
|
207
207
|
you want agents to use. Legacy `playbooks.<id>.players` blocks are rejected and
|
|
208
208
|
are not auto-migrated because choosing equal or distinct new player IDs chooses
|
|
209
209
|
conversation sharing or isolation; see [Migrating per-playbook
|
|
@@ -280,10 +280,11 @@ competing front end fails closed instead of forking the history.
|
|
|
280
280
|
An ordinary reopen reads current config and any opening `--with` fragments,
|
|
281
281
|
projects them to the stored catalog and player roster, and requires the stored
|
|
282
282
|
role bindings plus every structural setting to remain exact. Compatible
|
|
283
|
-
current `model`, `effort`, and optional `fastMode`
|
|
284
|
-
call. Boolean `false` explicitly resets model
|
|
285
|
-
default, but `fastMode: false` is a literal
|
|
286
|
-
the
|
|
283
|
+
current `model`, `effort`, and optional `fastMode` and `subagentModel` settings
|
|
284
|
+
apply to the next call. Boolean `false` explicitly resets model, effort, or
|
|
285
|
+
subagent model to the provider default, but `fastMode: false` is a literal
|
|
286
|
+
disabled request; omission selects the provider default or inherits the player
|
|
287
|
+
value at a role binding.
|
|
287
288
|
Provider conversations are local hints. Missing hints start fresh from stored
|
|
288
289
|
context; a definite pre-execution rejection permits one fresh attempt.
|
|
289
290
|
Unsupported settings and ambiguous failures do not trigger that fallback
|
package/docs/configuration.md
CHANGED
|
@@ -48,8 +48,8 @@ names, nesting, and ancestry never infer a binding.
|
|
|
48
48
|
|
|
49
49
|
Each `captain` or `players.<player-id>` value is either an adapter shorthand
|
|
50
50
|
(`claude`, `codex`) or a block carrying that agent's own `adapter`, `model`,
|
|
51
|
-
`effort`, `fastMode`, `instruction`, and `permissions`.
|
|
52
|
-
stable agent
|
|
51
|
+
`effort`, `fastMode`, `subagentModel`, `instruction`, and `permissions`.
|
|
52
|
+
Settings are inline per stable agent
|
|
53
53
|
([DR-021](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/021-inline-agent-settings.md)).
|
|
54
54
|
Dots in a player ID are literal characters, not YAML hierarchy. Other adapter
|
|
55
55
|
IDs pass through to `tmux-play` with a warning because `playbook` cannot
|
|
@@ -62,8 +62,8 @@ exactly once. The launcher injects the rest — you do not write host wiring by
|
|
|
62
62
|
hand.
|
|
63
63
|
|
|
64
64
|
The launcher seeds one adapter for the Captain and all stable players from
|
|
65
|
-
locally visible credentials: Claude first, then Codex. It uses Claude Opus 5
|
|
66
|
-
for Claude or GPT-
|
|
65
|
+
locally visible credentials: Claude first, then Codex. It uses Claude Opus 5.5
|
|
66
|
+
for Claude or GPT-6 Sol for Codex, with Captain and Coder at `high` effort
|
|
67
67
|
and Reviewer and Analyst at `xhigh`. If neither adapter is configured, it
|
|
68
68
|
seeds Claude and prints a notice. Credentials do not prove SDK availability
|
|
69
69
|
or remaining quota; launch still checks the configured adapter. Existing
|
|
@@ -75,7 +75,7 @@ The Claude default is shown below. A Codex seed also adds
|
|
|
75
75
|
```yaml
|
|
76
76
|
captain:
|
|
77
77
|
adapter: claude
|
|
78
|
-
model: claude-opus-5
|
|
78
|
+
model: claude-opus-5-5
|
|
79
79
|
effort: high
|
|
80
80
|
permissions:
|
|
81
81
|
mode: auto # protected auto mode for the Claude Captain
|
|
@@ -83,21 +83,21 @@ captain:
|
|
|
83
83
|
players:
|
|
84
84
|
dev.coder:
|
|
85
85
|
adapter: claude
|
|
86
|
-
model: claude-opus-5
|
|
86
|
+
model: claude-opus-5-5
|
|
87
87
|
effort: high
|
|
88
88
|
permissions:
|
|
89
89
|
mode: auto # protected auto mode for the Claude Coder
|
|
90
90
|
|
|
91
91
|
dev.reviewer:
|
|
92
92
|
adapter: claude
|
|
93
|
-
model: claude-opus-5
|
|
93
|
+
model: claude-opus-5-5
|
|
94
94
|
effort: xhigh
|
|
95
95
|
permissions:
|
|
96
96
|
mode: auto # protected auto mode for the Claude Reviewer
|
|
97
97
|
|
|
98
98
|
dev.analyst:
|
|
99
99
|
adapter: claude
|
|
100
|
-
model: claude-opus-5
|
|
100
|
+
model: claude-opus-5-5
|
|
101
101
|
effort: xhigh
|
|
102
102
|
permissions:
|
|
103
103
|
mode: auto # protected auto mode for the Claude Analyst
|
|
@@ -156,26 +156,37 @@ roles:
|
|
|
156
156
|
coder: dev.coder
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
Use a block to override only that role invocation's model, effort,
|
|
160
|
-
|
|
159
|
+
Use a block to override only that role invocation's model, effort, fast mode,
|
|
160
|
+
or subagent model:
|
|
161
161
|
|
|
162
162
|
```yaml
|
|
163
163
|
roles:
|
|
164
164
|
coder:
|
|
165
165
|
player: dev.coder
|
|
166
|
-
model:
|
|
166
|
+
model: claude-sonnet-5-5
|
|
167
167
|
effort: false # explicitly reset to this provider's default
|
|
168
168
|
fastMode: false # literal disabled request, not a default sentinel
|
|
169
|
+
reviewer:
|
|
170
|
+
player: dev.reviewer
|
|
171
|
+
subagentModel: claude-sonnet-5-5 # every subagent of this role runs on it
|
|
169
172
|
```
|
|
170
173
|
|
|
171
|
-
Omitting any override inherits that player's top-level default. For `model
|
|
172
|
-
and `
|
|
173
|
-
resumed conversation cannot accidentally retain an earlier
|
|
174
|
-
`fastMode`, `false` is a literal request to disable fast mode;
|
|
175
|
-
top-level setting selects the provider default. A present
|
|
176
|
-
|
|
177
|
-
cannot override adapter, instruction,
|
|
178
|
-
those define the stable player
|
|
174
|
+
Omitting any override inherits that player's top-level default. For `model`,
|
|
175
|
+
`effort`, and `subagentModel`, boolean `false` selects the provider default
|
|
176
|
+
explicitly, so a resumed conversation cannot accidentally retain an earlier
|
|
177
|
+
selection. For `fastMode`, `false` is a literal request to disable fast mode;
|
|
178
|
+
omitting the top-level setting selects the provider default. A present
|
|
179
|
+
fast-mode boolean or subagent model is accepted only for adapters Cligent
|
|
180
|
+
reports as supporting it. A role binding cannot override adapter, instruction,
|
|
181
|
+
permissions, workspace, or tool posture; those define the stable player
|
|
182
|
+
envelope, so an overriding model must be one that player's adapter serves.
|
|
183
|
+
|
|
184
|
+
A subagent model makes every subagent the agent starts run on that model, and
|
|
185
|
+
tells the agent to hand well-defined, fine-grained tasks to its subagents while
|
|
186
|
+
keeping the deep thinking, reasoning, and design work itself, without lowering
|
|
187
|
+
the quality of what it delivers
|
|
188
|
+
([DR-075](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/075-subagent-model-is-tuning.md)).
|
|
189
|
+
Nothing sets one by default.
|
|
179
190
|
|
|
180
191
|
## Sharing, isolation, and concurrency
|
|
181
192
|
|
|
@@ -198,8 +209,11 @@ second top-level player and change only its binding:
|
|
|
198
209
|
players:
|
|
199
210
|
review.coder:
|
|
200
211
|
adapter: codex
|
|
201
|
-
model: gpt-
|
|
212
|
+
model: gpt-6-sol
|
|
202
213
|
effort: ultra
|
|
214
|
+
permissions:
|
|
215
|
+
mode: auto
|
|
216
|
+
writablePaths: ['.git'] # lets the Codex Coder commit its review fixes
|
|
203
217
|
|
|
204
218
|
playbooks:
|
|
205
219
|
review:
|
|
@@ -247,7 +261,7 @@ playbook run --with fast-lineup.yaml "/code implement the approved change"
|
|
|
247
261
|
# fast-lineup.yaml — retune the shared Coder; nothing is written back.
|
|
248
262
|
players:
|
|
249
263
|
dev.coder:
|
|
250
|
-
model:
|
|
264
|
+
model: claude-sonnet-5-5
|
|
251
265
|
effort: medium
|
|
252
266
|
fastMode: false
|
|
253
267
|
```
|
|
@@ -272,9 +286,9 @@ The global file is never modified, and `--with` is not forwarded to
|
|
|
272
286
|
Overlays apply when creating a fresh session and as current-config input for a
|
|
273
287
|
compatible ordinary reopen. A selected session keeps its stored catalog,
|
|
274
288
|
player roster, role bindings, adapter, instruction, permissions, and working
|
|
275
|
-
directory; only model, effort,
|
|
276
|
-
both complete model and effort selections
|
|
277
|
-
boolean. An uncertain retry accepts no tuning overlay and only restores and reports the recorded attempt. Later turns use the current compatible settings.
|
|
289
|
+
directory; only model, effort, fast mode, and subagent model may change. The
|
|
290
|
+
next call reapplies both complete model and effort selections, the optional
|
|
291
|
+
effective fast-mode boolean, and the optional effective subagent model. An uncertain retry accepts no tuning overlay and only restores and reports the recorded attempt. Later turns use the current compatible settings.
|
|
278
292
|
|
|
279
293
|
## Session storage
|
|
280
294
|
|
|
@@ -315,8 +329,8 @@ public UUID. Presentation-only fields are inert headlessly.
|
|
|
315
329
|
An ordinary reopen reads current config and opening overlays, but first
|
|
316
330
|
projects them to the stored playbooks and referenced players. An unrelated new
|
|
317
331
|
entry cannot enter or invalidate the session. Structural drift fails closed;
|
|
318
|
-
compatible model, effort,
|
|
319
|
-
call. Legacy record, shell, runtime-snapshot, and trace schemas are rejected
|
|
332
|
+
compatible model, effort, fast-mode, or subagent-model changes apply on the
|
|
333
|
+
next provider call. Legacy record, shell, runtime-snapshot, and trace schemas are rejected
|
|
320
334
|
rather than having role or player identity guessed.
|
|
321
335
|
|
|
322
336
|
## External playbooks
|
|
@@ -365,12 +379,12 @@ playbooks:
|
|
|
365
379
|
code:
|
|
366
380
|
from: '@sublang/playbook/code/registry'
|
|
367
381
|
players:
|
|
368
|
-
coder: { adapter: claude, model: claude-opus-
|
|
382
|
+
coder: { adapter: claude, model: claude-opus-5-5 }
|
|
369
383
|
review:
|
|
370
384
|
from: '@sublang/playbook/review/registry'
|
|
371
385
|
players:
|
|
372
|
-
coder: { adapter: claude, model: claude-opus-
|
|
373
|
-
reviewer: { adapter: codex, model: gpt-
|
|
386
|
+
coder: { adapter: claude, model: claude-opus-5-5 }
|
|
387
|
+
reviewer: { adapter: codex, model: gpt-6-sol }
|
|
374
388
|
```
|
|
375
389
|
|
|
376
390
|
Move each provider agent into the flat top-level map, choose stable IDs, and
|
|
@@ -378,8 +392,8 @@ bind the local roles explicitly:
|
|
|
378
392
|
|
|
379
393
|
```yaml
|
|
380
394
|
players:
|
|
381
|
-
dev.coder: { adapter: claude, model: claude-opus-
|
|
382
|
-
dev.reviewer: { adapter: codex, model: gpt-
|
|
395
|
+
dev.coder: { adapter: claude, model: claude-opus-5-5 }
|
|
396
|
+
dev.reviewer: { adapter: codex, model: gpt-6-sol }
|
|
383
397
|
|
|
384
398
|
playbooks:
|
|
385
399
|
code:
|
package/docs/embedding.md
CHANGED
|
@@ -78,11 +78,11 @@ declare const playerAdapter: {
|
|
|
78
78
|
const roleBindings = {
|
|
79
79
|
coder: {
|
|
80
80
|
playerId: 'team.coder',
|
|
81
|
-
promptIdentity: 'claude-opus-
|
|
81
|
+
promptIdentity: 'claude-opus-5-5',
|
|
82
82
|
},
|
|
83
83
|
reviewer: {
|
|
84
84
|
playerId: 'team.reviewer',
|
|
85
|
-
promptIdentity: 'gpt-
|
|
85
|
+
promptIdentity: 'gpt-6-sol',
|
|
86
86
|
},
|
|
87
87
|
} satisfies Readonly<Record<string, PlaybookRoleBinding>>;
|
|
88
88
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sublang/playbook",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, DECIDE, DEV, BRANCH, and PR workflows driven by GEARS specs.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -243,15 +243,15 @@
|
|
|
243
243
|
"provenance": true
|
|
244
244
|
},
|
|
245
245
|
"dependencies": {
|
|
246
|
-
"@sublang/cligent": "^0.
|
|
246
|
+
"@sublang/cligent": "^0.29.0",
|
|
247
247
|
"@sublang/spex": "^3.0.0",
|
|
248
248
|
"p-queue": "^9.3.1",
|
|
249
249
|
"xstate": "^5.19.4",
|
|
250
250
|
"yaml": "^2.9.0"
|
|
251
251
|
},
|
|
252
252
|
"devDependencies": {
|
|
253
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
254
|
-
"@openai/codex-sdk": "^0.
|
|
253
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
|
|
254
|
+
"@openai/codex-sdk": "^0.159.0",
|
|
255
255
|
"@types/node": "^22.0.0",
|
|
256
256
|
"typescript": "^5.8.0",
|
|
257
257
|
"vitest": "^3.0.0"
|
|
@@ -442,7 +442,8 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
442
442
|
if (
|
|
443
443
|
binding.model !== undefined ||
|
|
444
444
|
binding.effort !== undefined ||
|
|
445
|
-
binding.fastMode !== undefined
|
|
445
|
+
binding.fastMode !== undefined ||
|
|
446
|
+
binding.subagentModel !== undefined
|
|
446
447
|
) {
|
|
447
448
|
let checkId;
|
|
448
449
|
do {
|
|
@@ -558,6 +559,7 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
558
559
|
? {}
|
|
559
560
|
: { fastMode: agent.fastMode }
|
|
560
561
|
: { fastMode: binding.fastMode }),
|
|
562
|
+
...effectiveSubagentModel(binding, agent),
|
|
561
563
|
},
|
|
562
564
|
];
|
|
563
565
|
}),
|
|
@@ -787,7 +789,8 @@ export async function normalizeLaunchPlan(
|
|
|
787
789
|
if (
|
|
788
790
|
binding.model !== undefined ||
|
|
789
791
|
binding.effort !== undefined ||
|
|
790
|
-
binding.fastMode !== undefined
|
|
792
|
+
binding.fastMode !== undefined ||
|
|
793
|
+
binding.subagentModel !== undefined
|
|
791
794
|
) {
|
|
792
795
|
let checkId;
|
|
793
796
|
do {
|
|
@@ -999,6 +1002,7 @@ export async function normalizeLaunchPlan(
|
|
|
999
1002
|
? {}
|
|
1000
1003
|
: { fastMode: agent.fastMode }
|
|
1001
1004
|
: { fastMode: binding.fastMode }),
|
|
1005
|
+
...effectiveSubagentModel(binding, agent),
|
|
1002
1006
|
},
|
|
1003
1007
|
];
|
|
1004
1008
|
}),
|
|
@@ -1380,10 +1384,12 @@ function assertLegacyRelocationLocatorsSafe(
|
|
|
1380
1384
|
|
|
1381
1385
|
// PBCLI-11 / DR-053: the adapters a seeded lineup may take, highest
|
|
1382
1386
|
// precedence first. A fixed order — not whichever the probe answers
|
|
1383
|
-
// first — is what makes one machine seed one file twice.
|
|
1387
|
+
// first — is what makes one machine seed one file twice. DR-074: each
|
|
1388
|
+
// row names the latest model of its adapter's line; the template names
|
|
1389
|
+
// the first row's model, because the default seed is its byte copy.
|
|
1384
1390
|
const SEED_LINEUP = Object.freeze([
|
|
1385
|
-
Object.freeze({ adapter: "claude", model: "claude-opus-5" }),
|
|
1386
|
-
Object.freeze({ adapter: "codex", model: "gpt-
|
|
1391
|
+
Object.freeze({ adapter: "claude", model: "claude-opus-5-5" }),
|
|
1392
|
+
Object.freeze({ adapter: "codex", model: "gpt-6-sol" }),
|
|
1387
1393
|
]);
|
|
1388
1394
|
|
|
1389
1395
|
/**
|
|
@@ -1482,7 +1488,7 @@ function migrateUserConfigIfRetired(userConfigPath, onNotice) {
|
|
|
1482
1488
|
throw new Error(
|
|
1483
1489
|
`cannot migrate the retired profiles config at ${userConfigPath}: ` +
|
|
1484
1490
|
`${errorMessage(error)} — edit it by hand: each agent takes its own ` +
|
|
1485
|
-
"adapter, model, effort, fast mode, and permissions",
|
|
1491
|
+
"adapter, model, effort, fast mode, subagent model, and permissions",
|
|
1486
1492
|
);
|
|
1487
1493
|
}
|
|
1488
1494
|
if (migrated === undefined) return;
|
|
@@ -1618,7 +1624,7 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1618
1624
|
throw new Error(
|
|
1619
1625
|
`top-level "profiles" was removed${where}: write each agent's settings ` +
|
|
1620
1626
|
"inline under captain and each top-level players.<player-id> " +
|
|
1621
|
-
"(adapter, model, effort, fast mode, permissions)",
|
|
1627
|
+
"(adapter, model, effort, fast mode, subagent model, permissions)",
|
|
1622
1628
|
);
|
|
1623
1629
|
}
|
|
1624
1630
|
const legacyPath = findLegacyPlayersPath(top);
|
|
@@ -1633,7 +1639,8 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1633
1639
|
if (isObject(block) && block.profile !== undefined) {
|
|
1634
1640
|
throw new Error(
|
|
1635
1641
|
`${path}.profile was removed${where}: write the agent's settings ` +
|
|
1636
|
-
"inline in that block (adapter, model, effort, fast mode,
|
|
1642
|
+
"inline in that block (adapter, model, effort, fast mode, " +
|
|
1643
|
+
"subagent model, permissions)",
|
|
1637
1644
|
);
|
|
1638
1645
|
}
|
|
1639
1646
|
}
|
|
@@ -2032,13 +2039,14 @@ function resolveRoleBinding(value, path) {
|
|
|
2032
2039
|
}
|
|
2033
2040
|
const block = requireObject(value, path);
|
|
2034
2041
|
const unknown = Object.keys(block).filter(
|
|
2035
|
-
(key) =>
|
|
2042
|
+
(key) =>
|
|
2043
|
+
!["player", "model", "effort", "fastMode", "subagentModel"].includes(key),
|
|
2036
2044
|
);
|
|
2037
2045
|
if (unknown.length > 0) {
|
|
2038
2046
|
throw new Error(`${path} has unknown ${formatKeyList(unknown)}`);
|
|
2039
2047
|
}
|
|
2040
2048
|
assertPlayerId(block.player, `${path}.player`);
|
|
2041
|
-
for (const field of ["model", "effort"]) {
|
|
2049
|
+
for (const field of ["model", "effort", "subagentModel"]) {
|
|
2042
2050
|
if (
|
|
2043
2051
|
block[field] !== undefined &&
|
|
2044
2052
|
block[field] !== false &&
|
|
@@ -2060,9 +2068,24 @@ function resolveRoleBinding(value, path) {
|
|
|
2060
2068
|
...(block.model === undefined ? {} : { model: block.model }),
|
|
2061
2069
|
...(block.effort === undefined ? {} : { effort: block.effort }),
|
|
2062
2070
|
...(block.fastMode === undefined ? {} : { fastMode: block.fastMode }),
|
|
2071
|
+
...(block.subagentModel === undefined
|
|
2072
|
+
? {}
|
|
2073
|
+
: { subagentModel: block.subagentModel }),
|
|
2063
2074
|
};
|
|
2064
2075
|
}
|
|
2065
2076
|
|
|
2077
|
+
// A binding's subagent model overrides the player's; `false` selects the
|
|
2078
|
+
// provider default, which a complete call expresses by omission.
|
|
2079
|
+
function effectiveSubagentModel(binding, agent) {
|
|
2080
|
+
if (binding.subagentModel === false) return {};
|
|
2081
|
+
if (binding.subagentModel !== undefined) {
|
|
2082
|
+
return { subagentModel: binding.subagentModel };
|
|
2083
|
+
}
|
|
2084
|
+
return agent.subagentModel === undefined
|
|
2085
|
+
? {}
|
|
2086
|
+
: { subagentModel: agent.subagentModel };
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2066
2089
|
function applyTuningOverrides(agent, binding) {
|
|
2067
2090
|
const effective = { ...agent };
|
|
2068
2091
|
if (binding.model === false) delete effective.model;
|
|
@@ -2073,6 +2096,10 @@ function applyTuningOverrides(agent, binding) {
|
|
|
2073
2096
|
else effective.effort = binding.effort;
|
|
2074
2097
|
}
|
|
2075
2098
|
if (binding.fastMode !== undefined) effective.fastMode = binding.fastMode;
|
|
2099
|
+
if (binding.subagentModel === false) delete effective.subagentModel;
|
|
2100
|
+
else if (binding.subagentModel !== undefined) {
|
|
2101
|
+
effective.subagentModel = binding.subagentModel;
|
|
2102
|
+
}
|
|
2076
2103
|
return effective;
|
|
2077
2104
|
}
|
|
2078
2105
|
|
|
@@ -2087,6 +2114,14 @@ function sessionAgentFromHostAgent(agent, path) {
|
|
|
2087
2114
|
...(agent.fastMode === undefined
|
|
2088
2115
|
? {}
|
|
2089
2116
|
: { fastMode: fastModeSelection(agent.fastMode, `${path}.fastMode`) }),
|
|
2117
|
+
...(agent.subagentModel === undefined
|
|
2118
|
+
? {}
|
|
2119
|
+
: {
|
|
2120
|
+
subagentModel: subagentModelSelection(
|
|
2121
|
+
agent.subagentModel,
|
|
2122
|
+
`${path}.subagentModel`,
|
|
2123
|
+
),
|
|
2124
|
+
}),
|
|
2090
2125
|
...(agent.instruction === undefined
|
|
2091
2126
|
? {}
|
|
2092
2127
|
: { instruction: agent.instruction }),
|
|
@@ -2112,6 +2147,9 @@ export function projectHostAgent(agent, path = "agent") {
|
|
|
2112
2147
|
...(normalized.fastMode === undefined
|
|
2113
2148
|
? {}
|
|
2114
2149
|
: { fastMode: normalized.fastMode }),
|
|
2150
|
+
...(normalized.subagentModel === undefined
|
|
2151
|
+
? {}
|
|
2152
|
+
: { subagentModel: normalized.subagentModel }),
|
|
2115
2153
|
...(normalized.instruction === undefined
|
|
2116
2154
|
? {}
|
|
2117
2155
|
: { instruction: normalized.instruction }),
|
|
@@ -2128,6 +2166,13 @@ function fastModeSelection(value, path) {
|
|
|
2128
2166
|
return value;
|
|
2129
2167
|
}
|
|
2130
2168
|
|
|
2169
|
+
function subagentModelSelection(value, path) {
|
|
2170
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2171
|
+
throw new Error(`${path} must be a nonblank string`);
|
|
2172
|
+
}
|
|
2173
|
+
return value;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2131
2176
|
function tuningSelection(value, path) {
|
|
2132
2177
|
if (
|
|
2133
2178
|
value !== undefined &&
|
|
@@ -966,8 +966,9 @@ function helpText({
|
|
|
966
966
|
" - set the top-level captain and each stable players.<id> to an",
|
|
967
967
|
" adapter shorthand (claude, codex) or an inline agent block",
|
|
968
968
|
" - bind every playbooks.<id>.roles.<role> explicitly to a player id;",
|
|
969
|
-
" a scalar names the id, while { player, model?, effort?, fastMode
|
|
970
|
-
" may retune one role;
|
|
969
|
+
" a scalar names the id, while { player, model?, effort?, fastMode?,",
|
|
970
|
+
" subagentModel? } may retune one role;",
|
|
971
|
+
" false selects provider-default model/effort/subagentModel,",
|
|
971
972
|
" while fastMode false is a literal disabled request",
|
|
972
973
|
" - reusing one id deliberately shares that provider conversation;",
|
|
973
974
|
" distinct ids stay isolated even when their agent blocks are equal",
|
|
@@ -11,7 +11,10 @@ import { attachSessionHints, validateSessionContext } from "./portable-codec.js"
|
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { resolve } from "node:path";
|
|
13
13
|
import { isDeepStrictEqual } from "node:util";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
assertFastModeSupported,
|
|
16
|
+
assertSubagentModelSupported,
|
|
17
|
+
} from "@sublang/cligent";
|
|
15
18
|
import { createTmuxPlayRuntime } from "@sublang/cligent/tmux-play";
|
|
16
19
|
import {
|
|
17
20
|
assertPlaybookEffectLedger,
|
|
@@ -1307,7 +1310,7 @@ export async function validateFrozenExecutionConfig(
|
|
|
1307
1310
|
structuralProjection,
|
|
1308
1311
|
executionProjection,
|
|
1309
1312
|
);
|
|
1310
|
-
|
|
1313
|
+
assertFrozenTuningSupported(config);
|
|
1311
1314
|
const catalogItems = Object.entries(config.catalog);
|
|
1312
1315
|
|
|
1313
1316
|
// Preserve the complete-catalog preparation transaction: prepare every
|
|
@@ -1364,28 +1367,42 @@ export async function validateFrozenExecutionConfig(
|
|
|
1364
1367
|
return config;
|
|
1365
1368
|
}
|
|
1366
1369
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1370
|
+
// Fast mode and the subagent model are adapter-scoped tuning (DR-075): a
|
|
1371
|
+
// frozen record re-validates each present value against its adapter.
|
|
1372
|
+
const FROZEN_ADAPTER_SCOPED_TUNING = [
|
|
1373
|
+
["fastMode", assertFastModeSupported],
|
|
1374
|
+
["subagentModel", assertSubagentModelSupported],
|
|
1375
|
+
];
|
|
1376
|
+
|
|
1377
|
+
function assertFrozenTuningSupported(config) {
|
|
1378
|
+
const assertSupported = (record, adapter, path) => {
|
|
1379
|
+
for (const [field, assertFieldSupported] of FROZEN_ADAPTER_SCOPED_TUNING) {
|
|
1380
|
+
if (record[field] !== undefined) {
|
|
1381
|
+
assertFieldSupported(adapter, `${path} ${field}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
assertSupported(config.captain, config.captain.adapter, "stored Captain");
|
|
1374
1386
|
|
|
1375
1387
|
const playerAdapters = new Map();
|
|
1376
1388
|
for (const player of config.players) {
|
|
1377
1389
|
playerAdapters.set(player.id, player.adapter);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1390
|
+
assertSupported(
|
|
1391
|
+
player,
|
|
1392
|
+
player.adapter,
|
|
1393
|
+
`stored player ${JSON.stringify(player.id)}`,
|
|
1394
|
+
);
|
|
1384
1395
|
}
|
|
1385
1396
|
|
|
1386
1397
|
for (const [playbookId, item] of Object.entries(config.catalog)) {
|
|
1387
1398
|
for (const [roleId, binding] of Object.entries(item.roles)) {
|
|
1388
|
-
if (
|
|
1399
|
+
if (
|
|
1400
|
+
FROZEN_ADAPTER_SCOPED_TUNING.every(
|
|
1401
|
+
([field]) => binding[field] === undefined,
|
|
1402
|
+
)
|
|
1403
|
+
) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1389
1406
|
const adapter = playerAdapters.get(binding.playerId);
|
|
1390
1407
|
if (adapter === undefined) {
|
|
1391
1408
|
throw new Error(
|
|
@@ -1393,10 +1410,11 @@ function assertFrozenFastModesSupported(config) {
|
|
|
1393
1410
|
`${JSON.stringify(roleId)} names an unknown player`,
|
|
1394
1411
|
);
|
|
1395
1412
|
}
|
|
1396
|
-
|
|
1413
|
+
assertSupported(
|
|
1414
|
+
binding,
|
|
1397
1415
|
adapter,
|
|
1398
1416
|
`stored playbook ${JSON.stringify(playbookId)} role ` +
|
|
1399
|
-
`${JSON.stringify(roleId)}
|
|
1417
|
+
`${JSON.stringify(roleId)}`,
|
|
1400
1418
|
);
|
|
1401
1419
|
}
|
|
1402
1420
|
}
|
|
@@ -1869,7 +1887,7 @@ function runHelpText(userConfigPath) {
|
|
|
1869
1887
|
"working directory and reports when it uses the global newest fallback.",
|
|
1870
1888
|
"An ordinary continued run restores that stored structure and working",
|
|
1871
1889
|
"directory, then reads current config and overlays for model, effort,",
|
|
1872
|
-
"and
|
|
1890
|
+
"fast mode, and subagent model.",
|
|
1873
1891
|
"Uncertain retry only restores and reports; later work uses current settings.",
|
|
1874
1892
|
"",
|
|
1875
1893
|
"Options:",
|
|
@@ -5402,7 +5402,7 @@ function validateCaptainSessionProjection(
|
|
|
5402
5402
|
: ['id', 'adapter', 'model', 'effort'],
|
|
5403
5403
|
structural
|
|
5404
5404
|
? ['instruction', 'permissions']
|
|
5405
|
-
: ['instruction', 'permissions', 'fastMode'],
|
|
5405
|
+
: ['instruction', 'permissions', 'fastMode', 'subagentModel'],
|
|
5406
5406
|
playerPath,
|
|
5407
5407
|
);
|
|
5408
5408
|
assertPlayerId(player.id, `${playerPath}.id`);
|
|
@@ -5503,12 +5503,16 @@ function validateCaptainSessionProjection(
|
|
|
5503
5503
|
structural
|
|
5504
5504
|
? ['playerId']
|
|
5505
5505
|
: ['playerId', 'model', 'effort'],
|
|
5506
|
-
structural ? [] : ['fastMode'],
|
|
5506
|
+
structural ? [] : ['fastMode', 'subagentModel'],
|
|
5507
5507
|
bindingPath,
|
|
5508
5508
|
);
|
|
5509
5509
|
assertPlayerId(binding.playerId, `${bindingPath}.playerId`);
|
|
5510
5510
|
if (!structural) {
|
|
5511
5511
|
validateProjectedFastMode(binding, `${bindingPath}.fastMode`);
|
|
5512
|
+
validateProjectedSubagentModel(
|
|
5513
|
+
binding,
|
|
5514
|
+
`${bindingPath}.subagentModel`,
|
|
5515
|
+
);
|
|
5512
5516
|
validateTuningSelection(binding.model, `${bindingPath}.model`);
|
|
5513
5517
|
const player = projection.players.find(
|
|
5514
5518
|
(candidate) => candidate.id === binding.playerId,
|
|
@@ -5556,16 +5560,26 @@ function validateProjectedFastMode(record, path) {
|
|
|
5556
5560
|
}
|
|
5557
5561
|
}
|
|
5558
5562
|
|
|
5563
|
+
// Like fast mode, absence is the canonical provider default (DR-075).
|
|
5564
|
+
function validateProjectedSubagentModel(record, path) {
|
|
5565
|
+
if (Object.hasOwn(record, 'subagentModel')) {
|
|
5566
|
+
requireNonblank(record.subagentModel, path);
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
|
|
5559
5570
|
function validateProjectedAgent(value, path, { structural, hasId = false }) {
|
|
5560
5571
|
const agent = requireRecord(value, path);
|
|
5561
|
-
if (!structural)
|
|
5572
|
+
if (!structural) {
|
|
5573
|
+
validateProjectedFastMode(agent, `${path}.fastMode`);
|
|
5574
|
+
validateProjectedSubagentModel(agent, `${path}.subagentModel`);
|
|
5575
|
+
}
|
|
5562
5576
|
if (!hasId) {
|
|
5563
5577
|
exactOptionalKeys(
|
|
5564
5578
|
agent,
|
|
5565
5579
|
structural ? ['adapter'] : ['adapter', 'model', 'effort'],
|
|
5566
5580
|
structural
|
|
5567
5581
|
? ['instruction', 'permissions']
|
|
5568
|
-
: ['instruction', 'permissions', 'fastMode'],
|
|
5582
|
+
: ['instruction', 'permissions', 'fastMode', 'subagentModel'],
|
|
5569
5583
|
path,
|
|
5570
5584
|
);
|
|
5571
5585
|
}
|
|
@@ -10,9 +10,14 @@ interface SessionAgent {
|
|
|
10
10
|
/** Adapter-scoped fast mode. Absence is the provider default; `false` is a
|
|
11
11
|
* literal request, so this carries no provider-default sentinel. */
|
|
12
12
|
readonly fastMode?: boolean;
|
|
13
|
+
/** The model every subagent of this agent runs on (DR-075). Absence is the
|
|
14
|
+
* provider default. */
|
|
15
|
+
readonly subagentModel?: string;
|
|
13
16
|
readonly instruction?: string;
|
|
14
17
|
readonly permissions?: PermissionPolicy;
|
|
15
18
|
}
|
|
19
|
+
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
20
|
+
type TuningKey = 'model' | 'effort' | 'fastMode' | 'subagentModel';
|
|
16
21
|
interface PlayerLedgerEntry {
|
|
17
22
|
readonly adapter: string;
|
|
18
23
|
readonly instruction?: string;
|
|
@@ -32,7 +37,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
32
37
|
readonly rootPlaybookId: string;
|
|
33
38
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
34
39
|
}
|
|
35
|
-
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent,
|
|
40
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
36
41
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
37
42
|
export interface ProgressChange {
|
|
38
43
|
snapshot?: PlaybookCaptainShellSnapshot | null;
|
|
@@ -2098,10 +2098,28 @@ function snapshotFastMode(value, path) {
|
|
|
2098
2098
|
}
|
|
2099
2099
|
return value;
|
|
2100
2100
|
}
|
|
2101
|
+
function snapshotSubagentModel(value, path) {
|
|
2102
|
+
if (value !== undefined &&
|
|
2103
|
+
(typeof value !== 'string' || value.trim().length === 0)) {
|
|
2104
|
+
throw new TypeError(`${path} must be a nonblank string`);
|
|
2105
|
+
}
|
|
2106
|
+
return value;
|
|
2107
|
+
}
|
|
2101
2108
|
function snapshotSessionAgent(value, path) {
|
|
2102
2109
|
const agent = snapshotRecord(value, path);
|
|
2103
|
-
rejectSnapshotKeys(agent, [
|
|
2104
|
-
|
|
2110
|
+
rejectSnapshotKeys(agent, [
|
|
2111
|
+
'adapter',
|
|
2112
|
+
'model',
|
|
2113
|
+
'effort',
|
|
2114
|
+
'fastMode',
|
|
2115
|
+
'subagentModel',
|
|
2116
|
+
'instruction',
|
|
2117
|
+
'permissions',
|
|
2118
|
+
], path);
|
|
2119
|
+
const fixed = snapshotFixedAgent(Object.fromEntries(Object.entries(agent).filter(([key]) => key !== 'model' &&
|
|
2120
|
+
key !== 'effort' &&
|
|
2121
|
+
key !== 'fastMode' &&
|
|
2122
|
+
key !== 'subagentModel')), path);
|
|
2105
2123
|
return {
|
|
2106
2124
|
adapter: fixed.adapter,
|
|
2107
2125
|
...(fixed.instruction === undefined
|
|
@@ -2115,6 +2133,11 @@ function snapshotSessionAgent(value, path) {
|
|
|
2115
2133
|
...(agent.fastMode === undefined
|
|
2116
2134
|
? {}
|
|
2117
2135
|
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
2136
|
+
...(agent.subagentModel === undefined
|
|
2137
|
+
? {}
|
|
2138
|
+
: {
|
|
2139
|
+
subagentModel: snapshotSubagentModel(agent.subagentModel, `${path}.subagentModel`),
|
|
2140
|
+
}),
|
|
2118
2141
|
};
|
|
2119
2142
|
}
|
|
2120
2143
|
function fixedAgent(agent) {
|
|
@@ -2126,12 +2149,15 @@ function fixedAgent(agent) {
|
|
|
2126
2149
|
}
|
|
2127
2150
|
function callSettings(agent, tuning = agent) {
|
|
2128
2151
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
2129
|
-
// omitted fastMode here is a request for the provider
|
|
2130
|
-
// inheritance of whatever the previous call left behind.
|
|
2152
|
+
// omitted fastMode or subagentModel here is a request for the provider
|
|
2153
|
+
// default, never an inheritance of whatever the previous call left behind.
|
|
2131
2154
|
return {
|
|
2132
2155
|
model: tuning.model,
|
|
2133
2156
|
effort: tuning.effort,
|
|
2134
2157
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
2158
|
+
...(tuning.subagentModel === undefined
|
|
2159
|
+
? {}
|
|
2160
|
+
: { subagentModel: tuning.subagentModel }),
|
|
2135
2161
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
2136
2162
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
2137
2163
|
};
|
|
@@ -2259,7 +2285,7 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2259
2285
|
for (const role of entry.requiredRoleIds) {
|
|
2260
2286
|
const path = `captain.options.playbooks.${id}.roles.${role}`;
|
|
2261
2287
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
2262
|
-
rejectSnapshotKeys(rawBinding, ['playerId', 'model', 'effort', 'fastMode'], path);
|
|
2288
|
+
rejectSnapshotKeys(rawBinding, ['playerId', 'model', 'effort', 'fastMode', 'subagentModel'], path);
|
|
2263
2289
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
2264
2290
|
if (!PLAYER_ID_PATTERN.test(playerId) || playerId === INTERNAL_CAPTAIN_ID) {
|
|
2265
2291
|
throw new Error(`${path}.playerId is not a canonical player id`);
|
|
@@ -2277,6 +2303,11 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2277
2303
|
: {
|
|
2278
2304
|
fastMode: snapshotFastMode(rawBinding.fastMode, `${path}.fastMode`),
|
|
2279
2305
|
}),
|
|
2306
|
+
...(rawBinding.subagentModel === undefined
|
|
2307
|
+
? {}
|
|
2308
|
+
: {
|
|
2309
|
+
subagentModel: snapshotSubagentModel(rawBinding.subagentModel, `${path}.subagentModel`),
|
|
2310
|
+
}),
|
|
2280
2311
|
agent,
|
|
2281
2312
|
});
|
|
2282
2313
|
}
|
|
@@ -69,10 +69,16 @@ interface SessionAgent {
|
|
|
69
69
|
/** Adapter-scoped fast mode. Absence is the provider default; `false` is a
|
|
70
70
|
* literal request, so this carries no provider-default sentinel. */
|
|
71
71
|
readonly fastMode?: boolean;
|
|
72
|
+
/** The model every subagent of this agent runs on (DR-075). Absence is the
|
|
73
|
+
* provider default. */
|
|
74
|
+
readonly subagentModel?: string;
|
|
72
75
|
readonly instruction?: string;
|
|
73
76
|
readonly permissions?: PermissionPolicy;
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
80
|
+
type TuningKey = 'model' | 'effort' | 'fastMode' | 'subagentModel';
|
|
81
|
+
|
|
76
82
|
interface PlayerLedgerEntry {
|
|
77
83
|
readonly adapter: string;
|
|
78
84
|
readonly instruction?: string;
|
|
@@ -107,9 +113,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
107
113
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
type SnapshotAgentEnvelope = DeepReadonly<
|
|
111
|
-
Omit<SessionAgent, 'model' | 'effort' | 'fastMode'>
|
|
112
|
-
>;
|
|
116
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
113
117
|
|
|
114
118
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
115
119
|
|
|
@@ -447,6 +451,7 @@ interface EffectivePlayerBinding {
|
|
|
447
451
|
readonly model: TuningSelection;
|
|
448
452
|
readonly effort: TuningSelection<Effort>;
|
|
449
453
|
readonly fastMode?: boolean;
|
|
454
|
+
readonly subagentModel?: string;
|
|
450
455
|
readonly agent: SessionAgent;
|
|
451
456
|
}
|
|
452
457
|
|
|
@@ -3722,6 +3727,19 @@ function snapshotFastMode(
|
|
|
3722
3727
|
return value;
|
|
3723
3728
|
}
|
|
3724
3729
|
|
|
3730
|
+
function snapshotSubagentModel(
|
|
3731
|
+
value: JsonValue | undefined,
|
|
3732
|
+
path: string,
|
|
3733
|
+
): string | undefined {
|
|
3734
|
+
if (
|
|
3735
|
+
value !== undefined &&
|
|
3736
|
+
(typeof value !== 'string' || value.trim().length === 0)
|
|
3737
|
+
) {
|
|
3738
|
+
throw new TypeError(`${path} must be a nonblank string`);
|
|
3739
|
+
}
|
|
3740
|
+
return value;
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3725
3743
|
function snapshotSessionAgent(
|
|
3726
3744
|
value: JsonValue | undefined,
|
|
3727
3745
|
path: string,
|
|
@@ -3729,14 +3747,25 @@ function snapshotSessionAgent(
|
|
|
3729
3747
|
const agent = snapshotRecord(value, path);
|
|
3730
3748
|
rejectSnapshotKeys(
|
|
3731
3749
|
agent,
|
|
3732
|
-
[
|
|
3750
|
+
[
|
|
3751
|
+
'adapter',
|
|
3752
|
+
'model',
|
|
3753
|
+
'effort',
|
|
3754
|
+
'fastMode',
|
|
3755
|
+
'subagentModel',
|
|
3756
|
+
'instruction',
|
|
3757
|
+
'permissions',
|
|
3758
|
+
],
|
|
3733
3759
|
path,
|
|
3734
3760
|
);
|
|
3735
3761
|
const fixed = snapshotFixedAgent(
|
|
3736
3762
|
Object.fromEntries(
|
|
3737
3763
|
Object.entries(agent).filter(
|
|
3738
3764
|
([key]) =>
|
|
3739
|
-
key !== 'model' &&
|
|
3765
|
+
key !== 'model' &&
|
|
3766
|
+
key !== 'effort' &&
|
|
3767
|
+
key !== 'fastMode' &&
|
|
3768
|
+
key !== 'subagentModel',
|
|
3740
3769
|
),
|
|
3741
3770
|
) as JsonValue,
|
|
3742
3771
|
path,
|
|
@@ -3754,12 +3783,18 @@ function snapshotSessionAgent(
|
|
|
3754
3783
|
...(agent.fastMode === undefined
|
|
3755
3784
|
? {}
|
|
3756
3785
|
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
3786
|
+
...(agent.subagentModel === undefined
|
|
3787
|
+
? {}
|
|
3788
|
+
: {
|
|
3789
|
+
subagentModel: snapshotSubagentModel(
|
|
3790
|
+
agent.subagentModel,
|
|
3791
|
+
`${path}.subagentModel`,
|
|
3792
|
+
),
|
|
3793
|
+
}),
|
|
3757
3794
|
};
|
|
3758
3795
|
}
|
|
3759
3796
|
|
|
3760
|
-
function fixedAgent(
|
|
3761
|
-
agent: SessionAgent,
|
|
3762
|
-
): Omit<SessionAgent, 'model' | 'effort' | 'fastMode'> {
|
|
3797
|
+
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey> {
|
|
3763
3798
|
return {
|
|
3764
3799
|
adapter: agent.adapter,
|
|
3765
3800
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
@@ -3769,15 +3804,18 @@ function fixedAgent(
|
|
|
3769
3804
|
|
|
3770
3805
|
function callSettings(
|
|
3771
3806
|
agent: SessionAgent,
|
|
3772
|
-
tuning: Pick<SessionAgent,
|
|
3807
|
+
tuning: Pick<SessionAgent, TuningKey> = agent,
|
|
3773
3808
|
): AgentCallSettings {
|
|
3774
3809
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
3775
|
-
// omitted fastMode here is a request for the provider
|
|
3776
|
-
// inheritance of whatever the previous call left behind.
|
|
3810
|
+
// omitted fastMode or subagentModel here is a request for the provider
|
|
3811
|
+
// default, never an inheritance of whatever the previous call left behind.
|
|
3777
3812
|
return {
|
|
3778
3813
|
model: tuning.model,
|
|
3779
3814
|
effort: tuning.effort,
|
|
3780
3815
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
3816
|
+
...(tuning.subagentModel === undefined
|
|
3817
|
+
? {}
|
|
3818
|
+
: { subagentModel: tuning.subagentModel }),
|
|
3781
3819
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
3782
3820
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
3783
3821
|
};
|
|
@@ -4005,7 +4043,7 @@ async function buildEnablements(
|
|
|
4005
4043
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
4006
4044
|
rejectSnapshotKeys(
|
|
4007
4045
|
rawBinding,
|
|
4008
|
-
['playerId', 'model', 'effort', 'fastMode'],
|
|
4046
|
+
['playerId', 'model', 'effort', 'fastMode', 'subagentModel'],
|
|
4009
4047
|
path,
|
|
4010
4048
|
);
|
|
4011
4049
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
@@ -4030,6 +4068,14 @@ async function buildEnablements(
|
|
|
4030
4068
|
`${path}.fastMode`,
|
|
4031
4069
|
),
|
|
4032
4070
|
}),
|
|
4071
|
+
...(rawBinding.subagentModel === undefined
|
|
4072
|
+
? {}
|
|
4073
|
+
: {
|
|
4074
|
+
subagentModel: snapshotSubagentModel(
|
|
4075
|
+
rawBinding.subagentModel,
|
|
4076
|
+
`${path}.subagentModel`,
|
|
4077
|
+
),
|
|
4078
|
+
}),
|
|
4033
4079
|
agent,
|
|
4034
4080
|
});
|
|
4035
4081
|
}
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
# The Captain and each stable session player carries its own defaults inline:
|
|
10
10
|
# an adapter shorthand (claude, codex) or a block with
|
|
11
|
-
# adapter/model/effort/fastMode/permissions. A role may override
|
|
12
|
-
# effort, or
|
|
13
|
-
# Omit an override to inherit the player default. For model
|
|
14
|
-
# selects the provider default; fastMode false is a
|
|
11
|
+
# adapter/model/effort/fastMode/subagentModel/permissions. A role may override
|
|
12
|
+
# only model, effort, fastMode, or subagentModel.
|
|
13
|
+
# Omit an override to inherit the player default. For model, effort, and
|
|
14
|
+
# subagentModel, false selects the provider default; fastMode false is a
|
|
15
|
+
# literal disabled request.
|
|
15
16
|
|
|
16
17
|
# Each adapter needs its vendor SDK installed as its own top-level
|
|
17
18
|
# install root — they are optional peer dependencies, so you pay only
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
# list, so those captains degrade to a prompt-level restriction (DR-013 A1).
|
|
32
33
|
captain:
|
|
33
34
|
adapter: claude
|
|
34
|
-
model: claude-opus-5
|
|
35
|
+
model: claude-opus-5-5
|
|
35
36
|
effort: high
|
|
36
37
|
permissions:
|
|
37
38
|
mode: auto
|
|
@@ -48,7 +49,7 @@ notifications:
|
|
|
48
49
|
players:
|
|
49
50
|
dev.coder:
|
|
50
51
|
adapter: claude
|
|
51
|
-
model: claude-opus-5
|
|
52
|
+
model: claude-opus-5-5
|
|
52
53
|
effort: high
|
|
53
54
|
# Adapter-scoped fast mode is validated by the installed Cligent contract;
|
|
54
55
|
# omitting it takes the provider default.
|
|
@@ -57,7 +58,7 @@ players:
|
|
|
57
58
|
|
|
58
59
|
dev.reviewer:
|
|
59
60
|
adapter: claude
|
|
60
|
-
model: claude-opus-5
|
|
61
|
+
model: claude-opus-5-5
|
|
61
62
|
effort: xhigh
|
|
62
63
|
permissions:
|
|
63
64
|
mode: auto
|
|
@@ -67,7 +68,7 @@ players:
|
|
|
67
68
|
# conversations (DR-044).
|
|
68
69
|
dev.analyst:
|
|
69
70
|
adapter: claude
|
|
70
|
-
model: claude-opus-5
|
|
71
|
+
model: claude-opus-5-5
|
|
71
72
|
effort: xhigh
|
|
72
73
|
permissions:
|
|
73
74
|
mode: auto
|