You're reading the 0.10.0 archive.

Choose credentials and model routes

Every codon in a hank.json names a model, and that single string decides more than it appears to: which provider serves the request, which agent harness runs it, and which credential has to exist before startup will proceed. When authentication fails, the error can come from several different places, and the fix depends on knowing which one. This page follows a model spelling from hank.json through resolution, harness dispatch, credential lookup, and the startup checks, so that each failure message points to a specific cause. Along the way we cover sentinel credentials, the ChatGPT subscription route, provider quota, and providers outside the registry's default routing.

Which key does each provider need?#

Start with the route in your codon's model field; we'll match it to a credential, then check that the selected harness can serve it. A codon is one model-backed task in a hank.json configuration. A sentinel is a separate monitoring task that can make its own provider call and use a separate credential. For an enforced provider, set the standard environment variable listed below.

Scroll to explore the table →
ProviderStandard variableSentinel overridePi-route credential handling
AnthropicANTHROPIC_API_KEYHANKWEAVE_SENTINEL_ANTHROPIC_API_KEYEnvironment key is enforced for the Pi route
OpenAIOPENAI_API_KEYHANKWEAVE_SENTINEL_OPENAI_API_KEYEnvironment key is enforced for the Pi route
GoogleGEMINI_API_KEYHANKWEAVE_SENTINEL_GEMINI_API_KEYEnvironment key is enforced for the Pi route
GroqGROQ_API_KEYHANKWEAVE_SENTINEL_GROQ_API_KEYPi resolves the credential
DeepSeekDEEPSEEK_API_KEYHANKWEAVE_SENTINEL_DEEPSEEK_API_KEYPi resolves the credential
AWS BedrockAWS_BEARER_TOKEN_BEDROCKHANKWEAVE_SENTINEL_AWS_BEARER_TOKEN_BEDROCKProvider-specific handling
Z.AI (0.7.4+)ZAI_API_KEYEnvironment key is enforced for the Pi route
OpenRouter (0.7.5+)OPENROUTER_API_KEYEnvironment key is enforced for the Pi route

Read the last column as the Pi-route behavior for that provider. First-party Anthropic and Anthropic-family Bedrock models normally use the Claude Agent SDK harness; an explicit pi/<provider>/<model> spelling forces the Pi harness instead. The openai-codex subscription route is a separate Pi-provider exception and uses Pi's OAuth store rather than OPENAI_API_KEY; see ChatGPT subscription below.

For the full variable catalog, including HANKWEAVE_ prefixes, trace and telemetry variables, and masking rules, see Environment variables.

One Google spelling caveat applies at 0.10.0. Since 0.8.0, GEMINI_API_KEY is the only Google key spelling read by the registry, Pi key injection, and wizard detection; the legacy GOOGLE_API_KEY alias was removed. Set GEMINI_API_KEY and unset the legacy name. A separate recorded run announced Using GOOGLE_API_KEY when both names were set, but that behavior is not defined by the 0.10.0 runtime, so treat GEMINI_API_KEY as the supported spelling. Environment variables also documents --validate's environment echo and name-based masking of secret values.

From a model string to a running codon#

Knowing which variable to set is only half of authentication. The other half is what the runtime does with your model string, because a credential can be present and correct while the route itself fails. When a codon does not authenticate as expected, inspect these decisions in order: authored spelling, retained identity, optional harness override, dispatch target, credential source, and startup self-test. Three terms carry the rest of this page. A harness is the agent runner selected for a given path. The registry is Hankweave's provider-and-model list. Pi is Hankweave's embedded agent harness for routes that do not use the Claude Agent SDK harness.

Note that the key-handling column in the table above identifies which component supplies or enforces a key; it does not determine the dispatch harness for every model.

Scroll to explore the table →
Decision pointWhat to inspectWhere to look next
Authored spellingThe codon's model: shortcut, full ID, provider-qualified ID, or explicit pi/<provider>/<model>Model resolution
Retained identityThe real (providerId, modelId); resolution does not rewrite it to a pseudo-providerModel resolution
harnessOverrideSet to "pi" only by an explicit pi/… spellingModel resolution
Dispatch targetFirst-party Anthropic and Anthropic-family Bedrock models use the Claude Agent SDK harness; other models use the embedded Pi harnessModel resolution
Credential sourceanthropic, google, openai, openrouter, and zai receive environment keys in memory; other Pi providers use the process environment or Pi's credential storeEnvironment variables
Startup self-testEach unique harness:provider/model gets a local self-test before any codon runsErrors and exit codes

The table gives the lookup view; here is the same path as the runtime walks it. Start with the model value in hank.json. For shortcut, phase, and fuzzy matching rules, see Model resolution; here we follow that spelling into the runtime.

Resolution retains the real provider and model pair, and harness selection is computed at dispatch time from that pair. An explicit pi/<provider>/<model> spelling sets harnessOverride to "pi", even when the default route would use the Claude Agent SDK harness.

The Claude Agent SDK harness handles first-party Anthropic and Anthropic-family Bedrock models. Other routes go to the embedded Pi harness through its target conversion. The conversion is alias-aware: zhipuai becomes zai, and moonshotai becomes openrouter/moonshotai/….

For the Pi harness, the five enforced providers receive their environment keys as in-memory runtime keys. Other Pi providers resolve credentials from the process environment or Pi's on-disk credential store.

At startup, the self-test checks every unique codon model in a temporary directory. It keys models by harness:provider/model, so an explicit-Pi spelling and a default spelling of the same model are tested separately. A failure stops startup before a codon runs. The checks cover SDK imports, executable and credential presence, and Pi catalog lookup. They make no billable API call at 0.10.0; replay mode is the only mode that skips them. The welcome wizard's separate pre-demo credit check is the one-token call, and the provider health check described below is a different check that makes its own provider call.

--validate resolves the configuration and performs harness setup, credential, and catalog checks; it does not execute codons or make a model-generation call. Runtime startup performs the provider health check separately.

The welcome wizard is the first-run interactive setup flow, while --init provides templates. See First run for the wizard and --init anatomy.

One route-specific timeout is worth knowing before the first long run: a pi/baseten/deepseek-ai/DeepSeek-V4-Pro route can idle silently for more than 15 minutes while reasoning without streamed agent events. The --shim-idle-timeout flag sets a per-codon harness idle timeout (default: 120 seconds); the documented anchor run used --shim-idle-timeout 1800 for that route.

Check-it 1: remove the provider key#

To see the startup failure rather than just read about it, unset ANTHROPIC_API_KEY, then start the minimal single-provider hank. The failure occurs before a codon runs, which is the signature of the self-test plane:

Output
…
[<ts>] [ERROR] Self-test FAILED: Some checks failed
[<ts>] [ERROR]   - authentication: ✗ No authentication found (set ANTHROPIC_API_KEY)
[ERROR] Server startup failed!
Error message: Self-test failed for 1 model(s):
…

The authentication check reports the missing variable by name and startup aborts. No codon executed; the self-test stopped the run first.

Why can an auth error survive editing hank.json?#

An edit to hank.json cannot change the model chosen by the provider health check, because two model-selection planes are involved and only one of them reads the hank.

Plane 1 is the codon self-test. It tests the resolved codon model, including the spelling you control in hank.json.

Plane 2 is the provider health check. A registry provider is initialized only when a credential source exists and provider creation succeeds; an unkeyed provider is not-configured. At startup, the check probes each initialized registry provider with a pinned model from PROVIDER_DEFINITIONS.healthCheckModels. The health-check model is not configurable from hank.json. This startup probe is also separate from a sentinel trigger: the probe uses the pinned registry model, while a sentinel uses its own model spelling.

The error text tells you which plane produced it. Self-test failed and Unknown pi provider identify the codon-model plane; provider-call errors identify the health-check plane. The pinned probe models are:

Scroll to explore the table →
ProviderPinned probe model
Anthropicclaude-haiku-4-5
OpenAIgpt-5.4-mini
Googlegemini-flash-latest
DeepSeekdeepseek-v4-flash
GroqNo pinned model; use the cheapest-model fallback
BedrockRegion-dependent function form

The health check makes a real generateText call with "Hi", maxOutputTokens: 16, and a five-second abort timeout. Candidates are tried in order. Startup can wait for all checks, allow a grace period, or let them continue in the background.

This split has bitten in practice. In the March 2026 issue-#1 failure, the health-check fallback selected the dated preview gemini-2.5-flash-lite-preview-09-2025, and changing the hank did not change that spelling. The fix pinned health models, filtered dated previews, and added the 0.10.0 model_catalog preflight.

