tinyhumansai/openhuman · error
learning_update_facet: {e:#}
Error message
learning_update_facet: {e:#} What it means
Thrown by the learning_update_facet agent tool when FacetCache::get() fails while looking up the facet by full key 'class/key'. FacetCache is a thin wrapper over ProfileStore, which owns the SQL against the user_profile table in the memory store's SQLite database, so this is a storage-layer failure (lock contention, I/O error, corrupted DB) — not a 'wrong key' condition (that is a separate 'facet not found' error). The {e:#} formatting prints the full anyhow error chain, e.g. the underlying rusqlite error.
Source
Thrown at src/openhuman/agent/learning/tools.rs:248
},
"required": ["class", "key", "value"]
})
}
fn permission_level(&self) -> PermissionLevel {
PermissionLevel::Write
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
log::debug!("[tool][learning] update_facet invoked");
let class_str = read_required_str(&args, "class")?;
let key_suffix = read_required_str(&args, "key")?;
let value = read_required_str(&args, "value")?;
let fk = full_key(&class_str, &key_suffix);
let cache = get_cache()?;
let mut facet = cache
.get(&fk)
.map_err(|e| anyhow::anyhow!("learning_update_facet: {e:#}"))?
.ok_or_else(|| anyhow::anyhow!("learning_update_facet: facet not found: {fk}"))?;
facet.value = value;
facet.user_state = UserState::Pinned;
cache
.upsert(&facet)
.map_err(|e| anyhow::anyhow!("learning_update_facet: upsert failed: {e:#}"))?;
Ok(ToolResult::success(serde_json::to_string(&json!({
"facet": facet_to_json(&facet),
}))?))
}
}
/// Set/clear a facet's pin via `set_user_state`. Shared by pin/unpin.
async fn set_pin(
args: serde_json::Value,
tool: &str,
state: UserState,
) -> anyhow::Result<ToolResult> {View on GitHub (pinned to a221052e0d)
Solutions
- Retry the tool call after the concurrent rebuild/reflection cycle finishes — SQLITE_BUSY is transient
- Reproduce the read failure with learning_get_facet (same class/key) to confirm it is the store, not the key
- Check disk space and that the workspace_dir memory DB file is present and writable
- If persistent, inspect the core log for the underlying rusqlite error chain and run the memory doctor / restart the core to reopen the DB
Example fix
// before: update raced a rebuild cycle
tool: learning_update_facet {"class":"style","key":"verbosity","value":"concise"} // -> learning_update_facet: database is locked
// after: wait for rebuild, then retry
tool: learning_get_facet {"class":"style","key":"verbosity"} // confirm readable
tool: learning_update_facet {"class":"style","key":"verbosity","value":"concise"} // ok Defensive patterns
Strategy: retry
Validate before calling
// Cheap read-only probe of the same table before mutating:
// via RPC: learning_get_facet {"class":c,"key":k} -> if it errors, the store is unhealthy;
// if it returns null, fix the key instead of retrying.
// In-process equivalent:
let cache = FacetCache::new(memory::global::client_if_ready().unwrap().profile_store());
assert!(cache.get(&format!("{class}/{key}")).is_ok(), "store unhealthy — defer update"); Try / catch
match tool_exec("learning_update_facet", args).await {
Ok(res) => res,
Err(e) if e.chain().any(|c| c.to_string().contains("database is locked")) => {
tokio::time::sleep(Duration::from_millis(250)).await;
tool_exec("learning_update_facet", args).await? // single bounded retry
}
Err(e) => return Err(e),
} Prevention
- Serialize learning mutator tools (update/pin/forget/rebuild/reset) within one agent turn instead of issuing them in parallel
- Keep one core process per workspace_dir to avoid cross-process SQLite contention
- Monitor for 'database is locked' in core logs as an early signal of write contention
When it happens
Trigger: Calling learning_update_facet {class, key, value} while another writer holds the SQLite write lock — the stability-detector rebuild cycle (learning_rebuild_cache), reflection persistence, or a concurrent RPC mutation from learning::schemas handlers. Also fires on disk-full, the workspace DB being deleted/moved under a running core, or a corrupted user_profile table.
Common situations: Agent pinning/updating a facet right after a transcript-ingest or scheduled stability rebuild started; two tool calls racing (tool_tracker allows parallel tool execution); workspace on a network/full disk; DB left locked after a crash.
Related errors
- learning_update_facet: upsert failed: {e:#}
- {tool}: set_user_state failed: {e:#}
- {tool}: re-read failed: {e:#}
- learning_forget_facet: {e:#}
- learning_forget_facet: upsert failed: {e:#}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/14f6789e1c12a2fd.
Report an issue: GitHub.