feat(tools): bound+frame tool results, spill full output to CAS, tool_output retrieval

Tool results injected into model context are now consistently framed and
globally bounded. Success output over the floor (TOOL_RESULT_MAX_CHARS=8000)
is head/tail-truncated with a marker naming a retrieval ref; the full raw
output spills to the artifact store (CAS) and its hash is recorded on the
ToolReceipt (fullOutputHash) in the event log. Agents recover the full text
via the new read-only tool_output(ref=...) tool.

- SessionOrchestrator: frameTruncatedToolResult + renderToolResult; char-cap
  head/tail so a single pathological long line can't defeat the bound.
- ToolReceipt.fullOutputHash (additive, nullable).
- ToolOutputTool (Tier T1, no fs capability) resolves ref -> full bytes.
- Wired via extraTools in Main.kt; tool_output added to ALWAYS_AVAILABLE_READ_TOOLS.
- Failure path keeps ERROR:/FATAL: prefixes (all-rejected breaker dependency).

Tests: FrameTruncatedToolResultTest, ToolOutputToolTest.
This commit is contained in:
2026-07-12 19:46:02 +04:00
parent 3a4e577b5b
commit 2912799fe0
7 changed files with 226 additions and 15 deletions
@@ -66,6 +66,7 @@ data class StageConfig(
companion object {
/** Read-only tools every tool-granting stage may call regardless of its declared set. */
val ALWAYS_AVAILABLE_READ_TOOLS: Set<String> = setOf("file_read", "list_dir", "glob", "grep")
val ALWAYS_AVAILABLE_READ_TOOLS: Set<String> =
setOf("file_read", "list_dir", "glob", "grep", "tool_output")
}
}