The planes can also disagree in the other direction. A sentinel's model spelling can cause No LLM provider available; that is a sentinel-trigger error, not the startup provider-health probe. A short name such as haiku passes --validate with GOOD TO RUN but fails every trigger at run time, because GOOD TO RUN means validation accepted the configuration, not that a trigger can resolve its model. Use a full registry spelling such as anthropic/claude-haiku-4-5 or google/gemini-2.5-flash so the sentinel can fire and write its log. This is a spelling and plane issue, not a credential-path divergence: codon credentials on the Pi path and sentinel/provider-registry credentials remain distinct resolution paths. The failure looks like this:

Output
…
[<ts>] [ERROR] [Sentinel:quality-observer] LLM call failed: Error: No LLM provider available
[<ts>] [ERROR] [Sentinel:quality-observer] Error in trigger <id>: Error: No LLM provider available
…

The sentinel name in the log line and the trigger-level error are what distinguish this from the startup probe.

When the registry knows a model Pi cannot serve#

A registry can recognize a model while Pi cannot serve the provider target. At startup, the model_catalog check applies the same lookup gate used for model spawning. A miss reports Pi model not found: <provider>/<model>; an unknown provider reports Unknown pi provider '<provider>' and lists known providers.

Following glm-5.3 through resolution shows why a bare spelling can produce an unfamiliar provider error:

  1. Start with the authored spelling glm-5.3. For glm-*, the registry first prefers zhipuai, but that provider has no glm-5.3. Because no preferred provider owns the model, the bare short key uses the last-loaded provider carrying it, so the spelling resolves to (zai-coding-plan, glm-5.3). The shipped registry carries glm-5.3 under opencode-go, zhipuai-coding-plan, and zai-coding-plan.
  2. Let the catalog check inspect zai-coding-plan. No Pi alias covers that provider, so Pi cannot serve zai-coding-plan/glm-5.3; the self-test fails with Unknown pi provider 'zai-coding-plan' before any codon runs.
  3. Change the spelling to pi/zai/glm-5.3. The explicit spelling sets harnessOverride, and the catalog check uses Pi's zai provider. Use this explicit form rather than relying on bare-key provider order.

Pi's catalog combines a static vendored snapshot with ~/.pi/agent/models.json. A miss at self-test time is therefore a miss at launch for that catalog state.

Check-it 2: verify the explicit spelling#

Use a hank with the explicit pi/zai/glm-5.3 spelling and set ZAI_API_KEY. The capture below is real validation output: the model resolves to its registry identity and the run reports GOOD TO RUN.

JSON
{
  "$schema": "https://unpkg.com/hankweave@0.10.0/schemas/hank.schema.json",
  "meta": {
    "name": "zai preflight probe",
    "version": "1.0.0"
  },
  "hank": [
    {
      "id": "m",
      "name": "probe glm",
      "model": "pi/zai/glm-5.3",
      "continuationMode": "fresh",
      "promptText": "Say ok."
    }
  ]
}
Output
…
      model: glm-5.3 (GLM 5.3 Preview) │ mode: fresh
…
╭─ GOOD TO RUN! ─────────────────────────────────────────────────────╮
│  1 codons • 0 prompts • 0 system prompts • 0 rigs • 0 checkpoints  │
╰────────────────────────────────────────────────────────────────────╯
…

How do you give sentinels their own keys?#

Set HANKWEAVE_SENTINEL_<apiKeyEnvVar> when a sentinel must use a different credential from the codons it watches. Sentinel lookup checks that variable first, then the standard provider variable, and the initialization log line records which source it used.

The six supported sentinel spellings are:

  • HANKWEAVE_SENTINEL_ANTHROPIC_API_KEY
  • HANKWEAVE_SENTINEL_OPENAI_API_KEY
  • HANKWEAVE_SENTINEL_GROQ_API_KEY
  • HANKWEAVE_SENTINEL_GEMINI_API_KEY
  • HANKWEAVE_SENTINEL_DEEPSEEK_API_KEY
  • HANKWEAVE_SENTINEL_AWS_BEARER_TOKEN_BEDROCK

Sentinel variables never reach codon agents. This lets the main agent use one provider while cost-tracking sentinels use a cheaper model on another provider.

Bedrock sentinels accept a narrower set of credentials than Bedrock codons. Sentinel authentication accepts the bearer token or an explicit AWS_ACCESS_KEY_ID plus AWS_SECRET_ACCESS_KEY pair. Profile, SSO, container, and IMDS credentials can work for codons but not for sentinels. The not-configured error names the sources a sentinel accepts.

How do you use a ChatGPT subscription?#

OpenAI offers two credential doors, and the model spelling has to match the one you hold: a metered API key or a ChatGPT subscription.

Scroll to explore the table →
DoorModel spellingCredential
Metered APIA normal OpenAI spellingOPENAI_API_KEY
ChatGPT Plus/Pro subscriptionThe verbatim Pi spelling pi/openai-codex/<model>OAuth credential in Pi's store at ~/.pi/agent/auth.json

The shipped --init scaffold uses the subscription door for its fourth codon, analyze-gpt. Put the OAuth credential in Pi's store with pi login, or delete that codon or change it to a keyed spelling. The keyed fallback named by the scaffold is gpt-5.5 with OPENAI_API_KEY. The scaffold's Auth bullet and the codon object show both halves of that contract:

MD
- The `pi/openai-codex/gpt-5.6-terra` codon runs on a **ChatGPT subscription** (no `OPENAI_API_KEY`): the OAuth credential is read from Pi's store at `~/.pi/agent/auth.json`. Put it there with `pi login` (choose "OpenAI (ChatGPT Plus/Pro)"). No subscription? Delete that codon, or switch its model to a keyed spelling like `gpt-5.5` with `OPENAI_API_KEY` set.
JSON
    {
      "id": "analyze-gpt",
      "name": "Analyze Project (GPT)",
      "model": "pi/openai-codex/gpt-5.6-terra",
      "continuationMode": "fresh",
      "promptFile": "./prompts/analyze-gpt.md",
      "checkpointedFiles": ["analysis-gpt.md"],
      "outputFiles": [
        {
          "copy": ["analysis-gpt.md"]
        }
      ]
    }

The gpt-5.6 family has -sol, -luna, and -terra variants. The init artifact selects the -terra subscription spelling; its keyed fallback is the older gpt-5.5 spelling named above.

openai-codex is not a provider for which Hankweave enforces OPENAI_API_KEY. Its self-test reports that Pi resolves credentials itself. Its catalog contains subscription-eligible models only. If a model is absent from that catalog, the model_catalog preflight fails at startup rather than when a codon starts.

Check-it 3: inspect the shipped subscription codon#

Create the shipped scaffold, then inspect its Auth section and the analyze-gpt object:

⌁ Terminal
bunx hankweave@0.10.0 --init

Confirm that the artifact names pi login, ~/.pi/agent/auth.json, and the option to delete or re-model the codon.

When does provider quota end a run first?#

Two limits can end a run independently. Provider quota is a budget axis that Hankweave does not model, so it can terminate a run before Hankweave's budget sees provider spend. An observed run using pi/zai/* failed mid-chain when Z.AI returned a 429 with code 1308 for its five-hour usage window. The failure was classified as billing/usage, marked non-retriable, and ended with exit 1.

The cost-reporting gap makes this sharper at 0.10.0: pi/zai/* codons report cost 0 on codon.completed. Consequently, budget.maxDollars does not bind for those codons, and maxTimeSeconds is the effective cap on that route. The provider can still enforce its own quota even though the reported cost is zero. This evidence is specific to pi/zai/*; it does not establish the same cost-reporting behavior for other Pi providers.

To reduce quota failures:

  1. Stagger codons that use the same provider.
  2. Split a chain across providers.
  3. Keep a time cap on every codon routed to a provider whose usage is not metered in the reported cost.

For the meanings of maxDollars, maxTimeSeconds, onExceeded, and the two-party budget contract, use Budgets.

How do you reach providers outside the registry route?#

Write an explicit pi/<provider>/<model> spelling when the registry does not route to that provider on its own. Validated Baseten spellings are:

  • pi/baseten/deepseek-ai/DeepSeek-V4-Pro
  • pi/baseten/moonshotai/Kimi-K3

Set BASETEN_API_KEY. Baseten is not key-enforced by Hankweave; the self-test reports that Pi resolves credentials itself, and Pi reads BASETEN_API_KEY from the process environment. A passing preflight checks setup and catalog availability; it does not prove that the Baseten key is valid for a live request.

The registry and Pi's shipped catalog can skew. The registry's Baseten block lists 19 models, including five zai-org GLM entries, while Pi's shipped baseten.json catalog serves 17 models. The registry-only MiniMaxAI/MiniMax-M2.5 and deepseek-ai/DeepSeek-V3.1 are absent from Pi's catalog; the model_catalog preflight catches this mismatch before a codon runs.

For Bedrock credential scoping, IAM, and limitations, use AWS Bedrock. Its Anthropic-versus-non-Anthropic routing split belongs to Model resolution.