HKUDS/DeepTutor · error · OpValidationError

add: refs must be non-empty

Error message

add: refs must be non-empty

What it means

OpValidationError raised in _validate when an AddOp carries an empty refs list. Every memory entry must cite at least one provenance reference (source message/span), which is what later consolidation and undo rely on; an entry with no refs cannot be traced back and is rejected before apply mutates anything.

Source

Thrown at deeptutor/services/memory/ops.py:81

class OpValidationError(Exception):
    """Raised when a batch fails pre-flight validation."""


def _validate(doc: Document, ops: list[Op]) -> None:
    edits: set[str] = set()
    deletes: set[str] = set()

    for op in ops:
        if isinstance(op, AddOp):
            if not op.text or len(op.text) > _MAX_TEXT_LEN:
                raise OpValidationError(
                    f"add: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.text)})"
                )
            if not op.section or len(op.section) > _MAX_SECTION_LEN:
                raise OpValidationError(f"add: invalid section {op.section!r}")
            if not op.refs:
                raise OpValidationError("add: refs must be non-empty")
            for ref in op.refs:
                if not is_valid_ref(ref):
                    raise OpValidationError(f"add: malformed ref {ref!r}")
        elif isinstance(op, EditOp):
            if not is_entry_id(op.target_id):
                raise OpValidationError(f"edit: malformed target_id {op.target_id!r}")
            if doc.find(op.target_id) is None:
                raise OpValidationError(f"edit: target_id {op.target_id} not found")
            if not op.new_text or len(op.new_text) > _MAX_TEXT_LEN:
                raise OpValidationError(
                    f"edit: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.new_text)})"
                )
            if not op.new_refs:
                raise OpValidationError("edit: refs must be non-empty")
            for ref in op.new_refs:
                if not is_valid_ref(ref):
                    raise OpValidationError(f"edit: malformed ref {ref!r}")
            if op.target_id in deletes:

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Always derive refs from the triggering source (e.g. message id / span ref) and pass them in AddOp.refs
  2. If your pipeline has no real source, synthesize a stable ref (e.g. 'manual:<uuid>' if the ref grammar allows) rather than an empty list
  3. Guard upstream: skip the write and log when the ref-extraction step yields nothing

Example fix

# before
op = AddOp(text=note, section="facts", refs=[])

# after
if not refs:
    refs = [f"msg:{source_message_id}"]
op = AddOp(text=note, section="facts", refs=refs)
Defensive patterns

Strategy: validation

Validate before calling

refs = [r for r in raw_refs if r]
if not refs:
    refs = [f"msg:{source_message_id}"]

Type guard

def has_valid_refs(refs) -> bool:
    return isinstance(refs, (list, tuple)) and len(refs) > 0

Try / catch

try:
    ops.apply(doc, [op])
except OpValidationError as e:
    if "refs must be non-empty" in str(e):
        op = replace(op, refs=[fallback_ref])
        ops.apply(doc, [op])
    else:
        raise

Prevention

When it happens

Trigger: AddOp(text=..., section=..., refs=[]) or refs=None passed to ops.apply — typically a caller forgetting to thread the originating message/source span into the op, or an LLM tool-call omitting the refs argument.

Common situations: New integration code writing memories without capturing source references; tests building AddOp fixtures minimally; ref extraction step returning an empty list that is passed through unchecked.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/b3d4396eb6d2dc7d. Report an issue: GitHub.