headroomlabs-ai/headroom · error · ValueError
Memories {old_memory_id} and {new_memory_id} do not form a r
Error message
Memories {old_memory_id} and {new_memory_id} do not form a reciprocal supersession edge What it means
Raised by detach_supersession when both memories exist but they do not form a reciprocal supersession edge — i.e. old.superseded_by != new_id or new.supersedes != old_id. The detach operation only removes verified two-way edges and never infers one-sided links, so any asymmetry is rejected.
Source
Thrown at headroom/memory/adapters/sqlite.py:735
if old_memory_id == new_memory_id:
raise ValueError("A memory cannot supersede itself")
with self._get_conn() as conn:
conn.execute("BEGIN IMMEDIATE")
rows = conn.execute(
"SELECT * FROM memories WHERE id IN (?, ?)",
(old_memory_id, new_memory_id),
).fetchall()
memories = {row["id"]: self._row_to_memory(row) for row in rows}
old_memory = memories.get(old_memory_id)
new_memory = memories.get(new_memory_id)
if old_memory is None:
raise ValueError(f"Memory {old_memory_id} not found")
if new_memory is None:
raise ValueError(f"Memory {new_memory_id} not found")
if old_memory.superseded_by != new_memory_id or new_memory.supersedes != old_memory_id:
raise ValueError(
f"Memories {old_memory_id} and {new_memory_id} do not form "
"a reciprocal supersession edge"
)
conn.execute(
"UPDATE memories SET valid_until = NULL, superseded_by = NULL WHERE id = ?",
(old_memory_id,),
)
conn.execute(
"UPDATE memories SET supersedes = NULL WHERE id = ?",
(new_memory_id,),
)
old_memory.valid_until = None
old_memory.superseded_by = None
new_memory.supersedes = None
return old_memory, new_memory
View on GitHub (pinned to 322425c43b)
Solutions
- Inspect both rows: SELECT superseded_by, supersedes for the pair and confirm they point at each other before detaching.
- If the edge is one-sided, repair the dangling column(s) explicitly rather than calling detach_supersession.
- For chains where the old memory was superseded by a different memory, call detach with the actual current partner IDs.
Example fix
// before
await store.detach_supersession(old_id, new_id) # assumed edge
// after
old = await store.get(old_id); new = await store.get(new_id)
if old.superseded_by == new_id and new.supersedes == old_id:
await store.detach_supersession(old_id, new_id)
else:
logger.warning("no reciprocal edge; manual repair needed") Defensive patterns
Strategy: validation
Validate before calling
old = await store.get(old_id); new = await store.get(new_id)
if not (old and new and old.superseded_by == new_id and new.supersedes == old_id):
logger.warning("not a reciprocal edge; manual repair required")
return
await store.detach_supersession(old_id, new_id) Type guard
def is_reciprocal(old: Memory, new: Memory) -> bool:
return old.superseded_by == new.id and new.supersedes == old.id Try / catch
try:
await store.detach_supersession(old_id, new_id)
except ValueError as e:
if "reciprocal" in str(e):
# inspect rows and repair dangling pointers manually
... Prevention
- Only use detach_supersession on edges created by supersede().
- Audit half-written edges after any crash during supersede; the two UPDATEs are transactional but external edits may not be.
When it happens
Trigger: Passing two memories that are adjacent in a chain but whose forward/back pointers disagree (e.g. old.superseded_by points to a different memory than new.supersedes); data corruption from partial updates; manually edited rows.
Common situations: Crash between the two UPDATEs in supersede leaving a half-written edge; a memory being superseded twice so pointers moved to a third node; repair scripts assuming adjacency equals an edge.
Related errors
- A memory cannot supersede itself
- Memory {new_memory_id} not found
- Memories {old_memory.id} and {new_memory.id} do not form a r
- Memory with ID {old_memory_id} not found
- Memory {old_memory_id} not found
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/2fbb634527fe422b.
Report an issue: GitHub.