orchestrator-workflow 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,48 @@ All notable changes to `orchestrator-workflow` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.22.0] - 2026-08-20
9
+
10
+ ### Changed
11
+
12
+ - **Default subagent spawns no longer inherit the orchestrator session's
13
+ effort.** Every unsuffixed default agent file (`explorer.md`,
14
+ `task-slicer.md`, `implementer.md`, `reviewer.md`, `advisor.md`) now
15
+ carries its own pinned default effort, unconditionally, regardless of
16
+ whether `--tiers` is on: `effort: medium` for explorer, task-slicer, and
17
+ implementer, `effort: high` for reviewer and advisor
18
+ (`TIER_DEFS[DEFAULT_TIER[role]].effort`, the same tier data `--tiers`
19
+ already used, applied by `composeClaudeAgent` for every install, tiers
20
+ flag or not). opencode gets the matching pinned line via
21
+ `opencodeEffortLine` (renamed from `opencodeVariantEffortLine`, now shared
22
+ by the default file and the tier variants): a resolved Claude-family model
23
+ gets `variant: high` for reviewer/advisor and no effort field at all for
24
+ the three medium-default roles (opencode's `variant:` option does not
25
+ distinguish an effort below `high`, the same collapse tier variants
26
+ already documented), a non-Claude-family provider-qualified model gets
27
+ `reasoningEffort: medium`/`reasoningEffort: high`, and Ollama, a
28
+ provider-less id, or an unresolved model gets no effort field either way.
29
+ Practically: on a high-effort orchestrator session, a default subagent
30
+ spawn used to silently run at that same high effort; it now runs at its
31
+ own role's pinned effort instead, which is weaker (and cheaper) for the
32
+ three medium-default roles. Escalate deliberately via the `-high`/`-xhigh`
33
+ tier variants (`--tiers`) when a task actually needs more.
34
+ **`CLAUDE_CODE_EFFORT_LEVEL` still beats this pin**: when the harness
35
+ environment sets that variable, it overrides every installed agent's
36
+ frontmatter `effort:`, default files and tier variants alike, the same
37
+ warning `--tiers` already carried. **opencode cannot express `medium` for
38
+ a Claude-family model at all**: the `variant:` field only distinguishes
39
+ `high`/`max`, so a Claude-family default file for explorer, task-slicer,
40
+ or implementer renders with no effort field, falling back to whatever the
41
+ resolved model's own default happens to be.
42
+ The default file's content stays byte-identical whether or not `--tiers`
43
+ is also on, since the pin does not read the `tiers` flag; this invariant
44
+ is now belt-and-suspenders tested directly (a two-target diff, not just
45
+ inferred from reading the source).
46
+ A `--tiers`-off install (the default) renders no variant files at all, so
47
+ it has no in-install escalation path off a default's pinned effort; pull
48
+ `init --tiers` afterward if a task needs one.
49
+
8
50
  ## [0.21.0] - 2026-08-20
9
51
 
10
52
  ### Added
package/INSTALL-AGENT.md CHANGED
@@ -146,10 +146,15 @@ steps in the repository you were asked to install into.
146
146
  for `full`; only `implementer` and `reviewer` for `minimal`),
147
147
  `.claude/agents/<role>.md` from
148
148
  `assets/agents/<role>.md` with `model: <operator's choice>` added as a
149
- new line directly after the `description:` line (that placement matches
150
- the installer's output byte for byte). For the explorer, reviewer, and
151
- advisor roles additionally, `disallowedTools: Edit, Write, NotebookEdit` goes on a new
152
- line directly after the `model:` line. Ensure `CLAUDE.md` exists and
149
+ new line directly after the `description:` line, then
150
+ `effort: <medium|high>` on the next line (`medium` for explorer,
151
+ task-slicer, and implementer; `high` for reviewer and advisor; this
152
+ pinned default effort is unconditional, not tied to whether the
153
+ operator asked for `--tiers`; see the package README's "Effort tiers"
154
+ section), that placement matching the installer's output byte for
155
+ byte. For the explorer, reviewer, and advisor roles additionally,
156
+ `disallowedTools: Edit, Write, NotebookEdit` goes on a new line
157
+ directly after the `effort:` line. Ensure `CLAUDE.md` exists and
153
158
  contains a line `@AGENTS.md`.
154
159
  - Codex: `.agents/skills/orchestrator-workflow/SKILL.md`, same skill file.
155
160
  - opencode: `.opencode/skills/orchestrator-workflow/SKILL.md` from
@@ -166,9 +171,21 @@ steps in the repository you were asked to install into.
166
171
  which is the safe portable fallback. The installed CLI resolves aliases
167
172
  to fully-qualified ids by running `opencode models` at install time; in a
168
173
  manual install you may not have a live catalog, so omitting `model:` is
169
- correct. For the explorer, reviewer, and advisor roles additionally, `permission:` goes on a new
170
- line directly after `mode: subagent` (or after `model:` when that line is
171
- present), followed by ` edit: deny` (two-space indent) on the next line.
174
+ correct. When you do have a fully-qualified `model:` value, add the same
175
+ pinned-default-effort line (see the package README's "Effort tiers"
176
+ section for the exact dispatch rule), keyed by
177
+ the role's own default tier instead of a suffix tier (`medium` for
178
+ explorer/task-slicer/implementer, `high` for reviewer/advisor): a
179
+ resolved Claude-family model gets `variant: high` for reviewer/advisor
180
+ and no effort line at all for the three medium-default roles, a
181
+ non-Claude-family provider-qualified model gets `reasoningEffort:
182
+ medium` or `reasoningEffort: high`, and Ollama or a provider-less id
183
+ gets no effort line either way; when `model:` is omitted (the common
184
+ manual-fallback case above), omit the effort line too, the same
185
+ no-live-catalog fallback. For the explorer, reviewer, and advisor roles
186
+ additionally, `permission:` goes on a new line directly after
187
+ `mode: subagent` (or after `model:`/the effort line when present),
188
+ followed by ` edit: deny` (two-space indent) on the next line.
172
189
  Example read-only role frontmatter (explorer, reviewer, or advisor) when
