> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Result Management

> Pairing, budgeting, progressive microcompaction, and disk persistence for tool results

ChatCLI implements a comprehensive **tool result management** system that ensures integrity, controls context size, and progressively compacts old results. This is essential for long agent sessions where dozens of tool calls can saturate the context window.

***

## Tool Result Pairing

Every `tool_use` (a tool call made by the model) must have a corresponding `tool_result` in the conversation history. When this pairing breaks -- due to interruption, timeout, or silent error -- the API rejects the history.

The `EnsureToolResultPairing` system automatically validates and repairs:

| Problem | Repair Action |
| :- | :- |
| `tool_use` without `tool_result` (orphan) | Injects synthetic error result |
| `tool_result` without `tool_use` (orphan) | Removes from history |
| Duplicate `tool_use` IDs | Keeps only the first occurrence |

### Synthetic Results

When a tool\_use has no corresponding result, ChatCLI injects:

```text theme={"system"}
[Tool result missing -- the tool execution was interrupted or failed silently.
Do NOT retry this tool call. Analyze what went wrong and try a different approach.]
```

<Warning>
  The message instructs the model to **not repeat** the failed tool call, preventing infinite retry loops.
</Warning>

### 3-Phase Validation

<Steps>
  <Step title="ID Collection">
    Traverses the entire history collecting `tool_use` IDs (from assistant messages) and `tool_result` IDs (from tool messages).
  </Step>

  <Step title="Misalignment Detection">
    Compares the two sets of IDs. Tool uses without a result are "missing". Tool results without a use are "orphans". Duplicate IDs are flagged for deduplication.
  </Step>

  <Step title="History Reconstruction">
    Rebuilds the history: removes orphans, deduplicates tool\_use IDs, and injects synthetic results after assistant messages with unmatched tool\_uses.
  </Step>
</Steps>

***

## Result Budget Enforcement

Tool results such as large file reads or command output can quickly consume the context window. The budget system limits the aggregate size at **three** levels:

| Level | Limit | Variable / Override | Default |
| :- | :- | :- | :- |
| **Per-tool** (new) | Cap applied inside the dispatch before aggregation | `TruncationAware.MaxResultChars()` capability per plugin | 30,000 chars (global) |
| **Per result** | Maximum size of a single result | `CHATCLI_TOOL_RESULT_MAX_CHARS` | 20,000 chars |
| **Per turn** | Aggregate size of all results in the turn | `CHATCLI_TOOL_RESULT_BUDGET_CHARS` | 200,000 chars |

### Per-tool truncation (capability)

Plugins that implement `plugins.TruncationAware` declare their own cap — useful when the tool has non-default context needs:

| Plugin | Cap | Rationale |
| - | - | - |
| `@read` | 80,000 | Large files (\~1500 lines) are the primary code-learning surface; a low cap blinds the model |
| `@search` | 60,000 | Breadth-oriented structured output (file:line:match) — the model needs reach |
| `@tree` | 50,000 | Monorepo listings easily exceed 30k |
| Other plugins | 30,000 | Global default |

Truncation preserves the historical head/tail shape (5000-char preview + 1000-char suffix + `[TRUNCATED N chars omitted, M kept]` marker).

### How Enforcement Works

The budget is applied in two passes:

<Tabs>
  <Tab title="Pass 1: Per Result">
    Each individual result is checked against `DefaultPerResultMaxChars` (20KB). If it exceeds the limit, the full content is saved to disk and replaced with a preview:

    ```text theme={"system"}
    [first 4,000 chars of the result]

    ... [85,432 chars omitted -- full output saved to /tmp/chatcli-tool-results/budget_tc_1_3f9a1c02d7e4b5a6.txt]

    [last 1,000 chars of the result]
    ```
  </Tab>

  <Tab title="Pass 2: Per Turn">
    If the total of all results in the turn exceeds `DefaultTurnBudgetChars` (200KB), the largest results are progressively truncated (from largest to smallest) until the turn fits within the budget.
  </Tab>
</Tabs>

### Disk Persistence

Truncated results are saved as temporary files **inside the [Session Workspace](/context/session-workspace)**, instead of the legacy global `/tmp/chatcli-tool-results/`:

```text theme={"system"}
$TMPDIR/chatcli-agent-<random>/tool-results/
  budget_tc_1_3f9a1c02d7e4b5a6.txt    # Full result from tool call 1
  budget_tc_2_8b04de11c6f2a9d3.txt    # Full result from tool call 2
  result_read_3.txt      # Secondary storage (workers package)
```

Moving to a per-session directory has two important effects:

1. **Isolation between sessions.** Multiple `chatcli` instances running in parallel on the same host no longer share the overflow pool.
2. **On-demand reads by the agent.** The scratch dir is on the agent's read allowlist, so when the model encounters the `[full output saved to ...]` marker in the preview, it can open the file with `read_file`:

```xml theme={"system"}
<tool_call name="@coder" args='{"cmd":"read","args":{"file":"/tmp/chatcli-agent-Xy7K3a/tool-results/budget_tc_3_5c71e0aa92bd4f18.txt","start":1200,"end":1500}}' />
```

Before this release, the path was a dead end — the read tool blocked it because it was outside the workspace boundary. The budget "saved the output" but the agent had no way to access it.

<Tip>
  Files are automatically cleaned up when the session ends (`ChatCLI.cleanup`), respecting `CHATCLI_AGENT_KEEP_TMPDIR=true` for debugging. A periodic global cleanup is no longer required.
</Tip>

### Preview: Head + Tail

The preview retains the beginning and end of the result to maximize usefulness:

| Component | Size |
| :- | :- |
| Head (beginning) | 4,000 chars (cuts at the last line break) |
| Reference | File path on disk |
| Tail (end) | 1,000 chars (cuts at the first line break) |

***

## Progressive Microcompaction

Microcompaction progressively reduces the size of old tool results as the conversation advances, without losing critical information:

| Result Age | Action | Details |
| :- | :- | :- |
| Current and previous turn | No change | Results preserved in full |
| 2+ turns ago | Truncated | Head (2,000 chars) + tail (500 chars) |
| 4+ turns ago | Summarized | One-line description: `[Old tool result cleared -- 450 lines, 28K chars, Go source]` |

With the [compression layer](/context/context-compression) active, both levels are **lossless**: the original result is archived in the CCR store before the cut and the preview/summary carries a `<<ccr:KEY>>` marker (preserved across levels) that the model expands with `@recall`.

### Content Type Detection

The summary automatically identifies the content type for context:

| Content | Detected Type |
| :- | :- |
| Starts with `{` or `[` | JSON |
| Contains `package ` | Go source |
| Contains `def ` | Python source |
| Contains `function ` | JavaScript source |
| Starts with `diff ` or `---` | diff |
| Starts with `commit ` | git log |
| Other | text |

### Microcompaction Configuration

| Variable | Description | Default |
| :- | :- | :- |
| `CHATCLI_MICROCOMPACT_TRUNCATE_TURNS` | Turns before truncating | 2 |
| `CHATCLI_MICROCOMPACT_SUMMARIZE_TURNS` | Turns before summarizing | 4 |
| `CHATCLI_HISTORY_REWRITE_PRESSURE` | Share of the compaction budget the history must reach before the age-based rewrites run against a warm prefix cache (`0` = every turn, as before) | 0.75 |

<Info>
  Only results larger than 3,000 chars are compacted. Small results are always preserved. Results from write and execution tools are preserved as they contain critical error information.
</Info>

### Cache-Aware Scheduling

Every rewrite of an old message invalidates the provider's cached prefix from that message onward: on Anthropic and Bedrock the whole tail is billed again as a cache write (1.25x or 2x the input price), on OpenAI, Gemini, xAI, Kimi and the other providers with a prompt cache it is billed at full price instead of the cached discount. Shaving a few hundred tokens off a result that is already served from cache does not pay for that.

So microcompaction, repeated-read dedup and skill aging are scheduled against the cache, on every provider:

* While the prefix cache is **warm** and the history is under `CHATCLI_HISTORY_REWRITE_PRESSURE` of the compaction budget, the passes wait. The old results keep riding as cheap cache reads.
* Once the history reaches that share, the passes run: shrinking it now is what keeps the far more expensive summarizing compaction away.
* With no warm prefix to protect (a provider that reports no cache, a cache that expired, the first request) the passes run every turn as they always did.

The same gate applies to squad, taskgraph and delegate workers. Set `CHATCLI_HISTORY_REWRITE_PRESSURE=0` to restore the age-only behavior.

***

## Complete Flow

Tool result management is applied in this order during the agent loop:

```text theme={"system"}
1. Tool executes and returns result
2. EnsureToolResultPairing -> fixes misalignments
3. EnforceToolResultBudget -> truncates large results
4. ApplyMicrocompact -> compacts old results
5. Clean history sent to the API
```

***

## Complete Configuration

| Environment Variable | Description | Default |
| :- | :- | :- |
| `CHATCLI_TOOL_RESULT_BUDGET_CHARS` | Aggregate budget per turn | 200,000 |
| `CHATCLI_TOOL_RESULT_MAX_CHARS` | Maximum size per result | 20,000 |
| `CHATCLI_MICROCOMPACT_TRUNCATE_TURNS` | Turns before truncation starts | 2 |
| `CHATCLI_MICROCOMPACT_SUMMARIZE_TURNS` | Turns before summarization starts | 4 |
| `CHATCLI_HISTORY_REWRITE_PRESSURE` | Share of the compaction budget before the rewrites run against a warm cache (`0` = every turn) | 0.75 |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Session Workspace" icon="folder-tree" href="/context/session-workspace">
    Where overflow files live and how the agent reads them.
  </Card>

  <Card title="Subagent Delegation" icon="diagram-project" href="/agents/subagent-delegation">
    Complementary strategy to avoid saturating context with raw data.
  </Card>

  <Card title="Context Recovery" icon="life-ring" href="/context/context-recovery">
    What happens when even with budgeting the context overflows.
  </Card>

  <Card title="Cost Tracking" icon="money-bill-trend-up" href="/providers/cost-tracking">
    Monitor token consumption including tool results.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.