NousResearch/hermes-agent · error · SubagentLifecycleError

metadata must be JSON-serializable.

Error message

metadata must be JSON-serializable.

What it means

Validation error from SubagentLifecycleManager._validate_request(): request.metadata must be JSON-serializable because the manager json.dumps()s it (sorted keys) both to measure size and to store it. If serialization raises TypeError/ValueError (sets, custom objects, datetime, recursive refs), the launch is rejected.

Source

Thrown at agent/subagent_lifecycle.py:523

            raise SubagentLifecycleError("role must be 'leaf' or 'orchestrator'.")
        if request.timeout_seconds is not None:
            raise SubagentLifecycleError(
                "Per-launch timeout is not supported; configure delegation timeout explicitly."
            )
        if request.working_directory is not None:
            raise SubagentLifecycleError(
                "working_directory is not supported because Hermes delegates use isolated task environments."
            )
        if request.blocked_tools:
            raise SubagentLifecycleError(
                "Per-tool blocking is not supported; use allowed_toolsets. Hermes always blocks unsafe child tools."
            )
        try:
            metadata_bytes = len(
                json.dumps(dict(request.metadata), sort_keys=True).encode()
            )
        except (TypeError, ValueError) as exc:
            raise SubagentLifecycleError("metadata must be JSON-serializable.") from exc
        if metadata_bytes > _MAX_METADATA_BYTES:
            raise SubagentLifecycleError("metadata exceeds 8192 bytes.")
        if request.allowed_toolsets:
            from toolsets import TOOLSETS

            unknown = set(request.allowed_toolsets) - set(TOOLSETS)
            if unknown:
                raise SubagentLifecycleError(
                    f"Unknown toolsets: {', '.join(sorted(unknown))}."
                )
            enabled = getattr(parent, "enabled_toolsets", None)
            if enabled is not None and not set(request.allowed_toolsets).issubset(
                set(enabled)
            ):
                raise SubagentLifecycleError(
                    "Requested toolsets would broaden parent permissions."
                )

View on GitHub (pinned to c896c09c42)

Solutions

  1. Pre-serialize metadata yourself: metadata = json.loads(json.dumps(metadata, default=str)) to coerce or fail fast with a clear error.
  2. Convert known non-JSON types explicitly (str(uuid), isoformat() for datetimes, sorted(list(s)) for sets).
  3. Keep metadata to plain str/int/float/bool/None/list/dict composed of the same.

Example fix

# before
request = SubagentLaunchRequest(goal=g, metadata={"tags": {"a", "b"}, "at": datetime.now()})

# after
request = SubagentLaunchRequest(
    goal=g,
    metadata={"tags": ["a", "b"], "at": datetime.now().isoformat()},
)
Defensive patterns

Strategy: validation

Validate before calling

import json
try:
    json.dumps(metadata, sort_keys=True)
except (TypeError, ValueError):
    metadata = json.loads(json.dumps(metadata, default=str))  # or fix at source
request = SubagentLaunchRequest(goal=goal, metadata=metadata)

Type guard

import json

def is_json_serializable(value: object) -> bool:
    try:
        json.dumps(value, sort_keys=True)
    except (TypeError, ValueError):
        return False
    return True

Prevention

When it happens

Trigger: Passing metadata containing set(), datetime objects, dataclass instances, numpy types, or objects without a JSON representation; metadata values coming straight from ORM/model objects; circular references in nested dicts.

Common situations: Stuffing ORM rows or pydantic-adjacent objects into metadata; timestamp fields left as datetime instead of ISO strings; third-party types (Decimal, UUID, tuples are fine in json but sets are not) leaking in from config parsing.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/d8f9cb47b613ad25. Report an issue: GitHub.