zeroclaw-labs/zeroclaw · critical · anyhow::Error
rename_agent not supported by this memory backend
Error message
rename_agent not supported by this memory backend
What it means
Final step of the DeviceRegistry::new warm-up: query_map executes the SELECT and builds the cache rows. The expect fires on runtime errors while running the query — most commonly SQLITE_BUSY ('database is locked') when another process or connection holds devices.db, and otherwise I/O errors such as disk full or a truncated file. Like the other constructor expects, it panics registry construction, taking pairing and startup with it.
Source
Thrown at crates/zeroclaw-api/src/memory_traits.rs:372
/// 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.
async fn rename_agent(&self, _from: &str, _to: &str) -> anyhow::Result<usize> {
anyhow::bail!("rename_agent not supported by this memory backend")
}
/// Read-only residue probe for the agent-rename cascade: the count
/// of state [`Self::rename_agent`] WOULD re-point for `agent_alias`, without
/// mutating anything. Used by the gateway to tell a genuine post-persist
/// partial failure (state still lagging at the old alias) apart from an
/// unrelated request, so a resume only fires on real residue.
///
/// MUST mirror exactly what `rename_agent` moves: for the SQL backends that
/// is the `agents` row (alias presence), NOT the memory-row count - an agent
/// with an `agents` row but zero memory rows still gets re-pointed, so a
/// memory-row probe would be a false negative. Default 0 (markdown/none have
/// no DB rows and their `rename_agent` is a no-op).
async fn count_agent(&self, _agent_alias: &str) -> anyhow::Result<usize> {
Ok(0)
}
/// Count total memoriesView on GitHub (pinned to 88bb9c8533)
Solutions
- Ensure exactly one gateway instance runs per workspace; stop overlapping processes or supervisors mid-restart.
- Close external sqlite3 sessions on devices.db before starting the gateway.
- Check free space on the workspace volume and integrity (`PRAGMA integrity_check;`).
- Retry startup once the competing holder exits — WAL permits concurrent readers, so a persistent failure means a writer is holding the database exclusively.
- As a maintainer, return Result and retry briefly on SQLITE_BUSY during warm-up.
Example fix
// before
let rows = stmt
.query_map([], |row| { ... })
.expect("Failed to query devices");
// after (maintainer fix)
let rows = stmt
.query_map([], |row| { ... })
.context("failed to warm device registry cache (devices.db locked or I/O error)")?; Defensive patterns
Strategy: validation
Validate before calling
// Advisory lock so two gateways never share devices.db:
fn workspace_exclusive(dir: &std::path::Path) -> bool {
use std::os::unix::io::AsRawFd;
let f = std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(dir.join(".gateway.lock"))
.ok();
match f {
Some(f) => unsafe { libc::flock(f.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) == 0 },
None => false,
}
} Try / catch
let reg = std::panic::catch_unwind(|| DeviceRegistry::new(&workspace_dir));
if reg.is_err() {
// check for a second gateway or sqlite3 session holding devices.db, then retry once cleared
} Prevention
- Enforce single-instance-per-workspace with a lock file or process supervisor
- Close external sqlite3 sessions before starting the gateway
- Restart sequentially during migrations so old and new processes never overlap
- Monitor disk space; SQLITE_FULL and SQLITE_IO errors surface in this same warm-up query
When it happens
Trigger: A second gateway instance (or an external sqlite3 session) holds devices.db while this process warms its cache; the filesystem runs out of space mid-query; the file is truncated after a hard crash.
Common situations: Two gateway processes pointed at the same workspace during a migration or restart overlap; an operator inspecting devices.db with the sqlite3 CLI while the gateway starts; disk-full events on the workspace volume.
Related errors
- purge_agent not supported by this memory backend
- 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
- ModelProvider returned non-prompt-guided tools payload ({pay
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/935dadd8250e3407.
Report an issue: GitHub.