{"record":{"id":"b851dcdd76b6d5e0","repo":"deepset-ai/haystack","slug":"min-keep-steps-must-be-at-least-1-got-min-keep","errorCode":null,"errorMessage":"`min_keep_steps` must be at least 1, got {min_keep_steps}. The most recent tool-calling step contains results the model may still need.","messagePattern":"`min_keep_steps` must be at least 1, got (.+?)\\. The most recent tool-calling step contains results the model may still need\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/hooks/compaction/tool_result_pruning.py","lineNumber":70,"sourceCode":"        skip_meta_keys: tuple[str, ...] = (\"tool_result_offloaded\",),\n    ) -> None:\n        \"\"\"\n        Initialize the compactor with the rules deciding which results it prunes.\n\n        :param min_keep_steps: The minimum number of recent tool-calling Agent steps whose results remain untouched,\n            even when they exceed the target. Must be at least 1, which ensures the current result batch remains intact\n            until the model has acted on it.\n        :param min_tokens: Only prune tool-result messages that use more than this many tokens. Small results cost\n            little and are often the ones worth keeping.\n        :param placeholder: The text left in place of a pruned result, replacing the built-in one. May contain\n            `{tool_name}`, which is filled in with the name of the tool that produced the result.\n        :param skip_meta_keys: Results whose `meta` contains any of these keys are left alone. The default covers\n            results that a `ToolResultOffloadHook` already replaced with a reference to stored content: pruning one of\n            those would destroy the reference the model needs to read it back.\n        :raises ValueError: If `min_keep_steps` is less than 1 or `min_tokens` is negative.\n        \"\"\"\n        if min_keep_steps < 1:\n            raise ValueError(\n                f\"`min_keep_steps` must be at least 1, got {min_keep_steps}. The most recent tool-calling step \"\n                f\"contains results the model may still need.\"\n            )\n        if min_tokens < 0:\n            raise ValueError(f\"`min_tokens` must be at least 0, got {min_tokens}.\")\n        self.min_keep_steps = min_keep_steps\n        self.min_tokens = min_tokens\n        self.placeholder = placeholder\n        # Normalized to a tuple so a round trip through `to_dict`, which has to emit a list, restores the same type.\n        self.skip_meta_keys = tuple(skip_meta_keys)\n\n    def compact(\n        self, messages: list[ChatMessage], target_tokens: int, token_counter: TokenCounter\n    ) -> list[ChatMessage] | None:\n        \"\"\"\n        Replace the content of prunable tool results with a placeholder.\n\n        Results are considered oldest first and pruning stops as soon as the conversation reaches `target_tokens`.","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/hooks/compaction/tool_result_pruning.py#L52-L88","documentation":"ToolResultPruningHook enforces min_keep_steps >= 1 in __init__ (haystack/hooks/compaction/tool_result_pruning.py:70). The most recent tool-calling step contains tool results the model may still need for its next reply, so pruning must always keep at least one step; 0 or negative is invalid.","triggerScenarios":"Constructing ToolResultPruningHook with min_keep_steps=0 or a negative number, usually from a computed keep-count or a config default of 0.","commonSituations":"Setting 0 intending 'no retention' without realizing at least one step is mandatory; arithmetic that underflows; copying a different compactor's default where 0 was allowed.","solutions":["Pass min_keep_steps >= 1, e.g. min_keep_steps=1.","Clamp: min_keep_steps = max(1, value) before constructing.","Fix the config source supplying 0."],"exampleFix":"// before\nhook = ToolResultPruningHook(min_keep_steps=0)\n// after\nhook = ToolResultPruningHook(min_keep_steps=1)","handlingStrategy":"validation","validationCode":"def validate_min_keep_steps(v):\n    if not isinstance(v, int) or v < 1:\n        raise ValueError(f\"min_keep_steps must be >= 1, got {v!r}\")\nvalidate_min_keep_steps(cfg.get(\"min_keep_steps\", 1))","typeGuard":"def is_positive_int(v) -> bool:\n    return isinstance(v, int) and not isinstance(v, bool) and v >= 1","tryCatchPattern":"try:\n    hook = ToolResultPruningHook(min_keep_steps=n)\nexcept ValueError as e:\n    logger.error(\"bad min_keep_steps: %s\", e)\n    hook = ToolResultPruningHook(min_keep_steps=1)","preventionTips":["Remember at least 1 step must be kept; never configure 0","Clamp with max(1, value) after computation","Document that 0 is invalid in your config schema"],"tags":["validation","constructor","config","tool-results","compaction"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}