Orchestrator recipe
Choose models for your orchestrator
Give your frontier-model orchestrator a model-selection step for each sub-task or role. ModelSpec is not a per-request router. Call it once per job or role, save the decision_id and snapshot in your job state, then route among the recommended band. Re-decide when requirements change or the snapshot moves.
Machine access is keyed. Only human lookup is free. Get access through pricing or modelspec key. Install the modelspec-dev package using the agent guide.
Add this to the orchestrator system prompt
Copy the block into the orchestrator instructions. The filenames and IDs are placeholders for your job state, not published decisions.
Use ModelSpec when choosing models for a sub-task or role.
Orient once with `modelspec help agent --json` and read the agent guide.
Call decide once per job or role, rather than once per inference request.
Translate the user's requirements into a Spec. Put exclusion gates in where
and ranking preferences in optimize.weights. A '-' prefix on a numeric
weight key means less is better. Never invent gates, facet names, weights,
or numeric tradeoffs. Free-text task is rejected. Find an unknown facet with
`modelspec vocab --search 'software engineering' --json`.
Start from a registry template, for example
`modelspec decide --template coding-balanced --json`, only when its gates
and preferences match the user's requirements. Otherwise adapt its Spec.
Use `modelspec decide --spec role.json --json` for your saved Spec.
Size task_tokens for the user's workload and state any assumptions.
On the first answer, save decision_id, snapshot, the Spec, and the answer in
the orchestrator's job state. Set the Spec's snapshot to that returned ID
for reproducible retries. ModelSpec's CLI does not cache decisions for you.
Check status before reading ranks. For no_feasible, propose relaxing only
a constraint the user stated, one at a time, using the returned relax hints.
Say what changed and why. A retry does not verify a removed requirement.
For partial or coverage, report what is missing rather than choosing a winner.
A non-separable tie has no single winner. Preserve every answer.members
member and offer next_move when present. Do not promote one from memory.
may_qualify means unknown, not a pass. A null cost is unknown, never zero.
Cite only returned evidence. Present summary_for_user unchanged and retain
every must_mention item. Follow reading.do_not_claim and explanation.fetch.
An omitted row is not an elimination.
Use the recommended best band for an actionable answer. If the user has
authorized routing among tied members, preserve the tie in the report and
route within that band without claiming a single best model. Otherwise
offer the returned next steps before dispatching. Export a config with
`modelspec decide --spec role.json --emit-router-config json --out router.json`.
Check exported placeholders and resolve missing verified IDs before use.
When requirements change, update the Spec and decide again. When the snapshot
moves, compare the saved Spec with `modelspec compare --spec role.json --to snap_SAVED --json`.
Replace snap_SAVED with the retained snapshot ID from the saved answer.
If comparison_snapshot_unavailable is returned, follow next. For a fresh
decision, remove the old snapshot pin, decide on the current snapshot, and
pin the returned ID again. State that no historical comparison was available.
Save the new decision_id and snapshot before using the new band.
After acting, submit feedback with
`modelspec feedback dec_SAVED --rating reliable` only if that rating fits
the observed result. Replace dec_SAVED with the saved decision_id. Never
include prompts, credentials, or personal details in feedback.
If the CLI exits 5, stop and tell the human about the key problem. Use
`modelspec key` for access instructions. Do not bypass keyed access.
Work through a coding role
This Spec comes directly from the registry template coding-balanced. Its numeric gates and weights are template assumptions, not measured results or requirements to add silently. Use it only if the user accepts those assumptions. Otherwise keep only the requirements the user stated and adjust the preferences to match. Save the JSON as role.json.
{
"spec_version": 1,
"where": [
"model.class = text-generator",
"model.lifecycle = active",
"model.context_window >= 200000"
],
"optimize": {
"weights": {
"software_engineering": 0.7,
"-offering.cost_per_task": 0.3
}
}
}The first call can use the current snapshot. Run modelspec decide --spec role.json --json, then add the returned snapshot ID to the Spec. Save the answer alongside its decision_id. State the workload assumptions behind cost estimates.
The following is illustrative control flow, not a captured decision or a model recommendation. Suppose the bounded answer has status: partial, answer.kind: tied, and next_move.kind: decide_by_testing. The orchestrator reports summary_for_user unchanged, retains every must_mention item, and offers the returned test steps for the returned candidates. It does not dispatch a supposed winner. Those fields decide the next action even if a result row has the highest rank.
For this role, handle the bounded answer as follows:
- Read
statusandcoverage. If there is no feasible answer, report the blocker and propose a stated constraint to relax. If the answer is partial, report the missing evidence. - Read
answer,summary_for_user, andmust_mention. Ifanswer.kindistied, report all members without selecting a winner. Offernext_movewhen present. Dispatch among tied members only under the user's routing authorization. - For an actionable answer, retain
bands.bestas the allowed band. Keep unknown costs unknown. Followexplanation.fetchif the required evidence was omitted. Never infer exclusions from omitted rows. - Export the band, check unresolved IDs, and record observed feedback after use. Re-decide for changed requirements. Use
modelspec compare --spec role.json --to snap_SAVED --jsonto compare against the saved snapshot when retained snapshots are available. This deployment may returncomparison_snapshot_unavailable. Follow its next steps and request a current decision without claiming a historical comparison. Remove the old snapshot pin for that fresh call, then pin the returned ID again.
Export the recommended band
LiteLLM and OpenRouter are config-export targets. Choose the format your application consumes. These commands request a decision and export its best band. Use the saved Spec with its pinned snapshot. An export preserves membership, not proof of a unique winner. It does not run inference.
modelspec decide --spec role.json --emit-router-config litellm --out router.yaml
modelspec decide --spec role.json --emit-router-config openrouter --out guardrail.json
modelspec decide --spec role.json --emit-router-config json --out router.jsonMissing verified provider IDs remain explicit placeholders. Resolve them before using a config. Do not substitute an ID from memory.
Use MCP instead of the CLI
For a tool-calling orchestrator, connect to https://api.modelspec.dev/mcp with an Authorization: Bearer <MODELSPEC_API_KEY> header. The Spec itself is the decide tool arguments. Do not wrap it in spec or task.
MCP decide returns a bounded answer of at most 16 KB, summary included. Drill down with evidence_for. Complete rows need fields:null; explanation.fetch names what was removed and how to fetch it.
Print a client-specific setup snippet with modelspec setup mcp --client generic. Replace generic with a supported client from the agent guide. Configure the key in that client's environment or secret settings. CLI authentication does not configure another client.