Skip to content

feat(context): bound per-tool output before it enters agent history - #880

Merged
0xallam merged 6 commits into
mainfrom
agent/tool-output-bounding
Jul 26, 2026
Merged

feat(context): bound per-tool output before it enters agent history#880
0xallam merged 6 commits into
mainfrom
agent/tool-output-bounding

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Restores the pre-v1 per-tool-output truncation that was lost in the OpenAI-Agents-SDK migration. Today a single verbose tool result (a recursive find, a noisy scanner, a full page dump) is appended raw to the SQLiteSession and replayed on every subsequent turn — pinning the conversation near the model's context window for the rest of the scan (and, once the window is exceeded, causing a hard failure since there's no compaction). This PR caps each result before it enters history.

This is layer 1 of a two-layer context-management effort; a follow-up will add provider-agnostic conversation compaction (the summarizer removed in 369fa56). This PR is independently useful and low-risk: caps are generous, so normal outputs are untouched.

Three parts:

1. New ContextSettings config group (strix/config/settings.py) — all env-tunable, with headroom for the upcoming compaction layer:

tool_output_max_tokens = 8_000   # STRIX_TOOL_OUTPUT_MAX_TOKENS  (SDK shell cap)
tool_output_max_lines  = 2_000   # STRIX_TOOL_OUTPUT_MAX_LINES
tool_output_max_bytes  = 50*1024 # STRIX_TOOL_OUTPUT_MAX_BYTES
# (+ auto_compact / buffer / keep / fallback / summary knobs reserved for the follow-up)

2. Default the SDK shell tools' own cap (factory.py). exec_command / write_stdin already accept max_output_tokens and truncate head+tail internally, but Strix never set it (⇒ None ⇒ unbounded). We now inject the configured default when the model omits it, and respect an explicit model-supplied value:

def _apply_shell_output_cap(parsed):
    if parsed.get("max_output_tokens") is None:
        parsed["max_output_tokens"] = load_settings().context.tool_output_max_tokens

3. Bound Strix's own tool results (strix/tools/output_store.py + factory.py). Every FunctionTool/CustomTool result (and capped error strings) passes through bound_text, which returns the text unchanged when small, else a head+tail preview joined by a notice recording what was dropped:

<first ~half of the lines/bytes>

[... 1234 lines (56789 bytes) truncated ...]

<last ~half of the lines/bytes>

Truncation triggers on whichever limit (line count or UTF-8 byte size) is hit first, and the byte split is multibyte-safe (never cuts a character in half). The base-tool wrapper is idempotent (base tools are shared singletons reused across every agent build).

Test plan

  • New tests/test_output_store.py: passthrough for small output, head+tail on line limit, byte-limit enforcement on a single long line, multibyte safety, dropped-count notice.
  • tests/test_agent_factory_shell.py: asserts the injected max_output_tokens default and that an explicit value is preserved.
  • uv run pytest green (2 pre-existing unrelated failures in test_runner_root_prompt.py exist on main too — a broken SimpleNamespace settings mock). ruff + mypy + bandit pass via pre-commit.

Link to Devin session: https://app.devin.ai/sessions/3461e7dce476402aa2091e4af3f55c00
Requested by: @0xallam

@0xallam 0xallam self-assigned this Jul 25, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR restores bounded per-tool output before results enter persistent agent history.

  • Adds validated, environment-configurable context and tool-output limits.
  • Clamps SDK shell output tokens and bounds FunctionTool and native CustomTool results.
  • Preserves head and tail output within the configured byte ceiling and reports dropped content.
  • Adds coverage for shell caps, native filesystem tools, configuration validation, byte safety, and truncation accounting.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
strix/agents/factory.py Applies configured output bounds across shell, FunctionTool, and native filesystem CustomTool paths; the previously reported bypasses are addressed.
strix/config/settings.py Introduces context-management settings with positive-value validation and a safe minimum byte ceiling.
strix/tools/output_store.py Implements UTF-8-safe head-and-tail truncation while accounting for metadata within the byte cap and reporting final retained-line counts.
tests/test_agent_factory_shell.py Covers default and oversized shell caps plus native and chat-completions filesystem tool handling.
tests/test_config_loader.py Verifies rejection of byte ceilings too small to contain truncation metadata.
tests/test_output_store.py Covers passthrough, line and byte truncation, multibyte safety, byte-cap enforcement, and dropped-line accounting.

Reviews (6): Last reviewed commit: "fix(context): reject tool-output byte ce..." | Re-trigger Greptile

Comment thread strix/agents/factory.py Outdated
Comment thread strix/config/settings.py Outdated
Comment thread strix/tools/output_store.py Outdated
0xallam added 2 commits July 25, 2026 22:31
Cap the size of every tool result so a single verbose command (recursive
find, noisy scanner, full page dump) can't pin the conversation near the
model's context window for the rest of a scan.

- New ContextSettings config group with env-tunable caps.
- Default the SDK shell tools' max_output_tokens so exec_command /
  write_stdin truncate head+tail instead of returning unbounded output.
- Bound Strix's own FunctionTool/CustomTool results (line + UTF-8 byte
  head+tail preview with a truncation notice) and cap error strings.
…ines

Treat tool_output_max_tokens as a ceiling so an explicit model-supplied
cap can't exceed it, and derive the truncation notice's dropped-line
count from the lines actually kept after the byte-trim pass. Also cast
the pygments fallback lexer so it satisfies the resolve_lexer return
type under the pre-commit mypy hook.
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-bounding branch from 2b43ccf to dab93bc Compare July 25, 2026 22:33
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/agents/factory.py
Chat-completions mode converts filesystem CustomTools to FunctionTools
(which bounds their result), but the Responses-API path kept them native
and unbounded, so a large read_file could still exhaust the context
window. Always configure the Filesystem capability to head+tail bound
tool output in both modes.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py
The head+tail slices could each take half of max_bytes, then the
truncation notice and its separators were appended on top, so the value
persisted to history could exceed the configured maximum. Reserve an
upper bound for the notice (and separators) out of the byte budget before
slicing so the whole joined result stays within max_bytes.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

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.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

@0xallam
0xallam merged commit 74f334c into main Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant