models.json
The editable table of model and effort profiles the commander dispatches with. Yours to change.
This file is yours to edit
.vexillum/models.json is meant to be edited by you. It holds the values (which model and effort for which kind of task); the judgment of which profile fits a task stays with the commander. vx init writes the defaults once and never overwrites the file, and vx upgrade never touches your changes.
Where it lives
Three layers are merged, later ones winning:
| Layer | Location | Notes |
|---|---|---|
| built-in defaults | embedded in the binary | what vx init writes to a new project |
| global base | ~/.vexillum/models.json | optional; applies to every project |
| project | .vexillum/models.json | per project; overrides the global base |
A missing file is skipped. Run vx models to see the merged result and which files were read.
Format
The file is JSON, in English:
{
"profiles": {
"<name>": { "model": "sonnet", "effort": "high", "when": "...", "harness": "claude" }
},
"default": { "model": "sonnet", "effort": "medium" }
}| Field | Meaning |
|---|---|
profiles.<name> | A named profile. The name has no whitespace and is not default. |
profiles.<name>.model | The model passed to the harness. On claude: haiku, sonnet, opus or fable. On pi: a provider/id such as anthropic/claude-sonnet-5, passed as --model. On opencode: a provider/model with an optional #variant, such as opencode/glm-5.3-flash-free or anthropic/claude-sonnet-5#high. |
profiles.<name>.effort | The effort passed to the harness. On claude: low, medium, high, xhigh or max. On pi: a thinking level, off, minimal, low, medium, high, xhigh or max, passed as --thinking (Pi clamps it to what the model supports). On opencode: any plain label, which is recorded on the task and never passed to OpenCode; the variant comes only from the model string. |
profiles.<name>.when | Free text that tells the commander when the profile applies. It must not be empty. |
profiles.<name>.harness | Optional. The agent CLI the profile's soldiers run on. claude (Claude Code) is the default when the field is absent. pi runs the profile's soldiers on the Pi coding agent. opencode runs them on OpenCode, 1.x or 2.x. Any other name is an error that names the file and the field. |
default.model, default.effort | The fallback when no profile fits. Same valid values. |
Unknown fields are rejected, so a typo does not silently do nothing. A value the profile's harness would not accept is an error that names the file and the field. Values are checked after the layers are merged, against the harness the profile ends up with: a project file can set a provider/id model on a pi profile the global base defined, and switching an existing profile to pi fails until its model is a provider/id. A pi model is checked for shape only, because Pi's models are whatever your providers offer; see Using Pi. The default entry has no harness: it always runs on claude. The file has no schema_version; the strict parsing already makes an older vexillum refuse a file that uses a field it does not know.
The built-in profiles, in priority order (the first that matches wins):
| Profile | Model, effort | When |
|---|---|---|
scout-facts | haiku, low | a scout whose question reduces to verifiable facts |
scout-judgment | sonnet, medium | a scout that needs judgment |
mission-mechanical | haiku, low | a trivial mechanical edit |
mission-big | sonnet, high | a big, ambiguous or risky mission |
opus-on-request | opus, high | only when you explicitly ask for Opus |
| default | sonnet, medium | nothing above fits |
How layers merge
The merge is per field, not per file:
- A layer can override just one field of an existing profile, such as
effort, and the rest is inherited. - A new profile must set
model,effortandwhen, because there is nothing to inherit from.harnessis always optional. - A profile cannot be deleted, only redefined.
defaultcan be changed one field at a time.defaultis a reserved name: it cannot be used as a profile name. It selects the default entry invx dispatch --profile default.
Reading it with vx models
vx models is read-only. It prints every profile as name (model, effort) (with , harness <name> added when the profile sets one) followed by its when line, then the default, then the files that were merged:
scout-facts (haiku, low)
Scout whose question reduces to verifiable facts ...
...
default (sonnet, medium)
Nothing above fits. Fall back to it, do not reach for it on purpose.
Source: built-in defaults onlySee vx models.
Dispatching with a profile
vx dispatch "update the changelog wording" --kind mission --profile mission-mechanical--profile <name> looks up the model, effort and harness. The harness is recorded on the task and kept when it is re-dispatched. An explicit --model or --effort wins over the profile, field by field. An unknown profile is an error that lists the valid ones, and --profile default selects the default entry. See vx dispatch.
How the commander uses it
The vexillum skill tells the commander to run vx models, read the when lines and pick the profile that fits the task, then dispatch with --profile. Choosing is still judgment, written in markdown. The file only holds the values, so you change a number instead of rewriting prose.
A profile on Pi
A profile that runs on Pi names a provider/id model, a thinking level as its effort, and the pi harness:
{
"profiles": {
"pi-scout": {
"model": "anthropic/claude-haiku-4-5",
"effort": "low",
"when": "Scout whose question reduces to verifiable facts, run on Pi.",
"harness": "pi"
}
}
}Keep a model id that ends in a colon segment, such as OpenRouter's ...:free, whole in model and put the thinking level in effort; vexillum passes it with --thinking and never as a suffix. Using Pi covers setup, what differs from a Claude Code soldier, and the privacy of free tiers.
A profile on OpenCode
A profile that runs on OpenCode names a provider/model (with an optional #variant), any plain label as its effort, and the opencode harness. The same profile works on OpenCode 1.x and 2.x:
{
"profiles": {
"opencode-scout": {
"model": "opencode/glm-5.3-flash-free",
"effort": "low",
"when": "Scout whose question reduces to verifiable facts, run on a free zero-retention model.",
"harness": "opencode"
}
}
}The effort is only recorded, because variant names differ per model; write a variant after the model, as in anthropic/claude-sonnet-5#high. The model is checked for shape only. Using OpenCode covers setup, the files vexillum writes in the camp, and the privacy of free models.
A worked example
Say you want large missions to run at xhigh effort, a cheap profile for documentation-only work, and Opus as the fallback. Write this to .vexillum/models.json:
{
"profiles": {
"mission-big": { "effort": "xhigh" },
"docs-sweep": {
"model": "haiku",
"effort": "medium",
"when": "Mission that only touches documentation or comments, no behavior change."
}
},
"default": { "model": "opus" }
}mission-big overrides only its effort, so it keeps sonnet and its built-in when. docs-sweep is new, so it sets all three fields. default changes only its model. vx models then shows:
mission-big (sonnet, xhigh)
Mission that is a big or ambiguous multi-file feature, a risky refactor, or work that requires holding many moving parts in mind.
opus-on-request (opus, high)
Only when the general explicitly asks for Opus on this task. Never chosen on your own.
docs-sweep (haiku, medium)
Mission that only touches documentation or comments, no behavior change.
default (opus, medium)
Nothing above fits. Fall back to it, do not reach for it on purpose.The new profile is listed after the built-in ones, and the commander can now pick it from its when line.
Related
- Skills and the
vexillumskill content. - Missions and scouts.
- Files.