liveBook · Manning

Compaction

As explained in Build an AI Agent (From Scratch)

A compaction strategy replaces processed or externally stored content in an agent's context with lightweight references that permit later retrieval.

Compaction keeps an agent’s context small by replacing large, already-processed tool data with lightweight references. The agent receives the minimum context needed to continue and can call a tool again if it needs the original data. This is an in-run context-management technique, not a way to preserve every detail of a conversation. section 6.2.4 section 6.1.3

Compact both sides of a tool interaction:

  • For a write operation such as create_file, replace large content in the ToolCall arguments after it has been saved. Keep a reference indicating that the content is in the file. section 6.2.4
  • For a read operation such as read_file or search_web, replace the ToolResult body after the agent has processed it. Keep enough information—such as a file path or query—to retrieve it again. section 6.2.4

An implementation can walk the request contents once. Record each ToolCall’s arguments under its call ID; because the call precedes its result, those arguments are available when compacting the matching ToolResult. Apply rules by tool name so the replacement is predictable rather than an LLM-generated summary. section 6.2.4 section 6.2.5

Listing 6.5 Compaction-strategy implementation
# Tools to compress ToolCall arguments
TOOLCALL_COMPACTION_RULES = {
    "create_file": "[Content saved to file]",
}

# Tools to compress ToolResult content
TOOLRESULT_COMPACTION_RULES = {
    "read_file": "File content from {file_path}. Re-read if needed.",
    "search_web": "Search results processed. Query: {query}. Re-search if needed.",
    "tavily_search": "Search results processed. Query: {query}. Re-search if needed.",
}

def apply_compaction(context: ExecutionContext, request: LlmRequest) -> None:
    """Compress tool calls and results into reference messages"""

    tool_call_args: Dict[str, Dict] = {}
    compacted = []

    for item in request.contents:
        if isinstance(item, ToolCall):
            # Save arguments (for use when compressing ToolResult later)
            tool_call_args[item.tool_call_id] = item.arguments

            # If the ToolCall itself is a compression target (create_file, etc.)
            if item.name in TOOLCALL_COMPACTION_RULES:
                compressed_args = {
                    k: TOOLCALL_COMPACTION_RULES[item.name] if k == "content" else v
                    for k, v in item.arguments.items()
                }
                compacted.append(ToolCall(
                    tool_call_id=item.tool_call_id,
                    name=item.name,
                    arguments=compressed_args
                ))
            else:
                compacted.append(item)

        elif isinstance(item, ToolResult):
            # If ToolResult is a compression target (read_file, search_web, etc.)
            if item.name in TOOLRESULT_COMPACTION_RULES:
                args = tool_call_args.get(item.tool_call_id, {})
                template = TOOLRESULT_COMPACTION_RULES[item.name]
                compressed_content = template.format(
                    file_path=args.get("file_path", args.get("path", "unknown")),
                    query=args.get("query", "unknown")
                )
                compacted.append(ToolResult(
                    tool_call_id=item.tool_call_id,
                    name=item.name,
                    status=item.status,
                    content=[compressed_content]
                ))
            else:
                compacted.append(item)
        else:
            compacted.append(item)

    request.contents = compacted

The implementation defines separate replacement rules for ToolCalls and ToolResults, then tracks call arguments while traversing the request. section 6.2.4

A report followed by a search

An agent saves a 10,000-character report with create_file, then processes search results. Instead of carrying the report body and full results into later requests, it retains a file reference and a query reference. If either source becomes necessary again, it calls the corresponding tool. section 6.2.4

Do not compact merely because a replacement is possible. Re-fetching can add fees and latency, and a repeated web search may return different results. Changing an earlier message can also make subsequent prompt content miss the cache. A practical optimizer counts tokens first, skips changes below a threshold, compacts when needed, and uses summarization only if the request remains too large. Unlike compaction, summarization cannot restore discarded reasoning by calling a tool. section 6.2.4 section 6.2.6 section 6.2.5

How Build an AI Agent (From Scratch) covers it

How deep it goes
Implements tool-name rules for ToolCalls and ToolResults, then places compaction behind a token threshold; defers lengthy conversation history to summarization.
Examples
Uses a large report saved with create_file, file reads, web searches, and Python replacement rules.
What it assumes
Assumes familiarity with agent tool calls and requests; explains the replacement strategy and its Python implementation.
What you can do after
Build it from scratch as a rule-based request transformation and place it before summarization in a threshold-driven optimizer.
How it connects
Connects context management and prompt caching with tool calls, tool results, token counting, and summarization.

Also explained in

Compare how 5 books explain compaction →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build an AI Agent (From Scratch). The section links point to the parts of the book it draws on.