NousResearch/hermes-agent · error · ValueError
bad memory node id: {node_id!r}
Error message
bad memory node id: {node_id!r} What it means
Raised by _parse_memory_id in the learning-mutation module when a memory node id does not match the required 'memory:<source>:<index>' shape, or the <source> segment is not one of the known memory files (MEMORY.md / USER.md). Every mutation API (edit, delete, inspect) parses the node id first, so any malformed id fails here.
Source
Thrown at agent/learning_mutations.py:40
_MEMORY_FILES = {"memory": "MEMORY.md", "profile": "USER.md"}
def parse_node_kind(node_id: str) -> str:
return "memory" if node_id.startswith("memory:") else "skill"
def _memories_dir() -> Path:
from hermes_constants import get_hermes_home
return get_hermes_home() / "memories"
def _parse_memory_id(node_id: str) -> tuple[str, int]:
"""``memory:<source>:<index>`` → (source, global_index)."""
parts = node_id.split(":", 2)
if len(parts) != 3 or parts[0] != "memory" or parts[1] not in _MEMORY_FILES:
raise ValueError(f"bad memory node id: {node_id!r}")
try:
return parts[1], int(parts[2])
except ValueError as exc:
raise ValueError(f"bad memory node id: {node_id!r}") from exc
def _memory_local_index(source: str, global_index: int) -> int:
"""Global card index → position within the source's own file.
``_memory_cards`` emits all ``MEMORY.md`` cards before ``USER.md`` cards, so
a profile card's local index is its global index minus the memory count.
"""
from agent.learning_graph import _memory_cards
cards = _memory_cards()
if not 0 <= global_index < len(cards):
raise IndexError(f"memory index {global_index} out of range")
if cards[global_index].get("source") != source:View on GitHub (pinned to c896c09c42)
Solutions
- Regenerate the learning graph (learning_graph module) and copy the exact node id it emits — ids must be 'memory:<source>:<global_index>'.
- Check the source segment is exactly 'memory' or 'user' (the keys of _MEMORY_FILES); other sources like 'skill' belong to a different node type.
- Ensure the third segment is a plain integer with no extra characters (no whitespace, no negative sign, no hex).
Example fix
# before
learning_mutations.node_detail("memory:MEMORY.md:3") # ValueError: bad memory node id
# after
learning_mutations.node_detail("memory:memory:3") # source must be a _MEMORY_FILES key Defensive patterns
Strategy: validation
Validate before calling
import re
from agent.learning_mutations import _MEMORY_FILES
_VALID_ID = re.compile(r"^memory:(\w+):(\d+)$")
def is_valid_memory_id(node_id: str) -> bool:
m = _VALID_ID.match(node_id or "")
return bool(m) and m.group(1) in _MEMORY_FILES Type guard
def is_valid_memory_id(node_id: str) -> bool:
"""Narrow a node id to the 'memory:<source>:<int>' shape with a known source."""
m = _VALID_ID.match(node_id or "")
return bool(m) and m.group(1) in _MEMORY_FILES Try / catch
try:
mutate(node_id)
except ValueError as e:
if "bad memory node id" in str(e):
node_id = regenerate_id_from_fresh_graph()
else:
raise Prevention
- Always copy node ids verbatim from a freshly rendered learning graph.
- Validate the id shape (memory:<known-source>:<int>) before calling any mutation API.
- Construct ids only via f"memory:{source}:{int(idx)}" so the index is canonicalized.
When it happens
Trigger: Passing an id like 'memory:memory' (missing index), 'skill:python:3' (wrong prefix), 'memory:unknown:2' (source not in _MEMORY_FILES), or 'memory:memory:abc' (non-integer index — the same message is reused at the int() failure site).
Common situations: Agent/model hallucinating a node id instead of copying one from learning_graph output; stale ids from an older id scheme; string manipulation bugs that drop the index segment.
Related errors
- memory index {global_index} out of range
- memory node id is stale — refresh the graph
- Preview mode — launching is disabled.
- no install root
- ${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/9be08c57521da748.
Report an issue: GitHub.