fix(context): reject tool-output byte ceilings below the notice size

A configured tool_output_max_bytes smaller than the truncation notice
itself can't fit a bounded preview, so a persisted result could exceed the
ceiling. Enforce a config floor (ge=1024) so nonsensical values are
rejected at load time instead of being worked around at runtime.
This commit is contained in:
Ahmed Allam
2026-07-26 14:38:12 -07:00
committed by Ahmed Allam
parent 0ebd3c6230
commit 8e9a6bf903
3 changed files with 25 additions and 3 deletions
+5 -1
View File
@@ -69,7 +69,11 @@ class ContextSettings(BaseSettings):
summary_max_tokens: int = Field(default=4_096, gt=0, alias="STRIX_CONTEXT_SUMMARY_TOKENS") summary_max_tokens: int = Field(default=4_096, gt=0, alias="STRIX_CONTEXT_SUMMARY_TOKENS")
tool_output_max_tokens: int = Field(default=8_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_TOKENS") tool_output_max_tokens: int = Field(default=8_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_TOKENS")
tool_output_max_lines: int = Field(default=2_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_LINES") tool_output_max_lines: int = Field(default=2_000, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_LINES")
tool_output_max_bytes: int = Field(default=50 * 1024, gt=0, alias="STRIX_TOOL_OUTPUT_MAX_BYTES") # Floor comfortably above the truncation-notice size so a preview
# (head+tail+notice) always fits within the configured ceiling.
tool_output_max_bytes: int = Field(
default=50 * 1024, ge=1024, alias="STRIX_TOOL_OUTPUT_MAX_BYTES"
)
class RuntimeSettings(BaseSettings): class RuntimeSettings(BaseSettings):
+2 -1
View File
@@ -49,7 +49,8 @@ def bound_text(text: str, *, max_lines: int, max_bytes: int) -> str:
byte size). The removed middle is replaced with a notice recording how byte size). The removed middle is replaced with a notice recording how
many lines and bytes were dropped so the agent knows output was elided. many lines and bytes were dropped so the agent knows output was elided.
``max_bytes`` bounds the *entire* joined result, notice and separators ``max_bytes`` bounds the *entire* joined result, notice and separators
included. included, and must be large enough to hold the notice itself (guaranteed by
the ``tool_output_max_bytes`` config floor).
""" """
lines = text.split("\n") lines = text.split("\n")
total_bytes = _byte_len(text) total_bytes = _byte_len(text)
+18 -1
View File
@@ -6,10 +6,11 @@ import json
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pytest import pytest
from pydantic import AliasChoices, Field from pydantic import AliasChoices, Field, ValidationError
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from strix.config import loader from strix.config import loader
from strix.config.settings import ContextSettings
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -120,6 +121,22 @@ def test_read_json_overrides_uses_json_when_no_alias_in_environ(tmp_path: Path)
assert loader._read_json_overrides(path) == {"llm": {"api_key": "sk-file"}} assert loader._read_json_overrides(path) == {"llm": {"api_key": "sk-file"}}
# --------------------------------------------------------------------------- #
# ContextSettings validation
# --------------------------------------------------------------------------- #
def test_tool_output_max_bytes_rejects_sub_notice_values() -> None:
# A ceiling below the truncation notice can't fit a bounded preview, so it
# is rejected at load time rather than producing over-cap persisted output.
with pytest.raises(ValidationError):
ContextSettings(STRIX_TOOL_OUTPUT_MAX_BYTES=64)
def test_tool_output_max_bytes_accepts_floor() -> None:
assert ContextSettings(STRIX_TOOL_OUTPUT_MAX_BYTES=1024).tool_output_max_bytes == 1024
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# _aliases_for # _aliases_for
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #