# Model Profile Resolution

How GMD turns a `(profile, complexity, agent)` triple into a concrete model string at Task-spawn time. The resolver is a single ~10-line function in `bin/lib/core.cjs` — this document explains the order of operations, the CLI surface, and the escape hatch.

See [model-profiles.md](./model-profiles.md) for the 9-cell `COMPLEXITY_MODEL` table, the behavioral promises (`quality` never haiku, `budget` never opus), and the complexity tier vocabulary.

## Resolution Order

The resolver follows **three steps**, in order. Each step is either terminal or falls through to the next:

1. **Override check (escape hatch).** If `config.model_overrides[agentType]` is set, return it immediately. The complexity tag is **ignored** on this path — an override means "I know what I want for this agent, don't be clever."
2. **Table lookup (the common path).** Look up `COMPLEXITY_MODEL[profile][complexity]`. Default the profile to `'balanced'` if missing from config. Default the complexity to `'standard'` if the caller omitted the arg or passed an unknown value (soft validation — see note below).
3. **Opus alias.** If the cell resolved to `'opus'`, return the literal string `"inherit"` instead. This preserves the pre-retrofit behavior where opus-tier spawns inherit the parent session's opus version rather than passing a specific version that org policies might reject.

That is the whole resolution path. There is no post-hoc agent-type ceiling, no heuristic complexity inference, no runtime adjustment.

**Source of truth:** `bin/lib/core.cjs` `resolveModelInternal(cwd, agentType, complexity = 'standard')`.

### Soft vs loud validation

- **Soft (inside the resolver):** An unknown complexity value silently falls back to `'standard'`. This keeps rollout safe for legacy 2-arg internal callers (18 sites in `bin/lib/init.cjs`) that might not yet pass a tag.
- **Loud (at the CLI boundary):** The `resolve-model` CLI rejects unknown complexity values with `exit 1` and stderr `invalid complexity: <val> (expected simple|standard|complex)`. CLI callers are humans and workflow scripts — they should see typos immediately.

Both validation layers read from the same `VALID_COMPLEXITIES` set in `core.cjs` so there is no drift between them.

## CLI Signature

```
gmd-tools resolve-model <agent-type> [--complexity <simple|standard|complex>] [--raw]
```

| Argument | Required? | Notes |
|---|---|---|
| `<agent-type>` | yes | One of the 12 agents in `KNOWN_AGENTS`. Unknown agents still resolve (so third-party skills work), but the JSON result includes `unknown_agent: true` |
| `--complexity <tier>` | no | Defaults to `standard` if omitted. Unknown values → exit 1 + stderr |
| `--raw` | no | Emit the bare model string on stdout instead of the full JSON result. Used inside bash variable assignments: `EXECUTOR=$(resolve-model gmd-executor --complexity complex --raw)` |

Arg order is flexible — `resolve-model gmd-executor --complexity complex --raw` and `resolve-model --raw --complexity complex gmd-executor` both work. The dispatcher scans `args[1..]` and binds the first non-flag token to `<agent-type>`.

## Worked Examples

All examples below assume you have `bin/gmd-tools.cjs` on hand and the repo cwd contains a `.planning/config.json`. Switch profiles with `/gmd:set-profile <profile>` (or `node bin/gmd-tools.cjs set-profile <profile>`) between examples.

### Under `balanced` profile

```
$ /gmd:set-profile balanced

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity simple --raw
haiku

$ node bin/gmd-tools.cjs resolve-model gmd-executor --raw
sonnet
# (no flag → defaults to standard → balanced.standard = sonnet)

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity complex --raw
inherit
# (balanced.complex = opus → "inherit" alias)
```

### Under `quality` profile

```
$ /gmd:set-profile quality

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity simple --raw
sonnet
# (quality floor — never drops to haiku)

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity standard --raw
sonnet

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity complex --raw
inherit
# (quality.complex = opus → "inherit")
```

### Under `budget` profile

```
$ /gmd:set-profile budget

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity simple --raw
haiku

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity standard --raw
sonnet

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity complex --raw
sonnet
# (budget cap — never reaches opus/inherit, even at the complex tier)
```

### Invalid complexity (loud CLI validation)

```
$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity fast --raw
Error: invalid complexity: fast (expected simple|standard|complex)
$ echo $?
1
```

The error goes to stderr; stdout is empty so `--raw` callers in bash don't silently pick up garbage.

### Override escape hatch

With `.planning/config.json`:

```json
{
  "model_profile": "budget",
  "model_overrides": {
    "gmd-executor": "opus"
  }
}
```

```
$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity simple --raw
inherit

$ node bin/gmd-tools.cjs resolve-model gmd-executor --complexity complex --raw
inherit
```

The override wins on both calls — the `budget` profile's "never reaches opus" cap does **not** apply when `model_overrides` is in play, because step 1 of the resolution order terminates before the table lookup is consulted. This is intentional: overrides are the documented escape hatch.

## Declaring Complexity at Spawn Sites

Workflow authors declare complexity at each `Task()` spawn, not in PLAN.md frontmatter or agent frontmatter. Two shapes are common in the repo today:

**Inline bash resolver** (used in `workflows/audit-milestone.md`, `workflows/validate-phase.md`, `workflows/diagnose-issues.md`):

```bash
INTEGRATION_MODEL=$(node "$HOME/.claude/get-magic-done/bin/gmd-tools.cjs" \
  resolve-model gmd-integration-checker --complexity complex --raw)
# ... later, pass INTEGRATION_MODEL to the Task spawn's model="..." attribute
```

**Init-produced placeholder** (used by compound commands like `execute-phase`, `plan-phase`, `new-project`, `new-milestone`, `quick`, `verify-work`, `map-codebase`, `progress`):

The compound-command entry point in `bin/lib/init.cjs` calls `resolveModelInternal(cwd, agent, complexity)` for every agent it plans to spawn, attaches the resolved model strings to its JSON result under keys like `executor_model` / `verifier_model` / `planner_model` / `debugger_model`, and the workflow interpolates those placeholders into its Task blocks.

Both shapes read from the same `COMPLEXITY_MODEL` table — the difference is only whether the complexity tag is declared in the workflow's bash prelude or in the init command call itself.

**Picking a tier:** The heuristic is blast radius, not difficulty. A foundational codebase-mapper spawn that produces the index used by every downstream phase is `complex` because a bad map poisons the whole project. An incremental reindex of one added directory is `simple`. A plan-checker verifying a single plan is `standard`; a plan-checker auditing the whole milestone is `complex`.

## Legacy 2-Arg Callers

`resolveModelInternal(cwd, agent)` (no third arg) still works and returns the `standard`-tier model for the active profile. This is the backwards-compat guarantee that made incremental retrofit possible in Phase 04.1 — untouched call sites keep working, and each site only needs to be upgraded when someone actually wants to pick a non-standard tier.

The same guarantee holds for the CLI: `resolve-model <agent>` (no `--complexity` flag) behaves identically to `--complexity standard`.
