@sublang/playbook 17.1.0 → 17.3.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 +8 -6
- package/docs/configuration.md +41 -18
- package/package.json +2 -2
- package/reference/sdlc/code.playbook/bin/launch-config.js +109 -15
- package/reference/sdlc/code.playbook/bin/playbook.js +6 -3
- package/reference/sdlc/code.playbook/bin/run.js +40 -19
- package/reference/sdlc/code.playbook/bin/session-store.js +31 -4
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +9 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +60 -5
- package/reference/sdlc/code.playbook/playbook-captain.ts +97 -12
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +9 -4
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,
|
|
206
|
+
subagent model, and subagent effort 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,12 @@ 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
|
|
285
|
-
|
|
286
|
-
|
|
283
|
+
current `model`, `effort`, and optional `fastMode`, `subagentModel`, and
|
|
284
|
+
`subagentEffort` settings apply to the next call. Boolean `false` explicitly
|
|
285
|
+
resets model, effort, subagent model, or subagent effort to the provider
|
|
286
|
+
default, but `fastMode: false` is a literal
|
|
287
|
+
disabled request; omission selects the provider default or inherits the player
|
|
288
|
+
value at a role binding.
|
|
287
289
|
Provider conversations are local hints. Missing hints start fresh from stored
|
|
288
290
|
context; a definite pre-execution rejection permits one fresh attempt.
|
|
289
291
|
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`, `
|
|
52
|
-
stable agent
|
|
51
|
+
`effort`, `fastMode`, `subagentModel`, `subagentEffort`, `instruction`, and
|
|
52
|
+
`permissions`. 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
|
|
@@ -156,8 +156,8 @@ 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
|
+
subagent model, or subagent effort:
|
|
161
161
|
|
|
162
162
|
```yaml
|
|
163
163
|
roles:
|
|
@@ -166,17 +166,38 @@ roles:
|
|
|
166
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: inherit # inherit | <model> | false
|
|
172
|
+
subagentEffort: medium # <effort> | false
|
|
169
173
|
```
|
|
170
174
|
|
|
171
|
-
Omitting any override inherits that player's top-level default. For `model
|
|
172
|
-
and `
|
|
173
|
-
|
|
174
|
-
`fastMode`, `false` is a literal request to
|
|
175
|
-
top-level setting selects the provider default.
|
|
176
|
-
|
|
177
|
-
cannot override adapter, instruction,
|
|
178
|
-
those define the stable player
|
|
179
|
-
that player's adapter serves.
|
|
175
|
+
Omitting any override inherits that player's top-level default. For `model`,
|
|
176
|
+
`effort`, `subagentModel`, and `subagentEffort`, boolean `false` selects the
|
|
177
|
+
provider default explicitly, so a resumed conversation cannot accidentally
|
|
178
|
+
retain an earlier selection. For `fastMode`, `false` is a literal request to
|
|
179
|
+
disable fast mode; omitting the top-level setting selects the provider default.
|
|
180
|
+
A present fast-mode boolean, subagent model, or subagent effort is accepted
|
|
181
|
+
only for adapters Cligent reports as supporting it. A role binding cannot override adapter, instruction,
|
|
182
|
+
permissions, workspace, or tool posture; those define the stable player
|
|
183
|
+
envelope, so an overriding model must be one that player's adapter serves.
|
|
184
|
+
|
|
185
|
+
`subagentModel` makes every subagent the agent starts run on one model —
|
|
186
|
+
`inherit` names the agent's own, a model name pins another, and `false` on a
|
|
187
|
+
`captain` or `players.<player-id>` block switches delegation off — and tells
|
|
188
|
+
the agent to hand well-defined, fine-grained tasks to its subagents while
|
|
189
|
+
keeping the deep thinking, reasoning, and design work itself, without lowering
|
|
190
|
+
the quality of what it delivers
|
|
191
|
+
([DR-075](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/075-subagent-model-is-tuning.md)).
|
|
192
|
+
`subagentEffort` pins the effort every subagent runs at, any effort the
|
|
193
|
+
adapter accepts other than `ultracode`; left unset, the agent chooses an effort
|
|
194
|
+
per task, so its own effort never silently governs its subagents.
|
|
195
|
+
An agent whose adapter serves a subagent model (Claude today) and whose block
|
|
196
|
+
leaves `subagentModel` unset resolves to `inherit`: it delegates by default.
|
|
197
|
+
The launcher resolves this on its own copy and never rewrites your config, and
|
|
198
|
+
the starter config names neither field
|
|
199
|
+
([DR-076](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/076-subagents-delegate-by-default.md)).
|
|
200
|
+
|
|
180
201
|
|
|
181
202
|
## Sharing, isolation, and concurrency
|
|
182
203
|
|
|
@@ -269,6 +290,7 @@ playbooks:
|
|
|
269
290
|
coder:
|
|
270
291
|
player: dev.coder
|
|
271
292
|
effort: low
|
|
293
|
+
subagentEffort: high # its subagents still run at high effort
|
|
272
294
|
```
|
|
273
295
|
|
|
274
296
|
The global file is never modified, and `--with` is not forwarded to
|
|
@@ -276,9 +298,10 @@ The global file is never modified, and `--with` is not forwarded to
|
|
|
276
298
|
Overlays apply when creating a fresh session and as current-config input for a
|
|
277
299
|
compatible ordinary reopen. A selected session keeps its stored catalog,
|
|
278
300
|
player roster, role bindings, adapter, instruction, permissions, and working
|
|
279
|
-
directory; only model, effort,
|
|
280
|
-
both complete model and effort selections
|
|
281
|
-
|
|
301
|
+
directory; only model, effort, fast mode, subagent model, and subagent effort
|
|
302
|
+
may change. The next call reapplies both complete model and effort selections,
|
|
303
|
+
the optional effective fast-mode boolean, and the optional effective subagent
|
|
304
|
+
model and subagent effort. An uncertain retry accepts no tuning overlay and only restores and reports the recorded attempt. Later turns use the current compatible settings.
|
|
282
305
|
|
|
283
306
|
## Session storage
|
|
284
307
|
|
|
@@ -319,8 +342,8 @@ public UUID. Presentation-only fields are inert headlessly.
|
|
|
319
342
|
An ordinary reopen reads current config and opening overlays, but first
|
|
320
343
|
projects them to the stored playbooks and referenced players. An unrelated new
|
|
321
344
|
entry cannot enter or invalidate the session. Structural drift fails closed;
|
|
322
|
-
compatible model, effort,
|
|
323
|
-
call. Legacy record, shell, runtime-snapshot, and trace schemas are rejected
|
|
345
|
+
compatible model, effort, fast-mode, subagent-model, or subagent-effort
|
|
346
|
+
changes apply on the next provider call. Legacy record, shell, runtime-snapshot, and trace schemas are rejected
|
|
324
347
|
rather than having role or player identity guessed.
|
|
325
348
|
|
|
326
349
|
## External playbooks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sublang/playbook",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.3.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,7 +243,7 @@
|
|
|
243
243
|
"provenance": true
|
|
244
244
|
},
|
|
245
245
|
"dependencies": {
|
|
246
|
-
"@sublang/cligent": "^0.
|
|
246
|
+
"@sublang/cligent": "^0.30.0",
|
|
247
247
|
"@sublang/spex": "^3.0.0",
|
|
248
248
|
"p-queue": "^9.3.1",
|
|
249
249
|
"xstate": "^5.19.4",
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
parseDocument as parseYamlDocument,
|
|
29
29
|
stringify as stringifyYaml,
|
|
30
30
|
} from "yaml";
|
|
31
|
+
import { isSubagentModelSupported } from "@sublang/cligent";
|
|
31
32
|
import { loadTmuxPlayConfig } from "@sublang/cligent/tmux-play";
|
|
32
33
|
import {
|
|
33
34
|
defaultCaptainSessionsDir,
|
|
@@ -379,12 +380,14 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
379
380
|
const configuredAgents = new Map();
|
|
380
381
|
for (const playerId of expectedMembers.playerIds) {
|
|
381
382
|
assertPlayerId(playerId, `players.${playerId}`);
|
|
382
|
-
const agent =
|
|
383
|
-
"id",
|
|
384
|
-
|
|
383
|
+
const agent = resolveDelegationDefault(
|
|
384
|
+
resolveAgent(playersCfg[playerId], `players.${playerId}`, ["id"]),
|
|
385
|
+
);
|
|
385
386
|
configuredAgents.set(playerId, agent);
|
|
386
387
|
}
|
|
387
|
-
let captain =
|
|
388
|
+
let captain = resolveDelegationDefault(
|
|
389
|
+
resolveAgent(top.captain, "captain", ["from", "options"]),
|
|
390
|
+
);
|
|
388
391
|
|
|
389
392
|
const playbooksCfg = requireObject(top.playbooks, "playbooks");
|
|
390
393
|
const tuningChecks = [];
|
|
@@ -442,7 +445,9 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
442
445
|
if (
|
|
443
446
|
binding.model !== undefined ||
|
|
444
447
|
binding.effort !== undefined ||
|
|
445
|
-
binding.fastMode !== undefined
|
|
448
|
+
binding.fastMode !== undefined ||
|
|
449
|
+
binding.subagentModel !== undefined ||
|
|
450
|
+
binding.subagentEffort !== undefined
|
|
446
451
|
) {
|
|
447
452
|
let checkId;
|
|
448
453
|
do {
|
|
@@ -558,6 +563,8 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
558
563
|
? {}
|
|
559
564
|
: { fastMode: agent.fastMode }
|
|
560
565
|
: { fastMode: binding.fastMode }),
|
|
566
|
+
...effectiveOptionalTuning(binding, agent, "subagentModel"),
|
|
567
|
+
...effectiveOptionalTuning(binding, agent, "subagentEffort"),
|
|
561
568
|
},
|
|
562
569
|
];
|
|
563
570
|
}),
|
|
@@ -687,9 +694,9 @@ export async function normalizeLaunchPlan(
|
|
|
687
694
|
const configuredAgents = new Map();
|
|
688
695
|
for (const playerId of allPlayerIds) {
|
|
689
696
|
assertPlayerId(playerId, `players.${playerId}`);
|
|
690
|
-
const agent =
|
|
691
|
-
"id",
|
|
692
|
-
|
|
697
|
+
const agent = resolveDelegationDefault(
|
|
698
|
+
resolveAgent(playersCfg[playerId], `players.${playerId}`, ["id"]),
|
|
699
|
+
);
|
|
693
700
|
if (
|
|
694
701
|
typeof agent.adapter !== "string" ||
|
|
695
702
|
agent.adapter.trim().length === 0
|
|
@@ -708,7 +715,9 @@ export async function normalizeLaunchPlan(
|
|
|
708
715
|
throw new Error("playbooks keys must be canonical trimmed nonblank ids");
|
|
709
716
|
}
|
|
710
717
|
|
|
711
|
-
let captain =
|
|
718
|
+
let captain = resolveDelegationDefault(
|
|
719
|
+
resolveAgent(top.captain, "captain", ["from", "options"]),
|
|
720
|
+
);
|
|
712
721
|
if (
|
|
713
722
|
typeof captain.adapter !== "string" ||
|
|
714
723
|
captain.adapter.trim().length === 0
|
|
@@ -787,7 +796,9 @@ export async function normalizeLaunchPlan(
|
|
|
787
796
|
if (
|
|
788
797
|
binding.model !== undefined ||
|
|
789
798
|
binding.effort !== undefined ||
|
|
790
|
-
binding.fastMode !== undefined
|
|
799
|
+
binding.fastMode !== undefined ||
|
|
800
|
+
binding.subagentModel !== undefined ||
|
|
801
|
+
binding.subagentEffort !== undefined
|
|
791
802
|
) {
|
|
792
803
|
let checkId;
|
|
793
804
|
do {
|
|
@@ -999,6 +1010,8 @@ export async function normalizeLaunchPlan(
|
|
|
999
1010
|
? {}
|
|
1000
1011
|
: { fastMode: agent.fastMode }
|
|
1001
1012
|
: { fastMode: binding.fastMode }),
|
|
1013
|
+
...effectiveOptionalTuning(binding, agent, "subagentModel"),
|
|
1014
|
+
...effectiveOptionalTuning(binding, agent, "subagentEffort"),
|
|
1002
1015
|
},
|
|
1003
1016
|
];
|
|
1004
1017
|
}),
|
|
@@ -1484,7 +1497,8 @@ function migrateUserConfigIfRetired(userConfigPath, onNotice) {
|
|
|
1484
1497
|
throw new Error(
|
|
1485
1498
|
`cannot migrate the retired profiles config at ${userConfigPath}: ` +
|
|
1486
1499
|
`${errorMessage(error)} — edit it by hand: each agent takes its own ` +
|
|
1487
|
-
"adapter, model, effort, fast mode,
|
|
1500
|
+
"adapter, model, effort, fast mode, subagent model, subagent effort, " +
|
|
1501
|
+
"and permissions",
|
|
1488
1502
|
);
|
|
1489
1503
|
}
|
|
1490
1504
|
if (migrated === undefined) return;
|
|
@@ -1620,7 +1634,8 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1620
1634
|
throw new Error(
|
|
1621
1635
|
`top-level "profiles" was removed${where}: write each agent's settings ` +
|
|
1622
1636
|
"inline under captain and each top-level players.<player-id> " +
|
|
1623
|
-
"(adapter, model, effort, fast mode,
|
|
1637
|
+
"(adapter, model, effort, fast mode, subagent model, subagent effort, " +
|
|
1638
|
+
"permissions)",
|
|
1624
1639
|
);
|
|
1625
1640
|
}
|
|
1626
1641
|
const legacyPath = findLegacyPlayersPath(top);
|
|
@@ -1635,7 +1650,8 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1635
1650
|
if (isObject(block) && block.profile !== undefined) {
|
|
1636
1651
|
throw new Error(
|
|
1637
1652
|
`${path}.profile was removed${where}: write the agent's settings ` +
|
|
1638
|
-
"inline in that block (adapter, model, effort, fast mode,
|
|
1653
|
+
"inline in that block (adapter, model, effort, fast mode, " +
|
|
1654
|
+
"subagent model, subagent effort, permissions)",
|
|
1639
1655
|
);
|
|
1640
1656
|
}
|
|
1641
1657
|
}
|
|
@@ -2034,13 +2050,21 @@ function resolveRoleBinding(value, path) {
|
|
|
2034
2050
|
}
|
|
2035
2051
|
const block = requireObject(value, path);
|
|
2036
2052
|
const unknown = Object.keys(block).filter(
|
|
2037
|
-
(key) =>
|
|
2053
|
+
(key) =>
|
|
2054
|
+
![
|
|
2055
|
+
"player",
|
|
2056
|
+
"model",
|
|
2057
|
+
"effort",
|
|
2058
|
+
"fastMode",
|
|
2059
|
+
"subagentModel",
|
|
2060
|
+
"subagentEffort",
|
|
2061
|
+
].includes(key),
|
|
2038
2062
|
);
|
|
2039
2063
|
if (unknown.length > 0) {
|
|
2040
2064
|
throw new Error(`${path} has unknown ${formatKeyList(unknown)}`);
|
|
2041
2065
|
}
|
|
2042
2066
|
assertPlayerId(block.player, `${path}.player`);
|
|
2043
|
-
for (const field of ["model", "effort"]) {
|
|
2067
|
+
for (const field of ["model", "effort", "subagentModel", "subagentEffort"]) {
|
|
2044
2068
|
if (
|
|
2045
2069
|
block[field] !== undefined &&
|
|
2046
2070
|
block[field] !== false &&
|
|
@@ -2062,9 +2086,46 @@ function resolveRoleBinding(value, path) {
|
|
|
2062
2086
|
...(block.model === undefined ? {} : { model: block.model }),
|
|
2063
2087
|
...(block.effort === undefined ? {} : { effort: block.effort }),
|
|
2064
2088
|
...(block.fastMode === undefined ? {} : { fastMode: block.fastMode }),
|
|
2089
|
+
...(block.subagentModel === undefined
|
|
2090
|
+
? {}
|
|
2091
|
+
: { subagentModel: block.subagentModel }),
|
|
2092
|
+
...(block.subagentEffort === undefined
|
|
2093
|
+
? {}
|
|
2094
|
+
: { subagentEffort: block.subagentEffort }),
|
|
2065
2095
|
};
|
|
2066
2096
|
}
|
|
2067
2097
|
|
|
2098
|
+
// A binding's subagent model or subagent effort overrides the player's;
|
|
2099
|
+
// `false` selects the provider default, which a complete call expresses by
|
|
2100
|
+
// omission (for the effort: the agent chooses per task).
|
|
2101
|
+
function effectiveOptionalTuning(binding, agent, field) {
|
|
2102
|
+
if (binding[field] === false) return {};
|
|
2103
|
+
if (binding[field] !== undefined) return { [field]: binding[field] };
|
|
2104
|
+
return agent[field] === undefined ? {} : { [field]: agent[field] };
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
// DR-076: an agent whose adapter serves a subagent model delegates by
|
|
2108
|
+
// default. Resolved on the launcher's detached copy of a Captain or player
|
|
2109
|
+
// block — never the user's file — before cligent normalization, so cligent
|
|
2110
|
+
// validates a subagent effort against the resolved model, and both front
|
|
2111
|
+
// ends and the headless run share the result. A block's `false` opts out:
|
|
2112
|
+
// nothing is sent and no delegation directive is composed.
|
|
2113
|
+
function resolveDelegationDefault(agent) {
|
|
2114
|
+
if (!isObject(agent)) return agent;
|
|
2115
|
+
if (agent.subagentModel === false) {
|
|
2116
|
+
const { subagentModel: _off, ...rest } = agent;
|
|
2117
|
+
return rest;
|
|
2118
|
+
}
|
|
2119
|
+
if (
|
|
2120
|
+
agent.subagentModel === undefined &&
|
|
2121
|
+
typeof agent.adapter === "string" &&
|
|
2122
|
+
isSubagentModelSupported(agent.adapter)
|
|
2123
|
+
) {
|
|
2124
|
+
return { ...agent, subagentModel: "inherit" };
|
|
2125
|
+
}
|
|
2126
|
+
return agent;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2068
2129
|
function applyTuningOverrides(agent, binding) {
|
|
2069
2130
|
const effective = { ...agent };
|
|
2070
2131
|
if (binding.model === false) delete effective.model;
|
|
@@ -2075,6 +2136,10 @@ function applyTuningOverrides(agent, binding) {
|
|
|
2075
2136
|
else effective.effort = binding.effort;
|
|
2076
2137
|
}
|
|
2077
2138
|
if (binding.fastMode !== undefined) effective.fastMode = binding.fastMode;
|
|
2139
|
+
for (const field of ["subagentModel", "subagentEffort"]) {
|
|
2140
|
+
if (binding[field] === false) delete effective[field];
|
|
2141
|
+
else if (binding[field] !== undefined) effective[field] = binding[field];
|
|
2142
|
+
}
|
|
2078
2143
|
return effective;
|
|
2079
2144
|
}
|
|
2080
2145
|
|
|
@@ -2089,6 +2154,22 @@ function sessionAgentFromHostAgent(agent, path) {
|
|
|
2089
2154
|
...(agent.fastMode === undefined
|
|
2090
2155
|
? {}
|
|
2091
2156
|
: { fastMode: fastModeSelection(agent.fastMode, `${path}.fastMode`) }),
|
|
2157
|
+
...(agent.subagentModel === undefined
|
|
2158
|
+
? {}
|
|
2159
|
+
: {
|
|
2160
|
+
subagentModel: nonblankSelection(
|
|
2161
|
+
agent.subagentModel,
|
|
2162
|
+
`${path}.subagentModel`,
|
|
2163
|
+
),
|
|
2164
|
+
}),
|
|
2165
|
+
...(agent.subagentEffort === undefined
|
|
2166
|
+
? {}
|
|
2167
|
+
: {
|
|
2168
|
+
subagentEffort: nonblankSelection(
|
|
2169
|
+
agent.subagentEffort,
|
|
2170
|
+
`${path}.subagentEffort`,
|
|
2171
|
+
),
|
|
2172
|
+
}),
|
|
2092
2173
|
...(agent.instruction === undefined
|
|
2093
2174
|
? {}
|
|
2094
2175
|
: { instruction: agent.instruction }),
|
|
@@ -2114,6 +2195,12 @@ export function projectHostAgent(agent, path = "agent") {
|
|
|
2114
2195
|
...(normalized.fastMode === undefined
|
|
2115
2196
|
? {}
|
|
2116
2197
|
: { fastMode: normalized.fastMode }),
|
|
2198
|
+
...(normalized.subagentModel === undefined
|
|
2199
|
+
? {}
|
|
2200
|
+
: { subagentModel: normalized.subagentModel }),
|
|
2201
|
+
...(normalized.subagentEffort === undefined
|
|
2202
|
+
? {}
|
|
2203
|
+
: { subagentEffort: normalized.subagentEffort }),
|
|
2117
2204
|
...(normalized.instruction === undefined
|
|
2118
2205
|
? {}
|
|
2119
2206
|
: { instruction: normalized.instruction }),
|
|
@@ -2130,6 +2217,13 @@ function fastModeSelection(value, path) {
|
|
|
2130
2217
|
return value;
|
|
2131
2218
|
}
|
|
2132
2219
|
|
|
2220
|
+
function nonblankSelection(value, path) {
|
|
2221
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2222
|
+
throw new Error(`${path} must be a nonblank string`);
|
|
2223
|
+
}
|
|
2224
|
+
return value;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2133
2227
|
function tuningSelection(value, path) {
|
|
2134
2228
|
if (
|
|
2135
2229
|
value !== undefined &&
|
|
@@ -966,9 +966,12 @@ 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;
|
|
971
|
-
"
|
|
969
|
+
" a scalar names the id, while { player, model?, effort?, fastMode?,",
|
|
970
|
+
" subagentModel?, subagentEffort? } may retune one role;",
|
|
971
|
+
" false selects provider-default model/effort/subagentModel/",
|
|
972
|
+
" subagentEffort, while fastMode false is a literal disabled request",
|
|
973
|
+
" - a Claude agent delegates by default: subagentModel resolves to",
|
|
974
|
+
" inherit (its own model) unless the block sets false",
|
|
972
975
|
" - reusing one id deliberately shares that provider conversation;",
|
|
973
976
|
" distinct ids stay isolated even when their agent blocks are equal",
|
|
974
977
|
" - the launcher injects captain.from and retains referenced player ids",
|
|
@@ -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,45 @@ export async function validateFrozenExecutionConfig(
|
|
|
1364
1367
|
return config;
|
|
1365
1368
|
}
|
|
1366
1369
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1370
|
+
// Fast mode, the subagent model and the subagent effort are adapter-scoped
|
|
1371
|
+
// tuning (DR-075, DR-076): a frozen record re-validates each present value
|
|
1372
|
+
// against its adapter. Cligent serves the subagent effort exactly where it
|
|
1373
|
+
// serves a subagent model, so one capability assertion covers both.
|
|
1374
|
+
const FROZEN_ADAPTER_SCOPED_TUNING = [
|
|
1375
|
+
["fastMode", assertFastModeSupported],
|
|
1376
|
+
["subagentModel", assertSubagentModelSupported],
|
|
1377
|
+
["subagentEffort", assertSubagentModelSupported],
|
|
1378
|
+
];
|
|
1379
|
+
|
|
1380
|
+
function assertFrozenTuningSupported(config) {
|
|
1381
|
+
const assertSupported = (record, adapter, path) => {
|
|
1382
|
+
for (const [field, assertFieldSupported] of FROZEN_ADAPTER_SCOPED_TUNING) {
|
|
1383
|
+
if (record[field] !== undefined) {
|
|
1384
|
+
assertFieldSupported(adapter, `${path} ${field}`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
assertSupported(config.captain, config.captain.adapter, "stored Captain");
|
|
1374
1389
|
|
|
1375
1390
|
const playerAdapters = new Map();
|
|
1376
1391
|
for (const player of config.players) {
|
|
1377
1392
|
playerAdapters.set(player.id, player.adapter);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1393
|
+
assertSupported(
|
|
1394
|
+
player,
|
|
1395
|
+
player.adapter,
|
|
1396
|
+
`stored player ${JSON.stringify(player.id)}`,
|
|
1397
|
+
);
|
|
1384
1398
|
}
|
|
1385
1399
|
|
|
1386
1400
|
for (const [playbookId, item] of Object.entries(config.catalog)) {
|
|
1387
1401
|
for (const [roleId, binding] of Object.entries(item.roles)) {
|
|
1388
|
-
if (
|
|
1402
|
+
if (
|
|
1403
|
+
FROZEN_ADAPTER_SCOPED_TUNING.every(
|
|
1404
|
+
([field]) => binding[field] === undefined,
|
|
1405
|
+
)
|
|
1406
|
+
) {
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1389
1409
|
const adapter = playerAdapters.get(binding.playerId);
|
|
1390
1410
|
if (adapter === undefined) {
|
|
1391
1411
|
throw new Error(
|
|
@@ -1393,10 +1413,11 @@ function assertFrozenFastModesSupported(config) {
|
|
|
1393
1413
|
`${JSON.stringify(roleId)} names an unknown player`,
|
|
1394
1414
|
);
|
|
1395
1415
|
}
|
|
1396
|
-
|
|
1416
|
+
assertSupported(
|
|
1417
|
+
binding,
|
|
1397
1418
|
adapter,
|
|
1398
1419
|
`stored playbook ${JSON.stringify(playbookId)} role ` +
|
|
1399
|
-
`${JSON.stringify(roleId)}
|
|
1420
|
+
`${JSON.stringify(roleId)}`,
|
|
1400
1421
|
);
|
|
1401
1422
|
}
|
|
1402
1423
|
}
|
|
@@ -1869,7 +1890,7 @@ function runHelpText(userConfigPath) {
|
|
|
1869
1890
|
"working directory and reports when it uses the global newest fallback.",
|
|
1870
1891
|
"An ordinary continued run restores that stored structure and working",
|
|
1871
1892
|
"directory, then reads current config and overlays for model, effort,",
|
|
1872
|
-
"and
|
|
1893
|
+
"fast mode, subagent model, and subagent effort.",
|
|
1873
1894
|
"Uncertain retry only restores and reports; later work uses current settings.",
|
|
1874
1895
|
"",
|
|
1875
1896
|
"Options:",
|
|
@@ -5402,7 +5402,13 @@ function validateCaptainSessionProjection(
|
|
|
5402
5402
|
: ['id', 'adapter', 'model', 'effort'],
|
|
5403
5403
|
structural
|
|
5404
5404
|
? ['instruction', 'permissions']
|
|
5405
|
-
: [
|
|
5405
|
+
: [
|
|
5406
|
+
'instruction',
|
|
5407
|
+
'permissions',
|
|
5408
|
+
'fastMode',
|
|
5409
|
+
'subagentModel',
|
|
5410
|
+
'subagentEffort',
|
|
5411
|
+
],
|
|
5406
5412
|
playerPath,
|
|
5407
5413
|
);
|
|
5408
5414
|
assertPlayerId(player.id, `${playerPath}.id`);
|
|
@@ -5503,12 +5509,13 @@ function validateCaptainSessionProjection(
|
|
|
5503
5509
|
structural
|
|
5504
5510
|
? ['playerId']
|
|
5505
5511
|
: ['playerId', 'model', 'effort'],
|
|
5506
|
-
structural ? [] : ['fastMode'],
|
|
5512
|
+
structural ? [] : ['fastMode', 'subagentModel', 'subagentEffort'],
|
|
5507
5513
|
bindingPath,
|
|
5508
5514
|
);
|
|
5509
5515
|
assertPlayerId(binding.playerId, `${bindingPath}.playerId`);
|
|
5510
5516
|
if (!structural) {
|
|
5511
5517
|
validateProjectedFastMode(binding, `${bindingPath}.fastMode`);
|
|
5518
|
+
validateProjectedSubagentTuning(binding, bindingPath);
|
|
5512
5519
|
validateTuningSelection(binding.model, `${bindingPath}.model`);
|
|
5513
5520
|
const player = projection.players.find(
|
|
5514
5521
|
(candidate) => candidate.id === binding.playerId,
|
|
@@ -5556,16 +5563,36 @@ function validateProjectedFastMode(record, path) {
|
|
|
5556
5563
|
}
|
|
5557
5564
|
}
|
|
5558
5565
|
|
|
5566
|
+
// Like fast mode, absence is the canonical provider default (DR-075); the
|
|
5567
|
+
// subagent model may be the literal `inherit`, and an absent subagent effort
|
|
5568
|
+
// leaves each subagent's effort to the agent (DR-076).
|
|
5569
|
+
function validateProjectedSubagentTuning(record, path) {
|
|
5570
|
+
for (const field of ['subagentModel', 'subagentEffort']) {
|
|
5571
|
+
if (Object.hasOwn(record, field)) {
|
|
5572
|
+
requireNonblank(record[field], `${path}.${field}`);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5559
5577
|
function validateProjectedAgent(value, path, { structural, hasId = false }) {
|
|
5560
5578
|
const agent = requireRecord(value, path);
|
|
5561
|
-
if (!structural)
|
|
5579
|
+
if (!structural) {
|
|
5580
|
+
validateProjectedFastMode(agent, `${path}.fastMode`);
|
|
5581
|
+
validateProjectedSubagentTuning(agent, path);
|
|
5582
|
+
}
|
|
5562
5583
|
if (!hasId) {
|
|
5563
5584
|
exactOptionalKeys(
|
|
5564
5585
|
agent,
|
|
5565
5586
|
structural ? ['adapter'] : ['adapter', 'model', 'effort'],
|
|
5566
5587
|
structural
|
|
5567
5588
|
? ['instruction', 'permissions']
|
|
5568
|
-
: [
|
|
5589
|
+
: [
|
|
5590
|
+
'instruction',
|
|
5591
|
+
'permissions',
|
|
5592
|
+
'fastMode',
|
|
5593
|
+
'subagentModel',
|
|
5594
|
+
'subagentEffort',
|
|
5595
|
+
],
|
|
5569
5596
|
path,
|
|
5570
5597
|
);
|
|
5571
5598
|
}
|
|
@@ -10,9 +10,17 @@ 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), or `inherit`
|
|
14
|
+
* for the agent's own model (DR-076). Absence is the provider default. */
|
|
15
|
+
readonly subagentModel?: string;
|
|
16
|
+
/** The effort every subagent of this agent runs at (DR-076). Absence leaves
|
|
17
|
+
* each subagent's effort to the agent. */
|
|
18
|
+
readonly subagentEffort?: string;
|
|
13
19
|
readonly instruction?: string;
|
|
14
20
|
readonly permissions?: PermissionPolicy;
|
|
15
21
|
}
|
|
22
|
+
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
23
|
+
type TuningKey = 'model' | 'effort' | 'fastMode' | 'subagentModel' | 'subagentEffort';
|
|
16
24
|
interface PlayerLedgerEntry {
|
|
17
25
|
readonly adapter: string;
|
|
18
26
|
readonly instruction?: string;
|
|
@@ -32,7 +40,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
32
40
|
readonly rootPlaybookId: string;
|
|
33
41
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
34
42
|
}
|
|
35
|
-
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent,
|
|
43
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
36
44
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
37
45
|
export interface ProgressChange {
|
|
38
46
|
snapshot?: PlaybookCaptainShellSnapshot | null;
|
|
@@ -2098,10 +2098,30 @@ function snapshotFastMode(value, path) {
|
|
|
2098
2098
|
}
|
|
2099
2099
|
return value;
|
|
2100
2100
|
}
|
|
2101
|
+
function snapshotNonblankTuning(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
|
+
'subagentEffort',
|
|
2117
|
+
'instruction',
|
|
2118
|
+
'permissions',
|
|
2119
|
+
], path);
|
|
2120
|
+
const fixed = snapshotFixedAgent(Object.fromEntries(Object.entries(agent).filter(([key]) => key !== 'model' &&
|
|
2121
|
+
key !== 'effort' &&
|
|
2122
|
+
key !== 'fastMode' &&
|
|
2123
|
+
key !== 'subagentModel' &&
|
|
2124
|
+
key !== 'subagentEffort')), path);
|
|
2105
2125
|
return {
|
|
2106
2126
|
adapter: fixed.adapter,
|
|
2107
2127
|
...(fixed.instruction === undefined
|
|
@@ -2115,6 +2135,16 @@ function snapshotSessionAgent(value, path) {
|
|
|
2115
2135
|
...(agent.fastMode === undefined
|
|
2116
2136
|
? {}
|
|
2117
2137
|
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
2138
|
+
...(agent.subagentModel === undefined
|
|
2139
|
+
? {}
|
|
2140
|
+
: {
|
|
2141
|
+
subagentModel: snapshotNonblankTuning(agent.subagentModel, `${path}.subagentModel`),
|
|
2142
|
+
}),
|
|
2143
|
+
...(agent.subagentEffort === undefined
|
|
2144
|
+
? {}
|
|
2145
|
+
: {
|
|
2146
|
+
subagentEffort: snapshotNonblankTuning(agent.subagentEffort, `${path}.subagentEffort`),
|
|
2147
|
+
}),
|
|
2118
2148
|
};
|
|
2119
2149
|
}
|
|
2120
2150
|
function fixedAgent(agent) {
|
|
@@ -2126,12 +2156,20 @@ function fixedAgent(agent) {
|
|
|
2126
2156
|
}
|
|
2127
2157
|
function callSettings(agent, tuning = agent) {
|
|
2128
2158
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
2129
|
-
// omitted fastMode here is a request for the provider
|
|
2130
|
-
//
|
|
2159
|
+
// omitted fastMode or subagentModel here is a request for the provider
|
|
2160
|
+
// default, and an omitted subagentEffort leaves each subagent's effort to
|
|
2161
|
+
// the agent — never an inheritance of whatever the previous call left
|
|
2162
|
+
// behind.
|
|
2131
2163
|
return {
|
|
2132
2164
|
model: tuning.model,
|
|
2133
2165
|
effort: tuning.effort,
|
|
2134
2166
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
2167
|
+
...(tuning.subagentModel === undefined
|
|
2168
|
+
? {}
|
|
2169
|
+
: { subagentModel: tuning.subagentModel }),
|
|
2170
|
+
...(tuning.subagentEffort === undefined
|
|
2171
|
+
? {}
|
|
2172
|
+
: { subagentEffort: tuning.subagentEffort }),
|
|
2135
2173
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
2136
2174
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
2137
2175
|
};
|
|
@@ -2259,7 +2297,14 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2259
2297
|
for (const role of entry.requiredRoleIds) {
|
|
2260
2298
|
const path = `captain.options.playbooks.${id}.roles.${role}`;
|
|
2261
2299
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
2262
|
-
rejectSnapshotKeys(rawBinding, [
|
|
2300
|
+
rejectSnapshotKeys(rawBinding, [
|
|
2301
|
+
'playerId',
|
|
2302
|
+
'model',
|
|
2303
|
+
'effort',
|
|
2304
|
+
'fastMode',
|
|
2305
|
+
'subagentModel',
|
|
2306
|
+
'subagentEffort',
|
|
2307
|
+
], path);
|
|
2263
2308
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
2264
2309
|
if (!PLAYER_ID_PATTERN.test(playerId) || playerId === INTERNAL_CAPTAIN_ID) {
|
|
2265
2310
|
throw new Error(`${path}.playerId is not a canonical player id`);
|
|
@@ -2277,6 +2322,16 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2277
2322
|
: {
|
|
2278
2323
|
fastMode: snapshotFastMode(rawBinding.fastMode, `${path}.fastMode`),
|
|
2279
2324
|
}),
|
|
2325
|
+
...(rawBinding.subagentModel === undefined
|
|
2326
|
+
? {}
|
|
2327
|
+
: {
|
|
2328
|
+
subagentModel: snapshotNonblankTuning(rawBinding.subagentModel, `${path}.subagentModel`),
|
|
2329
|
+
}),
|
|
2330
|
+
...(rawBinding.subagentEffort === undefined
|
|
2331
|
+
? {}
|
|
2332
|
+
: {
|
|
2333
|
+
subagentEffort: snapshotNonblankTuning(rawBinding.subagentEffort, `${path}.subagentEffort`),
|
|
2334
|
+
}),
|
|
2280
2335
|
agent,
|
|
2281
2336
|
});
|
|
2282
2337
|
}
|
|
@@ -69,10 +69,24 @@ 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), or `inherit`
|
|
73
|
+
* for the agent's own model (DR-076). Absence is the provider default. */
|
|
74
|
+
readonly subagentModel?: string;
|
|
75
|
+
/** The effort every subagent of this agent runs at (DR-076). Absence leaves
|
|
76
|
+
* each subagent's effort to the agent. */
|
|
77
|
+
readonly subagentEffort?: string;
|
|
72
78
|
readonly instruction?: string;
|
|
73
79
|
readonly permissions?: PermissionPolicy;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
83
|
+
type TuningKey =
|
|
84
|
+
| 'model'
|
|
85
|
+
| 'effort'
|
|
86
|
+
| 'fastMode'
|
|
87
|
+
| 'subagentModel'
|
|
88
|
+
| 'subagentEffort';
|
|
89
|
+
|
|
76
90
|
interface PlayerLedgerEntry {
|
|
77
91
|
readonly adapter: string;
|
|
78
92
|
readonly instruction?: string;
|
|
@@ -107,9 +121,7 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
107
121
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
type SnapshotAgentEnvelope = DeepReadonly<
|
|
111
|
-
Omit<SessionAgent, 'model' | 'effort' | 'fastMode'>
|
|
112
|
-
>;
|
|
124
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
113
125
|
|
|
114
126
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
115
127
|
|
|
@@ -447,6 +459,8 @@ interface EffectivePlayerBinding {
|
|
|
447
459
|
readonly model: TuningSelection;
|
|
448
460
|
readonly effort: TuningSelection<Effort>;
|
|
449
461
|
readonly fastMode?: boolean;
|
|
462
|
+
readonly subagentModel?: string;
|
|
463
|
+
readonly subagentEffort?: string;
|
|
450
464
|
readonly agent: SessionAgent;
|
|
451
465
|
}
|
|
452
466
|
|
|
@@ -3722,6 +3736,19 @@ function snapshotFastMode(
|
|
|
3722
3736
|
return value;
|
|
3723
3737
|
}
|
|
3724
3738
|
|
|
3739
|
+
function snapshotNonblankTuning(
|
|
3740
|
+
value: JsonValue | undefined,
|
|
3741
|
+
path: string,
|
|
3742
|
+
): string | undefined {
|
|
3743
|
+
if (
|
|
3744
|
+
value !== undefined &&
|
|
3745
|
+
(typeof value !== 'string' || value.trim().length === 0)
|
|
3746
|
+
) {
|
|
3747
|
+
throw new TypeError(`${path} must be a nonblank string`);
|
|
3748
|
+
}
|
|
3749
|
+
return value;
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3725
3752
|
function snapshotSessionAgent(
|
|
3726
3753
|
value: JsonValue | undefined,
|
|
3727
3754
|
path: string,
|
|
@@ -3729,14 +3756,27 @@ function snapshotSessionAgent(
|
|
|
3729
3756
|
const agent = snapshotRecord(value, path);
|
|
3730
3757
|
rejectSnapshotKeys(
|
|
3731
3758
|
agent,
|
|
3732
|
-
[
|
|
3759
|
+
[
|
|
3760
|
+
'adapter',
|
|
3761
|
+
'model',
|
|
3762
|
+
'effort',
|
|
3763
|
+
'fastMode',
|
|
3764
|
+
'subagentModel',
|
|
3765
|
+
'subagentEffort',
|
|
3766
|
+
'instruction',
|
|
3767
|
+
'permissions',
|
|
3768
|
+
],
|
|
3733
3769
|
path,
|
|
3734
3770
|
);
|
|
3735
3771
|
const fixed = snapshotFixedAgent(
|
|
3736
3772
|
Object.fromEntries(
|
|
3737
3773
|
Object.entries(agent).filter(
|
|
3738
3774
|
([key]) =>
|
|
3739
|
-
key !== 'model' &&
|
|
3775
|
+
key !== 'model' &&
|
|
3776
|
+
key !== 'effort' &&
|
|
3777
|
+
key !== 'fastMode' &&
|
|
3778
|
+
key !== 'subagentModel' &&
|
|
3779
|
+
key !== 'subagentEffort',
|
|
3740
3780
|
),
|
|
3741
3781
|
) as JsonValue,
|
|
3742
3782
|
path,
|
|
@@ -3754,12 +3794,26 @@ function snapshotSessionAgent(
|
|
|
3754
3794
|
...(agent.fastMode === undefined
|
|
3755
3795
|
? {}
|
|
3756
3796
|
: { fastMode: snapshotFastMode(agent.fastMode, `${path}.fastMode`) }),
|
|
3797
|
+
...(agent.subagentModel === undefined
|
|
3798
|
+
? {}
|
|
3799
|
+
: {
|
|
3800
|
+
subagentModel: snapshotNonblankTuning(
|
|
3801
|
+
agent.subagentModel,
|
|
3802
|
+
`${path}.subagentModel`,
|
|
3803
|
+
),
|
|
3804
|
+
}),
|
|
3805
|
+
...(agent.subagentEffort === undefined
|
|
3806
|
+
? {}
|
|
3807
|
+
: {
|
|
3808
|
+
subagentEffort: snapshotNonblankTuning(
|
|
3809
|
+
agent.subagentEffort,
|
|
3810
|
+
`${path}.subagentEffort`,
|
|
3811
|
+
),
|
|
3812
|
+
}),
|
|
3757
3813
|
};
|
|
3758
3814
|
}
|
|
3759
3815
|
|
|
3760
|
-
function fixedAgent(
|
|
3761
|
-
agent: SessionAgent,
|
|
3762
|
-
): Omit<SessionAgent, 'model' | 'effort' | 'fastMode'> {
|
|
3816
|
+
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey> {
|
|
3763
3817
|
return {
|
|
3764
3818
|
adapter: agent.adapter,
|
|
3765
3819
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
@@ -3769,15 +3823,23 @@ function fixedAgent(
|
|
|
3769
3823
|
|
|
3770
3824
|
function callSettings(
|
|
3771
3825
|
agent: SessionAgent,
|
|
3772
|
-
tuning: Pick<SessionAgent,
|
|
3826
|
+
tuning: Pick<SessionAgent, TuningKey> = agent,
|
|
3773
3827
|
): AgentCallSettings {
|
|
3774
3828
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
3775
|
-
// omitted fastMode here is a request for the provider
|
|
3776
|
-
//
|
|
3829
|
+
// omitted fastMode or subagentModel here is a request for the provider
|
|
3830
|
+
// default, and an omitted subagentEffort leaves each subagent's effort to
|
|
3831
|
+
// the agent — never an inheritance of whatever the previous call left
|
|
3832
|
+
// behind.
|
|
3777
3833
|
return {
|
|
3778
3834
|
model: tuning.model,
|
|
3779
3835
|
effort: tuning.effort,
|
|
3780
3836
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
3837
|
+
...(tuning.subagentModel === undefined
|
|
3838
|
+
? {}
|
|
3839
|
+
: { subagentModel: tuning.subagentModel }),
|
|
3840
|
+
...(tuning.subagentEffort === undefined
|
|
3841
|
+
? {}
|
|
3842
|
+
: { subagentEffort: tuning.subagentEffort as Effort }),
|
|
3781
3843
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
3782
3844
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
3783
3845
|
};
|
|
@@ -4005,7 +4067,14 @@ async function buildEnablements(
|
|
|
4005
4067
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
4006
4068
|
rejectSnapshotKeys(
|
|
4007
4069
|
rawBinding,
|
|
4008
|
-
[
|
|
4070
|
+
[
|
|
4071
|
+
'playerId',
|
|
4072
|
+
'model',
|
|
4073
|
+
'effort',
|
|
4074
|
+
'fastMode',
|
|
4075
|
+
'subagentModel',
|
|
4076
|
+
'subagentEffort',
|
|
4077
|
+
],
|
|
4009
4078
|
path,
|
|
4010
4079
|
);
|
|
4011
4080
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
@@ -4030,6 +4099,22 @@ async function buildEnablements(
|
|
|
4030
4099
|
`${path}.fastMode`,
|
|
4031
4100
|
),
|
|
4032
4101
|
}),
|
|
4102
|
+
...(rawBinding.subagentModel === undefined
|
|
4103
|
+
? {}
|
|
4104
|
+
: {
|
|
4105
|
+
subagentModel: snapshotNonblankTuning(
|
|
4106
|
+
rawBinding.subagentModel,
|
|
4107
|
+
`${path}.subagentModel`,
|
|
4108
|
+
),
|
|
4109
|
+
}),
|
|
4110
|
+
...(rawBinding.subagentEffort === undefined
|
|
4111
|
+
? {}
|
|
4112
|
+
: {
|
|
4113
|
+
subagentEffort: snapshotNonblankTuning(
|
|
4114
|
+
rawBinding.subagentEffort,
|
|
4115
|
+
`${path}.subagentEffort`,
|
|
4116
|
+
),
|
|
4117
|
+
}),
|
|
4033
4118
|
agent,
|
|
4034
4119
|
});
|
|
4035
4120
|
}
|
|
@@ -8,10 +8,15 @@
|
|
|
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
|
|
12
|
-
# effort, or
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
# adapter/model/effort/fastMode/subagentModel/subagentEffort/permissions. A
|
|
12
|
+
# role may override only model, effort, fastMode, subagentModel, or
|
|
13
|
+
# subagentEffort.
|
|
14
|
+
# Omit an override to inherit the player default. For model, effort,
|
|
15
|
+
# subagentModel, and subagentEffort, false selects the provider default;
|
|
16
|
+
# fastMode false is a literal disabled request.
|
|
17
|
+
# A Claude agent delegates by default: its subagents run on its own model
|
|
18
|
+
# (subagentModel: inherit) at efforts it chooses per task; subagentModel: false
|
|
19
|
+
# on its block switches delegation off.
|
|
15
20
|
|
|
16
21
|
# Each adapter needs its vendor SDK installed as its own top-level
|
|
17
22
|
# install root — they are optional peer dependencies, so you pay only
|