Skeleton Compression: Read Files for Fewer Tokens

Skeleton compression hands an AI coding agent a file's structure — signatures, types, exports — instead of every line. Same understanding, a fraction of the tokens.

Profile photo of Paul Irolla

By Paul Irolla

Founder · AI & developer tools · Tokenade

Ph.D. in AI · builds token-optimization tooling for AI coding agents

View author page
9 min read
Cite this page

What is skeleton compression for file reads?

Skeleton compression is a way of reading a source file that hands the agent the file's structure — its imports, type definitions, class and function signatures, exported symbols — while dropping the function bodies that the agent doesn't need yet. You get the map of the file for a fraction of the tokens, and the agent expands a specific function body only when it actually has to edit it. I build token-reduction tooling for a living, and of all the levers I've measured, this is the one people underestimate most. The intuition is wrong in a useful way: you assume a model needs to "see the code" to understand a file. It mostly needs to see the shape. A 600-line service module is maybe 8,000 tokens read whole; its skeleton — the signatures, the types, the exports, with bodies elided — is often under 1,500. And critically, that 1,500-token skeleton answers the question the agent usually has, which is "what lives in this file and how do I call it," not "what's on line 312." This is the file-level companion to the broader case I make in How to reduce AI coding agent token usage. Here I want to go deep on one mechanism, because it's the one that's easiest to get wrong and the one with the highest payoff per unit of effort.

Why do whole-file reads waste so many tokens?

