{"record":{"id":"05d54bd8c4849593","repo":"langchain-ai/deepagents","slug":"offload-compaction-requires-checkpointed-conversat","errorCode":null,"errorMessage":"Offload compaction requires checkpointed conversation messages.","messagePattern":"Offload compaction requires checkpointed conversation messages\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/offload_middleware.py","lineNumber":1447,"sourceCode":"        Args:\n            state: Checkpointed conversation and prior summarization event.\n            runtime: Run context carrier used to select the summarizer model.\n\n        Returns:\n            The checkpoint/archive plan, or `None` when nothing can be compacted.\n\n        Raises:\n            ValueError: If called directly with no messages. The owning server\n                operation handles an empty thread before reaching this helper.\n        \"\"\"\n        summarization = await asyncio.to_thread(\n            self._summarization_for_runtime, runtime\n        )\n        messages = state.get(\"messages\", [])\n        event = state.get(\"_summarization_event\")\n        if not messages:\n            msg = \"Offload compaction requires checkpointed conversation messages.\"\n            raise ValueError(msg)\n        effective = summarization._apply_event_to_messages(messages, event)\n        cutoff = summarization._determine_cutoff_index(effective)\n        if cutoff == 0:\n            return None\n        # Resolved once and threaded into the update below: the SDK call is the\n        # relative-to-absolute conversion, and computing it twice would let the\n        # value checked here drift from the value committed.\n        state_cutoff = summarization._compute_state_cutoff(event, cutoff)\n        if state_cutoff <= _event_cutoff(event):\n            # Degenerate chained compaction: everything eligible is already\n            # behind the prior event's cutoff, so only the previous summary\n            # would be re-summarized. Committing would spend a model call to\n            # replace the in-context summary with a lossier summary-of-a-summary\n            # and drop the prior `file_path` from the event -- while the client,\n            # which keys its report on the *absolute* cutoff advancing, still\n            # reported \"nothing to offload\". Stop before the model call so the\n            # report and the state agree.\n            return None","sourceCodeStart":1429,"sourceCodeEnd":1465,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/offload_middleware.py#L1429-L1465","documentation":"Forced compaction planning (`_aplan_forced_compaction_update`) reads `messages` from the checkpointed thread state and requires at least one message to summarize. If the state has no messages, there is nothing to compact, so it raises ValueError instead of producing an empty or malformed update. This guards the `/offload` execute path against running compaction on an empty or unpersisted thread.","triggerScenarios":"Calling `OffloadOperation.execute`/`arun_forced_compaction_update` against a thread whose state dict lacks `messages` or has an empty list — e.g. a brand-new thread, a state loaded from the wrong thread_id, or a test state fixture without messages.","commonSituations":"Offloading an empty thread via a scripted/API call; misconfigured thread_id pointing at a fresh checkpoint; test doubles that omit the `messages` key.","solutions":["Only run offload/forced compaction on threads that have checkpointed conversation messages.","Correct the thread_id/checkpoint_ns so the state resolves to the right thread.","In tests, provide a state fixture containing at least one message."],"exampleFix":"// before\nresult = await operation.execute({\"_summarization_event\": None}, runtime)\n// after\nstate = await client.threads.get_state(thread_id)\nif state[\"messages\"]:\n    result = await operation.execute(state, runtime)","handlingStrategy":"validation","validationCode":"state = await client.threads.get_state(thread_id)\nif not state.get(\"messages\"):\n    return skipped_result(\"nothing to offload\")","typeGuard":"def has_messages(state: dict[str, Any]) -> bool:\n    msgs = state.get(\"messages\")\n    return isinstance(msgs, list) and len(msgs) > 0","tryCatchPattern":"try:\n    result = await operation.execute(state, runtime)\nexcept ValueError as e:\n    if \"checkpointed conversation messages\" in str(e):\n        result = noop_result()  # empty thread: nothing to compact\n    else:\n        raise","preventionTips":["Check message count in the UI/API before offering /offload.","Verify thread_id/configurable keys resolve to the intended thread.","Include at least one message in test state fixtures for compaction tests."],"tags":["compaction","validation","offload","empty-state"],"backgroundTag":"empty-messages-state","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}