orchestrator-workflow 0.41.0 → 0.43.1

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/dist/routing.d.ts CHANGED
@@ -4,11 +4,13 @@ export interface ModelSelection {
4
4
  model: string;
5
5
  effort: Tier;
6
6
  }
7
+ export type CodexModelAlias = "small" | "balanced" | "strong";
7
8
  /**
8
9
  * A sparse harness-specific model-routing patch. Later layers supplied to
9
10
  * mergeRouting replace a selection leaf while preserving unrelated entries.
10
11
  */
11
12
  export type HarnessRouting = Partial<Record<Harness, Partial<Record<Role, Partial<Record<Tier, ModelSelection>>>>>>;
13
+ export declare function parseCodexModels(value: unknown): Partial<Record<CodexModelAlias, string>>;
12
14
  /**
13
15
  * Parses one strict, sparse routing layer. It deliberately never drops
14
16
  * unrecognised data: a typo in a harness, role, tier, or selection is an
@@ -17,80 +19,44 @@ export type HarnessRouting = Partial<Record<Harness, Partial<Record<Role, Partia
17
19
  export declare function parseRouting(value: unknown): HarnessRouting;
18
20
  /** Deeply merges sparse routing layers without mutating any input layer. */
19
21
  export declare function mergeRouting(...layers: (HarnessRouting | undefined)[]): HarnessRouting;
20
- declare const CODEX_DEFAULTS: {
22
+ declare const CODEX_DEFAULT_ALIASES: {
21
23
  explorer: {
22
- low: {
23
- model: string;
24
- effort: "low";
25
- };
26
- medium: {
27
- model: string;
28
- effort: "medium";
29
- };
30
- high: {
31
- model: string;
32
- effort: "high";
33
- };
24
+ low: "small";
25
+ medium: "balanced";
26
+ high: "balanced";
34
27
  };
35
28
  "task-slicer": {
36
- low: {
37
- model: string;
38
- effort: "low";
39
- };
40
- medium: {
41
- model: string;
42
- effort: "medium";
43
- };
44
- high: {
45
- model: string;
46
- effort: "high";
47
- };
29
+ low: "balanced";
30
+ medium: "balanced";
31
+ high: "balanced";
48
32
  };
49
33
  implementer: {
50
- low: {
51
- model: string;
52
- effort: "low";
53
- };
54
- medium: {
55
- model: string;
56
- effort: "medium";
57
- };
58
- high: {
59
- model: string;
60
- effort: "high";
61
- };
62
- xhigh: {
63
- model: string;
64
- effort: "xhigh";
65
- };
34
+ low: "small";
35
+ medium: "balanced";
36
+ high: "balanced";
37
+ xhigh: "strong";
66
38
  };
67
39
  reviewer: {
68
- medium: {
69
- model: string;
70
- effort: "medium";
71
- };
72
- high: {
73
- model: string;
74
- effort: "high";
75
- };
76
- xhigh: {
77
- model: string;
78
- effort: "xhigh";
79
- };
40
+ medium: "balanced";
41
+ high: "strong";
42
+ xhigh: "strong";
80
43
  };
81
44
  advisor: {
82
- high: {
83
- model: string;
84
- effort: "high";
85
- };
86
- xhigh: {
87
- model: string;
88
- effort: "xhigh";
89
- };
45
+ high: "strong";
46
+ xhigh: "strong";
90
47
  };
91
48
  };
49
+ export declare function codexModelsRoutingPatch(value: unknown): HarnessRouting;
50
+ type CodexDefaultRoleRouting<AliasRouting extends Partial<Record<Tier, CodexModelAlias>>> = {
51
+ [TierName in keyof AliasRouting]: TierName extends Tier ? {
52
+ model: string;
53
+ effort: TierName;
54
+ } : never;
55
+ };
92
56
  export type CodexDefaultRouting = HarnessRouting & {
93
- codex: typeof CODEX_DEFAULTS;
57
+ codex: {
58
+ [RoleName in Role]: CodexDefaultRoleRouting<(typeof CODEX_DEFAULT_ALIASES)[RoleName]>;
59
+ };
94
60
  };
95
61
  /** Returns a fresh complete Codex routing layer, including each default tier. */
96
62
  export declare function defaultCodexRouting(): CodexDefaultRouting;
package/dist/routing.js CHANGED
@@ -1,5 +1,11 @@
1
1
  import { HARNESSES } from "./detect.js";
2
+ import { readAsset } from "./assets.js";
2
3
  import { ROLE_TIERS, ROLES } from "./models.js";
4
+ const CODEX_MODEL_ALIASES = [
5
+ "small",
6
+ "balanced",
7
+ "strong",
8
+ ];
3
9
  const TIERS = ["low", "medium", "high", "xhigh"];
4
10
  const DANGEROUS_KEYS = new Set(["__proto__", "prototype", "constructor"]);
5
11
  const CLAUDE_ALIASES = new Set(["sonnet", "opus", "haiku"]);
@@ -58,6 +64,50 @@ function assertModelId(model, harness, location) {
58
64
  }
59
65
  return model;
60
66
  }
