Migrating from akm 0.5.x to 0.6.0
Also see the archived pre-1.0 plan. The architecture refactor landed mid-0.6 cycle. It removes the
openvikingsource provider, renames the config keystashes[]tosources[](auto-migrated in memory), adds thewritableanddefaultWriteTargetfields, and treats Context Hub as a regular git repo. If you are on 0.5.x and upgrading to a current 0.6.x build, read both this file and the archived plan.
This guide covers everything you need to know to upgrade from akm 0.5.x to 0.6.0. Most projects will work without changes thanks to automatic on-disk migrations; a small number of config fields and CLI flags require manual updates.
If you only want a one-line summary of breaking changes, run:
akm help migrate 0.6.0
That command prints the same content you are reading here, sourced directly
from CHANGELOG.md and the release notes embedded in this document.
Table of contents
- Installing 0.6.0
- What changed in 0.6.0
- Automatic migrations (no action required)
- Manual actions required
- Wire format change:
kits[]→stashes[] - Registry index schema v3
- Publisher / kit-maker changes
- Sibling repositories
- New in 0.6.0 (bonus features)
- Internal types and file renames (developers only)
- Removed without replacement
- Verifying the upgrade
- Troubleshooting
- Rolling back
Installing 0.6.0
Pick whichever install method you used for 0.5.x:
# npm
npm install -g akm-cli@0.6.0
# bun
bun install -g akm-cli@0.6.0
# From source (contributors)
git fetch origin && git checkout v0.6.0 && bun install
If you installed via akm upgrade on 0.5.x, you can use it again on
0.6.0 — the command re-runs the same underlying package install:
akm upgrade
The upgrade itself does not touch your stash files, your
config.json, or your registry index. Automatic migrations run the
first time a 0.6.0 command reads them. See
Automatic migrations for the
exact list.
What changed in 0.6.0
0.6.0 unifies the runtime domain model around two concepts: stashes
(any source of content akm can read) and registries (services that help
you discover stashes). Several historical layers — "kits" as a runtime
concept, the parallel installed[] config array, the special-cased
context-hub provider type — are removed in favour of that simpler model.
First run after upgrade triggers a full index.db rebuild because
DB_VERSION was bumped 8 → 9 to add the workflow_documents table. The
next akm index repopulates everything; workflow.db (run state) is
unaffected.
The high-level changes:
| Area | 0.5.x | 0.6.0 |
|---|---|---|
| Wire format used by registries | kits[] |
stashes[] |
| Discovery keyword / topic | akm-kit |
akm-stash |
| Registry index schema | v2 (with kits[]) |
v3 (with stashes[]) |
| Runtime domain noun | "source" / "kit" / "stash" | stash (one term) |
| Provider types | filesystem, git, openviking, context-hub (special-cased) |
filesystem, git, openviking (only) |
Stash source kinds (StashSource["type"]) |
local, npm, github, git, website |
filesystem, git, npm, github, website, openviking |
| Lock file | stash.lock |
akm.lock |
installed[] in config.json |
user-facing | machine-managed in akm.lock |
stashDir top-level field |
required for primary stash | deprecated; use primary: true on a stash entry |
disableGlobalStashes |
boolean | replaced by stashInheritance: "merge" | "replace" |
--for-agent flag |
top-level flag | deprecated; use --detail=agent |
akm enable context-hub |
hardcoded toggle | removed; add as a regular git stash |
Each of these is covered in detail below.
Automatic migrations (no action required)
These migrations happen silently on first run of v0.6.0. None of them lose data; all of them are idempotent.
1. stash.lock → akm.lock
If ~/.config/akm/stash.lock (or the equivalent path under AKM_CONFIG_DIR)
exists and akm.lock does not, akm renames it on startup. The first command
you run in 0.6.0 will perform the rename and then proceed normally.
If both files exist, akm prefers akm.lock and leaves the old stash.lock
in place untouched. You can delete stash.lock manually after confirming
your config is healthy with akm info.
2. installed[] → stashes[] + akm.lock
Every entry in config.installed[] is mapped to a StashEntry record in
config.stashes[], and its lock data (resolved version, cache directory,
install timestamp) is written to akm.lock. The installed[] field is
removed from config.json the next time akm writes it (for example, after
akm config set … or akm add …).
The mapping uses these rules:
installed[] field |
New location |
|---|---|
id |
stashes[].name (also kept as stashes[].id for compatibility) |
source (npm / github / git / local) |
stashes[].source.type |
ref |
stashes[].source.ref |
installRef |
stashes[].source.ref (resolved form) |
installedAt / version / cachePath |
akm.lock.stashes[<name>] |
Until your config.json is rewritten, both representations are read at
startup.
3. stashDir → primary: true
The top-level stashDir field is still read for backwards compatibility. At
load time, akm synthesizes a primary stash entry equivalent to:
{
"name": "primary",
"type": "filesystem",
"primary": true,
"source": { "type": "filesystem", "path": "<stashDir>" }
}
No on-disk rewrite happens automatically. The field is rewritten only when
you next run akm config set … against any other field.
4. Type alias normalization
Stash entries with type: "context-hub" or type: "github" are loaded as
type: "git" in memory. akm config list will show the normalized form.
Update your config files to use "git" directly (and the source.type in
the discriminated union) to remove the migration step on future runs.
| Alias on disk | Normalized to |
|---|---|
"context-hub" |
"git" |
"github" (as a stash type, not a source locator) |
"git" |
The locator forms github:owner/repo and git+https://… passed to
akm add are unaffected — they continue to mean what they always have.
Manual actions required
The following items are not auto-migrated. They are small in scope but must be applied to keep your scripts and project configs working.
1. Replace disableGlobalStashes
If your project .akm/config.json (or user config) contains:
{ "disableGlobalStashes": true }
Replace it with:
{ "stashInheritance": "replace" }
stashInheritance accepts two values:
| Value | Meaning |
|---|---|
"merge" (default) |
Project stashes are appended after user-level stashes |
"replace" |
Project stashes replace user-level stashes entirely |
This is a strict superset of the old behavior — disableGlobalStashes: true
maps exactly to stashInheritance: "replace".
2. Replace --for-agent with --detail=agent
In scripts, aliases, or agent prompts that use the --for-agent flag:
# Before
akm search "deployment" --for-agent # doclint:ignore (pre-0.7.0 flag, this is the "Before" side of the migration)
akm show script:deploy.sh --for-agent # doclint:ignore (pre-0.7.0 flag, this is the "Before" side of the migration)
# After
akm search "deployment" --detail=agent
akm show script:deploy.sh --detail=agent
--for-agent is still accepted in 0.6.0 but emits a deprecation warning to
stderr. It will be removed in 0.7.0.
3. Replace akm enable context-hub / akm disable context-hub
context-hub is no longer a special-cased provider. It is just a git repository like any other. The hardcoded enable/disable toggles are removed.
# Before
akm enable context-hub # doclint:ignore (pre-0.6.0 command, this is the "Before" side of the migration)
akm disable context-hub # doclint:ignore (pre-0.6.0 command, this is the "Before" side of the migration)
# After — add it explicitly if you want it
akm add github:andrewyng/context-hub --name context-hub # doclint:ignore (historical — pre-0.9.0 command spelling)
# To temporarily disable without removing
# Edit your config.json and set "enabled": false on the stash entry
The generalized commands akm registry enable/disable <name> and
akm stash enable/disable <name> are now available for toggling any
configured registry or stash by name. See CLI Reference for the
full syntax.
4. Update akm list parsing in scripts
The single tri-state kind field ("local" | "managed" | "remote") in
akm list --format json output is replaced by two orthogonal fields:
| Field | Values | Meaning |
|---|---|---|
origin |
"local", "remote" |
Where the content comes from |
access |
"indexed", "live" |
How akm queries it |
The old combinations map cleanly to the new ones:
Old kind |
New origin |
New access |
|---|---|---|
"local" |
"local" |
"indexed" |
"managed" |
"remote" |
"indexed" |
"remote" |
"remote" |
"live" |
# Before — checking for managed kits
akm list --format json | jq '.[] | select(.kind == "managed")' # doclint:ignore (historical — pre-0.9.0 command spelling)
# After — checking for remote indexed stashes
akm list --format json | jq '.[] | select(.origin == "remote" and .access == "indexed")' # doclint:ignore (historical — pre-0.9.0 command spelling)
The --kind filter on akm list is replaced by --origin and --access.
For backward compatibility during 0.6.x, --kind <legacy-value> is still
accepted and translated to the new pair.
5. Handle new error codes in automated scripts
Error JSON now includes a machine-readable code field on every failure:
{ "ok": false, "error": "Stash not found: foo", "code": "STASH_NOT_FOUND", "hint": "Run `akm list` to see configured stashes." }
Scripts that previously parsed error messages by string matching should
switch to code comparisons. The full code list is documented in the
CLI Reference.
Common codes you can rely on:
| Code | Meaning |
|---|---|
STASH_NOT_FOUND |
Named stash does not exist in config |
STASH_DUPLICATE |
A stash with that name is already configured |
REGISTRY_NOT_FOUND |
Named registry does not exist in config |
ASSET_NOT_FOUND |
akm show <ref> could not resolve the ref |
INSTALL_AUDIT_BLOCKED |
Install was blocked by the security audit |
CONFIG_INVALID |
Config file failed validation |
USAGE |
The CLI was invoked with bad flags or arguments |
Wire format change: kits[] → stashes[]
The wire format that registries publish, and that the akm CLI consumes
when searching a registry, has been renamed from kits[] to stashes[].
This is a clean break — there is no transparent fallback. akm-cli >= 0.6.0 only parses the v3 schema.
Before (v2 index, parsed by 0.5.x):
{
"version": 2,
"updatedAt": "2026-04-01T00:00:00Z",
"kits": [
{
"id": "npm:@scope/my-kit",
"name": "my-kit",
"description": "Deployment scripts and skills",
"ref": "@scope/my-kit",
"source": "npm",
"tags": ["deploy"],
"assetTypes": ["script", "skill"]
}
]
}
After (v3 index, parsed by 0.6.x):
{
"version": 3,
"updatedAt": "2026-04-23T00:00:00Z",
"stashes": [
{
"id": "npm:@scope/my-kit",
"name": "my-kit",
"description": "Deployment scripts and skills",
"tags": ["deploy"],
"assetTypes": ["script", "skill"],
"stash": {
"name": "my-kit",
"type": "npm",
"source": { "type": "npm", "ref": "@scope/my-kit" }
}
}
]
}
The new stash property carries a Partial<StashEntry> draft. akm add <registry-hit-id> takes that draft, fills in defaults (notably name if
not provided), and writes it directly into your config.stashes[]. This
removes the parallel source / ref / installRef fields that previously
had to be reassembled by the CLI.
The old source / ref / installRef fields may appear on a v3 entry
and are accepted for one version cycle as deprecated read-only fallbacks.
They will be removed in v4. Index generators should write only the new
stash field going forward.
Registry index schema v3
The schema previously shipped as registry-index.schema.json (removed from
the repository since; the highlights below still describe the v3 structure it
covered). Highlights:
versionenum is[1, 2, 3].akm-cli >= 0.6.0parses onlyversion: 3.- The top-level array is
stashes[](waskits[]). - Each entry carries a
stash: Partial<StashEntry>draft. - A new
$defs.StashSourcedefinition describes the discriminated union used bystash.source. - The legacy
source/ref/installReffields are present but markeddeprecated: truefor one version cycle.
Migrating a self-hosted registry
If you publish your own registry index, follow these steps:
- Bump
versionto3. akm 0.6.0 will refuse to loadversion: 1orversion: 2indexes and reportREGISTRY_INDEX_INCOMPATIBLE. - Rename
kitstostashes. A simple JSON rewrite is sufficient. - Add the
stashdraft to each entry. For an npm package:
For a GitHub repo:"stash": { "name": "my-kit", "type": "npm", "source": { "type": "npm", "ref": "@scope/my-kit" } }"stash": { "name": "my-kit", "type": "git", "source": { "type": "git", "url": "https://github.com/owner/repo", "ref": "main" } } - Optionally remove the legacy
source/ref/installReffields. They are deprecated in v3 and will be removed in v4. Keeping them does not hurt during the 0.6.x window if you have older clients to support. - Republish. akm 0.6.x clients will pick up the new index on their
next refresh (1-hour TTL for
static-indexproviders).
If you generate your index with the akm repository's index builder
(scripts/build-registry-index.ts, formerly the akm registry build-index
subcommand), upgrading to 0.6.0 is sufficient — the generator emits v3 by
default.
For a hand-rolled v1/v2 index, the migration is a small mechanical rewrite:
rename the top-level kits array to stashes, set version to 3, and
republish. A scripted helper (e.g. jq one-liner) is enough:
jq '{version: 3, updatedAt, stashes: .kits}' old-index.json > new-index.json
Publisher / kit-maker changes
If you publish a kit (npm package or GitHub repo) and want it to be discoverable through registry search, the keyword / topic has changed.
npm packages
Update keywords in your package.json:
{
"name": "@you/my-kit",
"version": "1.0.0",
- "keywords": ["akm-kit"]
+ "keywords": ["akm-stash"]
}
For maximum compatibility during the transition, you can include both:
{ "keywords": ["akm-stash", "akm-kit"] }
akm-cli >= 0.6.0 discovers packages tagged with akm-stash. The legacy
akm-kit keyword is no longer queried by the CLI and existing registry
build pipelines should switch to the new keyword. Older 0.5.x clients
that have not upgraded will only see your package if you also keep
akm-kit in your keywords list — drop it once your audience has moved.
GitHub repositories
Update the repo topic:
gh repo edit --add-topic akm-stash
gh repo edit --remove-topic akm-kit # optional, after the transition
Or update topics from the repository About sidebar in the GitHub web UI.
package.json akm.include and friends
The akm.include array and other publish-time package.json fields are
unchanged in 0.6.0. Existing kits do not need to be re-published unless
you also want to drop the akm-kit keyword.
Sibling repositories
akm ships as three coordinated repositories. All three shipped the 0.6.0 terminology cut together:
itlackey/akm(this repo) — the CLI. Ship asakm-cli@0.6.0.itlackey/akm-stash— the reference stash of official agent assets. Itspackage.jsonkeywordsand repo topic both moved fromakm-kittoakm-stash. If you depend on the published npm package, pin the 0.6.0-compatible release.itlackey/akm-registry— the build pipeline that generates the public registry index. It now emits schema v3 (stashes[]). If you self-host a registry built from this pipeline, pull the 0.6.0 tag and regenerate your index — older clients (akm-cli0.5.x) will not parse v3, so you may want to keep two index URLs during your transition window.
If you consume only the official hosted registry, no action is needed — the registry already serves a v3 index from every 0.6.0+ CLI build.
New in 0.6.0 (bonus features)
These are additive — none of them require action to upgrade — but they are worth a scan because they unlock new workflows once you're on 0.6.0.
- Memory frontmatter on
akm remember(#169).--tag,--expires,--source, plus opt-in--auto(heuristics) and--enrich(LLM) attach structured metadata to memories so the indexer can rank them. Bareakm remember "body"still writes a flat memory, so existing agent scripts keep working. Seeakm remember. - Workflow parser accepts intro prose (#158). You can now put a
short advisory paragraph between
# Workflow:and the first## Step:without tripping a validation error. - Workflow resume reclassifies blocked steps (#156). After
akm workflow resume <id>, the previously-blocked step becomes actionable again so you can mark itcompleted,failed, orskipped. - Workflow create works in clean stashes (#157).
akm workflow create <name>and--from <file>no longer false-positive on a path escape when any ancestor of your stash is a symlink (macOS/tmp→/private/tmp, symlinkedHOME, etc.). - Registry search filters incomplete hits (#159). Providers that
return partial records no longer surface as empty
{}objects in JSON output; dropped counts appear inwarnings. - Isolated sandbox recipe (#160). Introduced a one-terminal recipe for a
throwaway
HOME + XDG_* + AKM_STASH_DIRenvironment — handy for agent testing, CI, and issue reproduction. Seedocs/guides/getting-started.mdfor the current setup guide. - CI coverage for release branches (#170). If you fork the repo or
run downstream CI off it, the shipped
.github/workflows/ci.ymlnow fires on PRs targetingrelease/*in addition tomain.
Internal types and file renames (developers only)
These changes only affect contributors to akm itself or to projects that
import from akm's source. akm has no public API (no barrel exports, no
exports map in package.json), so importing from akm internals was
never officially supported — but we list the renames here for anyone who
was doing it anyway.
Type renames
| Before | After |
|---|---|
RegistryKitEntry |
RegistryStashEntry |
InstalledKitEntry |
StashEntry + StashLockEntry (split) |
InstalledKitListEntry |
StashListEntry |
KitInstallResult |
StashInstallResult |
KitInstallStatus |
StashInstallStatus |
KitSource ("npm" | "github" | "git" | "local") |
StashSource["type"] ("filesystem" | "git" | "npm" | "github" | "website" | "openviking") |
StashSource is a discriminated union per type — see
src/stash-source.ts.
Domain model unification (#123)
The old triplet of "source" / "kit" / "stash" is collapsed to a single
runtime concept: stash. StashEntry is the canonical shape used by
config, registry drafts, and the index. kit survives only as a
publishing concept (the bundle you ship via npm or GitHub).
The data shape of StashEntry:
type StashEntry = {
name: string; // required, unique within config
type: StashSource["type"]; // discriminator
primary?: boolean; // exactly one entry may set this
enabled?: boolean; // default true
source: StashSource; // discriminated union per type
options?: Record<string, unknown>;
};
akm.lock records the resolved state separately:
type StashLockEntry = {
name: string; // matches StashEntry.name
resolvedVersion?: string; // for npm / github / git
resolvedRef?: string; // commit SHA / tarball SHA-256
cachePath?: string; // absolute path under ~/.cache/akm/
installedAt: string; // ISO timestamp
};
File renames
| Before | After |
|---|---|
src/installed-kits.ts |
src/installed-stashes.ts |
src/registry-kit-entry.ts |
src/registry-stash-entry.ts |
docs/kit-makers.md |
docs/stash-makers.md (with a redirect stub at the old path) |
tests/installed-kits.test.ts |
tests/installed-stashes.test.ts |
The redirect stub at docs/kit-makers.md is a one-line link to
stash-makers.md so existing inbound links and akm hints snippets
continue to resolve.
context-hub removal (#124)
src/providers/context-hub.ts is removed. The old hardcoded provider type
is gone; akm enable context-hub and akm disable context-hub now error
with STASH_NOT_FOUND and a hint pointing at akm add github:andrewyng/context-hub. context-hub remains usable as an ordinary
git stash — it is just a public repo.
If you previously imported ContextHubProvider directly, switch to the
generic GitStashProvider from src/providers/git.ts.
Registry-install fold (#125)
The registry-install path is folded into the unified install pipeline
in src/install/. registry-install.ts no longer exists as a top-level
module. The behaviour is unchanged — registry hits hand off to the same
install pipeline used by akm add, and audit/lock/index steps run in
the same order.
cli.ts decomposition (#126)
The monolithic src/cli.ts is split into per-command modules under
src/cli/. The exported runCli() function and the akm binary
behaviour are unchanged; this is purely an internal refactor. Tests that
imported helpers from src/cli.ts should now import from the
corresponding src/cli/<command>.ts.
Provider / dep refactor (#127)
RegistryProvider and StashProvider interfaces are aligned around a
shared BaseProvider shape with explicit lifecycle (init, search,
show, dispose). The dependency-injection wiring moves into
src/provider-registry.ts. Provider authors should re-read
Registry Providers — the public
contract is unchanged but the internal hook points have moved.
Config ergonomics (#128)
Embedding and LLM connection config now share a BaseConnectionConfig
shape:
type BaseConnectionConfig = {
endpoint?: string;
apiKey?: string;
model?: string;
headers?: Record<string, string>;
timeoutMs?: number;
};
Both embedding and llm config keys accept this shape. Existing
configs continue to work — the previous fields are a strict subset.
A new output.detail: "agent" preset replaces the --for-agent flag
(see Manual actions required §2).
The ASSET_TYPES mutable array export is removed from public API. Use
getAssetTypes() from src/asset-spec.ts instead.
Removed without replacement
| Removed | Reason |
|---|---|
akm enable context-hub |
context-hub is just a git stash; use akm add github:andrewyng/context-hub |
akm disable context-hub |
same |
ASSET_TYPES exported array |
use getAssetTypes() (already existed) |
InstalledKitEntry type |
replaced by StashEntry + StashLockEntry (split) |
KitSource = "npm" | "github" | "git" | "local" type |
replaced by StashSource["type"] |
disableGlobalStashes config field |
replaced by stashInheritance: "merge" | "replace" |
installed[] config array |
now machine-managed in akm.lock |
kits[] registry wire format |
replaced by stashes[] (schema v3) |
akm-kit keyword / topic for discovery |
replaced by akm-stash |
context-hub as a stash type |
normalized to git at load time |
Verifying the upgrade
After upgrading, run:
akm info
Look for these signals that the migration completed cleanly:
versionreports0.6.0or higher.stashProviderslists onlyfilesystem,git, andopenviking.- The output does not include any
context-hubentries. assetTypesmatches the types you expect.
Then:
akm config list
akm list # doclint:ignore (historical — pre-0.9.0 command spelling)
akm config list should show your stashes[] populated (including
entries that were migrated from the old installed[] array). akm list
should show origin and access columns instead of the old kind
column.
If akm.lock was created from a previous stash.lock, you should see it
at ~/.config/akm/akm.lock (or under AKM_CONFIG_DIR).
A full reindex is not required by the upgrade. Run one only if you also changed your embedding model or want to pick up new asset types:
akm index --full
Post-upgrade checklist
Five-step confirmation that nothing regressed:
akm info --format text # 1. version 0.6.x, no context-hub provider
akm config list --format json | head -40 # 2. stashes[] populated; no `installed[]`
akm list # 3. your sources resolve; `origin`/`access` columns # doclint:ignore (historical — pre-0.9.0 command spelling)
akm workflow list --format json | head -20 # 4. any active workflow runs still show
akm search "<a query you know works>" --format json # 5. indexed content still matches
If any step returns an error or missing data, see Troubleshooting below.
Troubleshooting
"Unknown stash type 'context-hub'" after upgrade.
Legacy aliases normalize at config load, but a truly corrupt entry can
still slip through. Run akm config show to see how your entry is
parsed. If it still rejects, edit config.json to set
"type": "git" and restore the URL from your backup.
registry search returns warnings: ["schema version 2 unsupported"].
Your registry is still serving v2. Pull the latest akm-registry and
regenerate, or swap your registry URL to the official hosted one. See
Migrating a self-hosted registry.
akm list shows empty stashes[] but your 0.5.x config had entries.
The migration copies installed[] into stashes[] only on the first
0.6.0 command that writes config (e.g. akm add, akm config set).
If you've only run read-only commands, the new array may not yet be
populated. Run akm add <any-stash> or force a rewrite with
akm config set stashes "$(akm config get stashes)".
akm workflow create <name> in a fresh stash still errors path escapes the stash.
That was fixed in 0.6.0 (#157) — confirm you're on the 0.6.0 CLI with
akm info | grep version. If you are and still see it, the symlink
resolution is likely hitting an edge case; file an issue with the
output of realpath "$AKM_STASH_DIR".
akm.lock was not created.
akm only creates the lockfile when it first writes to the stash
directory. Run any write command (akm add, akm remember,
akm import, akm workflow create) to generate it.
--for-agent still works, but I want to migrate my scripts.
Replace it with --detail=agent — same behaviour, preferred spelling.
The old flag is retained as a deprecated alias through at least the
0.6.x series.
Rolling back
If you need to roll back to 0.5.x for any reason, the auto-migrations are
non-destructive but your config.json may have been rewritten to remove
installed[] if you ran any akm config set or akm add command on
0.6.0. Restore from a backup if you need the old shape.
akm.lock is not read by 0.5.x — it will be ignored. Your stash content
itself is unchanged across versions; only the metadata layer evolves.
To pin to the last 0.5.x release:
npm install -g akm-cli@0.5
# or
bun install -g akm-cli@0.5
If you find a regression in 0.6.0, please file an issue at
https://github.com/itlackey/akm/issues with the output of akm info
and a redacted copy of your config.json.