{"record":{"id":"0e7d0feb3a9b22d3","repo":"NousResearch/hermes-agent","slug":"malformed-subagent-handle","errorCode":null,"errorMessage":"Malformed subagent handle.","messagePattern":"Malformed subagent handle\\.","errorType":"exception","errorClass":"SubagentLifecycleError","httpStatus":null,"severity":"error","filePath":"agent/subagent_lifecycle.py","lineNumber":86,"sourceCode":"    subagent_id: str\n    parent_session_id: Optional[str]\n    correlation_id: Optional[str]\n    created_at: float\n    provider: Optional[str]\n    model: Optional[str]\n    role: str\n    depth: int\n    capability: str\n\n    def to_dict(self) -> dict[str, Any]:\n        return dataclasses.asdict(self)\n\n    @classmethod\n    def from_dict(cls, value: Mapping[str, Any]) -> \"SubagentHandle\":\n        try:\n            return cls(**dict(value))\n        except (TypeError, ValueError) as exc:\n            raise SubagentLifecycleError(\"Malformed subagent handle.\") from exc\n\n\n@dataclasses.dataclass(frozen=True)\nclass SubagentStatus:\n    handle: SubagentHandle\n    state: SubagentState\n    updated_at: float\n    diagnostic: Optional[str] = None\n\n\n@dataclasses.dataclass(frozen=True)\nclass SubagentTerminalState:\n    handle: SubagentHandle\n    state: SubagentState\n    completed: bool\n    timed_out: bool = False\n    diagnostic: Optional[str] = None\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/subagent_lifecycle.py#L68-L104","documentation":"Raised by SubagentHandle.from_dict() in agent/subagent_lifecycle.py when the input mapping cannot construct the frozen dataclass — wrong field names, extra keys, missing fields, or wrong value types cause cls(**dict(value)) to raise TypeError/ValueError. It protects the public serialized-handle contract for subagent handles.","triggerScenarios":"Deserializing a handle dict produced by a different (older/newer) contract version; hand-building a dict with typos or extra keys; passing a JSON blob where nested values (e.g. depth) are strings instead of ints; persisting a handle across a Hermes upgrade and reloading it.","commonSituations":"Plugin code storing handle.to_dict() in a database or file and reading it back after Hermes updates the handle schema; inter-process handoff where the dict was re-encoded and types changed (int -> str).","solutions":["Regenerate the handle via SubagentHandle.to_dict() from the same Hermes version that will consume it — do not hand-write the mapping.","If deserializing stored handles, verify the dict keys exactly match the dataclass fields (contract_version, subagent_id, parent_session_id, correlation_id, created_at, provider, model, role, depth, capability) and coerce value types.","Check PUBLIC_CONTRACT_VERSION before from_dict() and discard/re-launch handles from mismatched versions.","If the handle came from your own code, log the raw dict and diff its keys against the dataclass fields."],"exampleFix":"# before\nhandle = SubagentHandle.from_dict(loaded_json[\"handle\"])\n\n# after\nraw = loaded_json[\"handle\"]\nexpected = {f.name for f in dataclasses.fields(SubagentHandle)}\nif int(raw.get(\"contract_version\", -1)) != PUBLIC_CONTRACT_VERSION or set(raw) != expected:\n    raise ValueError(\"stored handle does not match current contract; re-launch subagent\")\nhandle = SubagentHandle.from_dict(raw)","handlingStrategy":"type-guard","validationCode":"import dataclasses\nfrom agent.subagent_lifecycle import SubagentHandle, PUBLIC_CONTRACT_VERSION\n\nFIELDS = {f.name for f in dataclasses.fields(SubagentHandle)}\n\ndef is_valid_handle_dict(value) -> bool:\n    return (\n        isinstance(value, dict)\n        and set(value) == FIELDS\n        and value.get(\"contract_version\") == PUBLIC_CONTRACT_VERSION\n        and isinstance(value.get(\"depth\"), int)\n        and isinstance(value.get(\"created_at\"), (int, float))\n    )","typeGuard":"def is_subagent_handle_dict(value: object) -> bool:\n    import dataclasses\n    if not isinstance(value, dict):\n        return False\n    names = {f.name for f in dataclasses.fields(SubagentHandle)}\n    return set(value.keys()) == names","tryCatchPattern":"try:\n    handle = SubagentHandle.from_dict(raw)\nexcept SubagentLifecycleError:\n    handle = None  # stale/corrupt handle: re-launch instead of reusing","preventionTips":["Only round-trip handles via to_dict()/from_dict() within the same Hermes version.","Never hand-construct handle dicts; always use launch() and serialize its return value.","Store PUBLIC_CONTRACT_VERSION alongside the handle and discard on mismatch."],"tags":["subagents","delegation","serialization","dataclass"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}