Skip to content

feat(orchestrate): configurable model catalog via ORCHESTRATE_MODEL_CATALOG (#171) - #1

Merged
JonnyPower merged 5 commits into
mainfrom
cursor/orchestrate-model-env-overrides-1a9d
Jul 27, 2026
Merged

feat(orchestrate): configurable model catalog via ORCHESTRATE_MODEL_CATALOG (#171)#1
JonnyPower merged 5 commits into
mainfrom
cursor/orchestrate-model-env-overrides-1a9d

Conversation

@JonnyPower

@JonnyPower JonnyPower commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Addresses cursor/plugins#171. A single environment variable, ORCHESTRATE_MODEL_CATALOG, replaces the built-in model catalog with the repo's own. When it is set, that JSON array is the complete menu: it is what planners choose tasks[].model from, what bun cli.ts models prints, and where each task type's default comes from. Nothing is merged with the built-in catalog, so what you write is exactly what runs.

The value is the same shape as MODEL_CATALOG in code, validated by a zod schema and published as schemas/model-catalog.schema.json for editor validation. bun cli.ts models --json emits the built-in catalog in exactly that shape, so configuring a repo is copy-then-edit rather than writing entries from scratch.

bun skills/orchestrate/scripts/cli.ts models --json > catalog.json
# edit: drop what you don't want, move defaultFor where you want it
export ORCHESTRATE_MODEL_CATALOG="$(cat catalog.json)"

Why the catalog and not a per-role default

A per-role fallback would only apply when tasks[].model is omitted, but the subplanner prompt actively tells planners to pick a model from the rendered catalog, and that catalog came from the hardcoded constant. Planners kept selecting the expensive built-ins regardless of any fallback. Replacing the catalog puts the operator's models in the list planners actually read, and gives them slugs that round-trip through resolveModelSelection with their params intact.

Entries

Every entry needs slug, selection, summary, strengths, speed, and use; defaultFor and selection.params are optional. summary, strengths, and use are required because planners select by capability rather than model name, and an entry with thin prose gets passed over. speed is a free-form string so new model vocabulary doesn't need a plugin release.

Each of worker, subplanner, and verifier needs a defaultFor somewhere in the list. Root planners aren't part of the catalog; they keep taking kickoff --model, defaulting to claude-opus-4-8.

Error handling

Validation is the shared parseJsonWithSchema helper, so config problems surface as field-level zod issues through the existing PlanValidationError exit-2 path, at CLI startup rather than mid-run:

ORCHESTRATE_MODEL_CATALOG failed zod validation:
  [0].summary: Required

Every entry needs slug, selection, summary, strengths, speed, and use. Unset the
variable and run `bun cli.ts models --json` to copy the built-in catalog as a
starting point.

A task type left without a default is caught by the same startup check.

Scope

models.ts is +79/-29, and most of that is wiring rather than new logic: the catalog is either MODEL_CATALOG or one parseModelCatalogJson call. agent-manager.ts and the kickoff CLI are untouched, since defaultModelForType and resolveModelSelection keep their signatures. The rest is the schema in schemas.ts, a startup check in cli/index.ts, --json in cli/inspect.ts, generator wiring, and docs. The subplanner prompt now tells planners the list is this repo's catalog and to omit tasks[].model when the marked default fits.

Known limits

This shapes what planners choose from; it is not a spend ceiling, since a planner can still write an arbitrary model id into tasks[].model, which passes through as a bare { id }. A hard clamp would be a separate opt-in knob. Also, each spawned agent reads its own environment, so this belongs in Cursor Cloud secrets for the repo rather than only the dispatcher's shell. Both are documented in the README.

Test plan

  • bun test (224 pass), including 15 new tests covering catalog replacement, task-type defaults, selection round-tripping, pass-through of unknown speed values, and each rejected config
  • A test feeds MODEL_CATALOG back through the schema, so the documented --json starting point is guaranteed to be valid input
  • bun run check (biome + tsc)
  • CLI smoke: models, models --json, a full round-trip of --json output back through the env var, and an incomplete entry
  • Live run: confirm a spawned worker uses the configured model end to end
Open in Web Open in Cursor 

cursoragent and others added 2 commits July 27, 2026 17:41
Support ORCHESTRATE_MODEL_{WORKER,SUBPLANNER,VERIFIER,ROOT} so cost-efficient
pairings can be set without editing MODEL_CATALOG. Values accept a catalog
slug, bare model id, or JSON ModelSelection for out-of-catalog models.

Co-authored-by: Jonny Alexander Power <JonnyPower@users.noreply.github.com>
Merge MODEL_CATALOG with ORCHESTRATE_MODEL_* env config so planners select
from the operator's list rather than the hardcoded constant. Env-named models
join the catalog by slug and round-trip through resolveModelSelection.

Adds ORCHESTRATE_MODEL_CATALOG for extra entries and
ORCHESTRATE_MODEL_CATALOG_MODE=env-only to drop the built-ins entirely, so a
team can publish an exact menu per task type. Config errors fail fast at CLI
startup instead of surfacing mid-run as a spawn failure.

Co-authored-by: Jonny Alexander Power <JonnyPower@users.noreply.github.com>
@cursor cursor Bot changed the title feat(orchestrate): env overrides for model role defaults (#171) feat(orchestrate): configurable model catalog via env (#171) Jul 27, 2026
…ALOG

Drop the per-role env vars and the catalog mode flag. A single JSON array
replaces the built-in catalog outright when set, which removes merge
precedence entirely: the configured list is the complete menu and its
defaultFor entries supply every role default, including the root planner.

Co-authored-by: Jonny Alexander Power <JonnyPower@users.noreply.github.com>
@cursor cursor Bot changed the title feat(orchestrate): configurable model catalog via env (#171) feat(orchestrate): configurable model catalog via ORCHESTRATE_MODEL_CATALOG (#171) Jul 27, 2026
cursoragent and others added 2 commits July 27, 2026 18:18
Reuse TaskType instead of a parallel role union: all three task types require
a default, and the root planner keeps its hardcoded kickoff default rather
than becoming a catalog role.

Stop validating descriptive fields. Speed, strengths, and defaultFor values
are passed through as written, so new model vocabulary doesn't need a plugin
release; only config the CLI genuinely can't read is rejected. Also drops the
JSON-in-model-field parsing that the removed per-role env vars needed, which
lets agent-manager and the kickoff CLI go back to their original code.

Co-authored-by: Jonny Alexander Power <JonnyPower@users.noreply.github.com>
Require ORCHESTRATE_MODEL_CATALOG to hold entries in the same shape as
MODEL_CATALOG, so the hand-rolled entry builder goes away: no id-only
shorthand, no built-in slug references, no synthesized prose. Parsing is now
one call to the shared parseJsonWithSchema helper, which also means field-level
errors and the existing PlanValidationError exit path instead of a bespoke
error class.

Publishes schemas/model-catalog.schema.json for editor validation, and adds
`models --json` to emit the catalog in that shape as a starting point.

Co-authored-by: Jonny Alexander Power <JonnyPower@users.noreply.github.com>
@JonnyPower
JonnyPower marked this pull request as ready for review July 27, 2026 19:00
@JonnyPower
JonnyPower merged commit c45c506 into main Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants