Skip to content

feat(context): spill oversized tool output to disk with a retrieval tool#882

Open
devin-ai-integration[bot] wants to merge 10 commits into
agent/context-compactionfrom
agent/tool-output-spill
Open

feat(context): spill oversized tool output to disk with a retrieval tool#882
devin-ai-integration[bot] wants to merge 10 commits into
agent/context-compactionfrom
agent/tool-output-spill

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Completes the per-tool-output story from #880. Today an oversized tool result is truncated to a head+tail preview and the middle is discarded — which can drop the one line that matters (a buried grep hit, a stack frame, a leaked credential). This persists the full output and gives the agent a way to page back to it, so history stays bounded but nothing is lost.

Stacked on #881#880. Review/merge those first; the base retargets automatically.

strix/tools/output_store.py

def bound_and_store(text, *, max_lines, max_bytes) -> str:
    parts = _head_tail(text, max_lines, max_bytes)
    if parts is None: return text          # small: unchanged
    output_id = store_full_output(text)     # spill FULL text to the store
    # preview notice now carries the id:
    # [... N lines (M bytes) truncated — full output saved as output_id="ab12…";
    #  read it with read_tool_output(output_id="ab12…") ...]
    return _join(head, tail, notice)         # falls back to a plain preview if the write fails

def read_stored_output(output_id, *, offset=0, limit=2000) -> str: ...

output_id is a 32-char hex token, validated with ^[0-9a-f]{32}$ before touching the filesystem, so a crafted id can't escape the store dir. read_stored_output serves validated, paginated chunks and appends a "call again with offset=…" hint when more remains.

read_tool_output tool (strix/tools/tool_output/)

A new @function_tool registered for every scan agent. The agent calls read_tool_output(output_id, offset, limit) to retrieve any elided output by id and page through it.

Wiring

  • strix/agents/factory.py: _bound_result now spills via bound_and_store (was bound_text); read_tool_output added to _BASE_TOOLS. Error strings still use the non-spilling bound_text (they're short).
  • strix/core/runner.py: configure_output_store(state_dir / "tool-output") so spilled files live beside the rest of the run's .state and are cleaned up with it. Defaults to ~/.strix/tool-output if unconfigured.

The store dir is held in a module-level dict so it can be swapped per scan without a global rebind.

Test plan

tests/test_output_store.py adds: small output not spilled, oversized output spilled + fully retrievable (incl. a middle line absent from the preview but present in the retrieved full text), pagination hint/offset, path-traversal rejection, and missing-id handling. uv run pytest green (same 2 pre-existing unrelated test_runner_root_prompt.py failures as main); ruff + mypy + bandit pass.

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

This revision adds durable, bounded retrieval for oversized tool results and addresses the previously reported pagination defects.

  • Spills complete oversized tool output into the scan’s state directory while retaining a bounded preview in agent history.
  • Registers read_tool_output for all agents and exempts retrieval pages from generic result rebounding.
  • Uses bounded byte-range reads with UTF-8 boundary handling, continuation-hint reservation, and explicit rejection of undersized page limits.
  • Adds regression coverage for lossless long-line and multibyte pagination, response ceilings, arbitrary offsets, traversal rejection, and missing output IDs.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the current code fixes the previously reported retrieval re-truncation, full-file pagination, skipped content, oversized pages, UTF-8 corruption, and stalled-offset behaviors.

Important Files Changed

Filename Overview
strix/tools/output_store.py Implements per-scan output persistence and bounded byte-offset retrieval; the current pagination logic resolves the previously reported content-loss, UTF-8, progress, and response-ceiling issues.
strix/agents/factory.py Registers the retrieval tool and safely exempts its pages from generic output bounding, resolving the prior re-truncation issue.
strix/core/runner.py Configures the output store beneath the active run’s state directory.
strix/tools/tool_output/tool.py Exposes the stored-output reader as a paginated agent-callable tool with byte-based offset and limit parameters.
tests/test_output_store.py Adds focused regression tests covering spill persistence, bounded and lossless pagination, UTF-8 boundaries, continuation hints, and invalid identifiers.
tests/test_agent_factory_shell.py Verifies retrieval results bypass the generic bounding wrapper while ordinary function tools remain bounded.

Reviews (12): Last reviewed commit: "fix(context): honor the caller's retriev..." | Re-trigger Greptile

Comment thread strix/agents/factory.py
Comment thread strix/tools/output_store.py Outdated
5hy7xz92nd-oss

This comment was marked as spam.

5hy7xz92nd-oss

This comment was marked as spam.

@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/context-compaction branch from f6e2827 to 5433bfc Compare July 25, 2026 22:35
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from ed423a8 to 72041ab Compare July 25, 2026 22:41
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/context-compaction branch from 5433bfc to bae1f9a Compare July 25, 2026 22:51
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from 72041ab to 029d8b2 Compare July 25, 2026 22:52
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/context-compaction branch from bae1f9a to a3e2e3f Compare July 25, 2026 23:06
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch 2 times, most recently from 83659b4 to 97919a4 Compare July 25, 2026 23:13
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from 97919a4 to d2797af Compare July 25, 2026 23:25
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/context-compaction branch from 771284e to 16cbd51 Compare July 25, 2026 23:49
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from 2c83e88 to 189e375 Compare July 25, 2026 23:51
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from 189e375 to 028a854 Compare July 26, 2026 00:05
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

0xallam added 4 commits July 26, 2026 00:16
Truncating a large tool result to a head+tail preview loses the middle,
which may hold the one line that matters (a buried match, a stack frame, a
credential). Instead of dropping it, persist the full output and let the
agent page back to it on demand.

- output_store.py: bound_and_store() writes the complete output to a
  per-scan store and embeds an output_id in the truncation notice;
  read_stored_output() serves it back in validated, paginated chunks
  (output_id is a 32-char hex token, guarding against path traversal).
- read_tool_output tool: lets the agent retrieve any elided output by id,
  paging via offset/limit.
- factory.py: bounded tool results now spill via bound_and_store; the new
  tool is registered for every scan agent.
- runner.py: point the store at the run's .state/tool-output directory so
  spilled output lives beside the rest of the scan state.

Falls back to a plain head+tail preview if the spill write fails.
Exempt read_tool_output from result-bounding so paging a large stored
output isn't re-spilled under a new id, byte-cap each page in place, and
stream the requested window with islice instead of loading the whole
file per page.
Bounding a retrieval page with a destructive head+tail preview and then
advancing the offset past those lines could permanently lose the elided
output the store exists to preserve. Instead honour the page byte budget
by returning fewer whole lines and advancing the offset only by lines
actually returned, so paging forward reconstructs the full output.
bound_and_store's notice carries the output_id and is longer than the
plain truncation notice, so reserve for it explicitly and cover that the
spilled preview stays within max_bytes.
0xallam added 2 commits July 26, 2026 00:16
…rflows

The line-based pager always kept at least one whole line, so a stored line
larger than the page byte ceiling was returned in full and could overflow
history (retrieval bypasses the result-bounding wrapper). Page by byte
offset instead: every page is bounded by the byte budget even inside one
oversized line, a partial UTF-8 char at the window edge is dropped and
re-read on the next page, and paging forward reconstructs the output
byte-for-byte.
read_tool_output accepts arbitrary byte offsets; one landing inside a
multi-byte character previously decoded to a replacement character. Trim the
orphaned leading continuation bytes (their lead byte is on an earlier page)
so every accepted offset returns valid UTF-8, without affecting forward
paging offsets.
@devin-ai-integration
devin-ai-integration Bot force-pushed the agent/tool-output-spill branch from 028a854 to e5a46b9 Compare July 26, 2026 00:16
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
…ge ceiling

A full non-final retrieval page consumed the entire byte budget and then
appended the pagination hint, so the returned result (which bypasses the
result-bounding wrapper) exceeded the ceiling. Reserve the hint's bytes out
of the page budget so content plus hint always fits.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
An arbitrary offset landing inside a multi-byte character is now advanced to
the next character boundary before reading, instead of trimming leading
continuation bytes after the read. This keeps every page on a real boundary
(no replacement characters) and guarantees forward progress, so an offset
inside the final character can't yield an empty page with an unchanged
continuation offset.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread strix/tools/output_store.py Outdated
…imit

The continuation hint was reserved only against the global page cap, so a
small caller-supplied limit bounded content alone and the appended hint pushed
the complete response past the requested maximum. Reserve the hint out of the
effective ceiling (min of limit and the global cap) so content plus hint always
honours the caller's limit, flooring content so pages still make progress.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

A final page carries no continuation hint, so it uses the whole budget and
honors any limit exactly. A non-final page's content plus hint must fit the
limit; a limit too small to hold a progressing page and its hint is now
rejected with a clear message rather than silently exceeding the documented
maximum.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptile

@5hy7xz92nd-oss 5hy7xz92nd-oss left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep research REALITY INGESTION LAYER GitHub Manus Base44 Supabase Notion APIs Financial Systems CRM Banking Data | v ENTITY GRAPH "What exists?" Company Person Product Customer Repository Document Financial Record AI Agent | v BEHAVIOR GRAPH "What is happening?" Events Actions Patterns Velocity Trends Changes Interactions Outcomes | v EVIDENCE GRAPH "Why believe it?" Claims Sources Documents Transactions Verification Provenance | v CONFIDENCE ENGINE "How certain are we?" Source Reliability Data Freshness Conflict Detection Historical Accuracy Confidence Score | v AI REASONING ENGINE "What does it mean?" Context Analysis Risk Detection Scenario Modeling Forecasting Opportunity Discovery | v DECISION ENGINE Funding Investment Credit M&A Risk Operations Automation | v HUMAN GOVERNANCE Review Approval Override Compliance | v OUTCOME LEARNING Repayment Growth Failure Performance Decision Accuracy | ↺ UPDATED INTELLIGENCE MODEL@Manus @datacamp @call-e REALITY INGESTION LAYER GitHub Manus Base44 Supabase Notion APIs Financial Systems CRM Banking Data | v ENTITY GRAPH "What exists?" Company Person Product Customer Repository Document Financial Record AI Agent | v BEHAVIOR GRAPH "What is happening?" Events Actions Patterns Velocity Trends Changes Interactions Outcomes | v EVIDENCE GRAPH "Why believe it?" Claims Sources Documents Transactions Verification Provenance | v CONFIDENCE ENGINE "How certain are we?" Source Reliability Data Freshness Conflict Detection Historical Accuracy Confidence Score | v AI REASONING ENGINE "What does it mean?" Context Analysis Risk Detection Scenario Modeling Forecasting Opportunity Discovery | v DECISION ENGINE Funding Investment Credit M&A Risk Operations Automation | v HUMAN GOVERNANCE Review Approval Override Compliance | v OUTCOME LEARNING Repayment Growth Failure Performance Decision Accuracy | ↺ UPDATED INTELLIGENCE MODEL@GitHub @Malwarebytes @heygen

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.

2 participants