My coding Agent A program that calls a language model in a loop and lets it use tools, such as search or code. reads a rule file on every turn. In this site's own repo, that file is AGENTS.md. It held 34 rules in plain English, and my agent loaded all of them every time. I measured the file with the Gemma Tokenizer The program that cuts text into the tokens a model reads. Each model family has its own.: 12,822 Token A small piece of text, often part of a word. Models read, write and bill text by the token. a turn. The Schema A formal description of the shape data must have, such as which fields exist and their types. that enforces the same rules in code is 180 tokens. Then I dropped a component into a template without importing it. Every text check passed. The Server render The server builds a page's HTML before it sends it. A crash there sends the reader an error page. failed with a 500. The script behind the schema caught the missing import and Exit code The number a program returns when it ends. 0 means success; any other number, such as 1, means failure..
That was one file. When I loaded 10 Markdown A plain text format with light marks, such as # for a heading. Agents often keep notes in it. skill files into the System prompt Fixed instructions at the start of every request. They tell the model its role and its rules., my Harness The code around an agent that hands it the task, runs its tools and checks its work. spent 22,000 input tokens a turn before I typed anything. At Gemini 3.1 Pro input pricing, that is $0.044 per turn in skill tokens alone. On smaller models, instruction-following collapsed at turn 4.
The context trap and fragile models
Prompts that grow every turn
As my agent took on more jobs, I wrote more markdown files of instructions. They described CLI Command line interface. A program I run by typing its name and options in a terminal. flags, formatting rules and how to recover from errors. My early agent harness put all of these files into the system prompt on every turn.
That habit caused three severe problems in my production workloads:
- A token tax that compounds: Every turn paid for the files again. Over a 20-turn session, 22,000 tokens of instructions became 440,000 input tokens. My API bill grew in a straight line with the length of the conversation.
- Attention that fades: Once the Context window All the text a model reads for one answer: its instructions, the chat so far, and any documents. filled past 70% of KV cache The attention keys and values a model saved for tokens it already read. Reusing them skips recomputing that prefix. capacity, my models suffered severe Attention The step where a model compares each token with the tokens before it to decide what matters. decay. They missed critical rules buried in the middle paragraphs.
- Drift from turn to turn: Instructions in plain English are suggestions. The model follows them most of the time, not every time. My models sometimes skipped validation steps, invented CLI parameters that do not exist, or formatted their output in different ways.
Recent 2026 tool-attention research confirms what I measured in production. Eager prompt skill injection puts every skill into the prompt up front. It costs 10,000 to 60,000 tokens per turn, and it breaks multi-step reasoning once KV cache use crosses 70%. Lazy tool schema loading waits until a tool is needed, and it cuts token overhead by 95.0%.
Why written skills fail on light models
Written skills quietly tied my stack to massive frontier models. Frontier reasoning models had enough capacity to follow my multi-step instructions, even when the prompt was unclear. Smaller runtimes failed at once.
When I tried to run my markdown-based agent on lighter runtimes, it broke in two different ways:
- On-device runtimes: Models that run on a phone or in a browser have small context windows, often 2K to 8K tokens, and a strict local compute budget. They could not take in ten pages of markdown rules and still keep track of the conversation.
- Fast serverless endpoints: Light cloud models have large context windows. But when I filled them with markdown skills, attention degraded at turn 4, each turn got slower, and my token costs multiplied across multi-turn sessions.
Parsing a timestamp or checking a JSON A common text format for structured data. A parser needs the whole object; half of one fails. payload is simple work. If my agent needs a frontier model to do it, my system is fragile, both in cost and in design.
Get the next essay by email
One email per new essay: a production failure, taken apart, and the code that fixed it. Reply to any of them to unsubscribe.
The brain and the exoskeleton
The exoskeleton vs. the brain
I want agents that work on every model tier. So I split each agent into two layers. One layer reasons. The other layer executes, and it is Deterministic Always gives the same output for the same input. Ordinary code works this way; a model does not.: the same input always gives the same result. I call this split my Elastic Cognitive Envelope:
- The brain (probabilistic reasoning): Working out what the user wants, breaking a vague goal into steps, creative synthesis and high-level strategy. I keep this layer inside the model.
- The exoskeleton (deterministic code): State machine Code that tracks which step a task is on and allows only the moves defined from that step., schema validation, arithmetic, API authentication and changes to files. I enforce this layer in compiled or interpreted code.
The code itself adds nothing to the prompt; only its schema does. It runs in milliseconds, and ordinary Unit test A small automatic test that runs one function with a known input and checks the output. can cover it. When I move the rules that must always hold into code, my system prompt shrinks from thousands of lines to a 180-token tool schema.
Where the exoskeleton runs depends on security, compute and platform limits. I use three setups:
- In-process execution: In my local developer tools and CLI agents, the exoskeleton runs inside the host process, like my git engine below. It runs system commands with zero network overhead.
- Client-side runtimes: In my mobile and web apps, I call Gemini models through client SDK Software development kit. A library a vendor ships so my app can call its service. such as Firebase AI Logic. My app can then run local tools on the device, in its own process.
- Stateless serverless services: Some tools need private credentials, heavy dependencies or privileged infrastructure. I package those as stateless containers on Google Cloud Run. When client apps call these endpoints, I guard them with Firebase App Check. It uses cryptographic Attestation Proof, signed by the device or platform, that a request comes from my real app and not from a script. to block calls that do not come from my app.
Three stages from prose to code
To move from written instructions to deterministic code, I run a three-stage distillation workflow:
- Stage 1 (frontier exploration): A frontier model with extended thinking explores a problem that is still unclear. It calls the APIs and finds the edge cases.
- Stage 2 (code distillation): Once the workflow is stable, I ask the frontier model to turn the multi-turn session into a typed script with strict input and output schemas.
- Stage 3 (light runtime deployment): I expose the script as a single tool call. Fast serverless and on-device models call it with 180 tokens of schema overhead and zero execution drift.
Implementation and benchmarks
Here is the gate from the lead, step by step. It is a short Node script, verify_import_coverage.mjs, and it checks one rule from AGENTS.md: a template must import every component it renders. Figure 2 runs it on the same broken template. The video after it records the agent's run in my editor, from the token count to the exit 1.
The gate lists imports and rendered tags
ProofVideo has no import, so exit 1
Add the import, and the check passes
Show the shell command (reproduce locally)
node verify_import_coverage.mjs broken.astroA distilled tool in TypeScript
Here is one distilled tool. Instead of reading a 500-line markdown guide on git branching and commit hygiene, my agent calls this typed function. The function enforces the rules the same way every time:
import { z } from 'zod';
import { execFileSync, spawnSync } from 'node:child_process';
import { statSync } from 'node:fs';
import { join } from 'node:path';
// 1. Strict input schema replaces 40 lines of prompt formatting rules
export const CommitActionSchema = z.object({
branch: z.string().regex(/^[A-Za-z0-9._/-]+$/, 'Invalid branch name format').refine(b => !b.startsWith('-'), 'Branch name cannot start with a dash'),
message: z.string().min(10).refine(m => m.split('\n')[0].length <= 72, 'Subject line must be 72 characters or fewer'),
base: z.string().regex(/^[A-Za-z0-9._/-]+$/).refine(b => !b.startsWith('-')).optional(),
files: z.array(z.string().refine(f => f.split('/').every(s => s !== '' && s !== '.' && s !== '..'), 'Must be a file path relative to the repository')).nonempty(),
signoff: z.boolean().default(true),
});
// z.input keeps signoff optional for callers; parse() fills in the default
export type CommitAction = z.input<typeof CommitActionSchema>;
// Every git call gets a fixed repo, a timeout, captured stderr, and no pathspec magic
const git = (repo: string, args: string[]) =>
execFileSync('git', ['--literal-pathspecs', ...args], { cwd: repo, encoding: 'utf8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] });
// 2. Deterministic execution engine replaces multi-turn prompt retries
export class DistilledGitEngine {
public static execute(action: CommitAction, repo: string): { success: boolean; hash?: string; committed?: boolean; error?: string } {
try {
// Validate schema contracts before touching disk
const { branch, base, message, files, signoff } = CommitActionSchema.parse(action);
for (const f of files) if (statSync(join(repo, f), { throwIfNoEntry: false })?.isDirectory()) throw new Error(`${f} is a directory; name each file`);
// switch never reads the name as a path; create the branch only if it does not exist
const exists = spawnSync('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: repo }).status === 0;
git(repo, exists ? ['switch', branch] : ['switch', '-c', branch, ...(base ? [base] : [])]);
// '--' blocks option injection; --literal-pathspecs blocks '.' and ':/' magic
git(repo, ['add', '--', ...files]);
// Commit only the named files, never whatever else was staged; a retry with nothing new is a no-op
const committed = git(repo, ['diff', '--cached', '--name-only', '--', ...files]) !== '';
if (committed) git(repo, ['commit', '-m', message, ...(signoff ? ['--signoff'] : []), '--', ...files]);
// Retrieve commit hash deterministically rather than parsing stdout
const hash = git(repo, ['rev-parse', 'HEAD']).trim();
return { success: true, hash, committed };
} catch (err) {
// Return structured, actionable error instead of raw stack trace
return { success: false, error: err instanceof Error ? err.message : String(err) };
}
}
}The schema rejects a bad branch name, a long subject line or a path outside the repo before git runs at all. The -- before each file list stops a file name from being read as a flag. The function returns a plain result, so the agent gets a clear error instead of a stack trace.
Written skill or code: the trade-offs
When I decide whether a job belongs in a written skill or in a distilled code tool, I compare five things:
| Dimension | Prompt-Heavy Written Skill | Distilled Code-First Tool | Hybrid Distillation Pattern |
|---|---|---|---|
| Context overhead | 22,000 tokens per turn (10 skill files) | 180 tokens (schema only) | 180 tokens (schema only) |
| Execution latency | 1,100 to 2,800 ms per turn | About 85 ms (five git calls) | About 85 ms (deterministic code) |
| Model tier support | Frontier reasoning models only | All tiers (On-Device, Workhorse, Frontier) | Frontier for authoring, Workhorse for runtime |
| Reliability | Probabilistic (collapses at turn 4 on light models) | Deterministic | Deterministic, verified by unit tests |
| Authoring velocity | Fast initial draft | Requires manual engineering | Fast (frontier model synthesizes code) |
Industry validation and benchmarks
- Tool Attention: Lazy Gated MCP Schema Loading (Apr 2026): Confirms that eager prompt skill injection consumes 10K to 60K tokens per turn and fractures reasoning at around 70% KV cache, whereas lazy tool distillation cuts token overhead by 95.0%.
- Beyond Code Generation: Reliability, Verification, and Cost Economics in the Agentic Software Development Lifecycle (Sep 2026): Confirms that replacing prompt-based verification with deterministic code harnesses reduces multi-turn agent failure rates and token cost.