173
190
  no model is resolved:
174
191
  ```yaml
package/README.md CHANGED
@@ -215,8 +215,27 @@ non-default effort tier, alongside the one default (unsuffixed) agent file
215
215
  `--profile` already installs. Each tier variant is a standalone subagent
216
216
  definition, not a modification of the default file: the default file
217
217
  (`<role>.md`) stays byte-identical to what a tiers-off install already
218
- produces (still `manifest.models[role]`, no `effort:` key), and each variant
219
- lives next to it as `<role>-<tier>.md`.
218
+ produces, and each variant lives next to it as `<role>-<tier>.md`.
219
+
220
+ **Every default file carries its own pinned effort, independent of
221
+ `--tiers`.** Since 0.22.0, `composeClaudeAgent`/`composeOpencodeAgent` add
222
+ the role's own `TIER_DEFS[DEFAULT_TIER[role]].effort` to the default
223
+ (unsuffixed) file unconditionally: `effort: medium` for explorer,
224
+ task-slicer, and implementer; `effort: high` for reviewer and advisor
225
+ (opencode: a `variant: high` line when the resolved model is Claude-family,
226
+ following the same dispatch rule tier variants use, `reasoningEffort:
227
+ medium`/`reasoningEffort: high` for a non-Claude-family provider-qualified
228
+ model, nothing for Ollama, a provider-less id, or an unresolved model). This
229
+ pin does not depend on `tiers`, so a plain install (no `--tiers`) already
230
+ carries it; the flag only controls whether the additional `<role>-<tier>.md`
231
+ variant files are also rendered. The motivation: a default spawn used to
232
+ silently inherit the orchestrator session's own effort, so a `high`-effort
233
+ orchestrator session made every default subagent spawn at `high` too,
234
+ regardless of the role's own intended weight; the pin makes each role's
235
+ effort deterministic and independent of the caller's session. A `--tiers`-off
236
+ install (the default) has no variant files and therefore no in-install
237
+ escalation path off a default's pinned effort; run `init --tiers` afterward
238
+ if a task ever needs one.
220
239
 
221
240
  Default off, like every optional pack in this kit: a fresh install renders
222
241
  no variant files unless asked. `--tiers` turns the feature on for that run,
@@ -307,6 +326,14 @@ agent, tier variants and default files alike, not just the one this feature
307
326
  adds. Check for it before relying on a specific tier variant's requested
308
327
  effort actually taking effect.
309
328
 
329
+ The pin is also emitted unconditionally regardless of which model the role
330
+ resolves to via `--models`, including a model with no effort support at all
331
+ (e.g. `--models reviewer=haiku` still renders `model: haiku` followed by
332
+ `effort: high`). A wire probe on 2026-08-19 (not re-measured for this doc)
333
+ showed the Claude Code CLI silently drops the `effort:` parameter for Haiku
334
+ 4.5 rather than rejecting it: the value is ignored, not an install-time or
335
+ run-time error.
336
+
310
337
  ## Ownership and re-runs
311
338
 
312
339
  `init` is idempotent: a second run changes nothing. The rules:
@@ -60,6 +60,11 @@ default, not a ritual.
60
60
  downshift at all: its default `high` is already its only non-`-xhigh`
61
61
  tier. Spawn only variants that are actually installed. Tier choice is a
62
62
  conscious decision, not a ritual; when unsure, use the default.
63
+ - Every unsuffixed default subagent carries its own pinned default effort
64
+ baked into its own file, not inherited from the orchestrator session:
65
+ medium for the explorer, the task-slicer, and the implementer; high for
66
+ the reviewer and the advisor. This holds whether or not tier variants are
67
+ installed; it is not gated on `--tiers`.
63
68
  - Under the `full` profile, an advisor subagent is available for escalation
64
69
  only: architectural uncertainty, requirements that contradict each other,
65
70
  multiple valid solution paths where committing to one is expensive to
@@ -134,10 +134,13 @@ directory and the subagents.
134
134
  enough, testable, ordered correctly, and aligned with the goal. Fix the
135
135
  slicing before any implementation starts.
136
136
  6. **Delegate implementation.** Send each implementer subagent one narrow task
137
- contract (format below). When tier variants are installed, pick the
138
- implementer tier (the installed `implementer-<tier>` subagents, if any) by
139
- the task's complexity and risk, at your own judgment, defaulting to the
140
- unsuffixed subagent when unsure; record a non-default tier choice with a
137
+ contract (format below). The unsuffixed implementer carries a pinned
138
+ effort: `medium` in its own file, whether or not tier variants are
139
+ installed, so a default spawn no longer inherits the session's effort.
140
+ When tier variants are installed, pick the implementer tier (the
141
+ installed `implementer-<tier>` subagents, if any) by the task's
142
+ complexity and risk, at your own judgment, defaulting to the unsuffixed
143
+ subagent when unsure; record a non-default tier choice with a
141
144
  one-line reason in `03-decisions.md` when the task is non-trivial. When a
142
145
  task's acceptance rests on a test that must fail without the change, name
143
146
  the mutation probes to run in the task assignment; the implementer reports
package/dist/init.js CHANGED
@@ -95,6 +95,22 @@ export function readInstalledManifest(targetDir) {
95
95
  function yamlQuote(value) {
96
96
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
97
97
  }
98
+ /**
99
+ * Composes the unsuffixed default agent file. It carries a pinned
100
+ * `effort: <TIER_DEFS[DEFAULT_TIER[role]].effort>` line unconditionally
101
+ * (medium for explorer/task-slicer/implementer, high for reviewer/advisor),
102
+ * regardless of whether `--tiers` is on: this is the deterministic tier
103
+ * ladder's floor, not a tier-variant feature, so a default spawn no longer
104
+ * silently inherits the orchestrator session's effort. Since the pin does
105
+ * not depend on the `tiers` flag, the default file's content still stays
106
+ * byte-identical whether or not tier variants are also rendered, the same
107
+ * invariant `composeClaudeAgentVariant`'s own doc comment below describes.
108
+ * Computed inline here rather than passed in like `composeOpencodeAgent`'s
109
+ * `effortLine`: `TIER_DEFS[DEFAULT_TIER[role]].effort` is a pure function of
110
+ * `role` alone, with no model-dependent dispatch on Claude Code the way
111
+ * opencode's family-based `opencodeEffortLine` has, so there is no second,
112
+ * potentially-diverging computation here to guard against.
113
+ */
98
114
  function composeClaudeAgent(role, model) {
99
115
  const asset = readAgentAsset(role);
100
116
  const frontmatter = [
@@ -102,6 +118,7 @@ function composeClaudeAgent(role, model) {
102
118
  `name: ${asset.name}`,
103
119
  `description: ${yamlQuote(asset.description)}`,
104
120
  `model: ${claudeModelValue(model)}`,
121
+ `effort: ${TIER_DEFS[DEFAULT_TIER[role]].effort}`,
105
122
  ];
106
123
  // Read-only roles keep every read/search tool but cannot mutate files.
107
124
  if (READ_ONLY_ROLES.has(role)) {
@@ -110,7 +127,15 @@ function composeClaudeAgent(role, model) {
110
127
  frontmatter.push("---");
111
128
  return [...frontmatter, "", asset.body.trimEnd(), ""].join("\n");
112
129
  }
113
- function composeOpencodeAgent(role, modelValue) {
130
+ /**
131
+ * Composes the unsuffixed default opencode agent file. `effortLine` is the
132
+ * same pinned-default-effort line a tier variant would carry, decided once
133
+ * by the caller via `opencodeEffortLine(DEFAULT_TIER[role], modelValue)`
134
+ * (single source of truth, the same pattern `composeOpencodeAgentVariant`
135
+ * already uses for its own effort line) and passed in rather than
136
+ * recomputed here.
137
+ */
138
+ function composeOpencodeAgent(role, modelValue, effortLine) {
114
139
  const asset = readAgentAsset(role);
115
140
  const frontmatter = [
116
141
  "---",
@@ -122,6 +147,9 @@ function composeOpencodeAgent(role, modelValue) {
122
147
  if (modelValue) {
123
148
  frontmatter.push(`model: ${modelValue}`);
124
149
  }
150
+ if (effortLine) {
151
+ frontmatter.push(effortLine);
152
+ }
125
153
  if (READ_ONLY_ROLES.has(role)) {
126
154
  frontmatter.push("permission:", " edit: deny");
127
155
  }
@@ -134,8 +162,8 @@ function tierDescriptionSuffix(asset, tier) {
134
162
  /**
135
163
  * Composes a tier-variant sibling of `composeClaudeAgent` (`<role>-<tier>.md`).
136
164
  * The default-tier file is never rendered here (callers skip it via
137
- * `DEFAULT_TIER`), so `composeClaudeAgent`'s own output stays byte-identical
138
- * whether or not tiers are on.
165
+ * `DEFAULT_TIER`), so `composeClaudeAgent`'s own output (pinned default
166
+ * effort included) stays byte-identical whether or not tiers are on.
139
167
  */
140
168
  function composeClaudeAgentVariant(role, tier) {
141
169
  const asset = readAgentAsset(role);
@@ -177,8 +205,12 @@ function isClaudeFamilyModel(modelValue) {
177
205
  * effort passthrough, and every other model accepts a plain
178
206
  * `reasoningEffort:` value. An unresolved (`undefined`) model gets no
179
207
  * effort field either, since there is then no model to key the decision on.
208
+ * Shared by both the default (unsuffixed) agent file, keyed by the role's
209
+ * own `DEFAULT_TIER`, and the tier-variant files, keyed by the variant's own
210
+ * suffix tier: the dispatch rule is identical either way, only which tier
211
+ * gets passed in differs.
180
212
  */
181
- function opencodeVariantEffortLine(tier, modelValue) {
213
+ function opencodeEffortLine(tier, modelValue) {
182
214
  if (!modelValue)
183
215
  return undefined;
184
216
  if (isClaudeFamilyModel(modelValue)) {
@@ -197,7 +229,7 @@ function opencodeVariantEffortLine(tier, modelValue) {
197
229
  /**
198
230
  * Composes a tier-variant sibling of `composeOpencodeAgent`. `effortLine` is
199
231
  * decided once, at the single call site in the opencode tier loop of
200
- * `runInit` (via `opencodeVariantEffortLine`), and passed in rather than
232
+ * `runInit` (via `opencodeEffortLine`), and passed in rather than
201
233
  * recomputed here, so there is exactly one place that decides it instead
202
234
  * of a second, independent source of truth for the same value.
203
235
  */
@@ -356,7 +388,8 @@ export function runInit(options) {
356
388
  const modelValue = options.opencodeModels !== undefined
357
389
  ? options.opencodeModels[role]
358
390
  : opencodeModelValue(options.models[role]);
359
- installKitFile(join(".opencode", "agents", `${role}.md`), composeOpencodeAgent(role, modelValue));
391
+ const defaultEffortLine = opencodeEffortLine(DEFAULT_TIER[role], modelValue);
392
+ installKitFile(join(".opencode", "agents", `${role}.md`), composeOpencodeAgent(role, modelValue, defaultEffortLine));
360
393
  if (tiers) {
361
394
  for (const tier of ROLE_TIERS[role]) {
362
395
  if (tier === DEFAULT_TIER[role])
@@ -364,8 +397,8 @@ export function runInit(options) {
364
397
  const modelClass = TIER_DEFS[tier].modelClass;
365
398
  const variantModelValue = options.opencodeClassModels?.[modelClass];
366
399
  if (variantModelValue === undefined) {
367
- // No model resolved for this class: opencodeVariantEffortLine
368
- // always returns undefined too when its modelValue argument is
400
+ // No model resolved for this class: opencodeEffortLine always
401
+ // returns undefined too when its modelValue argument is
369
402
  // undefined (it short-circuits on that first), so this variant
370
403
  // would carry neither a model: nor an effort line, a silent
371
404
  // no-op duplicate of the base file's own (possibly also
@@ -375,10 +408,10 @@ export function runInit(options) {
375
408
  // (e.g. a low/medium tier on a Claude-family model, or any
376
409
  // Ollama model) is NOT skipped by this check: it still renders
377
410
  // with just its model: line, see the effort-field dispatch
378
- // rules in opencodeVariantEffortLine above.
411
+ // rules in opencodeEffortLine above.
379
412
  continue;
380
413
  }
381
- const effortLine = opencodeVariantEffortLine(tier, variantModelValue);
414
+ const effortLine = opencodeEffortLine(tier, variantModelValue);
382
415
  installKitFile(join(".opencode", "agents", `${role}-${tier}.md`), composeOpencodeAgentVariant(role, tier, variantModelValue, effortLine));
383
416
  }
384
417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrator-workflow",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Installer for an orchestrator-led agent workflow: .ai/ run state, an AGENTS.md policy section, and per-harness subagent definitions for Claude Code, OpenAI Codex, and opencode",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",