Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Progressive Disclosure

LLM context windows are finite. Every token spent on output you didn’t need is a token that could have held something useful. codescout is designed around a single principle to address this: show the minimum that is actionable, and reveal detail only when asked.

The Problem

Without guardrails, code intelligence tools can produce enormous output:

ToolWorst case
symbols(dir)Walks the entire project, dumps every symbol in every file
symbols(pattern)Project-wide search with thousands of matches
referencesPopular symbols referenced in hundreds of files
tree(recursive=true)Full directory tree of a large monorepo
run_command("git blame file") (no line range)Every line in a long file

Filling the context window with irrelevant symbols, boilerplate bodies, and off-target files makes it harder to reason about the code you actually care about. It also wastes time: you pay the cost of processing all that output before you can identify what you need.

The Solution: Two Modes

codescout tools operate in one of two modes, controlled by the detail_level parameter.

Exploring mode (the default) produces compact summaries: names, kinds, file paths, and line numbers. Results are capped at 200 items. No function bodies, no full diffs, no deep trees. The goal is to give you a map of the territory so you can identify your target.

Focused mode (detail_level: "full") produces complete detail: function bodies, full symbol trees, entire diffs. Results are paginated via offset and limit (default page size: 50). Use this only after you know what you are looking for.

How OutputGuard Enforces This

Every tool that can produce unbounded output delegates its output control to a shared OutputGuard struct (in src/tools/output.rs). Tools do not implement their own truncation logic; the guard enforces consistent behavior project-wide.

OutputGuard::from_input() reads three optional fields from a tool’s JSON input:

  • detail_level: "full" activates Focused mode; anything else (or absent) gives Exploring mode.
  • offset: where to start in paginated output (default: 0).
  • limit: page size in Focused mode (default: 50). If you pass an explicit limit in Exploring mode, the guard honours it as a cap.

should_include_body() returns true only in Focused mode. Tools use this to decide whether to fetch and include function bodies from the language server.

cap_items() and cap_files() enforce the limits:

  • In Exploring mode: keep the first max_results (or max_files) items, discard the rest, attach an overflow object describing what was omitted.
  • In Focused mode: apply offset/limit pagination, attach an overflow object that includes next_offset when more pages remain.

When results exceed the cap, the overflow object tells you what to do next:

{
  "overflow": {
    "shown": 47,
    "total": 312,
    "hint": "Narrow with a file path or glob pattern"
  }
}

In Focused mode, the overflow includes next_offset for sequential pagination:

{
  "overflow": {
    "shown": 50,
    "total": 312,
    "hint": "Use offset/limit to page through results",
    "next_offset": 50
  }
}

The Pattern: Explore, Identify, Focus

The intended workflow has three steps:

  1. Explore broadly. Use tools in their default Exploring mode to get a compact map of the area you care about.
  2. Identify your target. Read the compact output to find the file, symbol, or range that contains what you need.
  3. Focus narrowly. Switch to Focused mode on exactly that target to get full detail.

Example

You want to understand the authentication logic in a service layer.

Step 1 — get the map:

{ "tool": "symbols", "arguments": { "path": "src/services/" } }

Response (compact, exploring mode):

{
  "files": [
    {
      "file": "src/services/auth.rs",
      "symbols": [
        { "name": "AuthService", "kind": "Struct", "start_line": 12 },
        { "name": "handle_login", "kind": "Function", "start_line": 34 },
        { "name": "verify_token", "kind": "Function", "start_line": 61 },
        { "name": "refresh_session", "kind": "Function", "start_line": 89 }
      ]
    },
    {
      "file": "src/services/user.rs",
      "symbols": [
        { "name": "UserService", "kind": "Struct", "start_line": 8 },
        { "name": "find_by_email", "kind": "Function", "start_line": 22 }
      ]
    }
  ]
}

Four symbols in auth.rs, verify_token looks relevant. Total context consumed: a few dozen tokens.

Step 2 — identify the target: verify_token in src/services/auth.rs.

Step 3 — focus on it:

{
  "tool": "symbols",
  "arguments": {
    "pattern": "verify_token",
    "relative_path": "src/services/auth.rs",
    "include_body": true,
    "detail_level": "full"
  }
}

Now you get the full function body — but only for the one function you identified, not for every symbol in the file.

This workflow minimizes context usage at every step. The broad exploration is cheap; the focused read is exact.

Further Reading

  • Output Modes — the detail_level, offset, and limit parameters in full detail, with examples for every tool
  • Tool Selection — matching your level of knowledge to the right tool, including the anti-patterns that cause context bloat