deepset-ai/haystack · error
`min_keep_steps` must be at least 0, got {min_keep_steps}.
Error message
`min_keep_steps` must be at least 0, got {min_keep_steps}. What it means
SlidingWindowCompactor validates min_keep_steps in __init__ (haystack/hooks/compaction/sliding_window.py:239) and rejects negative values with ValueError. min_keep_steps controls how many recent conversation steps are always retained; a negative count is meaningless.
Source
Thrown at haystack/hooks/compaction/sliding_window.py:239
tools=[web_search],
hooks={"before_llm": [hook]},
)
```
"""
def __init__(self, *, min_keep_steps: int = 1, omission_note: str | None = _DEFAULT_OMISSION_NOTE) -> None:
"""
Initialize the compactor.
:param min_keep_steps: The fewest complete recent Agent steps to keep even when they exceed the target. A step
is an assistant message and all immediately following tool results. `0` allows all completed steps to be
removed when none fit.
:param omission_note: The user message left in place of what was removed, or None to remove the messages
silently. Include `{num_removed}` to have the number of removed messages substituted in.
:raises ValueError: If `min_keep_steps` is negative.
"""
if min_keep_steps < 0:
raise ValueError(f"`min_keep_steps` must be at least 0, got {min_keep_steps}.")
self.min_keep_steps = min_keep_steps
self.omission_note = omission_note
def compact(
self, messages: list[ChatMessage], target_tokens: int, token_counter: TokenCounter
) -> list[ChatMessage] | None:
"""
Drop older history while preserving the task anchor and a complete recent conversation window.
:param messages: The conversation to compact, oldest to newest.
:param target_tokens: The size the kept conversation should come in under.
:param token_counter: The `TokenCounter` to measure messages with.
:returns: The conversation that survived, with an omission note if configured standing where the removed
messages used to sit; or None when there is nothing to remove but an earlier note.
"""
if token_counter.count(messages=messages) <= target_tokens:
return None
kept, note_index, removable = _task_and_step_split(View on GitHub (pinned to e318778c9b)
Solutions
- Pass min_keep_steps >= 0, e.g. min_keep_steps=2.
- Clamp before construction: min_keep_steps = max(0, configured_value).
- Fix the config source if it supplies negative values.
Example fix
// before compactor = SlidingWindowCompactor(min_keep_steps=-1) // after compactor = SlidingWindowCompactor(min_keep_steps=max(0, configured_keep))
Defensive patterns
Strategy: validation
Validate before calling
def validate_min_keep_steps(v):
if not isinstance(v, int) or v < 0:
raise ValueError(f"min_keep_steps must be >= 0, got {v!r}")
validate_min_keep_steps(cfg["min_keep_steps"]) Type guard
def is_non_negative_int(v) -> bool:
return isinstance(v, int) and not isinstance(v, bool) and v >= 0 Try / catch
try:
compactor = SlidingWindowCompactor(min_keep_steps=n)
except ValueError as e:
logger.error("bad min_keep_steps: %s", e)
compactor = SlidingWindowCompactor(min_keep_steps=0) Prevention
- Clamp computed values with max(0, value)
- Validate YAML/JSON config at load time
- Prefer explicit positive defaults in config templates
When it happens
Trigger: Constructing SlidingWindowCompactor with min_keep_steps < 0, e.g. min_keep_steps=-1, typically from a subtraction or a bad config value.
Common situations: Computing min_keep_steps as (desired - something) that goes negative; loading a negative value from YAML/JSON config; off-by-one in defaults.
Related errors
- `context_window` must be a positive number of tokens, got {c
- `compact_at` and `compact_to` must satisfy 0 < compact_to <
- `min_keep_steps` must be at least 0, got {min_keep_steps}.
- `approximate_summary_tokens` must be a positive number of to
- `min_keep_steps` must be at least 1, got {min_keep_steps}. T
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/f8eb4de3ffe52025.
Report an issue: GitHub.