CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-05-20Advanced

Compressing Tool Results in Claude Agents — Aggregating Large Responses Without Bloating Context

When a database returns 8,000 rows, a scrape returns 200KB of HTML, or a file read returns several megabytes, dropping the raw payload into your Claude tool result wrecks both cost and quality. This guide presents a three-layer compression architecture — schema projection, summarization, and reference handles — with TypeScript examples from a production agent pipeline.

claude-agent2tool-use22context-management5mcp18production111

Premium Article

What happens when your database tool returns 8,000 rows in a single call, or your web fetch tool drops 200KB of HTML into the conversation? In my own multi-site auto-posting pipeline as an indie developer running six sites alongside an app business that has crossed 50 million cumulative downloads, this was the first wall I hit. A single tool call would consume more than 60,000 tokens, get called three or four times per loop, and saturate the context window before the agent could finish its job. Cost went up, but more painfully, output quality went down.

Tool result compression is a quiet but decisive part of agent design that compounds as you run agents longer. Anthropic's official docs do a great job explaining how to define tools, but the question of how to constrain the return side is essentially left to the implementer. Here I want to share the three-layer architecture I run in production for content generation, content auditing, and revenue analysis agents — with TypeScript samples you can adapt.

Three failure modes that bloated tool results trigger

The reasoning "the context window is bigger now, so raw payloads are fine" is wrong on both cost and quality grounds. Three things degrade at once:

  1. Direct token cost: Claude Sonnet 4.6 at $3 per million input tokens, multiplied by 60,000 tokens × 4 tool calls = $0.72 per request. A pipeline doing 200 such requests per day burns roughly $4,300 per month in pure overhead
  2. Instruction-following degradation: The longer the input, the weaker the relative weight of your system prompt and instructions. In my own runs, the rate of style-guide violations roughly doubles once cumulative tool results pass 30,000 tokens
  3. Cache efficiency collapse: With prompt caching enabled, tool results that change every call destroy your cache-hit rate, dragging it down in proportion to how many tools you call

So compression is not just a cost-optimization concern. It is a reliability-level architectural decision for the whole agent.

The three-layer compression model

The taxonomy I use in production looks like this. The top layer is safest (least information loss), the bottom layer is the most powerful (highest compression ratio). In practice I pick one layer per tool, and sometimes stack two.

Layer 1: Schema projection (return only the fields that matter)

The least lossy form of compression is to never include unnecessary fields. A Stripe Charge object has 50+ fields, but an agent doing spend analysis usually only needs id, amount, currency, created, and description.

async function searchCharges(query: SearchQuery) {
  const charges = await stripe.charges.search({
    query: query.text,
    limit: 100,
  });
 
  // 50+ fields × 100 rows ~= 100k+ tokens → 5 fields × 100 rows ~= 10k tokens
  return {
    data: charges.data.map((c) => ({
      id: c.id,
      amount: c.amount,
      currency: c.currency,
      created: c.created,
      description: c.description,
    })),
    has_more: charges.has_more,
    next_cursor: charges.data[charges.data.length - 1]?.id,
  };
}

Projection alone usually buys 70–90% token reduction. When you write MCP servers that wrap external APIs, ask yourself which fields exist for human dashboard consumption vs. which fields the agent actually uses to decide. That distinction lands you in this layer naturally.

Layer 2: Summarization (have Haiku digest the payload first)

Some tools return free-form text that schema projection cannot squeeze. Web search, file reads, log retrieval — all of these. The pattern that works best for me is a cascade where Haiku compresses the payload before it ever reaches the Sonnet/Opus loop.

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
 
async function fetchAndSummarize(url: string, focus: string) {
  const html = await fetch(url).then((r) => r.text());
  const text = stripHtml(html).slice(0, 50_000); // about 25,000 tokens
 
  // 5,000-token input → 400-token summary
  const summary = await client.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 400,
    system: `Summarize the following web page. Focus angle: ${focus}.
Facts only. No speculation. Keep all important numbers verbatim.`,
    messages: [{ role: "user", content: text }],
  });
 
  return {
    url,
    focus,
    summary: summary.content[0].type === "text" ? summary.content[0].text : "",
    original_length: text.length,
  };
}

The critical detail is passing the focus as a tool parameter. When Claude itself declares what it wants to know at the moment of calling, Haiku produces summaries aligned with the caller's intent, which dramatically reduces follow-up turns.

This mirrors a pattern Dolice (my indie app studio operating since 2014) has used on the ad-monetization side: a cheap layer screens, an expensive layer commits. The same economic logic applies to agent design — a Haiku cascade in front of Sonnet/Opus is the AdMob mediation of LLM orchestration.

Layer 3: Reference handles (return only an ID, not the data)

When the agent doesn't need to read the payload, only act on it, the strongest move is to make the tool return "saved, here is the handle" and let Claude drill down via separate tools.

const blobStore = new Map<string, unknown>();
 
async function executeQuery(sql: string) {
  const rows = await db.query(sql);
  const handle = `query-${crypto.randomUUID()}`;
  blobStore.set(handle, rows);
 
  // Don't return thousands of rows; return the handle plus a tiny preview
  return {
    handle,
    row_count: rows.length,
    sample: rows.slice(0, 3),
    columns: rows[0] ? Object.keys(rows[0]) : [],
    note: "use `query_result_aggregate` or `query_result_filter` with the handle to drill down",
  };
}
 
async function queryResultAggregate(handle: string, groupBy: string, agg: string) {
  const rows = (blobStore.get(handle) ?? []) as Record<string, unknown>[];
  return aggregate(rows, groupBy, agg);
}

The reference-handle pattern positions Claude as an analyst issuing aggregation directives rather than someone scrolling the raw data. In production I back blobStore with Cloudflare KV or Durable Objects with a one-hour TTL.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Quantify the three failure modes that bloated tool results cause — token cost, instruction-following degradation, and cache breakdown
Adopt a three-layer compression model (projection / summarization / reference handle) with TypeScript code you can paste in
Walk away with rules of thumb for choosing the right compression layer per tool, plus production metrics to track
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-06-29
When Context Editing Made My Agent Re-run the Same Search — Field Notes on Clear Boundaries and Cache Invalidation
After turning on Context Editing to auto-clear tool results, the agent forgot what it had just read, re-ran the same tool, and the cache rebuilt every turn so costs went up. Field notes on instrumenting the silent regression and setting trigger, keep, and clear_at_least from measured data.
API & SDK2026-06-22
Putting a Ceiling on the pause_turn Loop: Running Long Server Tools Safely Unattended
A production design for continuing pause_turn safely in unattended runs, where long server tools like web_search and code execution are involved. Covers branching all four stop_reason values in one loop, capping continuations and wall-clock time, and accumulating usage across paused segments.
API & SDK2026-06-21
Don't Carry Search Results Twice: Trimming Consumed Blocks with response_inclusion
When an agent runs dynamic filtering, output tokens balloon because the raw search-result blocks a code execution call already consumed get echoed back into the response. Here is when response_inclusion: excluded is safe to use, when you must keep full, with implementation and a decision table.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →