Choose A Storage Backend - alexherrero/agentm GitHub Wiki
Note
Status: implemented. V5-1 part 5 shipped backend selection, the fail-loud guard, and the doctor storage preview this page describes β including V5-7 Part 2's --requires <CAPS> capability pre-flight (see step 4). The engine cutover shipped separately, in V5-3 (v5.5.0); the Memory-storage seam design Β§ 3 is the canonical account of what it changed.
Goal: Tell agentm which storage backend the memory engine should use, and confirm β before any memory operation could refuse β that the named backend's plugin is installed.
Prereqs: agentm with V5-1 part 5 installed; python3 on PATH; the config CLI (scripts/agentm_config.py).
The selected backend resolves from the on-host .agentm-config.json (config reference). The resolution chain: an explicit storage.backend value wins; otherwise an existing vault_path resolves to the built-in vault; otherwise a fresh install (no vault_path) resolves to device-local. If storage.backend names a backend whose plugin is not installed, the engine refuses the memory operation with a clear "install the <name> backend plugin" error β it never silently falls back. Use the doctor preview to see the selected backend and its plugin-installed state before that refusal could bite.
-
Inspect what selection resolves today (no config change), via the read-only
doctorstorage preview:python3 scripts/backend_selection.py --doctor # or, in a host that ships the doctor skill: /doctorIt prints a single status line β
storage [OK] β¦,storage [WARN] β¦, orstorage [FAIL] β¦β naming the selected backend, where the selection came from (explicitstorage.backend, an existingvault_path, or the fresh-install default), and whether that backend's plugin is registered. The preview is read-only: it resolves the selection without constructing a backend (construction wouldmkdirthedevice-localroot), so running it never mutates anything. See what[OK]/[WARN]/[FAIL]mean below. Exit code is1only on[FAIL];[OK]and[WARN]exit0. -
Set an explicit backend (optional β leave unset to use the fresh/vault default):
python3 scripts/agentm_config.py --storage-backend <name> # e.g. python3 scripts/agentm_config.py --storage-backend device-local
<name>is a registered protocol name (device-local,vault, or a plugin-provided name). The setter validates the value is non-empty only β it does not check the backend is installed, so you can configure a backend whose plugin lands later. It is idempotent (a silent no-op when the value is unchanged) and round-trips through--get storage.backend/--unset storage.backend. Leave it unset to let the resolver pick: a fresh install (novault_path) resolvesdevice-local; an existingvault_pathresolvesvault. -
Re-run the
doctorstorage check to confirm the named backend's plugin is registered before relying on it:python3 scripts/backend_selection.py --doctor
A clean result names the backend you just set and reports it registered:
storage [OK] selected backend 'device-local' (configured (storage.backend)) β registered; root ~/.agentm/memory writableIf instead you set a
storage.backendwhose plugin is not installed, the preview returns[FAIL]with the install-the-plugin message β the same one the engine would raise at the first memory operation:storage [FAIL] storage backend 'foo' is configured (storage.backend) but no installed plugin registers it. Install the plugin that provides the 'foo' backend, or set storage.backend to an installed backend (currently registered: device-local, vault).The fix is in the message: install the named plugin, or set
storage.backendback to a registered name with the setter from step 2.
Step 4 (optional): pre-flight a capability requirement {#step-4-optional-pre-flight-a-capability-requirement}
Use --requires to confirm the selected backend satisfies a named capability before any memory operation could refuse. This is the operator-facing surface of the required= parameter on select_backend (V5-7):
python3 scripts/backend_selection.py --requires concurrent_writers,encryptionPass a comma-separated list of capability names from the Capabilities dataclass (concurrent_writers, conflict_files, encryption, sync). The flag validates names first β an unknown name prints an error to stderr and exits 1 without touching any backend:
error: unknown capability name(s): encryp. Valid names: concurrent_writers, conflict_files, encryption, sync
On valid names it calls select_backend(required=...) and prints exactly one line:
PASS: backend 'vault' satisfies required capabilities: concurrent_writers, encryption
or
FAIL: backend 'device-local' does not satisfy required capabilities: concurrent_writers, encryption
Exit code is 0 on PASS, 1 on FAIL (including an unregistered backend or missing vault path, which also print FAIL). Use this in install scripts or CI environment checks to gate on a specific backend capability before starting a long operation. No backend is constructed if name validation fails; construction happens only when the name check passes.
The preview emits exactly one status row. The same --doctor exit code drives whether the doctor skill reports a hard failure:
| Status | Exit | When | What to do |
|---|---|---|---|
[OK] |
0 |
Selected backend is registered and ready β vault seeded from the resolved vault_path, a writable device-local root, or a registered third-party protocol. |
Nothing. Selection will succeed. |
[WARN] |
0 |
device-local is selected but its root (~/.agentm/memory/) is not writable. Preventive, never build-blocking β the engine will still try, but the write would fail. |
Fix the directory permissions before relying on memory writes. |
[FAIL] |
1 |
The selected backend has no registered plugin, or vault is selected with no resolvable vault_path to seed it, or the config file exists but is unparseable / names a non-string storage.backend. |
Follow the printed message β install the plugin, set vault_path, or correct the config value. |
Important
The [FAIL] preview is byte-identical to the engine's live refusal. The preview and the runtime fail-loud guard both build the install-the-plugin message from the same _install_plugin_message helper, so the preview cannot drift from what the engine would actually refuse with. doctor's storage check is the one structural check that legitimately [FAIL]s β it is the fail-loud preview shown before the engine itself refuses, not a second-guess of a separate enforcer.
# 1. Default selection on a vault-configured host resolves 'vault', registered + seeded:
python3 scripts/backend_selection.py --doctor # β storage [OK] selected backend 'vault' (existing vault_path) β registered; seeded from <vault>; exit 0
# 2. An uninstalled plugin fails loud (no silent device-local fallback):
python3 scripts/agentm_config.py --storage-backend foo
python3 scripts/backend_selection.py --doctor # β storage [FAIL] storage backend 'foo' β¦ Install the plugin β¦; exit 1
python3 scripts/agentm_config.py --unset storage.backend # restoreThe [FAIL] row proving the preview never silently demotes to device-local is pinned by test_no_silent_device_local_fallback (the TestFailLoud class) for the engine guard and by TestStoragePreview for the preview surface (scripts/test_backend_selection.py#L324).
| Surface | Where |
|---|---|
StoragePreview NamedTuple (status / protocol / line) |
scripts/backend_selection.py#L227 |
storage_preview(...) β read-only resolution, never constructs a backend |
scripts/backend_selection.py#L255 |
[FAIL] message reuses the guard's _install_plugin_message (byte-identical) |
#L293 (guard at #L196) |
device-local root writability probe (_root_is_writable) |
scripts/backend_selection.py#L240 |
--doctor CLI entry + statusβexit mapping (_doctor_main) |
scripts/backend_selection.py#L623, runnable via if __name__ == "__main__" at #L708
|
--requires <CAPS> flag on _doctor_main (V5-7 Part 2) β name validation + select_backend(required=...) + PASS/FAIL output |
scripts/backend_selection.py#L655 |
doctor skill structural check 4d
|
harness/skills/doctor.md (Claude Code adapter: adapters/claude-code/skills/doctor/SKILL.md) |
Tests β TestStoragePreview (6 cases) |
scripts/test_backend_selection.py#L324 |
Tests β TestDoctorRequires (5 cases: unknown field, mismatch, satisfied cap, multiple unknown, no-flag compat) |
scripts/test_backend_selection.py |
-
Storage seam β the verb-by-verb contract, the
BackendRegistry, and the backend selection (part 5) surface. - The memoryβstorage seam β why selection fails loud instead of demoting, and why the engine cutover was a step beyond V5-1 (it shipped in V5-3).
-
Installer CLI reference β the
agentm_config.pyconfig CLI that storesstorage.backendalongsidevault_path/state_mode. -
Run without a vault β the related
state_mode/ device-local path on a vault-less machine.