---
title: "Code Over Context: Why Written Agent Skills Break in Production"
date: "August 25, 2026"
description: "10 markdown skill files cost my agent 22,000 tokens per turn and broke on smaller models at turn 4. I distill written skills into deterministic code tools."
category: "Agent Architecture"
canonical: "https://ulukaya.dev/posts/code-over-context"
---

# Code Over Context: Why Written Agent Skills Break in Production

My coding agent 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: 12,822 tokens a turn. The schema 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 failed with a 500. The script behind the schema caught the missing import and exited 1.

		

That was one file. When I loaded 10 markdown skill files into the system prompt, my harness 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.

		

> My core thesis: Stop writing prompts for what code can guarantee. A frontier model explores a task once. I distill what it did into a typed tool. From then on, fast serverless and on-device models call the tool instead of rereading the instructions.

	

	
	

## The context trap and fragile models

	
		

### 01. Prompts that grow every turn

		

As my agent took on more jobs, I wrote more markdown files of instructions. They described CLI 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 filled past 70% of KV cache capacity, my models suffered severe attention 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%.

	

	
		

### 02. Why written skills fail on light models

		

Written skills quietly tied my stack to [massive frontier models](/posts/leaderboard-mirage-llm-ranking-traps). 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 payload is simple work. If my agent needs a frontier model to do it, my system is fragile, both in cost and in design.

	

	
	

## The brain and the exoskeleton

	
		

### 03. 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: 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 machines, 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 tests 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 SDKs such as [Firebase AI Logic](https://firebase.google.com/docs/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](https://cloud.google.com/run/docs). When client apps call these endpoints, I guard them with [Firebase App Check](https://firebase.google.com/docs/app-check). It uses cryptographic attestation to block calls that do not come from my app.

		
	

	
		

### 04. 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.

	
		
		
		
	
	

	
		

### 05. A 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 &#123; z &#125; from 'zod';
import &#123; execFileSync, spawnSync &#125; from 'node:child_process';
import &#123; statSync &#125; from 'node:fs';
import &#123; join &#125; from 'node:path';

// 1. Strict input schema replaces 40 lines of prompt formatting rules
export const CommitActionSchema = z.object(&#123;
  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  !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),
&#125;);

// z.input keeps signoff optional for callers; parse() fills in the default
export type CommitAction = z.input;

// 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], &#123; cwd: repo, encoding: 'utf8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] &#125;);

// 2. Deterministic execution engine replaces multi-turn prompt retries
export class DistilledGitEngine &#123;
  public static execute(action: CommitAction, repo: string): &#123; success: boolean; hash?: string; committed?: boolean; error?: string &#125; &#123;
    try &#123;
      // Validate schema contracts before touching disk
      const &#123; branch, base, message, files, signoff &#125; = CommitActionSchema.parse(action);
      for (const f of files) if (statSync(join(repo, f), &#123; throwIfNoEntry: false &#125;)?.isDirectory()) throw new Error(`$&#123;f&#125; 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/$&#123;branch&#125;`], &#123; cwd: repo &#125;).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 &#123; success: true, hash, committed &#125;;
    &#125; catch (err) &#123;
      // Return structured, actionable error instead of raw stack trace
      return &#123; success: false, error: err instanceof Error ? err.message : String(err) &#125;;
    &#125;
  &#125;
&#125;
```

		
		

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.

	

	
		

### 06. 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)](https://arxiv.org/abs/2604.21816): 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)](https://arxiv.org/abs/2609.04681v1): Confirms that replacing prompt-based verification with deterministic code harnesses reduces multi-turn agent failure rates and token cost.
