Upgrade Hankweave without upgrading blind

Upgrading a runtime that executes agent work is riskier than upgrading a library. A new release can reject a hank the old one accepted, and a resumed execution can keep a model plan that no longer matches your intent. Neither failure is obvious until a run is already underway and spending money.

The safe order is: validate the hank with the new release before any execution, repair what it reports, then decide deliberately between a fresh execution and a resume. We walk that sequence for the newest releases first, then work back through the migration boundaries behind them, the model-spelling drift that can appear after any release, and the older changes that matter when a hank has crossed several versions at once.

Validate before the new release spends a run#

The first check costs no agent tokens and runs no codons. Point the new release at your hank:

⌁ Terminal
bunx hankweave@0.11.0 --validate ./my-project/hank.json

Run it where ./my-project/hank.json exists, then fix every reported error and repeat until it passes. A codon is a configured unit of agent work; validation loads the hank and checks its schema, authored file references, model resolution, the Pi catalog for Pi-routed models, and required environment variables, without running codons or making a model-generation self-test call. A passing validation prints ✓ Configuration is valid! and the GOOD TO RUN! box, and exits 0:

Output
✓ Configuration is valid!

╭──────────────────────────────────────────────────────────────────────────────╮
│  Minimal single provider v1.0.0                                              │
│  1 codon • 0 loops                                                           │
╰──────────────────────────────────────────────────────────────────────────────╯

└─ [1] summarize-notes (Summarize the notes)
      model: haiku │ mode: fresh │ prompts: 1 (13 lines)
      checkpointedGlobs: 1

╭─ GOOD TO RUN! ─────────────────────────────────────────────────────╮
│  1 codons • 1 prompts • 0 system prompts • 0 rigs • 1 checkpoints  │
╰────────────────────────────────────────────────────────────────────╯

The summary box confirms what the runtime loaded: one codon, its model and mode, and the checkpoint globs it will honor. With more codons, each appears in the same listing, which makes this output a quick way to spot a codon resolving to a model you did not expect.

Passing validation does not prove a run will start. Two things it does not cover:

  • Git. The current release requires a runnable git (see the next section), and --validate exits before the git check, so a host without git passes validation and fails only when a run starts. Some other conditions, such as a missing copy-target parent directory, also first fail at run time.
  • Provider health. Runtime startup, not validation, performs the provider health checks, which make real generateText("Hi", maxOutputTokens: 16) calls against available registry providers, not only the models your hank selects. Those calls can be billable and sit outside the tracked-codon capture total. See authentication and models for the two model-selection planes.

Validation is also not read-only or fully offline. It can add $schema to the hank and writes a temporary validation log; it emits CLI telemetry, and Bedrock credential discovery on the Claude-SDK route can probe instance metadata.

Once validation passes, choose whether the execution is new or resumed. After an upgrade this choice matters more than day to day, because a resume reuses the persisted model plan:

  • For an upgrade rehearsal, use a fresh execution:

    ⌁ Terminal
    bunx hankweave@0.11.0 ./my-project --start-new -y
    
  • A managed resume (no -e) picks the newest execution whose data hash matches and never compares the hank hash, so it needs no -y after a hank edit. Only an explicit -e/--execution resume compares the recorded hank hash. On that path, a non-interactive run without -y/--force fails closed before the server starts with hank.json does not match the configuration recorded for this execution ... refusing to resume in non-interactive mode. and exit 1; -y or --force accepts the change. (The old pair Non-interactive mode, skipping confirmation prompt. / Operation cancelled by user. is history.)

  • If you changed a codon's model, use --start-new. A plan is built only when a fresh (non-continuation) run starts, and each entry stores its codon and resolved model in .hankweave/state.json executionPlan. A resume reuses that plan verbatim: every entry already in it, failed or not yet started, runs on its persisted model, and neither --model nor an edited-and-accepted hank re-resolves an entry's model.

For the fresh run, look for the Hankweave Server Started box, then verify that each codon.completed event has "success":true and that the run ends with the terminal RunCompleted transition:

Output
══════════════════════════════════════════════════
  Hankweave Server Started
  WebSocket: ws://localhost:<port>
