langchain-ai/deepagents · error · RuntimeError

Server offload operations may not write {sorted(forbidden)}

Error message

Server offload operations may not write {sorted(forbidden)} to the checkpoint.

What it means

The server-side offload handler validates that the merged state update only touches allowlisted checkpoint channels (`_WRITABLE_STATE_CHANNELS`). If the offload execution result or the cost-preparation update contributes any other channel (notably `messages`), it rolls back the drained cost record and raises this RuntimeError. This is a deliberate security boundary: because the route commits to the latest checkpoint rather than the one it read, an unattributed `messages` write could clobber messages a concurrent run appended.

Source

Thrown at libs/code/deepagents_code/offload_api.py:993

        prepared = prepare_operation_cost(state, thread_id)
        update: dict[str, Any] = {**execution.update, **prepared.update}
        if forbidden := set(update) - _WRITABLE_STATE_CHANNELS:
            # A security boundary, not a defensive assertion: this route commits
            # to the latest checkpoint rather than the one it read, so a
            # `messages` write here would be unattributed to any run and could
            # clobber messages a concurrent run appended in that window. See
            # THREAT_MODEL.md (TB10/DF27) before relaxing this.
            #
            # Checked as an allowlist against `OffloadStateUpdate` rather than
            # for `messages` alone, so the runtime guard enforces the same
            # invariant the type states instead of a subset of it: a future
            # merge that adds any other channel is refused here too.
            msg = (
                "Server offload operations may not write "
                f"{sorted(forbidden)} to the checkpoint."
            )
            prepared.rollback()
            raise RuntimeError(msg)
        if not update:
            # Nothing to persist, but `prepare_operation_cost` already drained
            # the recorder. Returning without rolling back would delete that
            # spend from the thread's lifetime total (the drain is destructive).
            prepared.rollback()
            return {"status": "complete", "result": execution.result}
        commit = asyncio.create_task(
            _commit_deferred_archive(
                client,
                thread_id,
                checkpoint_id,
                execution,
                update,
                prepared,
            )
        )
        cancellation = await _join_task_deferring_cancellation(commit)
        commit.result()

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Change the offload operation to return only allowlisted channels (e.g. `_summarization_event`, token counters) instead of `messages` or other channels.
  2. If a new channel is legitimately required, extend `_WRITABLE_STATE_CHANNELS` after reviewing THREAT_MODEL.md (TB10/DF27).
  3. If caused by an SDK upgrade, pin the previous `deepagents` version and update the offload middleware to the new summarization-event contract.

Example fix

// before
class MyOffload(OffloadOperation):
    def execute(self, state, runtime):
        return {"messages": summarized, ...}
// after
class MyOffload(OffloadOperation):
    def execute(self, state, runtime):
        # emit an event, not a messages write
        return {"_summarization_event": {"cutoff_index": idx, "summary_message": summary}}
Defensive patterns

Strategy: validation

Validate before calling

FORBIDDEN = set(execution.update) - _WRITABLE_STATE_CHANNELS
assert not FORBIDDEN, f"offload update writes {FORBIDDEN} — restrict to allowlisted channels"

Type guard

def is_allowlisted(update: dict[str, Any]) -> bool:
    return set(update) <= _WRITABLE_STATE_CHANNELS

Try / catch

try:
    result = await offload_endpoint(request)
except RuntimeError as e:
    if "may not write" in str(e):
        fix_operation_channels()  # inspect operation.update keys
    raise

Prevention

When it happens

Trigger: Calling POST offload (via `offload`) where `OffloadOperation.execute` returns an update dict containing channels outside the allowlist — e.g. a custom OffloadOperation writing `messages`, or a modified/merged execution returning extra state keys.

Common situations: Custom offload/compaction operations that write summarized `messages` server-side (moved to client-side event-based compaction); SDK upgrades that add channels to `OffloadStateUpdate` without updating `_WRITABLE_STATE_CHANNELS`; test doubles of the operation that return full state.

Understand the failure class

Related errors


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