Back to writing

The Comprehension Tax: Why Your AI Agent Writes Mediocre Code in Your Codebase

AI AgentsClaude CodeLLMCodebase DesignDeveloper Experience
6 min read
0views

Every morning, a brilliant mechanic walks into your garage. He has rebuilt hundreds of engines, knows every fastener by feel — but he has never seen your bike, and whatever he learned about it yesterday is gone. Whether he fixes the fault in ten minutes or spends the hour hunting for the fuse box depends entirely on one thing: what you left on the bench.

That mechanic is your AI coding agent. And most of us hand him a bike with no manual, no service history, and a check-engine light that just says "something."

The budget nobody talks about

An agent works inside a fixed context budget. Every token it spends understanding your codebase — reading files, tracing conventions, reverse-engineering how you do things — is a token it doesn't spend on the actual problem.

Here's the part that stings: when the budget runs out before understanding is complete, the agent doesn't stop and say so. It guesses. And because it's good at guessing, the output looks right.

Watch it happen. You say: "Add this new carrier to the shipping-rates endpoint." In an unprepared codebase, the agent greps around, opens fifteen files, and finds three different response patterns from three different eras of your code. It picks one — or worse, invents a blend of all three. The code compiles. The thin tests pass. The PR reads fine. And it's wrong in ways you'll discover in production, because the convention it needed was never written down anywhere a grep could find it.

I call this the comprehension tax. You pay it every single session, and when the agent can't afford it, confidence fills the gap. The failure mode of an under-informed agent isn't refusal — it's plausible mediocrity.

The fix isn't a smarter model or a longer prompt. It's paying the understanding cost once, in writing, instead of every session, in tokens.

What I keep on the bench

Four things. Each one is boring, cheap to write, and compounds forever.

1. A CLAUDE.md that states invariants, not philosophy

This file gets read at the start of every session — it's the one guaranteed piece of context. Don't waste it on prose about your architecture's elegance. Commands and laws:

# shipping-api
Node 22 + Express + Postgres (Drizzle). pnpm only.

## Commands
pnpm dev · pnpm test · pnpm typecheck

## Invariants
- Handlers never call carrier APIs directly — always through
  an adapter in src/adapters/
- Every response uses the envelope from src/core/response.ts.
  No exceptions.
- New tables need a migration AND a rollback. Never push
  schema directly.

Thirty lines. It just replaced fifteen exploratory file reads — and unlike the file reads, it can't be misread.

2. Skills: job cards for recurring work

Any task you'll ask for more than twice deserves a playbook. They live in .claude/skills/, one folder per task type: adding a carrier, wiring an alert, documenting an endpoint, testing conventions.

A skill is where conventions stop being tribal knowledge:

# Adding a carrier

1. Adapter goes in src/adapters/<carrier>.ts — transport ONLY
   (auth, requests, error mapping). No business logic.
2. Rate logic lives in src/rates/ — validation, normalization,
   response shaping.
3. Responses use makeResponse(). Error codes come from
   errors.md — never invent new ones.
4. Done means: code + unit tests (happy path AND failure
   path) + docs entry + carrier added to the health check.

Now "add carrier X to the rates endpoint" is a complete prompt. The other four hundred words it used to need are already written, versioned, and identical every session. The agent that reads one existing adapter can write the tenth — because the tenth is supposed to look exactly like the first.

3. Memory: the service logbook

When a debugging session teaches you something, the last step of the fix is writing it down — one file per lesson:

---
name: webhook-signature-drift
description: Carrier webhooks fail signature checks
             after key rotation
---
Symptom: 401s on /webhooks/<carrier>, starting minutes
  after a key rotation.
Cause: we cache verification keys for 15m; the carrier
  rotates instantly.
Fix: bust the key cache on the first 401 and retry once
  (src/webhooks/verify.ts).
Regression check: grep KEY_CACHE_TTL — if someone raised
  it, this is back.

The difference this makes is not subtle. The next time those 401s appear, the agent doesn't launch an investigation — it does a lookup. A bug you've debugged once becomes a bug you never debug again. Debugging goes from O(codebase) to O(1).

4. Alerts that carry their context — and tests that catch the guess

These are two halves of one feedback loop: the alert tells the agent where to start, the tests tell it when it's actually done.

A bare alert — "shipping-api p99 > 2s" — forces the agent to ask where to look, and forces you to answer. An enriched alert carries the service, the exact condition and query that fired, and links to the dashboard and runbook. That changes what a prompt can be. My favorite interaction with my own setup is pasting a screenshot of an alert and typing nothing else. The alert is the prompt; the observability skill already lists the standard diagnostic queries; the agent goes straight to root-cause analysis.

Tests are the other half, and they matter more with agents than they ever did with humans — because an agent iterates against feedback, and your test suite is the only reviewer that's awake at 2am. Strict types plus real coverage (failure paths, not just happy paths) is what converts "plausible" into "correct" before the PR, instead of during the incident.

The payoff

The honest measure of all this is what your prompts look like.

Before — every session, three paragraphs: here's the architecture, adapters live here, don't touch the response format, remember we use the envelope pattern, the alert queries are in that dashboard... And after all that, still a coin-flip on conventions.

After:

"Add carrier X to the rates endpoint."
"Here's the alert screenshot. What broke?"

One line each. Not because the agent got smarter — because the understanding was already paid for, once, in files that cost nothing to re-read.

The bolt torqued to "feels right"

None of this is exotic. You'll notice the four things are exactly what a good team wishes it had anyway: an accurate README, runbooks, a post-mortem culture, observability, tests. The overlap isn't a coincidence — an agent is just the teammate that exposes what was always missing.

The difference is that a human teammate, when confused, asks in Slack. The agent doesn't. It torques the bolt to "feels about right," hands you back the wrench, and the bolt holds — until it doesn't.

The mechanic will keep showing up tomorrow with amnesia. That part you can't change. What's on the bench when he arrives — that part is entirely up to you.

Key Takeaways

Comprehension is a tax you pay every session

Every token an agent spends understanding your codebase is a token not spent on the problem — and when the budget runs out, it guesses confidently instead of stopping.

Pay the understanding cost once, in writing

CLAUDE.md for invariants, skills for recurring tasks, memory for debugged lessons, enriched alerts plus real tests for the feedback loop. Boring artifacts, compounding returns.

The goal is one-line prompts

"Add carrier X" or an alert screenshot should be a complete prompt. If you're re-explaining your architecture every session, you're paying the tax in the most expensive currency.


Thanks for reading