langchain-ai/deepagents · error · ValueError

retention_revisions must be nonnegative

Error message

retention_revisions must be nonnegative

What it means

TranscriptStore keeps a configurable number of historical transcript revisions; a negative retention_revisions is nonsensical (it would mean deleting more revisions than exist / infinite pruning). __init__ validates up front and raises ValueError.

Source

Thrown at libs/code/deepagents_code/hooks/transcript.py:173

    def __init__(
        self,
        root: Path,
        *,
        retention_revisions: int = DEFAULT_RETENTION_REVISIONS,
    ) -> None:
        """Create a store rooted at `root`.

        Args:
            root: Directory that will contain per-thread transcript files.
            retention_revisions: Maximum prior `.bak-*` revisions retained per
                transcript after each rewrite.

        Raises:
            ValueError: If `retention_revisions` is negative.
        """
        if retention_revisions < 0:
            msg = "retention_revisions must be nonnegative"
            raise ValueError(msg)
        self.root = root.expanduser().resolve()
        self.retention_revisions = retention_revisions
        self._buffers: dict[tuple[str, str | None], _TranscriptBuffer] = {}
        self._lock = threading.RLock()
        _ensure_private_directories(self.root, self.root)

    def thread_path(self, thread_id: str) -> Path:
        """Return the materialized path for a thread transcript.

        Args:
            thread_id: Conversation thread identifier.

        Returns:
            Absolute JSONL path for the thread.
        """
        return self.root / f"{_safe_component(thread_id)}.jsonl"

    def agent_path(self, thread_id: str, agent_id: str) -> Path:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Pass retention_revisions >= 0 (0 typically meaning keep no extra historical revisions; check docs for the keep-current semantics).
  2. If "unlimited" was intended, use the store's option for disabling retention instead of a negative number.
  3. Clamp or validate config at load time before constructing the store.

Example fix

// before
store = TranscriptStore(root, retention_revisions=-1)  # ValueError

// after
revisions = max(0, config.get("retention_revisions", 3))
store = TranscriptStore(root, retention_revisions=revisions)
Defensive patterns

Strategy: validation

Validate before calling

revisions = config.get("retention_revisions", 3)
if not isinstance(revisions, int) or revisions < 0:
    raise ValueError("retention_revisions must be a nonnegative int")

Prevention

When it happens

Trigger: Constructing the transcript store (TranscriptStore(root, retention_revisions=...)) with a negative integer, typically from a parsed config value like retention_revisions=-1 or a subtracting expression that underflowed.

Common situations: Config file with retention_revisions: -1 intended as "unlimited"; computing retention as some_count - kept where kept > count; copying an example default incorrectly.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/9a140569aa39b305. Report an issue: GitHub.