Reference

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:

LayerLocationNotes
built-in defaultsembedded in the binarywhat vx init writes to a new project
global base~/.vexillum/models.jsonoptional; applies to every project
project.vexillum/models.jsonper 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" }
}
FieldMeaning
profiles.<name>A named profile. The name has no whitespace and is not default.
profiles.<name>.modelThe 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>.effortThe 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>.whenFree text that tells the commander when the profile applies. It must not be empty.
profiles.<name>.harnessOptional. 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.effortThe 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):

ProfileModel, effortWhen
scout-factshaiku, lowa scout whose question reduces to verifiable facts
scout-judgmentsonnet, mediuma scout that needs judgment
mission-mechanicalhaiku, lowa trivial mechanical edit
mission-bigsonnet, higha big, ambiguous or risky mission
opus-on-requestopus, highonly when you explicitly ask for Opus
defaultsonnet, mediumnothing 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, effort and when, because there is nothing to inherit from. harness is always optional.
  • A profile cannot be deleted, only redefined.
  • default can be changed one field at a time.
  • default is a reserved name: it cannot be used as a profile name. It selects the default entry in vx 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 only

See 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.

On this page