-
Notifications
You must be signed in to change notification settings - Fork 1
Choose A Storage Backend
Note
Status: implemented (part-5 tasks 1–4 of 4 shipped + V5-7 Part 2 shipped) — describes the V5-1 part 5 behavior (.harness/PLAN.md — selection-and-fail-loud). Shipped: the storage.backend config key (the --storage-backend setter, task 1), the selection resolver (task 1), the kernel capabilities-read (task 2), the polished install-the-plugin fail-loud refusal (task 3), and — task 4, this page — the doctor storage preview. V5-7 Part 2 (shipped): --requires <CAPS> flag on python3 scripts/backend_selection.py for operator capability pre-flight — see step 4. Part 5 ships selection + the fail-loud guard + the doctor preview only — the engine cutover (routing harness_state_dir / read_state_file / write_state_file / phase_recall / resolve_documenter_context to device-local exclusively) shipped separately in V5-3 (Memory-storage seam) as part of v5.5.0.
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 is deferred beyond V5-1.
-
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.
🔧 How-to
- Your first install
- Install into a project
- Configure a new project
- Update an installed harness
- Cut a release
- Use auto-context in phases
- Use per-project install
- Audit the vault
- Find missing note links
- Use AgentMemory in any agent
- Tune auto-orchestration
- Run without a vault
- Choose a storage backend
- Stand up the memory MCP server