tinyhumansai/openhuman · error

learning_forget_facet: upsert failed: {e:#}

Error message

learning_forget_facet: upsert failed: {e:#}

What it means

Thrown by learning_forget_facet when the facet was found and mutated in memory (user_state=Forgotten, state=Dropped) but FacetCache::upsert() failed. Consequence to understand: the forget did NOT persist — the facet remains active/pinned and will keep appearing in prompts until a successful retry.

Source

Thrown at src/openhuman/agent/learning/tools.rs:391

        PermissionLevel::Write
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        log::debug!("[tool][learning] forget_facet invoked");
        let class_str = read_required_str(&args, "class")?;
        let key_suffix = read_required_str(&args, "key")?;
        let fk = full_key(&class_str, &key_suffix);
        let cache = get_cache()?;
        let facet_json = match cache
            .get(&fk)
            .map_err(|e| anyhow::anyhow!("learning_forget_facet: {e:#}"))?
        {
            Some(mut f) => {
                f.user_state = UserState::Forgotten;
                f.state = FacetState::Dropped;
                cache
                    .upsert(&f)
                    .map_err(|e| anyhow::anyhow!("learning_forget_facet: upsert failed: {e:#}"))?;
                facet_to_json(&f)
            }
            None => serde_json::Value::Null,
        };
        Ok(ToolResult::success(serde_json::to_string(&json!({
            "facet": facet_json,
        }))?))
    }
}

/// Rebuild the facet cache (heavyweight stability cycle). Default-OFF.
pub struct LearningRebuildCacheTool;

#[async_trait]
impl Tool for LearningRebuildCacheTool {
    fn name(&self) -> &str {
        "learning_rebuild_cache"
    }

View on GitHub (pinned to a221052e0d)

Solutions

  1. Retry the forget once the lock clears and confirm the facet now shows user_state 'forgotten' via learning_get_facet
  2. Serialize forget/rebuild/reset calls — they all write the same table
  3. Check disk space / single-core-per-workspace if it recurs
Defensive patterns

Strategy: retry

Try / catch

if let Err(e) = tool_exec("learning_forget_facet", args).await {
    if e.to_string().contains("upsert failed") {
        tokio::time::sleep(Duration::from_millis(500)).await;
        tool_exec("learning_forget_facet", args).await?; // retry
        // then confirm: learning_get_facet -> user_state == forgotten
    }
}

Prevention

When it happens

Trigger: Forget racing learning_rebuild_cache or another writer holding the SQLite lock; disk full; read-only DB. The read succeeded moments earlier, so this is a write-time failure.

Common situations: User asks to 'stop remembering X' right as a scheduled reflection persist runs; the retry is then skipped because the tool reported failure and the caller assumes it worked.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/cbd4e4c95b27eef2. Report an issue: GitHub.