{"record":{"id":"e7ae3770c064d96d","repo":"tinyhumansai/openhuman","slug":"tool-facet-not-found-fk","errorCode":null,"errorMessage":"{tool}: facet not found: {fk}","messagePattern":"(.+?): facet not found: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/openhuman/agent/learning/tools.rs","lineNumber":275,"sourceCode":"        }))?))\n    }\n}\n\n/// Set/clear a facet's pin via `set_user_state`. Shared by pin/unpin.\nasync fn set_pin(\n    args: serde_json::Value,\n    tool: &str,\n    state: UserState,\n) -> anyhow::Result<ToolResult> {\n    let class_str = read_required_str(&args, \"class\")?;\n    let key_suffix = read_required_str(&args, \"key\")?;\n    let fk = full_key(&class_str, &key_suffix);\n    let cache = get_cache()?;\n    let updated = cache\n        .set_user_state(&fk, state)\n        .map_err(|e| anyhow::anyhow!(\"{tool}: set_user_state failed: {e:#}\"))?;\n    if !updated {\n        return Err(anyhow::anyhow!(\"{tool}: facet not found: {fk}\"));\n    }\n    let facet = cache\n        .get(&fk)\n        .map_err(|e| anyhow::anyhow!(\"{tool}: re-read failed: {e:#}\"))?;\n    Ok(ToolResult::success(serde_json::to_string(&json!({\n        \"facet\": facet.as_ref().map(facet_to_json),\n    }))?))\n}\n\n/// Pin a facet. Default-OFF.\npub struct LearningPinFacetTool;\n\n#[async_trait]\nimpl Tool for LearningPinFacetTool {\n    fn name(&self) -> &str {\n        \"learning_pin_facet\"\n    }\n","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/learning/tools.rs#L257-L293","documentation":"Thrown by set_pin (learning_pin_facet / learning_unpin_facet) when set_user_state returned Ok(false) — the UPDATE on user_profile matched zero rows, so no facet exists with the composed full key '{class}/{key}'. This is a semantic not-found, not a storage failure: the class prefix or key suffix does not match any row, or the facet was evicted/deleted earlier.","triggerScenarios":"Pinning/unpinning with a class/key pair the agent invented instead of one returned by learning_list_facets; the facet was evicted by a stability rebuild (drop_below_threshold) or removed by learning_reset_cache (non-pinned facets are deleted) between listing and pinning; whitespace/format drift in the key suffix.","commonSituations":"LLM hallucinating plausible facet keys like 'style/tone' when the cache holds 'style/verbosity'; acting on a stale facet list from earlier in a long session; pinning after the user reset the cache.","solutions":["Call learning_list_facets (optionally filtered by class) and pin using a key verbatim from the result","If the list is empty or the facet is missing, re-derive it via learning_rebuild_cache or normal conversation before pinning","Check exact spelling and the class prefix — keys are 'class/suffix' strings and the class must match FacetClass vocabulary"],"exampleFix":"// before: guessed key\ntool: learning_pin_facet {\"class\":\"style\",\"key\":\"tone\"} // -> facet not found: style/tone\n// after: list first, use verbatim key\ntool: learning_list_facets {\"class\":\"style\"}     // returns key \"style/verbosity\"\ntool: learning_pin_facet {\"class\":\"style\",\"key\":\"verbosity\"}","handlingStrategy":"validation","validationCode":"// Resolve the real key from the cache before pinning:\nlet facets = tool_exec(\"learning_list_facets\", json!({\"class\": class})).await?; // returns keys like \"style/verbosity\"\nlet suffix = facets.iter().find(|f| f.key == format!(\"{class}/{key}\"))\n    .ok_or_else(|| anyhow::\"facet {class}/{key} not in cache — re-derive it first\")?;\ntool_exec(\"learning_pin_facet\", json!({\"class\": class, \"key\": key})).await","typeGuard":"fn known_facet_key(listed: &[String], class: &str, key: &str) -> bool {\n    listed.iter().any(|k| k == &format!(\"{class}/{key}\"))\n}","tryCatchPattern":"match tool_exec(\"learning_pin_facet\", args).await {\n    Err(e) if e.to_string().contains(\"facet not found\") => {\n        let listed = tool_exec(\"learning_list_facets\", json!({})).await?; // refresh, pick verbatim key\n        /* re-issue with a key from `listed` or surface to the user */\n        unimplemented!()\n    }\n    other => other,\n}","preventionTips":["Always source facet keys from learning_list_facets output, never from the model's memory","Treat an empty facet list after learning_reset_cache as expected — re-derive facets before pinning","Remember keys are 'class/suffix' strings; the class must be valid FacetClass vocabulary"],"tags":["rust","learning","agent-tools","not-found","validation"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}