67
+ function assertConcreteCodexModelId(model, location) {
68
+ const value = assertModelId(model, "codex", location);
69
+ if (CODEX_MODEL_ALIASES.includes(value)) {
70
+ throw new Error(`${location}.model must not use an internal Codex alias`);
71
+ }
72
+ return value;
73
+ }
74
+ function loadBundledCodexModels() {
75
+ let value;
76
+ try {
77
+ value = JSON.parse(readAsset("codex-models.json"));
78
+ }
79
+ catch (error) {
80
+ const reason = error instanceof Error ? error.message : String(error);
81
+ throw new Error(`Could not read bundled Codex model aliases: ${reason}`);
82
+ }
83
+ if (!isRecord(value)) {
84
+ throw new Error("Bundled Codex model aliases must be an object");
85
+ }
86
+ assertSafeKeys(value, "bundled Codex model aliases");
87
+ const result = {};
88
+ for (const alias of CODEX_MODEL_ALIASES) {
89
+ if (!(alias in value)) {
90
+ throw new Error(`Bundled Codex model aliases must contain "${alias}"`);
91
+ }
92
+ result[alias] = assertConcreteCodexModelId(value[alias], `bundled Codex model alias "${alias}"`);
93
+ }
94
+ for (const key of Object.keys(value)) {
95
+ assertKnownKey(key, CODEX_MODEL_ALIASES, "bundled Codex model alias");
96
+ }
97
+ return result;
98
+ }
99
+ export function parseCodexModels(value) {
100
+ if (!isRecord(value)) {
101
+ throw new Error("Codex model aliases must be an object");
102
+ }
103
+ assertSafeKeys(value, "Codex model aliases");
104
+ const result = {};
105
+ for (const [alias, model] of Object.entries(value)) {
106
+ assertKnownKey(alias, CODEX_MODEL_ALIASES, "Codex model alias");
107
+ result[alias] = assertConcreteCodexModelId(model, `Codex model alias "${alias}"`);
108
+ }
109
+ return result;
110
+ }
61
111
  function parseSelection(value, harness, location) {
62
112
  if (!isRecord(value)) {
63
113
  throw new Error(`${location} must be a selection object`);
@@ -144,36 +194,55 @@ export function mergeRouting(...layers) {
144
194
  }
145
195
  return result;
146
196
  }
147
- const CODEX_DEFAULTS = {
197
+ const CODEX_DEFAULT_ALIASES = {
148
198
  explorer: {
149
- low: { model: "gpt-5.6-luna", effort: "low" },
150
- medium: { model: "gpt-5.6-sol", effort: "medium" },
151
- high: { model: "gpt-5.6-sol", effort: "high" },
199
+ low: "small",
200
+ medium: "balanced",
201
+ high: "balanced",
152
202
  },
153
203
  "task-slicer": {
154
- low: { model: "gpt-5.6-luna", effort: "low" },
155
- medium: { model: "gpt-5.6-sol", effort: "medium" },
156
- high: { model: "gpt-5.6-sol", effort: "high" },
204
+ low: "balanced",
205
+ medium: "balanced",
206
+ high: "balanced",
157
207
  },
158
208
  implementer: {
159
- low: { model: "gpt-5.6-luna", effort: "low" },
160
- medium: { model: "gpt-5.6-terra", effort: "medium" },
161
- high: { model: "gpt-5.6-terra", effort: "high" },
162
- xhigh: { model: "gpt-6-astra", effort: "xhigh" },
209
+ low: "small",
210
+ medium: "balanced",
211
+ high: "balanced",
212
+ xhigh: "strong",
163
213
  },
164
214
  reviewer: {
165
- medium: { model: "gpt-5.6-terra", effort: "medium" },
166
- high: { model: "gpt-6-astra", effort: "high" },
167
- xhigh: { model: "gpt-6-astra", effort: "xhigh" },
215
+ medium: "balanced",
216
+ high: "strong",
217
+ xhigh: "strong",
168
218
  },
169
219
  advisor: {
170
- high: { model: "gpt-6-astra", effort: "high" },
171
- xhigh: { model: "gpt-6-astra", effort: "xhigh" },
220
+ high: "strong",
221
+ xhigh: "strong",
172
222
  },
173
223
  };
224
+ function codexRoutingForAliases(models) {
225
+ const routing = { codex: {} };
226
+ for (const role of ROLES) {
227
+ for (const tier of ROLE_TIERS[role]) {
228
+ const alias = CODEX_DEFAULT_ALIASES[role][tier];
229
+ if (alias === undefined)
230
+ continue;
231
+ const model = models[alias];
232
+ if (model === undefined)
233
+ continue;
234
+ routing.codex[role] ??= {};
235
+ routing.codex[role][tier] = { model, effort: tier };
236
+ }
237
+ }
238
+ return routing;
239
+ }
240
+ export function codexModelsRoutingPatch(value) {
241
+ return codexRoutingForAliases(parseCodexModels(value));
242
+ }
174
243
  /** Returns a fresh complete Codex routing layer, including each default tier. */
175
244
  export function defaultCodexRouting() {
176
- return mergeRouting({ codex: CODEX_DEFAULTS });
245
+ return codexRoutingForAliases(loadBundledCodexModels());
177
246
  }
178
247
  function normalizeCodexCatalog(catalog) {
179
248
  const modelsValue = Array.isArray(catalog)
@@ -0,0 +1,44 @@
1
+ # Architecture: why this shape
2
+
3
+ The orchestrator/subagent loop `orchestrator-workflow` installs, and the
4
+ reasoning behind it. See the [package README](../README.md) for installation
5
+ and day-to-day usage.
6
+
7
+ ```text
8
+ Operator
9
+ goal | ^ handoff: what changed, how verified,
10
+ v | what remains open
11
+ explorer --> Orchestrator . . . . . .ai/runs/<date>-<slug>/
12
+ optional, session model 00-goal 04-implementation-summary
13
+ read-only plans, validates slices, 01-plan 05-review-findings
14
+ terrain map decides acceptance 02-tasks 06-handoff
15
+ | 03-decisions
16
+ narrow | ^ structured (state lives in files,
17
+ contracts v | YAML evidence not in chat history)
18
+ +-------------+-------------+
19
+ | | |
20
+ task-slicer implementer reviewer
21
+ sonnet sonnet opus
22
+ small, one narrow skeptical, severity-rated
23
+ testable task, plus findings, no rewrites
24
+ slices tests
25
+ ```
26
+
27
+ Two effects fall out of this shape:
28
+
29
+ - **Token efficiency.** The orchestrator's context stays small: subagents
30
+ receive narrow task contracts instead of the whole conversation, return
31
+ structured YAML evidence instead of transcripts, and durable state lives
32
+ in run files that survive context compaction. The cheap models do the
33
+ volume work; the strongest model is spent only on orchestration decisions
34
+ and the skeptical review. The ceremony scales to the task: a trivial change
35
+ is done directly, the full flow is for non-trivial work, and a read-only
36
+ explorer maps the terrain first only when the solution is unclear. When
37
+ available, the explorer prefers each of a repo's configured knowledge
38
+ bundles (`knowledge` in `.ai/workflow/manifest.json`; default `docs/okf/`)
39
+ or a connected semantic code-search tool over hand-mapping terrain with
40
+ grep.
41
+ - **Quality through structure.** Writing and reviewing are separated by
42
+ role and model, task slices are validated before any implementation
43
+ starts, acceptance is decided on evidence (tests executed, findings
44
+ addressed), and every run leaves an auditable trail in `.ai/runs/`.
@@ -0,0 +1,38 @@
1
+ # Harnesses: installed files and read-only posture
2
+
3
+ See the [package README](../README.md) for install commands and the rest of
4
+ the CLI surface.
5
+
6
+ Each installed skill includes the compact `SKILL.md` entrypoint and every
7
+ regular Markdown file from its adjacent `references/` directory. The entrypoint
8
+ routes run-state/harness, contracts, evidence/probes, and review/recovery work
9
+ to those files; references are part of the installed skill, not optional docs.
10
+
11
+ | Harness | Files | Notes |
12
+ |---|---|---|
13
+ | Claude Code | `.claude/skills/orchestrator-workflow/{SKILL.md,references/*.md}`, `.claude/agents/{explorer,task-slicer,implementer,reviewer,advisor}.md`, `CLAUDE.md` | Claude Code reads `CLAUDE.md`, not `AGENTS.md`; the installer adds an additive `@AGENTS.md` import. Subagent models go into the `model:` frontmatter; the read-only explorer, reviewer, and advisor also get `disallowedTools: Edit, Write, NotebookEdit`. |
14
+ | OpenAI Codex | `.agents/skills/orchestrator-workflow/{SKILL.md,references/*.md}`, `.codex/agents/{explorer,task-slicer,implementer,reviewer,advisor}.toml` | Codex reads `AGENTS.md` natively. Native custom-agent files carry the canonical role instructions plus `model` and `model_reasoning_effort`. Explorer and advisor request a read-only sandbox; reviewer inherits the caller's sandbox so it can run temporary/build checks, while its prompt prohibits source edits. |
15
+ | opencode | `.opencode/skills/orchestrator-workflow/{SKILL.md,references/*.md}`, `.opencode/agents/{explorer,task-slicer,implementer,reviewer,advisor}.md` | opencode reads `AGENTS.md` natively. Subagents get `mode: subagent`; the read-only explorer, reviewer, and advisor also get `permission: edit: deny`. Model resolution is described in [Model routing reference](model-routing-reference.md). |
16
+
17
+ **Read-only posture, honestly stated.** Claude Code disables file-mutation
18
+ tools for explorer, reviewer, and advisor; opencode denies edits for those
19
+ roles. Codex requests a read-only sandbox for explorer and advisor. Its
20
+ reviewer inherits the caller's sandbox so temporary/build checks remain
21
+ possible, while its prompt prohibits source edits. In inherited or otherwise
22
+ write-enabled sandboxes, shell-level mutation (`git checkout`,
23
+ `git restore`, `git clean`, `git stash`, `git reset`, `sed -i`, redirecting
24
+ output into a file, which the reviewer may do only inside its write boundary below) is guarded by instruction only: the agent prompts forbid
25
+ it explicitly, but the role definition itself does not prevent it. A native
26
+ read-only sandbox can block those writes. This residual has bitten in practice (a
27
+ reviewer ran `git checkout` and discarded uncommitted work), which is why the
28
+ prompts now name the forbidden commands instead of just saying "read-only".
29
+ The reviewer's own write boundary is narrower than "read-only": it may write
30
+ to its own scratchpad (a scratch copy or replay of the repository) and to
31
+ the run directory's `evidence/`, and nowhere else. It never writes into the
32
+ reviewed tree, its index, its refs, or its object store: no `git fetch`, no
33
+ `git merge-tree --write-tree`, no `git update-ref`, no `git gc`, on top of
34
+ the working-tree and index mutations already forbidden above. A write a
35
+ declared check or the probe runner's own isolation leaves behind is expected
36
+ wherever that tool places it, not an exception to this rule.
37
+ Marker- or verdict-style enforcement of the Bash residual (sandboxing,
38
+ PreToolUse hooks) is harness territory and out of this kit's scope.
@@ -0,0 +1,95 @@
1
+ # Install reference
2
+
3
+ Details behind the install commands in the [package README](../README.md):
4
+ what the agent-led installer's conflict check does, the exact rules for
5
+ `--harness none` (templates-only mode) on a re-run, the optional
6
+ `knowledge` manifest field for a repository whose bundle is not at the
7
+ default location, and the file-ownership rules a re-run follows.
8
+
9
+ ## Agent-led installation: bundle and conflict handling
10
+
11
+ The compact skill entrypoint and its routed references form one installed
12
+ bundle. On a reinstall, the installer checks the core and every required
13
+ reference for local conflicts before activating a new core; it leaves the
14
+ current coherent bundle intact unless an explicitly authorized `--force` run
15
+ replaces the affected files.
16
+
17
+ ## Templates-only mode (`--harness none`)
18
+
19
+ `--harness none` (the literal word `none`, on its own) installs only
20
+ `.ai/workflow/**` and `.ai/runs/.gitkeep`: no `AGENTS.md`, no `CLAUDE.md`, no
21
+ harness-specific directory, and a manifest recording `harnesses: []`. Use it
22
+ for a repo that wants the run-state templates and the workflow itself, but no
23
+ per-harness subagent files yet (e.g. no harness has been chosen, or the files
24
+ were dropped by hand). `none` combined with a real harness name
25
+ (`--harness none,claude`) is rejected as ambiguous rather than silently
26
+ picking one. A plain **non-interactive** re-run (no `--harness` flag) after a
27
+ templates-only install stays templates-only, for `init` and `apply` alike,
28
+ even when `apply`'s own operator-defaults name a harness or the target has
29
+ harness files on disk from something else; add a harness back with an
30
+ explicit `--harness <list>` on a later run, the same explicit-flag-wins rule
31
+ `--profile`/`--models`/`--tiers` use, applied to the no-harness case. An
32
+ **interactive** re-run is different: it still prompts, with nothing forced
33
+ pre-selected, instead of silently skipping straight back to templates-only
34
+ without asking; deselect every checkbox to stay templates-only. `init` and
35
+ `apply` both pre-check nothing at all on this prompt, and both still
36
+ annotate what is detected on disk with a " (detected)" label; select a
37
+ harness to install it.
38
+
39
+ ```bash
40
+ npx orchestrator-workflow init --harness none --yes
41
+ ```
42
+
43
+ ## The `knowledge` manifest field
44
+
45
+ `manifest.json` may also carry a `knowledge` list of `{ path, repoRoot }`
46
+ entries, for a repo whose knowledge bundle is not at the default location (a
47
+ workspace-level bundle with sources in a sub-repo, a bundle elsewhere, or
48
+ several bundles). `path` is the bundle directory and `repoRoot` (default
49
+ `"."`) the root of the repository the bundle's sources live in. Each is a
50
+ relative path resolved against the worktree top level on its own (`path` is
51
+ not nested under `repoRoot`), so a workspace bundle for a sub-repo's sources
52
+ reads `{ "path": "kb/app", "repoRoot": "app" }`. Entries are stored
53
+ normalised (`./kb/app/` becomes `kb/app`); an empty or absolute path
54
+ (POSIX, or a Windows form such as `C:/x`), any other path starting with a
55
+ Windows drive letter (the drive-relative `C:x` or `C:..`), a `path` of `.`, a
56
+ path escaping the worktree top level, and any path containing a backslash are
57
+ invalid (use `/` as the separator on every platform). The absolute, drive and
58
+ escape rules apply both as written and to the normalised value that is stored,
59
+ so `./C:x` and `docs/../C:/x` are invalid too. The CLI has no flag for the
60
+ field: edit it in the manifest by hand, and every re-install preserves its
61
+ valid entries (the programmatic `runInit` option `knowledge` writes it and
62
+ refuses an invalid entry). A hand-edited invalid entry is ignored on read
63
+ and reported by `doctor`; a re-install that rewrites the manifest removes it
64
+ from disk and prints a note naming its index and reason. The field carries
65
+ no check argv; the concrete bundle-check command still lives in the
66
+ repository-bound verification set (see the package README's
67
+ "Verification sets" section), so there is one source of argv truth. When
68
+ `knowledge` in `.ai/workflow/manifest.json` is absent or an empty list, the
69
+ default `docs/okf/` applies, today's behaviour. `doctor` prints a
70
+ `knowledge:` detail line (the `knowledgeWarnings` key in `--json`) for a
71
+ configured `path` or `repoRoot` that is not a directory, for each ignored
72
+ invalid
73
+ entry, and when a non-empty list omits an existing default bundle directory.
74
+ These warnings never change the status or the exit code.
75
+
76
+ ## Ownership and re-runs
77
+
78
+ `init` is idempotent: a second run changes nothing. `apply` installs
79
+ through that same `runInit` path and is subject to the same
80
+ conflict/`--force`/ownership rules; on the repository side it changes
81
+ nothing either, but it refreshes this target's entry in the operator
82
+ manifest on every run. The rules:
83
+
84
+ - `AGENTS.md` and `CLAUDE.md` belong to you. The installer only appends its
85
+ fenced section or the import line, and on re-run replaces only the content
86
+ between its own markers. A broken or duplicated marker fence is reported as
87
+ a conflict and left alone.
88
+ - Templates, skills, and subagent definitions are kit-owned. The manifest
89
+ records a hash of each file as installed, so a re-run after a kit upgrade
90
+ updates files you never touched and reports files you edited as conflicts
91
+ instead of overwriting them; `--force` overwrites those too.
92
+ - `.ai/workflow/manifest.json` is the kit's state file. It records the applied
93
+ version, harnesses, role profile, models, the `--tiers` flag, the optional
94
+ kit-version pin, and file hashes, and is rewritten whenever that state
95
+ changes; do not edit it by hand.