{"record":{"id":"6c09eb45d34f72de","repo":"bytedance/deer-flow","slug":"fact-confidence-must-be-a-number-between-0-and-1","errorCode":null,"errorMessage":"fact.confidence must be a number between 0 and 1","messagePattern":"fact\\.confidence must be a number between 0 and 1","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py","lineNumber":201,"sourceCode":"    safely rebased after that shared revision changed.\n    \"\"\"\n    if not isinstance(fact, dict):\n        raise ValueError(\"fact must be an object\")\n    normalized = copy.deepcopy(fact)\n    normalized[\"id\"] = str(normalized.get(\"id\") or f\"fact_{uuid.uuid4().hex}\")\n    # Validate the id through the canonical path builder's public contract.\n    if not normalized[\"id\"] or any(character not in \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\" for character in normalized[\"id\"]):\n        raise ValueError(\"fact.id may contain only letters, numbers, '_' and '-'\")\n    normalized[\"schemaVersion\"] = 2\n    if not isinstance(normalized.get(\"content\"), str):\n        raise ValueError(\"fact.content must be a string\")\n    normalized[\"content\"] = normalized[\"content\"].strip()\n    if not normalized[\"content\"]:\n        raise ValueError(\"fact.content must not be empty\")\n    _normalize_category(normalized)\n    confidence = normalized.get(\"confidence\", 0.5)\n    if isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1:\n        raise ValueError(\"fact.confidence must be a number between 0 and 1\")\n    normalized[\"confidence\"] = float(confidence)\n    status = normalized.get(\"status\", \"active\")\n    if status != \"active\":\n        raise ValueError(\"fact.status must be 'active'; deletion is physical\")\n    normalized[\"status\"] = \"active\"\n    normalized[\"scope\"] = copy.deepcopy(scope)\n    _require_string_list(normalized, \"topics\")\n    _require_string_list(normalized, \"consolidatedFrom\")\n    revision = normalized.get(\"revision\", 1)\n    if isinstance(revision, bool) or not isinstance(revision, int) or revision < 1:\n        raise ValueError(\"fact.revision must be an integer >= 1\")\n    source = normalized.get(\"source\")\n    if isinstance(source, str):\n        if source in {\"manual\", \"consolidation\", \"import\", \"unknown\"}:\n            normalized[\"source\"] = {\"type\": source, \"threadId\": None}\n        else:\n            normalized[\"source\"] = {\"type\": \"conversation\", \"threadId\": source}\n    elif not isinstance(source, dict):","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py#L183-L219","documentation":"fact['confidence'] must be a real number in [0, 1]; the default is 0.5. Booleans are explicitly rejected even though bool subclasses int, and NaN fails the range check (NaN comparisons are false). Out-of-range numbers (1.5, -0.1) and strings ('0.8') also raise ValueError.","triggerScenarios":"Saving {'confidence': 'high'}, {'confidence': 85} (percent instead of fraction), {'confidence': True}, or float('nan').","commonSituations":"LLMs emitting confidence as a percentage or a word; producers using 0-100 scales; JSON configs with stringly-typed numbers.","solutions":["Normalize the scale before saving: divide by 100 when the numeric input is greater than 1.","Map verbal levels: {'high': 0.9, 'medium': 0.5, 'low': 0.2}, or omit the key to accept the 0.5 default.","Reject bool explicitly at the boundary since bool passes naive isinstance(x, (int, float)) checks."],"exampleFix":"# before\nmemory.save_fact({\"content\": \"...\", \"confidence\": 85})\n# after\nmemory.save_fact({\"content\": \"...\", \"confidence\": 0.85})","handlingStrategy":"validation","validationCode":"c = fact.get(\"confidence\", 0.5)\nif isinstance(c, bool) or not isinstance(c, (int, float)) or not 0 <= c <= 1:\n    c = min(max(float(c), 0.0), 1.0) if not isinstance(c, bool) and isinstance(c, (int, float)) else 0.5\nfact[\"confidence\"] = c","typeGuard":"import math\n\ndef is_valid_confidence(v: object) -> bool:\n    return not isinstance(v, bool) and isinstance(v, (int, float)) and math.isfinite(v) and 0 <= v <= 1","tryCatchPattern":null,"preventionTips":["Convert percentage scales (0-100) to fractions at the boundary.","Map verbal confidence ('high'/'low') to numbers before saving.","Never pass booleans where confidence is expected."],"tags":["deermem","memory","validation","confidence"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}