deepset-ai/haystack · error

`min_keep_steps` must be at least 1, got {min_keep_steps}. T

Error message

`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.

What it means

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.

Source

Thrown at haystack/hooks/compaction/tool_result_pruning.py:70

        skip_meta_keys: tuple[str, ...] = ("tool_result_offloaded",),
    ) -> None:
        """
        Initialize the compactor with the rules deciding which results it prunes.

        :param min_keep_steps: The minimum number of recent tool-calling Agent steps whose results remain untouched,
            even when they exceed the target. Must be at least 1, which ensures the current result batch remains intact
            until the model has acted on it.
        :param min_tokens: Only prune tool-result messages that use more than this many tokens. Small results cost
            little and are often the ones worth keeping.
        :param placeholder: The text left in place of a pruned result, replacing the built-in one. May contain
            `{tool_name}`, which is filled in with the name of the tool that produced the result.
        :param skip_meta_keys: Results whose `meta` contains any of these keys are left alone. The default covers
            results that a `ToolResultOffloadHook` already replaced with a reference to stored content: pruning one of
            those would destroy the reference the model needs to read it back.
        :raises ValueError: If `min_keep_steps` is less than 1 or `min_tokens` is negative.
        """
        if min_keep_steps < 1:
            raise ValueError(
                f"`min_keep_steps` must be at least 1, got {min_keep_steps}. The most recent tool-calling step "
                f"contains results the model may still need."
            )
        if min_tokens < 0:
            raise ValueError(f"`min_tokens` must be at least 0, got {min_tokens}.")
        self.min_keep_steps = min_keep_steps
        self.min_tokens = min_tokens
        self.placeholder = placeholder
        # Normalized to a tuple so a round trip through `to_dict`, which has to emit a list, restores the same type.
        self.skip_meta_keys = tuple(skip_meta_keys)

    def compact(
        self, messages: list[ChatMessage], target_tokens: int, token_counter: TokenCounter
    ) -> list[ChatMessage] | None:
        """
        Replace the content of prunable tool results with a placeholder.

        Results are considered oldest first and pruning stops as soon as the conversation reaches `target_tokens`.

View on GitHub (pinned to e318778c9b)

Solutions

  1. Pass min_keep_steps >= 1, e.g. min_keep_steps=1.
  2. Clamp: min_keep_steps = max(1, value) before constructing.
  3. Fix the config source supplying 0.

Example fix

// before
hook = ToolResultPruningHook(min_keep_steps=0)
// after
hook = ToolResultPruningHook(min_keep_steps=1)
Defensive patterns

Strategy: validation

Validate before calling

def validate_min_keep_steps(v):
    if not isinstance(v, int) or v < 1:
        raise ValueError(f"min_keep_steps must be >= 1, got {v!r}")
validate_min_keep_steps(cfg.get("min_keep_steps", 1))

Type guard

def is_positive_int(v) -> bool:
    return isinstance(v, int) and not isinstance(v, bool) and v >= 1

Try / catch

try:
    hook = ToolResultPruningHook(min_keep_steps=n)
except ValueError as e:
    logger.error("bad min_keep_steps: %s", e)
    hook = ToolResultPruningHook(min_keep_steps=1)

Prevention

When it happens

Trigger: Constructing ToolResultPruningHook with min_keep_steps=0 or a negative number, usually from a computed keep-count or a config default of 0.

Common situations: 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.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/b851dcdd76b6d5e0. Report an issue: GitHub.