cyberine
UsageRun recipes

Oversized tool results — spill-to-disk + retrieval (spill_retrieve)

Every tool call an agent adapter makes (LoopAgentAdapter, AnthropicAgentAdapter, BedrockAdapter, VertexAdapter — including MCP tools consumed via McpToolAdapter, which previously had no cap at all) is funneled through one dispatch pipeline (src/core/toolDispatch.ts). A registered post-execute policy (src/tools/spillPolicy.ts) measures every result in UTF-8 bytes; a result over config.spillByteBudget (default 30000 bytes, CYBERINE_SPILL_BYTE_BUDGET) is written in full under the run's own directory (.local/runs/<runId>/spill/, never the worker's disposable worktree) and replaced with a bounded head+tail preview plus a spill://<runId>/<file> locator:

[run_bash result spilled: 812004 bytes exceeds the 30000-byte budget. Showing head+tail below;
call spill_retrieve with locator "spill://<runId>/run_bash-....txt" to read the full text (or a
byte range of it).]
--- head (first 6000 bytes) ---
...
--- tail (last 3000 bytes) ---
...

The worker reads it back with the spill_retrieve tool (registered in both the implementer and read-only toolsets), optionally slicing by byte offset/length instead of pulling the whole thing. run_bash, code_execution, fetch_url, and delegate_subtask no longer truncate their own output internally — they hand the FULL result to this one coordinated policy instead of each discarding its own tail at a separate hardcoded size. read_file is excluded (it is already line-windowed, so spilling it would set up a read → spill → retrieve(=read again) loop for no benefit).

Known Limitations and Deferred Work (src/tools/spillPolicy.ts, src/core/spillStore.ts). A tool call made outside the workflow engine (a standalone script, Orchestrator.dispatch without Task.runId set) still spills correctly but lands under an ad-hoc _adhoc bucket instead of a real run directory, since there is no run to scope it to. Spilled files are never swept — they live as long as the run directory does; a very long-running fleet that dispatches many oversized results will accumulate spill files under .local/runs/<runId>/spill/ until the run directory itself is cleaned up by whatever process manages .local/runs retention. grep_files / glob_files / list_dir keep their own independent count-based caps (max_results / MAX_ENTRIES) rather than being converted to this byte-based policy — they are still covered as a backstop (an oversized result from any of them is spilled too), but a worker that legitimately needs more than 200 matches still has to narrow its query rather than paginate via retrieval.