══════════════════════════════════════════════════
JSON
{"id": "<id>", "timestamp": "<ts>", "type": "codon.completed", "data": {"codonId": "summarize-notes", "success": true, "cost":"<n>", "duration":"<n>", "exitStatus": {"type": "success"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "RunCompleted", "runId": "<id>", "transition": {"type": "RunCompleted", "data": {"runId": "<id>"}}, "resultingState": {"currentRunId": null, "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}

Check-it: the validation command exits 0 only after the configuration is accepted and prints the two success markers above. After the fresh run, inspect its output or event stream for the startup box, successful codon completion, and terminal RunCompleted; these are separate checks, not proof that validation ran a codon.

What this release breaks – and changes#

The changelog for this release marks exactly three changes as breaking, and each needs an action before a run succeeds.

Git is required. [changed 0.11.0] Checkpoints, rollback and crash recovery are built on the shadow git repository. Before it creates, wipes or copies into the execution directory for a run, the CLI runs git --version; when that cannot run it prints Error: and the GitUnavailableError message and exits 1, with the execution directory untouched (a schema-less hank.json may already have gained $schema, because the auto-add runs before the check). The runtime repeats the check when it initializes checkpoints. There is no opt-out, and --validate exits before the check, so passing validation does not catch a missing git. Migration: install git and put it on PATH, including CI and container images.

file.updated events carry fingerprints, never file bodies. [changed 0.11.0] Every file.updated event, journaled and broadcast, carries exactly path, filename, action, sha256, bytes and source, and no inline content. The change's bytes live in the assistant.action receipt that source.toolUseId joins. The file-level contract is EVENT_SCHEMA_VERSION 2, and it is not additive. Migration: a consumer that read file.updated.content moves to fingerprints plus receipts. The full v2 event contract is owned by reference/events.

Watched-file matching follows resolver semantics. [changed 0.11.0] The per-tool-call check that decides whether a Write/Edit/MultiEdit target is a watched file uses the shared resolver (micromatch plus cached gitignore rules) instead of minimatch with matchBase: a slash-less pattern such as *.md matches only at the project root on the tool path, and gitignored or hard-excluded paths no longer emit file.updated on tool use. Migration: write **/*.md for recursive intent. Watched-file pattern semantics are owned by the hank.json reference.

Two further changes are not breaking but may matter. A finished run's journal is dieted at finalize when dietOnFinalize is on (the default), but only when the runtime can still prove it owns runtime.lock and the run ended completed or failed; otherwise the plain events.jsonl stays and can be dieted later with bunx hankweave@0.11.0 --diet-journal <exec> and rebuilt with bunx hankweave@0.11.0 --restore-journal <exec>. The format and conditions are owned by the runtime config reference. Also: rerunning an already-completed execution prints a "nothing to do" notice and still exits 0; --yes is a long alias for -y; the parser accepts --idle-timeout 0 as a strict integer, but nothing consumes the idle value, so the flag does not set or disable the WebSocket or proxy idle timeout; and the Claude Agent SDK, the Pi packages and the models-dev-data catalog were refreshed.

Check-it: on a host without a runnable git, --validate passes, but starting a run prints Error: with the GitUnavailableError message and exits 1 (code-derived; no fixture backs this). On a host with git, the three breaking changes above each have a migration action you can perform before the next run.

What strict paths changed#

If validation fails after crossing to a release with strict paths, the most likely cause is the new path contract, that release's one breaking change. Authored references in hank.json – promptFile, appendSystemPromptFile, globalSystemPromptFile, rigSetup.copy.from, and string sentinel references – must be portable relative POSIX paths inside the hank directory. Absolute paths, .. climbs above the hank root, and symlink traversals are rejected at load. The full ref contract is owned by the hank.json reference; this page covers the migration shape.

The repair is usually to make an in-hank reference explicit:

JSON
{
  "promptFile": "./prompts/review.md"
}

Concrete migrations:

  • An absolute path such as /path/to/project/prompts/foo.md becomes ./prompts/foo.md.
  • A shared file outside the hank (../shared/prompt.md) is copied into the hank directory and referenced locally.
  • rigSetup.copy.from may not name the hank directory itself, including ., sub/.., and symlink spellings – rejected at load. Templates must live inside the hank directory.
  • The copy-tree scan extends into copy.from directory trees: any symlink, FIFO, socket, or device file at any depth is a load-time violation. A hank that passed locally may now fail at load; remove the offending entry.

Run --validate after moving the files; do not rely on an editor accepting the JSON.

Paths are not the only new load-time check. That release also added the Pi catalog preflight: at every non-replay run startup (fresh or resumed) and during --validate, every Pi-routed model is checked against Pi's catalog. A registry-known-but-Pi-unservable model fails at config load with a diagnostic listing the available models for that provider. Replay passes skipSelfTests, so it runs no per-model self-test and no catalog preflight. This is the class of error that survives editing hank.json: the model spelling is correct by the registry but Pi cannot serve it. Diagnostics and error templates are owned by model resolution.

Harness choice is late-bound: harnessOverride: "pi" forces the Pi harness even for models that would otherwise run on the Claude Agent SDK. On resume, the legacy pi pseudo-provider encoding in state files is reverse-mapped, so an old plan that forced an Anthropic model onto Pi re-routes to the Claude Agent SDK.

Two smaller fixes: the published schema root now recognizes requirements and globalSystemPrompt*, so editors no longer flag them as unknown; and sentinel refs inside file-based sentinel configs resolve per-config-directory rather than against the first config's directory.

Check-it: put an absolute reference in a disposable copy of the hank, then run the pinned --validate command. The load must fail with "<path>" is an absolute or drive-qualified path; hank refs must be relative paths inside the hank directory before a codon runs; the hank.json path contract gives the exact rule that applies.

Make CI trust copy-failure exit codes#

One version back, the change that matters most is in CI rather than in the hank. If your pipeline treats a nonzero process status as the run-failure signal, the important edge case is fixed: [changed 0.9.0] an outputFiles copy or beforeCopy failure now fails the run and exits 1; before that release the failure could be logged while the process continued toward an apparently completed run with exit 0. Remove log-scanning workarounds that existed only to compensate for that old exit behavior, and keep the exit-code contract in errors and exit codes.

That release also brought AWS Bedrock and provenance: [since 0.9.0] AWS Bedrock codons and sentinels, with credential rules and permanent-failure classification, and npm trusted publishing and provenance: published tarballs are cryptographically linked to the commit and workflow run that built them, verifiable with npm audit signatures (the documented verification command), and the package declares repository and license. The dedicated AWS Bedrock reference owns credential scope and limitations.

Check-it: make the output-stage command fail in a controlled CI test and assert the process status is 1, rather than asserting on a log line. The runtime's output-stage failure path is the backing contract; do not treat a successful codon event as a successful run when output copying failed.

Rewire compaction, keys, and harnesses#

Treat this as the larger migration boundary. Several defaults and integrations changed at once:

  • Opt into compaction per codon. [changed 0.8.0] autoCompact now defaults false and is per-codon; codons that relied on silent auto-compaction fail at the context window unless autoCompact: true is set. With compaction off, the provider's context-overflow error surfaces; terminateOn: contextExceeded now recognizes overflow results and compact boundaries, so old loops that failed to terminate on overflow work.
  • Rename Google's key. [changed 0.8.0] GOOGLE_API_KEY was removed; the Gemini env var is GEMINI_API_KEY only, including the sentinel-scoped HANKWEAVE_SENTINEL_GEMINI_API_KEY. Hankweave reads Google's key from GEMINI_API_KEY only, so setting only GOOGLE_API_KEY leaves Google unconfigured in hankweave's own key resolution. Rename it in your environment and CI config. One caveat: on a pi/google/... route, the Google SDK that the shipped Pi dependency loads prints Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using GOOGLE_API_KEY. when both variables are set, but requests still use the GEMINI_API_KEY value hankweave passed. Unsetting GOOGLE_API_KEY also silences that warning.
  • Remove shim assumptions. There are exactly two in-process harnesses: the Claude Agent SDK for Anthropic models and the embedded Pi agent for everything else. [changed 0.8.0] The gemini-cli, codex, and opencode subprocess shims, and their subprocess machinery, are removed. Existing opencode/<provider>/<model> spellings are auto-rewritten to pi/<provider>/<model> at load; you need not edit them, but verify they point to the models you intend.
  • Update authentication. Auth is key-only: the Anthropic OAuth token door is removed, and Codex-CLI session pickup (codex login, ~/.codex/auth.json) is gone. The ChatGPT subscription route through pi/openai-codex/* plus pi login is the only OAuth path.
  • Recheck the init shape. The --init scaffold went from five codons to four, with the codex and opencode examples removed. [changed 0.8.0] It scaffolds analyze-haiku, analyze-gemini, analyze-pi and analyze-gpt today, and the gpt codon needs pi login. The full init codon table and file tree is owned by the first-run guide.
  • Review retries and execution location. Network failures are retriable, and the retriable/permanent taxonomy is unified across both harnesses; the full classifier table is owned by errors and exit codes. HANKWEAVE_RUNTIME_EXECUTION_BASE_DIR is the effective environment variable for moving the managed execution root; hankweave.json's executionBaseDir field is informational only.
  • Know what the Pi harness isolates. Each Pi session uses an in-memory settings manager, so a personal ~/.pi/agent/settings.json (compaction, retry, steering) is not read, and a resource loader inside the execution turns extensions, skills, prompt templates and themes off. That isolation stops at settings and resource discovery: Pi's model catalog still includes ~/.pi/agent/models.json, and stored credentials come from ~/.pi/agent/auth.json (from pi login) unless the runtime overrides them, so a personal models.json or auth.json can change which Pi models resolve and which credential a codon runs with. Detail is owned by authentication and models.

Keep the harness change in mind when auditing old model names: a model routes through one of the two in-process agents rather than through a subprocess shim wrapping an external CLI. The current routing rules are in model resolution.

Check-it: in CI, unset GOOGLE_API_KEY, set GEMINI_API_KEY, and run the validation/self-test for the affected configuration. The code-derived assertion is that the old name cannot satisfy the Gemini requirement in hankweave's key resolution while the new name can; do not use an old shim command as the test.

Recheck model spellings after every release#

Model data can change independently of your hank, so this check applies after every upgrade, not only at the migration boundaries above. Every release may refresh the model catalog: recheck any pinned model IDs in your hank against the current registry, because a pinned ID that was valid in an earlier release may be retired by a later one. The shortcuts haiku, sonnet, and opus are stable; pinned full IDs need periodic recheck.

For the current GPT shortcut, bare gpt-5.6 auto-routes to gpt-5.6-sol; gpt-5.6-high and gpt-5.6-xhigh route to the same base with their effort suffixes preserved. The full shortcut matrix is owned by model resolution.

Kimi's current flagship is kimi-k3, but the Pi catalog also lists older Kimi IDs including kimi-k2.5, kimi-k2.6, and kimi-k2.7-code. Repair a pinned Kimi ID only when the current catalog no longer resolves it, confirmed with --validate. The model-shortcut stability table is owned by model resolution; this page never hand-maintains its rows.

A practical policy: use a stable shortcut while developing, then pin only when the deployment requires a specific identity. That avoids silently retaining a retired dated ID across upgrades.

Check-it: after editing a pinned ID, run the pinned --validate and read the resolved model shown for each codon. If the ID is absent from the current catalog or Pi catalog, repair the spelling.

Account for earlier changes#

If your hank predates the compaction-and-harnesses boundary, start with the versions it has crossed and follow the details you actually use:

  • 0.7.x: [added 0.7.x] Braintrust/Langfuse trace upload arrived through hankweave-trace and HANKWEAVE_TRACE_*; 0.7.0 added self-test failure classification (launch, binary-missing, auth, check, unknown) so pre-run gating routes you to the right fix; 0.7.3 added headless fail-fast for retriable failure under onFailure: "abort" and a 30-second shutdown watchdog. Continue in observability.
  • 0.6.1: [added 0.6.1] The budget system arrived, including maxDollars, maxTimeSeconds, maxOutputTokens, maxContextTokens, onExceeded, and loop allocation. Continue in budgets.
  • 0.5.6–0.5.7: [added 0.5.6] --replay, rig.output streaming, and HANKWEAVE_RUNTIME_SHOW_COSTS arrived. Continue in the CLI reference and the runbook.
  • 0.3.0: [added 0.3.0] The welcome wizard appeared for bare invocation. For the current first-touch behavior, use first run.

Check-it: identify the oldest runtime that created your hank, then check each applicable row's owner before changing configuration. The exit-code and retry contracts are version-sensitive; use the current error reference instead of inferring behavior from an old log.

Quick look: what breaks at each version#

Work through the sections above version by version, applying each breaking or migration-relevant change your hank has crossed. There is no machine-computed version-to-version compatibility matrix at this version; the authored per-version sections above are the coverage, each grounded in the changelog's breaking-change entries and the current source and schema. For the authoritative release-by-release list, read the changelog; for the pinned configuration target, use hank.json.

A modern hank shape is available in the quote-template-unification example: its $schema targets hankweave@0.11.0, authored references use relative POSIX paths, sentinel references are present, and codons carry budgets and retry configuration. Use it as a shape to inspect, not as a compatibility table or a replacement for validating your own hank.

One limitation to know: no end-to-end N→N+1 upgrade was captured or exercised for this page. The shipped fixtures are same-version captures (some carried over from the previous release), not a rehearsal of a live cross-version upgrade, so the migration actions here are changelog- and schema-derived guidance. Validate your own hank and start a fresh execution when changing a frozen model plan.

Check-it: compare your hank's $schema target and authored path spellings with the current contract, run bunx hankweave@0.11.0 --validate ./my-project/hank.json, and record any repair before starting a fresh execution. Do not report the same-version validation capture as proof of a completed N→N+1 rehearsal.