zeroclaw-labs/zeroclaw · critical · anyhow::Error
purge_agent not supported by this memory backend
Error message
purge_agent not supported by this memory backend
What it means
DeviceRegistry::new warms an in-memory cache by preparing a SELECT over every devices column (token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities). prepare fails when the SQL cannot compile against the existing schema — classically when devices.db predates a column (only capabilities has an additive ALTER TABLE migration in this constructor), or the file is not a valid SQLite database at all. The expect panics the constructor at startup.
Source
Thrown at crates/zeroclaw-api/src/memory_traits.rs:352
/// override this; agent-scoped wrappers use it instead of composing a
/// session list with key-only deletes.
async fn purge_session_for_agent(
&self,
_session_id: &str,
_agent_id: &str,
) -> anyhow::Result<usize> {
anyhow::bail!("purge_session_for_agent not supported by this memory backend")
}
/// Remove every memory row attributed to the given agent alias.
/// Returns the number of deleted entries. Called when an agent alias is
/// removed from `[agents.<alias>]` so the database doesn't accumulate
/// rows for retired aliases.
/// Default: returns unsupported error. Backends with per-agent storage
/// (sqlite, postgres) override this; backends without (markdown, none)
/// keep the default and the caller logs a warning.
async fn purge_agent(&self, _agent_alias: &str) -> anyhow::Result<usize> {
anyhow::bail!("purge_agent not supported by this memory backend")
}
/// Export every memory row attributed to `agent_alias`, for the agent-
/// deletion archive (export-then-delete,). Pairs with
/// [`Self::purge_agent`]: the surface exports these rows to the archive,
/// then purges. Default: empty (backends without per-agent export).
async fn export_agent(&self, _agent_alias: &str) -> anyhow::Result<Vec<MemoryEntry>> {
Ok(Vec::new())
}
/// Re-point every memory row from the `from` alias to the `to` alias,
/// returning the number of rows re-pointed. Called when an alias is renamed.
/// For the SQL backends (sqlite/postgres) memory rows ride the
/// agent's UUID, so this is a single `UPDATE agents SET alias` and the count
/// is the agents-row count (0 or 1); payload-keyed backends (qdrant) rewrite
/// the alias on every matching memory point and return that count.
/// Default: unsupported error; backends with per-agent storage override.
/// Markdown/none keep the default and the caller logs a warning.View on GitHub (pinned to 88bb9c8533)
Solutions
- Rename or delete devices.db and restart — the registry rebuilds empty and devices re-pair (pairing data is a cache, not authoritative identity).
- If preserving pairings matters, inspect the table (`sqlite3 devices.db '.schema devices'`) and add missing columns manually, mirroring the additive ALTER pattern used for capabilities.
- Verify file integrity with `sqlite3 devices.db 'PRAGMA integrity_check;'`.
- As a maintainer, add an additive migration per new column and return Result with schema details on failure.
Example fix
// before
let mut stmt = conn
.prepare("SELECT token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities FROM devices")
.expect("Failed to prepare device select");
// after (maintainer fix: additive migration + Result)
let _ = conn.execute("ALTER TABLE devices ADD COLUMN ip_address TEXT", []);
let mut stmt = conn
.prepare("SELECT ... FROM devices")
.with_context(|| "device registry schema drifted; rename devices.db to re-pair")?; Defensive patterns
Strategy: validation
Validate before calling
// Fail with an actionable message instead of the prepare panic:
fn devices_schema_ok(conn: &rusqlite::Connection) -> bool {
conn.prepare(
"SELECT token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities FROM devices",
)
.is_ok()
}
// Run against the existing devices.db before handing it to DeviceRegistry::new. Try / catch
let reg = std::panic::catch_unwind(|| DeviceRegistry::new(&workspace_dir));
if reg.is_err() {
// likely schema drift: back up devices.db, delete it, restart, re-pair devices
} Prevention
- Treat devices.db as a disposable cache: back it up, but be ready to delete and re-pair after upgrades
- Check `.schema devices` with the sqlite3 CLI after version upgrades
- Maintainers: add an additive ALTER per new column so upgrades never hit the prepare panic
- Keep workspace state versioned so schema drift is detectable before boot
When it happens
Trigger: An older devices.db from a previous build whose devices table lacks a column referenced by the SELECT; a truncated or corrupt devices.db; a non-SQLite file left at the path.
Common situations: Upgrading the gateway across versions on a long-lived workspace where the pairing schema evolved; restoring devices.db from a partial backup; the additive-migration pattern in this file only covers the capabilities column, so any other drift resurfaces here.
Related errors
- rename_agent not supported by this memory backend
- OpenClaw memories table found but no content-like column was
- memory backend '{}' does not support StoreOptions kind/pinne
- memory backend '{}' does not support agent-attributed StoreO
- live model listing is not supported for this model_provider
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/6f2ad3250448e5db.
Report an issue: GitHub.