Whole-file reads waste tokens because the agent re-pays for every line on every subsequent turn, even though it only ever needed a tiny slice of the file. Agentic coding loops re-read the entire conversation history on each step — so a 8,000-token file you read on turn 3 is still being billed as input on turn 20. Read a dozen files that way and you've quietly built a context window that costs more to carry than the code costs to write. Two things compound the waste: Most of a file is body, and most bodies are irrelevant. When an agent reads src/billing/invoice.ts to add a field to one function, the other fifteen functions in that file are pure ballast. Their implementations don't inform the change; their signatures might, so the agent knows what's available to call. Skeleton compression keeps exactly that — names, parameters, return types — and elides the rest. The model attends worse to bloated context. This isn't just about cost. Models reason most reliably over tight, high-signal context and degrade on long, noisy windows — the "lost in the middle" effect that Liu et al. documented for long-context retrieval (https://arxiv.org/abs/2307.03172). Padding the window with function bodies the agent will never reference doesn't just cost money; it can make the answer worse. Trimming to the skeleton raises the signal-to-noise ratio. See context compression for the general principle.

How does skeleton compression actually work?

It works by parsing the file into its syntax tree and emitting only the structural nodes, with each function or method body replaced by a short placeholder. In practice the output for a TypeScript file looks like the real file with the insides hollowed out:
import { Stripe } from "stripe";
import { db } from "@/lib/db";

export interface InvoiceDraft {
customerId: string;
lineItems: LineItem[];
currency: "usd" | "eur";
}

export async function createDraft(input: InvoiceDraft): Promise<Invoice> { // }

export async function finalizeInvoice(id: string): Promise<Invoice> { // }

class InvoiceRepository {
async findById(id: string): Promise<Invoice | null> { // }
async save(invoice: Invoice): Promise<void> { // }
}
Everything that tells the agent how to use this file survived: the imports tell it what's in scope, the interface tells it the data shape, the signatures tell it what to call and what comes back. Everything that's only relevant when you're editing a specific body got collapsed to /* … */. When the agent decides it needs finalizeInvoice's actual logic, it expands that one function — and pays for one body instead of the whole file. A few details matter for this to be safe rather than lossy:
  • Signatures are sacred. Parameter names, types, return types, generics, decorators, visibility modifiers — all kept verbatim. The skeleton is only useful if it's a faithful interface.
  • Types and constants stay in full. A type, an enum, an exported const config object — these are the structure. You don't elide them.
  • It's language-aware, not regex. You parse the tree (tree-sitter or the language's own parser), so you correctly distinguish a function body from a string literal that happens to contain function. A naïve line-based heuristic will mangle real code; this is why structure-first reads need a real parser. It's the same family of static analysis behind semantic code search.

When should the agent read the full body instead?

The agent should read the full body the moment it needs to modify that specific function, reason about its internal logic, or debug behaviour that lives inside it — and skeleton compression should make that expansion cheap and targeted, not all-or-nothing. The whole point is a two-phase read: skeleton first to orient, then a precise expansion of the one or two bodies that actually matter for the task. Good expansion triggers:
  • "Why does finalizeInvoice double-charge on retry?" → expand finalizeInvoice, leave the rest skeletal.
  • "Add a metadata param to createDraft" → expand createDraft plus anything that calls it.
  • A failing test points at a specific assertion inside one method → expand that method.
Bad triggers — the anti-pattern this technique exists to kill:
  • "Read the file so you understand the module." You don't need bodies to understand a module's surface; you need the skeleton. Reach for full bodies on a hunch and you're back to paying for everything.
If you've internalised the discipline in context engineering, this is just that discipline applied at the granularity of a single file: load the cheapest representation that answers the current question, expand only on demand.

How much does this actually save?

On structure-heavy files the saving is large — frequently 70–90% of the file's tokens — and it compounds because that saving is multiplied across every turn the file stays in context. The exact number depends entirely on the body-to-signature ratio. A file that's mostly type definitions and short functions compresses modestly, because its structure is most of its content. A file with a handful of long, hairy function bodies compresses enormously, because those bodies are exactly what the skeleton drops. Put rough numbers on it with current pricing. Suppose an agent reads ten files averaging 6,000 tokens each at the start of a session and carries them for fifteen turns. That's 60,000 input tokens re-billed fifteen times — 900,000 token-reads. On Claude Sonnet 4.6 at $3 per million input tokens, that's about $2.70 of pure file-carrying, before the agent writes a line. Compress those reads to skeletons averaging 1,500 tokens and you're carrying 15,000 tokens per turn instead of 60,000 — roughly $0.68 for the same fifteen turns. Same understanding, a quarter of the bill. (And if the prefix is stable, prompt caching drops the carried portion to ≈10% of a fresh read on top of that.) I'm deliberately not quoting a single headline percentage, because anyone who gives you one number for "how much skeleton compression saves" is selling you the number, not the technique. Measure your own file mix. The AI coding agent token costs breakdown has the per-model pricing if you want to run the math on your own pricing tier.

How to apply this today

  1. Stop asking the agent to "read the file." Ask it for the structure first: "show me the signatures and types in invoice.ts," then "now expand finalizeInvoice." Two cheap reads beat one expensive one.
  2. Lead with the symbol, not the file. "Find the function that finalizes invoices and show its signature" gets you a skeleton-style answer for free; "read src/billing/invoice.ts" gets you the whole thing.
  3. Expand surgically. When you do need a body, name the function. Don't re-read the file to get it.
  4. Keep types and exports verbatim in any manual summary you write for the agent — those are the load-bearing parts.
  5. Automate it so you don't have to remember any of the above mid-task.
That last point is where tooling earns its keep. Doing skeleton compression by hand means narrating "show signatures, then expand X" on every file, which nobody sustains past the first hour. Tokenade applies skeleton compression automatically: when your agent reads a file in Claude Code, Cursor, Codex, Copilot or Windsurf, it returns the structure first and expands bodies on demand, alongside semantic code search and output filtering, with a dashboard that shows the tokens you saved. It's source-available under MIT, free up to ~10M tokens a month, and $19.90/mo (excl. tax) for Pro with unlimited machines — so you can verify the savings claim against your own usage rather than taking mine.

What goes wrong (anti-patterns)

Regex "skeletonizers." Trying to strip bodies with line-based pattern matching instead of a real parser will eventually eat a brace inside a string, drop a closing token, and hand the agent code that won't parse. Use the language's parser. A broken skeleton is worse than a full read because it's confidently wrong. Eliding types along with bodies. Some implementations collapse everything that isn't a top-level function. That throws away the interfaces and enums that are the structure — and now the agent doesn't know the data shapes it's calling against. Keep types in full; elide bodies only. Expanding too eagerly. If the agent reflexively expands every body "just in case," you've reconstructed the whole-file read with extra steps. Skeleton compression only pays off if expansion is the exception, triggered by a concrete need. Treating it as output reduction. Skeleton compression is an input lever — it trims what you feed the model. Telling the model to "be brief" trims its output, which is the cheap direction: on Claude Opus 4.8, output runs $25 per million tokens against $5 input, but output volume is small. The tokens that dominate the bill are the ones you keep re-reading into the window, and that's exactly what this technique attacks.

Frequently asked questions

Does skeleton compression make the agent's answers worse?

No — when it's done faithfully it tends to make them better, for the same reason trimming any noise does: the model attends more reliably to a tight, high-signal window than a bloated one. You're removing function bodies that were irrelevant to the current task, not the signatures and types the agent reasons over. The only way to hurt quality is to elide something load-bearing — which is why a correct implementation keeps every signature, type and export verbatim and expands a body the instant the task needs it. They're complementary, not the same. Semantic code search answers "which code is relevant to this task" by ranking symbols across the whole repo by meaning. Skeleton compression answers "how do I read this file cheaply once I've found it." In a good setup, search points the agent at the right file, and skeleton compression governs how that file enters the context — structure first, bodies on demand.

Which agents can use it?

Any token-billed coding agent that reads files benefits, because the mechanism is agent-agnostic — it's about how a file is represented before it enters the context window, not about any one agent's internals. The practical question is tooling: doing it by hand works in Claude Code or Cursor but you have to drive it every read. Automating it across Claude Code, Cursor, Codex, Copilot and Windsurf is what Tokenade does, so the structure-first read happens whether or not you remember to ask for it.

Doesn't prompt caching already solve the re-billing problem?

Caching helps a lot but it's a different axis. Prompt caching makes re-reading a stable prefix cheap — about 10% of a fresh token — but it doesn't make the prefix smaller, and it only helps when the prefix is byte-identical across turns. Skeleton compression shrinks the thing being carried in the first place, so a cached skeleton costs 10% of a small number rather than 10% of a large one. Use both: compress the read, then let the cache carry the compressed version cheaply.
See also:

Ranked #1 on the Token-Harness Optimizer Leaderboard.

Tokenade ranks #1 in the Token-Harness Optimizer Leaderboard — an end-to-end benchmark of agent token optimizers measured on real coding sessions. Set it up once, it works on every prompt. Works with Claude Code, Cursor, Codex, Copilot & more.