@sublang/playbook 17.2.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 +6 -5
- package/docs/configuration.md +31 -18
- package/package.json +2 -2
- package/reference/sdlc/code.playbook/bin/launch-config.js +82 -31
- package/reference/sdlc/code.playbook/bin/playbook.js +5 -3
- package/reference/sdlc/code.playbook/bin/run.js +6 -3
- package/reference/sdlc/code.playbook/bin/session-store.js +25 -12
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +6 -3
- package/reference/sdlc/code.playbook/playbook-captain.js +30 -6
- package/reference/sdlc/code.playbook/playbook-captain.ts +48 -9
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +9 -5
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, fast mode,
|
|
206
|
-
subagent model in a `--with` 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,9 +280,10 @@ 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
|
-
apply to the next call. Boolean `false` explicitly
|
|
285
|
-
|
|
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
|
|
286
287
|
disabled request; omission selects the provider default or inherits the player
|
|
287
288
|
value at a role binding.
|
|
288
289
|
Provider conversations are local hints. Missing hints start fresh from stored
|
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`, `subagentModel`, `instruction`, and
|
|
52
|
-
Settings are inline per 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
|
|
@@ -157,7 +157,7 @@ roles:
|
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
Use a block to override only that role invocation's model, effort, fast mode,
|
|
160
|
-
or subagent
|
|
160
|
+
subagent model, or subagent effort:
|
|
161
161
|
|
|
162
162
|
```yaml
|
|
163
163
|
roles:
|
|
@@ -168,25 +168,36 @@ roles:
|
|
|
168
168
|
fastMode: false # literal disabled request, not a default sentinel
|
|
169
169
|
reviewer:
|
|
170
170
|
player: dev.reviewer
|
|
171
|
-
subagentModel:
|
|
171
|
+
subagentModel: inherit # inherit | <model> | false
|
|
172
|
+
subagentEffort: medium # <effort> | false
|
|
172
173
|
```
|
|
173
174
|
|
|
174
175
|
Omitting any override inherits that player's top-level default. For `model`,
|
|
175
|
-
`effort`, and `
|
|
176
|
-
explicitly, so a resumed conversation cannot accidentally
|
|
177
|
-
selection. For `fastMode`, `false` is a literal request to
|
|
178
|
-
omitting the top-level setting selects the provider default.
|
|
179
|
-
fast-mode boolean or subagent
|
|
180
|
-
reports as supporting it. A role binding cannot override adapter, instruction,
|
|
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,
|
|
181
182
|
permissions, workspace, or tool posture; those define the stable player
|
|
182
183
|
envelope, so an overriding model must be one that player's adapter serves.
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
186
189
|
keeping the deep thinking, reasoning, and design work itself, without lowering
|
|
187
190
|
the quality of what it delivers
|
|
188
191
|
([DR-075](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/075-subagent-model-is-tuning.md)).
|
|
189
|
-
|
|
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
|
+
|
|
190
201
|
|
|
191
202
|
## Sharing, isolation, and concurrency
|
|
192
203
|
|
|
@@ -279,6 +290,7 @@ playbooks:
|
|
|
279
290
|
coder:
|
|
280
291
|
player: dev.coder
|
|
281
292
|
effort: low
|
|
293
|
+
subagentEffort: high # its subagents still run at high effort
|
|
282
294
|
```
|
|
283
295
|
|
|
284
296
|
The global file is never modified, and `--with` is not forwarded to
|
|
@@ -286,9 +298,10 @@ The global file is never modified, and `--with` is not forwarded to
|
|
|
286
298
|
Overlays apply when creating a fresh session and as current-config input for a
|
|
287
299
|
compatible ordinary reopen. A selected session keeps its stored catalog,
|
|
288
300
|
player roster, role bindings, adapter, instruction, permissions, and working
|
|
289
|
-
directory; only model, effort, fast mode,
|
|
290
|
-
next call reapplies both complete model and effort selections,
|
|
291
|
-
effective fast-mode boolean, and the optional effective subagent
|
|
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.
|
|
292
305
|
|
|
293
306
|
## Session storage
|
|
294
307
|
|
|
@@ -329,8 +342,8 @@ public UUID. Presentation-only fields are inert headlessly.
|
|
|
329
342
|
An ordinary reopen reads current config and opening overlays, but first
|
|
330
343
|
projects them to the stored playbooks and referenced players. An unrelated new
|
|
331
344
|
entry cannot enter or invalidate the session. Structural drift fails closed;
|
|
332
|
-
compatible model, effort, fast-mode,
|
|
333
|
-
next provider 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
|
|
334
347
|
rather than having role or player identity guessed.
|
|
335
348
|
|
|
336
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 = [];
|
|
@@ -443,7 +446,8 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
443
446
|
binding.model !== undefined ||
|
|
444
447
|
binding.effort !== undefined ||
|
|
445
448
|
binding.fastMode !== undefined ||
|
|
446
|
-
binding.subagentModel !== undefined
|
|
449
|
+
binding.subagentModel !== undefined ||
|
|
450
|
+
binding.subagentEffort !== undefined
|
|
447
451
|
) {
|
|
448
452
|
let checkId;
|
|
449
453
|
do {
|
|
@@ -559,7 +563,8 @@ export async function normalizeSelectedLaunchPlanDataOnly(
|
|
|
559
563
|
? {}
|
|
560
564
|
: { fastMode: agent.fastMode }
|
|
561
565
|
: { fastMode: binding.fastMode }),
|
|
562
|
-
...
|
|
566
|
+
...effectiveOptionalTuning(binding, agent, "subagentModel"),
|
|
567
|
+
...effectiveOptionalTuning(binding, agent, "subagentEffort"),
|
|
563
568
|
},
|
|
564
569
|
];
|
|
565
570
|
}),
|
|
@@ -689,9 +694,9 @@ export async function normalizeLaunchPlan(
|
|
|
689
694
|
const configuredAgents = new Map();
|
|
690
695
|
for (const playerId of allPlayerIds) {
|
|
691
696
|
assertPlayerId(playerId, `players.${playerId}`);
|
|
692
|
-
const agent =
|
|
693
|
-
"id",
|
|
694
|
-
|
|
697
|
+
const agent = resolveDelegationDefault(
|
|
698
|
+
resolveAgent(playersCfg[playerId], `players.${playerId}`, ["id"]),
|
|
699
|
+
);
|
|
695
700
|
if (
|
|
696
701
|
typeof agent.adapter !== "string" ||
|
|
697
702
|
agent.adapter.trim().length === 0
|
|
@@ -710,7 +715,9 @@ export async function normalizeLaunchPlan(
|
|
|
710
715
|
throw new Error("playbooks keys must be canonical trimmed nonblank ids");
|
|
711
716
|
}
|
|
712
717
|
|
|
713
|
-
let captain =
|
|
718
|
+
let captain = resolveDelegationDefault(
|
|
719
|
+
resolveAgent(top.captain, "captain", ["from", "options"]),
|
|
720
|
+
);
|
|
714
721
|
if (
|
|
715
722
|
typeof captain.adapter !== "string" ||
|
|
716
723
|
captain.adapter.trim().length === 0
|
|
@@ -790,7 +797,8 @@ export async function normalizeLaunchPlan(
|
|
|
790
797
|
binding.model !== undefined ||
|
|
791
798
|
binding.effort !== undefined ||
|
|
792
799
|
binding.fastMode !== undefined ||
|
|
793
|
-
binding.subagentModel !== undefined
|
|
800
|
+
binding.subagentModel !== undefined ||
|
|
801
|
+
binding.subagentEffort !== undefined
|
|
794
802
|
) {
|
|
795
803
|
let checkId;
|
|
796
804
|
do {
|
|
@@ -1002,7 +1010,8 @@ export async function normalizeLaunchPlan(
|
|
|
1002
1010
|
? {}
|
|
1003
1011
|
: { fastMode: agent.fastMode }
|
|
1004
1012
|
: { fastMode: binding.fastMode }),
|
|
1005
|
-
...
|
|
1013
|
+
...effectiveOptionalTuning(binding, agent, "subagentModel"),
|
|
1014
|
+
...effectiveOptionalTuning(binding, agent, "subagentEffort"),
|
|
1006
1015
|
},
|
|
1007
1016
|
];
|
|
1008
1017
|
}),
|
|
@@ -1488,7 +1497,8 @@ function migrateUserConfigIfRetired(userConfigPath, onNotice) {
|
|
|
1488
1497
|
throw new Error(
|
|
1489
1498
|
`cannot migrate the retired profiles config at ${userConfigPath}: ` +
|
|
1490
1499
|
`${errorMessage(error)} — edit it by hand: each agent takes its own ` +
|
|
1491
|
-
"adapter, model, effort, fast mode, subagent model,
|
|
1500
|
+
"adapter, model, effort, fast mode, subagent model, subagent effort, " +
|
|
1501
|
+
"and permissions",
|
|
1492
1502
|
);
|
|
1493
1503
|
}
|
|
1494
1504
|
if (migrated === undefined) return;
|
|
@@ -1624,7 +1634,8 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1624
1634
|
throw new Error(
|
|
1625
1635
|
`top-level "profiles" was removed${where}: write each agent's settings ` +
|
|
1626
1636
|
"inline under captain and each top-level players.<player-id> " +
|
|
1627
|
-
"(adapter, model, effort, fast mode, subagent model,
|
|
1637
|
+
"(adapter, model, effort, fast mode, subagent model, subagent effort, " +
|
|
1638
|
+
"permissions)",
|
|
1628
1639
|
);
|
|
1629
1640
|
}
|
|
1630
1641
|
const legacyPath = findLegacyPlayersPath(top);
|
|
@@ -1640,7 +1651,7 @@ function assertNoRetiredProfiles(top, configPath) {
|
|
|
1640
1651
|
throw new Error(
|
|
1641
1652
|
`${path}.profile was removed${where}: write the agent's settings ` +
|
|
1642
1653
|
"inline in that block (adapter, model, effort, fast mode, " +
|
|
1643
|
-
"subagent model, permissions)",
|
|
1654
|
+
"subagent model, subagent effort, permissions)",
|
|
1644
1655
|
);
|
|
1645
1656
|
}
|
|
1646
1657
|
}
|
|
@@ -2040,13 +2051,20 @@ function resolveRoleBinding(value, path) {
|
|
|
2040
2051
|
const block = requireObject(value, path);
|
|
2041
2052
|
const unknown = Object.keys(block).filter(
|
|
2042
2053
|
(key) =>
|
|
2043
|
-
![
|
|
2054
|
+
![
|
|
2055
|
+
"player",
|
|
2056
|
+
"model",
|
|
2057
|
+
"effort",
|
|
2058
|
+
"fastMode",
|
|
2059
|
+
"subagentModel",
|
|
2060
|
+
"subagentEffort",
|
|
2061
|
+
].includes(key),
|
|
2044
2062
|
);
|
|
2045
2063
|
if (unknown.length > 0) {
|
|
2046
2064
|
throw new Error(`${path} has unknown ${formatKeyList(unknown)}`);
|
|
2047
2065
|
}
|
|
2048
2066
|
assertPlayerId(block.player, `${path}.player`);
|
|
2049
|
-
for (const field of ["model", "effort", "subagentModel"]) {
|
|
2067
|
+
for (const field of ["model", "effort", "subagentModel", "subagentEffort"]) {
|
|
2050
2068
|
if (
|
|
2051
2069
|
block[field] !== undefined &&
|
|
2052
2070
|
block[field] !== false &&
|
|
@@ -2071,19 +2089,41 @@ function resolveRoleBinding(value, path) {
|
|
|
2071
2089
|
...(block.subagentModel === undefined
|
|
2072
2090
|
? {}
|
|
2073
2091
|
: { subagentModel: block.subagentModel }),
|
|
2092
|
+
...(block.subagentEffort === undefined
|
|
2093
|
+
? {}
|
|
2094
|
+
: { subagentEffort: block.subagentEffort }),
|
|
2074
2095
|
};
|
|
2075
2096
|
}
|
|
2076
2097
|
|
|
2077
|
-
// A binding's subagent model overrides the player's;
|
|
2078
|
-
// provider default, which a complete call expresses by
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
if (binding
|
|
2082
|
-
|
|
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;
|
|
2083
2118
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
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;
|
|
2087
2127
|
}
|
|
2088
2128
|
|
|
2089
2129
|
function applyTuningOverrides(agent, binding) {
|
|
@@ -2096,9 +2136,9 @@ function applyTuningOverrides(agent, binding) {
|
|
|
2096
2136
|
else effective.effort = binding.effort;
|
|
2097
2137
|
}
|
|
2098
2138
|
if (binding.fastMode !== undefined) effective.fastMode = binding.fastMode;
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
effective
|
|
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];
|
|
2102
2142
|
}
|
|
2103
2143
|
return effective;
|
|
2104
2144
|
}
|
|
@@ -2117,11 +2157,19 @@ function sessionAgentFromHostAgent(agent, path) {
|
|
|
2117
2157
|
...(agent.subagentModel === undefined
|
|
2118
2158
|
? {}
|
|
2119
2159
|
: {
|
|
2120
|
-
subagentModel:
|
|
2160
|
+
subagentModel: nonblankSelection(
|
|
2121
2161
|
agent.subagentModel,
|
|
2122
2162
|
`${path}.subagentModel`,
|
|
2123
2163
|
),
|
|
2124
2164
|
}),
|
|
2165
|
+
...(agent.subagentEffort === undefined
|
|
2166
|
+
? {}
|
|
2167
|
+
: {
|
|
2168
|
+
subagentEffort: nonblankSelection(
|
|
2169
|
+
agent.subagentEffort,
|
|
2170
|
+
`${path}.subagentEffort`,
|
|
2171
|
+
),
|
|
2172
|
+
}),
|
|
2125
2173
|
...(agent.instruction === undefined
|
|
2126
2174
|
? {}
|
|
2127
2175
|
: { instruction: agent.instruction }),
|
|
@@ -2150,6 +2198,9 @@ export function projectHostAgent(agent, path = "agent") {
|
|
|
2150
2198
|
...(normalized.subagentModel === undefined
|
|
2151
2199
|
? {}
|
|
2152
2200
|
: { subagentModel: normalized.subagentModel }),
|
|
2201
|
+
...(normalized.subagentEffort === undefined
|
|
2202
|
+
? {}
|
|
2203
|
+
: { subagentEffort: normalized.subagentEffort }),
|
|
2153
2204
|
...(normalized.instruction === undefined
|
|
2154
2205
|
? {}
|
|
2155
2206
|
: { instruction: normalized.instruction }),
|
|
@@ -2166,7 +2217,7 @@ function fastModeSelection(value, path) {
|
|
|
2166
2217
|
return value;
|
|
2167
2218
|
}
|
|
2168
2219
|
|
|
2169
|
-
function
|
|
2220
|
+
function nonblankSelection(value, path) {
|
|
2170
2221
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2171
2222
|
throw new Error(`${path} must be a nonblank string`);
|
|
2172
2223
|
}
|
|
@@ -967,9 +967,11 @@ function helpText({
|
|
|
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
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
|
|
972
|
-
" while fastMode false is a literal disabled request",
|
|
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",
|
|
973
975
|
" - reusing one id deliberately shares that provider conversation;",
|
|
974
976
|
" distinct ids stay isolated even when their agent blocks are equal",
|
|
975
977
|
" - the launcher injects captain.from and retains referenced player ids",
|
|
@@ -1367,11 +1367,14 @@ export async function validateFrozenExecutionConfig(
|
|
|
1367
1367
|
return config;
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
|
-
// Fast mode and the subagent
|
|
1371
|
-
// frozen record re-validates each present value
|
|
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.
|
|
1372
1374
|
const FROZEN_ADAPTER_SCOPED_TUNING = [
|
|
1373
1375
|
["fastMode", assertFastModeSupported],
|
|
1374
1376
|
["subagentModel", assertSubagentModelSupported],
|
|
1377
|
+
["subagentEffort", assertSubagentModelSupported],
|
|
1375
1378
|
];
|
|
1376
1379
|
|
|
1377
1380
|
function assertFrozenTuningSupported(config) {
|
|
@@ -1887,7 +1890,7 @@ function runHelpText(userConfigPath) {
|
|
|
1887
1890
|
"working directory and reports when it uses the global newest fallback.",
|
|
1888
1891
|
"An ordinary continued run restores that stored structure and working",
|
|
1889
1892
|
"directory, then reads current config and overlays for model, effort,",
|
|
1890
|
-
"fast mode, and subagent
|
|
1893
|
+
"fast mode, subagent model, and subagent effort.",
|
|
1891
1894
|
"Uncertain retry only restores and reports; later work uses current settings.",
|
|
1892
1895
|
"",
|
|
1893
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,16 +5509,13 @@ function validateCaptainSessionProjection(
|
|
|
5503
5509
|
structural
|
|
5504
5510
|
? ['playerId']
|
|
5505
5511
|
: ['playerId', 'model', 'effort'],
|
|
5506
|
-
structural ? [] : ['fastMode', 'subagentModel'],
|
|
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`);
|
|
5512
|
-
|
|
5513
|
-
binding,
|
|
5514
|
-
`${bindingPath}.subagentModel`,
|
|
5515
|
-
);
|
|
5518
|
+
validateProjectedSubagentTuning(binding, bindingPath);
|
|
5516
5519
|
validateTuningSelection(binding.model, `${bindingPath}.model`);
|
|
5517
5520
|
const player = projection.players.find(
|
|
5518
5521
|
(candidate) => candidate.id === binding.playerId,
|
|
@@ -5560,10 +5563,14 @@ function validateProjectedFastMode(record, path) {
|
|
|
5560
5563
|
}
|
|
5561
5564
|
}
|
|
5562
5565
|
|
|
5563
|
-
// Like fast mode, absence is the canonical provider default (DR-075)
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
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
|
+
}
|
|
5567
5574
|
}
|
|
5568
5575
|
}
|
|
5569
5576
|
|
|
@@ -5571,7 +5578,7 @@ function validateProjectedAgent(value, path, { structural, hasId = false }) {
|
|
|
5571
5578
|
const agent = requireRecord(value, path);
|
|
5572
5579
|
if (!structural) {
|
|
5573
5580
|
validateProjectedFastMode(agent, `${path}.fastMode`);
|
|
5574
|
-
|
|
5581
|
+
validateProjectedSubagentTuning(agent, path);
|
|
5575
5582
|
}
|
|
5576
5583
|
if (!hasId) {
|
|
5577
5584
|
exactOptionalKeys(
|
|
@@ -5579,7 +5586,13 @@ function validateProjectedAgent(value, path, { structural, hasId = false }) {
|
|
|
5579
5586
|
structural ? ['adapter'] : ['adapter', 'model', 'effort'],
|
|
5580
5587
|
structural
|
|
5581
5588
|
? ['instruction', 'permissions']
|
|
5582
|
-
: [
|
|
5589
|
+
: [
|
|
5590
|
+
'instruction',
|
|
5591
|
+
'permissions',
|
|
5592
|
+
'fastMode',
|
|
5593
|
+
'subagentModel',
|
|
5594
|
+
'subagentEffort',
|
|
5595
|
+
],
|
|
5583
5596
|
path,
|
|
5584
5597
|
);
|
|
5585
5598
|
}
|
|
@@ -10,14 +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)
|
|
14
|
-
* provider default. */
|
|
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
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;
|
|
16
19
|
readonly instruction?: string;
|
|
17
20
|
readonly permissions?: PermissionPolicy;
|
|
18
21
|
}
|
|
19
22
|
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
20
|
-
type TuningKey = 'model' | 'effort' | 'fastMode' | 'subagentModel';
|
|
23
|
+
type TuningKey = 'model' | 'effort' | 'fastMode' | 'subagentModel' | 'subagentEffort';
|
|
21
24
|
interface PlayerLedgerEntry {
|
|
22
25
|
readonly adapter: string;
|
|
23
26
|
readonly instruction?: string;
|
|
@@ -2098,7 +2098,7 @@ function snapshotFastMode(value, path) {
|
|
|
2098
2098
|
}
|
|
2099
2099
|
return value;
|
|
2100
2100
|
}
|
|
2101
|
-
function
|
|
2101
|
+
function snapshotNonblankTuning(value, path) {
|
|
2102
2102
|
if (value !== undefined &&
|
|
2103
2103
|
(typeof value !== 'string' || value.trim().length === 0)) {
|
|
2104
2104
|
throw new TypeError(`${path} must be a nonblank string`);
|
|
@@ -2113,13 +2113,15 @@ function snapshotSessionAgent(value, path) {
|
|
|
2113
2113
|
'effort',
|
|
2114
2114
|
'fastMode',
|
|
2115
2115
|
'subagentModel',
|
|
2116
|
+
'subagentEffort',
|
|
2116
2117
|
'instruction',
|
|
2117
2118
|
'permissions',
|
|
2118
2119
|
], path);
|
|
2119
2120
|
const fixed = snapshotFixedAgent(Object.fromEntries(Object.entries(agent).filter(([key]) => key !== 'model' &&
|
|
2120
2121
|
key !== 'effort' &&
|
|
2121
2122
|
key !== 'fastMode' &&
|
|
2122
|
-
key !== 'subagentModel'
|
|
2123
|
+
key !== 'subagentModel' &&
|
|
2124
|
+
key !== 'subagentEffort')), path);
|
|
2123
2125
|
return {
|
|
2124
2126
|
adapter: fixed.adapter,
|
|
2125
2127
|
...(fixed.instruction === undefined
|
|
@@ -2136,7 +2138,12 @@ function snapshotSessionAgent(value, path) {
|
|
|
2136
2138
|
...(agent.subagentModel === undefined
|
|
2137
2139
|
? {}
|
|
2138
2140
|
: {
|
|
2139
|
-
subagentModel:
|
|
2141
|
+
subagentModel: snapshotNonblankTuning(agent.subagentModel, `${path}.subagentModel`),
|
|
2142
|
+
}),
|
|
2143
|
+
...(agent.subagentEffort === undefined
|
|
2144
|
+
? {}
|
|
2145
|
+
: {
|
|
2146
|
+
subagentEffort: snapshotNonblankTuning(agent.subagentEffort, `${path}.subagentEffort`),
|
|
2140
2147
|
}),
|
|
2141
2148
|
};
|
|
2142
2149
|
}
|
|
@@ -2150,7 +2157,9 @@ function fixedAgent(agent) {
|
|
|
2150
2157
|
function callSettings(agent, tuning = agent) {
|
|
2151
2158
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
2152
2159
|
// omitted fastMode or subagentModel here is a request for the provider
|
|
2153
|
-
// default,
|
|
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.
|
|
2154
2163
|
return {
|
|
2155
2164
|
model: tuning.model,
|
|
2156
2165
|
effort: tuning.effort,
|
|
@@ -2158,6 +2167,9 @@ function callSettings(agent, tuning = agent) {
|
|
|
2158
2167
|
...(tuning.subagentModel === undefined
|
|
2159
2168
|
? {}
|
|
2160
2169
|
: { subagentModel: tuning.subagentModel }),
|
|
2170
|
+
...(tuning.subagentEffort === undefined
|
|
2171
|
+
? {}
|
|
2172
|
+
: { subagentEffort: tuning.subagentEffort }),
|
|
2161
2173
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
2162
2174
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
2163
2175
|
};
|
|
@@ -2285,7 +2297,14 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2285
2297
|
for (const role of entry.requiredRoleIds) {
|
|
2286
2298
|
const path = `captain.options.playbooks.${id}.roles.${role}`;
|
|
2287
2299
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
2288
|
-
rejectSnapshotKeys(rawBinding, [
|
|
2300
|
+
rejectSnapshotKeys(rawBinding, [
|
|
2301
|
+
'playerId',
|
|
2302
|
+
'model',
|
|
2303
|
+
'effort',
|
|
2304
|
+
'fastMode',
|
|
2305
|
+
'subagentModel',
|
|
2306
|
+
'subagentEffort',
|
|
2307
|
+
], path);
|
|
2289
2308
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
2290
2309
|
if (!PLAYER_ID_PATTERN.test(playerId) || playerId === INTERNAL_CAPTAIN_ID) {
|
|
2291
2310
|
throw new Error(`${path}.playerId is not a canonical player id`);
|
|
@@ -2306,7 +2325,12 @@ async function buildEnablements(options, loadModule, hostCapabilities) {
|
|
|
2306
2325
|
...(rawBinding.subagentModel === undefined
|
|
2307
2326
|
? {}
|
|
2308
2327
|
: {
|
|
2309
|
-
subagentModel:
|
|
2328
|
+
subagentModel: snapshotNonblankTuning(rawBinding.subagentModel, `${path}.subagentModel`),
|
|
2329
|
+
}),
|
|
2330
|
+
...(rawBinding.subagentEffort === undefined
|
|
2331
|
+
? {}
|
|
2332
|
+
: {
|
|
2333
|
+
subagentEffort: snapshotNonblankTuning(rawBinding.subagentEffort, `${path}.subagentEffort`),
|
|
2310
2334
|
}),
|
|
2311
2335
|
agent,
|
|
2312
2336
|
});
|
|
@@ -69,15 +69,23 @@ 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)
|
|
73
|
-
* provider default. */
|
|
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
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;
|
|
75
78
|
readonly instruction?: string;
|
|
76
79
|
readonly permissions?: PermissionPolicy;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
80
|
-
type TuningKey =
|
|
83
|
+
type TuningKey =
|
|
84
|
+
| 'model'
|
|
85
|
+
| 'effort'
|
|
86
|
+
| 'fastMode'
|
|
87
|
+
| 'subagentModel'
|
|
88
|
+
| 'subagentEffort';
|
|
81
89
|
|
|
82
90
|
interface PlayerLedgerEntry {
|
|
83
91
|
readonly adapter: string;
|
|
@@ -452,6 +460,7 @@ interface EffectivePlayerBinding {
|
|
|
452
460
|
readonly effort: TuningSelection<Effort>;
|
|
453
461
|
readonly fastMode?: boolean;
|
|
454
462
|
readonly subagentModel?: string;
|
|
463
|
+
readonly subagentEffort?: string;
|
|
455
464
|
readonly agent: SessionAgent;
|
|
456
465
|
}
|
|
457
466
|
|
|
@@ -3727,7 +3736,7 @@ function snapshotFastMode(
|
|
|
3727
3736
|
return value;
|
|
3728
3737
|
}
|
|
3729
3738
|
|
|
3730
|
-
function
|
|
3739
|
+
function snapshotNonblankTuning(
|
|
3731
3740
|
value: JsonValue | undefined,
|
|
3732
3741
|
path: string,
|
|
3733
3742
|
): string | undefined {
|
|
@@ -3753,6 +3762,7 @@ function snapshotSessionAgent(
|
|
|
3753
3762
|
'effort',
|
|
3754
3763
|
'fastMode',
|
|
3755
3764
|
'subagentModel',
|
|
3765
|
+
'subagentEffort',
|
|
3756
3766
|
'instruction',
|
|
3757
3767
|
'permissions',
|
|
3758
3768
|
],
|
|
@@ -3765,7 +3775,8 @@ function snapshotSessionAgent(
|
|
|
3765
3775
|
key !== 'model' &&
|
|
3766
3776
|
key !== 'effort' &&
|
|
3767
3777
|
key !== 'fastMode' &&
|
|
3768
|
-
key !== 'subagentModel'
|
|
3778
|
+
key !== 'subagentModel' &&
|
|
3779
|
+
key !== 'subagentEffort',
|
|
3769
3780
|
),
|
|
3770
3781
|
) as JsonValue,
|
|
3771
3782
|
path,
|
|
@@ -3786,11 +3797,19 @@ function snapshotSessionAgent(
|
|
|
3786
3797
|
...(agent.subagentModel === undefined
|
|
3787
3798
|
? {}
|
|
3788
3799
|
: {
|
|
3789
|
-
subagentModel:
|
|
3800
|
+
subagentModel: snapshotNonblankTuning(
|
|
3790
3801
|
agent.subagentModel,
|
|
3791
3802
|
`${path}.subagentModel`,
|
|
3792
3803
|
),
|
|
3793
3804
|
}),
|
|
3805
|
+
...(agent.subagentEffort === undefined
|
|
3806
|
+
? {}
|
|
3807
|
+
: {
|
|
3808
|
+
subagentEffort: snapshotNonblankTuning(
|
|
3809
|
+
agent.subagentEffort,
|
|
3810
|
+
`${path}.subagentEffort`,
|
|
3811
|
+
),
|
|
3812
|
+
}),
|
|
3794
3813
|
};
|
|
3795
3814
|
}
|
|
3796
3815
|
|
|
@@ -3808,7 +3827,9 @@ function callSettings(
|
|
|
3808
3827
|
): AgentCallSettings {
|
|
3809
3828
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
3810
3829
|
// omitted fastMode or subagentModel here is a request for the provider
|
|
3811
|
-
// default,
|
|
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.
|
|
3812
3833
|
return {
|
|
3813
3834
|
model: tuning.model,
|
|
3814
3835
|
effort: tuning.effort,
|
|
@@ -3816,6 +3837,9 @@ function callSettings(
|
|
|
3816
3837
|
...(tuning.subagentModel === undefined
|
|
3817
3838
|
? {}
|
|
3818
3839
|
: { subagentModel: tuning.subagentModel }),
|
|
3840
|
+
...(tuning.subagentEffort === undefined
|
|
3841
|
+
? {}
|
|
3842
|
+
: { subagentEffort: tuning.subagentEffort as Effort }),
|
|
3819
3843
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
3820
3844
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
3821
3845
|
};
|
|
@@ -4043,7 +4067,14 @@ async function buildEnablements(
|
|
|
4043
4067
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
4044
4068
|
rejectSnapshotKeys(
|
|
4045
4069
|
rawBinding,
|
|
4046
|
-
[
|
|
4070
|
+
[
|
|
4071
|
+
'playerId',
|
|
4072
|
+
'model',
|
|
4073
|
+
'effort',
|
|
4074
|
+
'fastMode',
|
|
4075
|
+
'subagentModel',
|
|
4076
|
+
'subagentEffort',
|
|
4077
|
+
],
|
|
4047
4078
|
path,
|
|
4048
4079
|
);
|
|
4049
4080
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
@@ -4071,11 +4102,19 @@ async function buildEnablements(
|
|
|
4071
4102
|
...(rawBinding.subagentModel === undefined
|
|
4072
4103
|
? {}
|
|
4073
4104
|
: {
|
|
4074
|
-
subagentModel:
|
|
4105
|
+
subagentModel: snapshotNonblankTuning(
|
|
4075
4106
|
rawBinding.subagentModel,
|
|
4076
4107
|
`${path}.subagentModel`,
|
|
4077
4108
|
),
|
|
4078
4109
|
}),
|
|
4110
|
+
...(rawBinding.subagentEffort === undefined
|
|
4111
|
+
? {}
|
|
4112
|
+
: {
|
|
4113
|
+
subagentEffort: snapshotNonblankTuning(
|
|
4114
|
+
rawBinding.subagentEffort,
|
|
4115
|
+
`${path}.subagentEffort`,
|
|
4116
|
+
),
|
|
4117
|
+
}),
|
|
4079
4118
|
agent,
|
|
4080
4119
|
});
|
|
4081
4120
|
}
|
|
@@ -8,11 +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/subagentModel/permissions. A
|
|
12
|
-
# only model, effort, fastMode, or
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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.
|
|
16
20
|
|
|
17
21
|
# Each adapter needs its vendor SDK installed as its own top-level
|
|
18
22
|
# install root — they are optional peer dependencies, so you pay only
|