feat(orchestrate): configurable model catalog via ORCHESTRATE_MODEL_CATALOG (#171) - #1
Merged
Merged
Conversation
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>
…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>
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
marked this pull request as ready for review
July 27, 2026 19:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 choosetasks[].modelfrom, whatbun cli.ts modelsprints, 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_CATALOGin code, validated by a zod schema and published asschemas/model-catalog.schema.jsonfor editor validation.bun cli.ts models --jsonemits the built-in catalog in exactly that shape, so configuring a repo is copy-then-edit rather than writing entries from scratch.Why the catalog and not a per-role default
A per-role fallback would only apply when
tasks[].modelis 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 throughresolveModelSelectionwith their params intact.Entries
Every entry needs
slug,selection,summary,strengths,speed, anduse;defaultForandselection.paramsare optional.summary,strengths, anduseare required because planners select by capability rather than model name, and an entry with thin prose gets passed over.speedis a free-form string so new model vocabulary doesn't need a plugin release.Each of
worker,subplanner, andverifierneeds adefaultForsomewhere in the list. Root planners aren't part of the catalog; they keep taking kickoff--model, defaulting toclaude-opus-4-8.Error handling
Validation is the shared
parseJsonWithSchemahelper, so config problems surface as field-level zod issues through the existingPlanValidationErrorexit-2 path, at CLI startup rather than mid-run:A task type left without a default is caught by the same startup check.
Scope
models.tsis +79/-29, and most of that is wiring rather than new logic: the catalog is eitherMODEL_CATALOGor oneparseModelCatalogJsoncall.agent-manager.tsand the kickoff CLI are untouched, sincedefaultModelForTypeandresolveModelSelectionkeep their signatures. The rest is the schema inschemas.ts, a startup check incli/index.ts,--jsonincli/inspect.ts, generator wiring, and docs. The subplanner prompt now tells planners the list is this repo's catalog and to omittasks[].modelwhen 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 unknownspeedvalues, and each rejected configMODEL_CATALOGback through the schema, so the documented--jsonstarting point is guaranteed to be valid inputbun run check(biome + tsc)models,models --json, a full round-trip of--jsonoutput back through the env var, and an incomplete entry