# Model resolution Every codon in a hank names a model, and that one string has to answer several questions before any work starts: which provider and model identity does it mean, which harness will run the session, and does the target actually exist in the catalogs Hankweave can see? This page is the reference for that pipeline. It covers how a `model` spelling is resolved, how the harness is chosen, where validation can fail, and what changed in 0.10.0. The terms used throughout: a **hank** is the workflow configuration in `hank.json`; a **codon** is one task in that workflow; a model-session **harness** is the component that executes the codon. Hankweave first resolves the codon's `model` spelling to a provider and model identity, then selects a harness. The sections below follow that order: the field and its shortcuts, the resolution steps, harness routing, aliases and special cases, and finally the failure points. ## Why model resolution is inspectable Model resolution is deterministic, and you can observe it without running a codon. The shipped registry is Hankweave's provider/model catalog; it keeps the resolved identity separate from the harness selected later at dispatch. Shortcut expansion, provider inference, and harness routing all follow fixed rules, and `--validate` exposes the result. For a local hank, invoke the root flag as `bunx hankweave@0.10.0 --validate `. One consequence of the separation is worth knowing up front: registry resolution and Pi catalog lookup are distinct checks, so a spelling can resolve in the registry and still fail catalog preflight before a codon runs. When the default route is not the one you need, an explicit `pi//` spelling is the supported way to force the Pi harness. Pi is Hankweave's in-process coding-agent harness, supplied by `@earendil-works/pi-coding-agent`; the other model-session harness is the Claude Agent SDK harness. See [Hank JSON](/0.10.0/files/reference/hank-json) for the codon field and [Authentication and models](/0.10.0/files/operate/authentication-and-models) for the two model-selection planes. ## Choosing shortcuts safely ### The codon model field Everything on this page starts from one field: the codon's `model` value, a non-empty string. The schema strip below is the full field contract. | field | type | default | required | constraints | description | | ------- | -------- | ------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | `string` | | yes | minLength 1 | Model to use for this codon. Can be a Claude model ('sonnet', 'opus'), Gemini model ('gemini-2.0-flash-exp', 'flash'), or any other model supported by the configured shim. | The description in that strip still contains the older phrase "configured shim". Here, a shim means the removed provider-adapter path, not a third current harness: shim execution was removed in 0.8.0, and current execution uses the two harnesses described above and below. ### Shortcut expansion To keep hanks short, a few spellings expand before resolution. The shortcuts `opus`, `sonnet`, and `haiku` expand to Claude model names. `gpt-5.6` expands to the `gpt-5.6-sol` sibling variant; `-sol` is part of that model ID, not a separate harness. Expansion also applies after a provider prefix and before the `-high` or `-xhigh` effort suffixes used by reasoning-capable OpenAI variants. The table shows each shortcut, its bare expansion, and how it behaves with a provider prefix or effort suffix. | Shortcut | Bare resolves to | Provider-qualified example | Effort-suffixed example | | --------- | ---------------- | ---------------------------------------------- | ----------------------------------- | | `opus` | `claude-opus` | `anthropic/opus` → `anthropic/claude-opus` | – | | `sonnet` | `claude-sonnet` | `anthropic/sonnet` → `anthropic/claude-sonnet` | – | | `haiku` | `claude-haiku` | `anthropic/haiku` → `anthropic/claude-haiku` | – | | `gpt-5.6` | `gpt-5.6-sol` | `openai/gpt-5.6` → `openai/gpt-5.6-sol` | `gpt-5.6-high` → `gpt-5.6-sol-high` | For a provider-qualified Claude shortcut, expansion applies to the segment after the first slash. A spelling such as `sonnet-high` expands textually to `claude-sonnet-high`, but effort variants are registered only for reasoning-capable OpenAI models; do not assume that the expanded Claude spelling is a registered effort variant. The complete `gpt-5.6` matrix below is useful when checking both qualification and effort, since it shows every accepted spelling and its resolved form: | You write | Resolved spelling | | ---------------------- | -------------------------- | | `gpt-5.6` | `gpt-5.6-sol` | | `openai/gpt-5.6` | `openai/gpt-5.6-sol` | | `gpt-5.6-high` | `gpt-5.6-sol-high` | | `gpt-5.6-xhigh` | `gpt-5.6-sol-xhigh` | | `openai/gpt-5.6-xhigh` | `openai/gpt-5.6-sol-xhigh` | Shortcuts track current provider spellings. Dated model IDs can be retired, so recheck pinned IDs after an upgrade; the `--validate` model self-test exposes the model check, and the startup self-test also performs the catalog check. ## How a model string resolves Resolution normalizes the input and then follows the path below. The `matchType` values describe the successful registry phase. The four entries are presented in order; the implementation calls them steps 0–3, with explicit-Pi as step 0. 1. **Step 0 – explicit Pi spelling:** `pi//` sets `harnessOverride: "pi"`; registry capabilities are used when the underlying model is known, and the Pi target is retained in the spelling supplied after the `pi/` prefix. 2. **Step 1 – provider-scoped exact lookup:** when the registry is called with a provider scope, an exact provider/model lookup returns `exact`; codon validation does not supply that optional scope. 3. **Step 2 – ordinary resolution with provider inference:** normal config validation supplies the model spelling without a provider argument. A direct match found after preferred-provider inference returns `exact-with-inferred-provider`; a qualified spelling such as `anthropic/claude-sonnet-4-5` follows this ordinary path in a codon. 4. **Step 3 – fuzzy matching:** the registry applies its model-match threshold, preferred-provider boost, and recency tiebreak; a match here returns `fuzzy`. Before these phases, legacy `opencode/...` is rewritten to the explicit `pi/...` escape-hatch form, `openrouter//` is rewritten to `pi/openrouter/...`, and qualified provider aliases are normalized. Those input rewrites are distinct from the retained registry identity: the `pi/` prefix in a user's spelling is a Hankweave harness override, not a registry provider name. The table summarizes the same four steps with their match types and scopes: | Step | `matchType` | Provider / scope | Result | | --------------------------- | ------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------ | | 0. Explicit Pi | `exact` | Registry provider when known, or the qualified provider supplied after `pi/` | Pi override with the supplied target | | 1. Provider-scoped lookup | `exact` | Explicit provider argument | Canonical registry model | | 2. Inferred-provider lookup | `exact-with-inferred-provider` | Preferred provider, then a direct match | Canonical registry model | | 3. Fuzzy match | `fuzzy` | Preferred provider when available | Closest registry model above the model-match threshold | Steps 2 and 3 depend on provider inference, which checks exact name patterns first: | Pattern | Preferred provider | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `claude` | `anthropic` | | `gemini` | `google` | | `deepseek` | `deepseek` | | `glm-` | `zhipuai` | | `kimi` | `moonshotai` | | `gpt-`, `o1-`, or `o3-` | `openai` | | Fuzzy fallback: `claude`, `gemini`, `gpt`, or `deepseek` | The first matching term in that order when the full id or one of its words reaches 0.75 similarity | The 0.75 threshold in the last row belongs to provider inference. It is separate from the 0.6 threshold used by the later model fuzzy-match phase. A bare `kimi`-prefixed ID therefore prefers `moonshotai`; the shipped 0.10.0 identity is `moonshotai/kimi-k3`, replacing the `kimi-k2.x` spellings the old docs suggested. The bare prefix is not a shortcut that appends `-k3`, so write `kimi-k3` when you want that exact shipped model. The older Kimi rows remain in the catalog. Use exact IDs or shortcuts in production hanks: fuzzy matching is helpful for development but unreliable for production. For provider setup, see [Authentication and models](/0.10.0/files/operate/authentication-and-models). For the codon's `model` field, see [Hank JSON](/0.10.0/files/reference/hank-json). ## How harness routing is selected Once a model identity is resolved, harness selection happens later, at dispatch, from that retained provider/model identity. The registry does not rewrite the identity into a `pi/…` value. An explicit `harnessOverride: "pi"` forces the Pi harness, including for a model that would otherwise use the Claude Agent SDK. The routing rules are: | Model identity | Harness | Notes | | -------------------------------------------------- | ------------------------ | --------------------------------------------------- | | First-party Anthropic, or Anthropic-family Bedrock | Claude Agent SDK harness | Bedrock uses `CLAUDE_CODE_USE_BEDROCK`. | | Non-Anthropic Bedrock | Pi harness | Routed through Pi. | | Everything else | Pi harness | An explicit `harnessOverride: "pi"` also forces Pi. | At dispatch, Hankweave derives the route Pi receives from the retained identity. `zhipuai` becomes `zai/`, `moonshotai` becomes `openrouter/moonshotai/`, and other providers become `/`. These are dispatch targets; the `pi/` prefix is the separate input spelling that requests the Pi harness. | Registry provider | Pi route | | ------------------ | ------------------------------- | | `zhipuai` | `zai/` | | `moonshotai` | `openrouter/moonshotai/` | | Any other provider | `/` | Bedrock has its own split. Anthropic-family IDs use the Claude Agent SDK path. Non-Anthropic IDs use Pi; `pi/amazon-bedrock/` is the explicit Pi spelling for forcing Anthropic-on-Bedrock through Pi. The spellings and their routes: | Spelling | Harness | Auth / purpose | | -------------------------------------------------------------------------------------------------- | ------------------------ | ---------------------------------------------- | | `anthropic.claude-…` or `.anthropic.claude-…` (`us`, `eu`, `jp`, `au`, `global`, or `us-gov`) | Claude Agent SDK harness | Bedrock mode via `CLAUDE_CODE_USE_BEDROCK`. | | `pi/amazon-bedrock/` | Pi harness | Runs a non-Anthropic Bedrock model through Pi. | | `pi/amazon-bedrock/` | Pi harness | Forces Anthropic-on-Bedrock onto Pi. | Credentials, IAM, and Bedrock limitations belong to [AWS Bedrock](/0.10.0/files/reference/aws-bedrock). Note that `pi//` is an escape hatch, not an unchecked launch: the spelling goes through model validation, sets the harness override, and must be present in Pi's catalog at startup self-test. When the underlying model is known, its registry capabilities are retained for validation. ## Handling aliases and special cases Beyond the main path, a few spellings need normalization or extra handling. Qualified aliases normalize input before matching; Pi then derives its dispatch route from the resolved provider identity. The registry's `zhipuai` identity and Pi's `zai` route are deliberately different names, as the normalization table shows: | You write | Qualified input normalizes to | Pi routes as | | --------------------- | ----------------------------- | ------------------------------- | | `zhipuai/` | `zai/` | `zai/` | | `z-ai/` | `zai/` | `zai/` | | `moonshot/` | `moonshotai/` | `openrouter/moonshotai/` | | `moonshot-ai/` | `moonshotai/` | `openrouter/moonshotai/` | | `moonshotai/` | `moonshotai/` | `openrouter/moonshotai/` | GLM routing is provider inference (`glm-` → `zhipuai`) followed by the `zhipuai` → `zai` route alias. A stale registry comment names `rewriteGlmModel`, but there is no such function in the 0.10.0 model validator and no additional rewrite step. ### Reasoning effort in Pi Reasoning-capable OpenAI models receive auto-generated `-high` and `-xhigh` variants. Pi strips the suffix at dispatch, resolves the base model, and passes the suffix as `thinkingLevel`. An unsuffixed OpenAI model defaults to `high`; `high` has a ×1 multiplier and `xhigh` has a ×2 multiplier against the registered base-model cost. The three spellings and their costs: | Spelling | Effort | Cost multiplier | | ------------------- | ---------------- | --------------: | | `gpt-5.6-sol` | `high` (default) | ×1 | | `gpt-5.6-sol-high` | `high` | ×1 | | `gpt-5.6-sol-xhigh` | `xhigh` | ×2 | ### Legacy stored identities A stored execution plan is the saved model identity for a resumable run. Older plans can contain removed Gemini, Codex, or OpenCode shim provider IDs, or the retired `pi` pseudo-provider encoding. Migration restores the real provider/model identity; a retired `pi` model ID is split at its first slash, and a legacy plan that forced Anthropic onto Pi resumes on the Claude Agent SDK route. Each legacy encoding and its migration: | Legacy encoding | Migration | Resume behavior | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | | Removed Gemini and Codex shims stored as provider IDs `google` and `openai` | Shims are removed; models run natively. | Resume uses the native provider/model route. | | Removed OpenCode shim stored as provider ID `opencode` | Shim is removed; bare spellings are resolved through the registry. | Resume uses the native provider/model route. | | Retired `pi` pseudo-provider with `providerId: "pi"` and `modelId: "/"` | Split the model ID on its first slash; `openrouter/…` remains provider `openrouter`. | Legacy Anthropic-on-Pi plans resume on the Claude Agent SDK route. | See [Hanks](/0.10.0/files/concepts/hanks) for model-override semantics and configuration precedence; those settings are separate from this resolution path. ## Where model validation fails Resolution can succeed while execution still fails, so it helps to know exactly which gates a model passes through. Validation has two model-availability gates here: registry resolution and the Pi catalog when Pi will run the model. Provider health checks are a separate mechanism; they use candidates from provider configuration rather than the codon's model value. The candidates per provider: | Provider | Health-check candidates | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `anthropic` | `claude-haiku-4-5` (pinned, stable) | | `openai` | `gpt-5.4-mini` (pinned) | | `google` | `gemini-flash-latest` (pinned) | | `deepseek` | `deepseek-v4-flash` (pinned) | | `groq` | None; falls back to `findCheapestModel`. | | `amazon-bedrock` | `bedrockHealthCheckModels()`; region-dependent candidates use `us.`, `eu.`, `jp.`, `au.`, `global.`, or `us-gov.`. | The provider health-check candidate is in provider configuration, not `hank.json`; changing a codon's model does not change that candidate. The full distinction between the codon's self-test and provider health check, including when the health check runs and what it tests, belongs to [Authentication and models](/0.10.0/files/operate/authentication-and-models). Since 0.10.0, startup self-test performs the `model_catalog` check for Pi-routed models on every startup, not only with `--validate`. Any registry result that routes to Pi–including an inferred or fuzzy match–still faces this catalog gate; a Claude Agent SDK route does not. Pi's catalog combines a static vendored snapshot with `~/.pi/agent/models.json`, and the same lookup is used when Pi spawns a session. A catalog miss therefore fails config load before any Pi-routed codon runs. The two stages and their failure behavior: | Stage | What it checks | Where it runs / failure | | ---------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1. Registry resolution | `validateModel` confirms the model identity and capabilities. | Model validation. | | 2. Pi catalog check | `lookupPiModel` confirms presence in the static snapshot plus `~/.pi/agent/models.json`. | Startup self-test; a miss fails config load before codon execution. `Pi model not found: /. Available '' models: ` or `Unknown pi provider ''. Known providers: `. | `CodonRunner.canRun(model)` delegates to `selectHarness(model)`. That selector is total in 0.10.0: every registry-resolvable model maps to one of the two harnesses, with Pi as the fallback. The supported-provider list `anthropic, amazon-bedrock, pi` is a list of provider IDs, not a list of three production harnesses; it should not be read as an additional harness-failure gate. | Guard | Check | 0.10.0 behavior | | --------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | | `CodonRunner.canRun(model)` | `selectHarness(model)` chooses Claude Agent SDK or Pi. | Returns true for every registry-resolvable model; the Pi catalog gate remains the relevant preflight for Pi-routed models. | There is no `hankweave models list` subcommand. The `--validate` output shows each codon's resolved model, and a per-model self-test failure reports its reason, including a Pi-catalog miss. The curated supported-models table and registry/Pi selection universe belong to [Models and harnesses](/0.10.0/files/reference/models-and-harnesses), not this page. See [Authentication and models](/0.10.0/files/operate/authentication-and-models) for the two model-selection planes, [Models and harnesses](/0.10.0/files/reference/models-and-harnesses) for the curated model table, and [Errors and exit codes](/0.10.0/files/reference/errors-and-exit-codes) for error classification. ## What changed in 0.10.0 For a 0.9.x user, the relevant 0.10.0 changes are late-bound harness selection, retired Pi pseudo-provider encodings, and Pi catalog preflight. The remaining rows identify when the older resolution surfaces first shipped, which matters when reading documentation written against an earlier version: | Model-resolution surface | Since | | -------------------------------------------------------------------------------- | --------- | | GLM routing via `pi/zai` and `ZAI_API_KEY` | `0.7.4+` | | GPT-5.6 `→ -sol` shortcut and Kimi routing via OpenRouter / `OPENROUTER_API_KEY` | `0.7.5+` | | Two-harness routing and shim removal | `0.8.0+` | | Late-bound harness selection and Pi pseudo-provider retirement | `0.10.0+` | | Pi catalog preflight during startup self-test | `0.10.0+` | The `0.10.0+` markers cover late-bound harness selection, retired Pi pseudo-provider encodings, and catalog preflight. See [Upgrading](/0.10.0/files/start/upgrading) when checking a pinned model spelling after a version change.