Context at AI Speed
Context at AI Speed
Last Updated: December 2025 Status: Living document — updated as we learn
The Team
Maya has twelve years in the industry. She’s seen frameworks come and go. She uses AI occasionally — autocomplete, boilerplate — but writes the hard parts herself. “I need to understand what I’m shipping.”
Dev has five years. He’s a pragmatist. AI lets him ship twice as fast, and he’s got metrics to prove it. “The code works. Tests pass. Why slow down?”
Kai is eighteen months out of bootcamp. AI is just how coding works. They’ve never written a for-loop without Copilot suggesting the next line. Fast, curious, sometimes too confident.
Three developers. Three relationships with AI. One project.
The Project
Q4. A growing SaaS company needs to modernize its payment system before the holiday rush.
The work:
- New subscription service (greenfield) — Kai’s excited
- Payment processing updates (brownfield) — Dev’s territory
- Invoice system integration (legacy) — Maya drew the short straw
Eight weeks. Aggressive but doable. Everyone knows their piece. The APIs are spec’d. The types are shared. What could go wrong?
Week 2: Everyone’s Shipping
Kai finishes the subscription service in six days. Their AI assistant suggested an elegant retry pattern for the payment calls. Tests pass. PR approved.
Dev refactors the payment processor. His AI helped him clean up years of technical debt while adding the new endpoints. “This is the cleanest this code has ever been.”
Maya integrates with the legacy invoice system. Slow going. She’s reading old code, tracing data flows, asking questions the AI can’t answer. “Why does this field exist? Who consumes this?”
Standup is optimistic. Everyone’s ahead of schedule.
Week 4: First Integration
Kai’s subscription service calls Dev’s payment processor for the first time.
It works.
Then it doesn’t.
Kai's Service Dev's Service
│ │
│ charge($50) │
▼ ▼
┌─────────┐ ┌─────────┐
│ Request │─────────────────────▶│ Process │───▶ Stripe
└─────────┘ └─────────┘ │
│ │ │
│ TIMEOUT (10s) │ ✓ charged
│ │ │
▼ │ │
┌─────────┐ │ │
│ Retry │ │ │
│ (AI │─────────────────────▶ Process ───▶ Stripe
│ pattern)│ │ │
└─────────┘ │ ✓ charged
│ │ AGAIN
▼
Customer charged $100 for $50 purchase
What happened:
Kai’s AI suggested retry logic. Best practice for resilience.
Dev’s AI didn’t add idempotency checks. The caller handles that, right?
Neither discussed it. Both assumptions were reasonable. The contract was never defined.
The fix: Dev adds idempotency keys. Two hours of work. “Should have thought of that.”
But should he have? His AI didn’t suggest it. Kai’s AI didn’t warn about it. The gap was invisible.
Week 5: The Second Bug
Maya’s invoice system needs to check payment status before generating invoices.
She queries the payment cache. Her code is careful — she even added a fallback to the database if cache misses.
Timeline ────────────────────────────────────────────────────────▶ 0ms 100ms 200ms 300ms 400ms 500ms ┌─────────┐ │ Payment │ Customer pays │ Success │ └────┬────┘ │ ├────▶ Stripe: ✓ charged │ ├────▶ Event published │ │ ┌────────────┐ │ │ Maya's │ Generate invoice │ │ Service │ "Check if paid" │ └──────┬─────┘ │ │ │ ▼ │ Cache: ❌ NOT FOUND │ │ │ ▼ │ DB fallback: ❌ NOT YET WRITTEN │ │ │ ▼ │ "Payment required" error │ (customer just paid) │ │ ┌──────────────┐ └──────────────────────────────▶│ Cache + DB │ │ updated │ │ (500ms) │ └──────────────┘
What happened:
Dev’s service publishes payment events. Cache and DB update asynchronously — about 500ms.
Maya’s service runs 100ms after payment. Her AI suggested the cache-then-DB pattern. Solid.
But 100ms < 500ms. The data isn’t there yet.
The conversation:
Maya: “How long until the cache updates?”
Dev: “I don’t know. The AI set up the event handler. Let me check.”
Twenty minutes later.
Dev: “About 500ms. The event goes through a queue.”
Maya: “My invoice generation runs in the payment webhook. That’s like 100ms after.”
Dev: “Oh.”
Week 5: The Third Bug
Customer support tickets start coming in. Users are seeing HTTP 500 errors during checkout.
Kai investigates. The errors are coming from Maya’s invoice service.
Stripe throws: CardDeclinedError Dev's Service Maya's Service Response ───────────── ───────────── ──────── │ │ │ ▼ │ │ ┌─────────┐ │ │ │ catch │ │ │ │ wrap │ │ │ └────┬────┘ │ │ │ │ │ ▼ │ │ PaymentError │ │ { cause: CardDeclined } │ │ │ │ │ └─────────────────────────▶│ │ ▼ │ ┌───────────┐ │ │ catch │ │ │ switch │ │ └─────┬─────┘ │ │ │ CardDeclinedError? ──── NO │ │ (wrapped!) │ PaymentError? ──────── YES ──────▶│ 402 │ │ StripeError? ─────── NO │ │ (wrapped!) │ ▼ │ 500 ERROR ◀───────────────────┘ (wrong!) Dev wraps errors (his AI's suggestion) Maya expects raw errors (her AI's assumption) Result: everything becomes 500
The standup:
Kai: “Maya’s service is throwing 500s on card declines.”
Maya: “I’m checking for CardDeclinedError. It should return 402.”
Dev: “I wrap all Stripe errors in PaymentError. Cleaner API surface.”
Maya: “Since when?”
Dev: “Since… my AI suggested it three weeks ago?”
Week 6: The Pattern
Three bugs. None caught by types. None caught by tests. All caught by production.
INFRASTRUCTURE CONTRACTS
Business logic is easy. The wiring is where it breaks.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ RETRY │ │ FAILURE │ │ CONSISTENCY │
│ SEMANTICS │ │ PHILOSOPHY │ │ WINDOWS │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ Who retries? │ │ Fail-fast? │ │ How stale │
│ Who's idem- │ │ Degrade? │ │ is OK? │
│ potent? │ │ Assume? │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ NULL │ │ TIMEOUT │ │ ERROR │
│ CONTRACTS │ │ BUDGETS │ │ PROPAGATION │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ Return null? │ │ Who owns │ │ Wrap? │
│ Throw? │ │ the clock? │ │ Pass? │
│ Assume? │ │ │ │ Translate? │
└──────────────┘ └──────────────┘ └──────────────┘
Each AI optimized locally. No AI saw the system.
┌─────────────────────────────────────────────────────────────┐ │ FRIDAY RETROSPECTIVE │ │ Room 3B │ └─────────────────────────────────────────────────────────────┘ Maya: "It's not the AI. It's us. We're optimizing for the wrong scarcity." Dev: "What do you mean?" Maya: "AI collapsed the cost of writing code to nearly zero. But it didn't touch the cost of UNDERSTANDING code. That's still expensive." ┌────────────────────────────────────────────┐ │ This sprint: │ │ │ │ 47 commits shipped │ │ ~24 we could debug at 3am │ │ │ │ Code: cheap │ │ Understanding: expensive │ │ We optimized for: code ← WRONG │ └────────────────────────────────────────────┘ Dev: "I approved AI suggestions in thirty of them. I understood the code. I didn't think about how it connected to yours." Kai: "Same. My retry pattern made sense. I never asked what happens if Dev's service isn't idempotent." Maya: "And I assumed error types were stable. Never asked." ┌────────────────────────────────────────────┐ │ │ │ 3 developers │ │ 3 AI assistants │ │ 3 separate conversations │ │ 0 shared context │ │ │ └────────────────────────────────────────────┘ Maya: "Code is disposable now. Understanding isn't. We need to protect what's actually scarce."
Week 7: The Experiment
Maya proposes something unusual.
“What if we couldn’t proceed until we verified?”
Not documentation after the fact. Gates that block progress until verification happens.
VERIFICATION GATES
┌──────────┐ ┌─────────────┐ ┌──────────┐
│ VERIFY │──────▶│ VERIFY │──────▶│ VERIFY │
│CONTRACTS │ │UNDERSTANDING│ │OWNERSHIP │
└──────────┘ └─────────────┘ └──────────┘
│ │ │
▼ ▼ ▼
"Who consumes "Can I explain "Can I debug
this? Show me this from this at 3am?"
evidence." memory?"
───────────────────────────────────────────────────
Each gate blocks progress. Artifacts persist.
Dev is skeptical. “This sounds like it’ll slow us down.”
“We lost a week to bugs,” Maya says. “How fast were we really going?”
The Three Gates
Gate 1: Verify Contracts — Before planning, answer: Who calls this? What do they expect on failure? Who do I call? Have I verified? (“Various services” fails. Name them.)
Gate 2: Verify Understanding — Before implementing AI suggestions: Paraphrase from memory (docs closed). List 3+ alternatives. Enumerate 5+ failure modes.
Dev tries it on a circuit breaker pattern. “From memory: tracks failures, opens after threshold, half-opens to recover…” He pauses. “What’s the threshold? What happens to in-flight requests?” He checks the AI’s code. Threshold is 5. In-flight requests… he doesn’t know. “I was about to ship this.”
Gate 3: Verify Ownership — Before committing: Write expectations before viewing the diff. Walk line-by-line through the complex parts. For each risk: Accept, Mitigate, or Reject explicitly.
Kai resists. “This feels like bureaucracy.” Then they’re on-call at 2am, debugging code they wrote three weeks ago with heavy AI assistance. The state machine logic is unreadable. “I wish I’d done the ownership check.”
What the gates would have caught:
| Bug | Gate | Question That Would Have Surfaced It |
|---|---|---|
| Double charge | Contracts | ”Who handles retries? Show me evidence.” |
| Timing bug | Contracts | ”What’s the consistency window? Verified?” |
| Error wrapping | Contracts | ”What error types does downstream expect?” |
Week 8: The Gates In Action
Thursday. Kai is building a webhook handler for subscription renewals. Their AI suggests a clean pattern — retry failed charges with exponential backoff.
Before the gates, Kai would have shipped it. Tests pass. Code looks good.
Now they hit the contracts gate.
“Who calls this webhook?” Kai writes in their verification doc.
Stripe. On renewal events.
“What does downstream expect on failure?”
Kai pauses. The webhook calls Dev’s payment service. What does Dev expect?
They walk over to Dev’s desk. “If my webhook retries a failed charge, what happens?”
Dev checks his service. “I retry internally. Three times with backoff.”
“So if I also retry…”
“Six charges. Maybe nine.” Dev pulls up the code. “Actually, I changed this last week. Now it’s five internal retries.”
Kai updates their verification doc: Payment service retries 5x internally. Webhook must NOT retry — would cause 5-25x charge attempts.
The bug that didn’t ship: A retry storm that would have hammered Stripe’s API and potentially charged customers multiple times. Caught in a 10-minute conversation, not a 3am incident.
Is it worth it? Dev runs the numbers: “We spent maybe 4 hours total on gates this sprint. We spent 12 hours debugging integration issues last sprint. And that was a light week.”
What they learned:
Dev: “The ‘write expectations first’ thing catches me every time. I often don’t know what I expect until I have to write it down. That’s the point.”
Kai: “I used to stop at the happy path. Now I automatically ask ‘what if this times out? what if it returns null?’ The 5 failure modes requirement rewired how I think.”
Maya: “‘Name every consumer’ sounds trivial. It’s not. That one question would have caught every bug we shipped.”
The Pattern
None of these bugs were type errors. All passed code review. Each decision was reasonable in isolation.
The divergence was in infrastructure contracts:
- Who retries?
- Who validates?
- How stale is ok?
- What does null mean?
- Who owns the timeout?
- How do errors flow?
Business logic is the easy part. The wiring is where AI-assisted development falls apart.
Each AI optimized locally. No AI saw the system. The integration failures were invisible until production.
The New Asymmetry
CODE COST ████████████████████████████████ 2020 ████████████████ 2023 ████ 2025 ▼ approaching zero UNDERSTANDING COST ████████████████████████████████ 2020 ████████████████████████████████ 2023 ████████████████████████████████ 2025 ━━━ unchanged AI made code cheap. Understanding is still expensive. We're optimizing for the wrong scarcity.
As Chad Fowler observes: “The marginal cost of producing code is collapsing toward zero. The cost of knowing what the code does remains expensive.”
This asymmetry is the story of modern development. Code is cheap. Understanding is expensive. And when code becomes disposable — regenerated, refactored, replaced — understanding becomes the only stable layer.
Better Shapes, Not Better Prompts
In another essay, Fowler identifies a “gradient of trust” in code: simple functions with clear inputs and outputs inspire confidence immediately. Complex, stateful code requires review regardless of who wrote it — human or AI.
His conclusion: “The real leverage isn’t better prompts. It’s better shapes.”
But what is a shape?
A shape is a behavioral guarantee at a boundary. Not the code inside — the contract at the edge. The thing that survives when you rewrite the implementation.
“Wait,” Dev says. “We have contracts. OpenAPI specs. TypeScript interfaces.”
“Those describe syntax,” Maya says. “Request format. Response format. What they don’t tell you: Is this endpoint idempotent? How long until the cache updates? Do you wrap errors or pass them through?”
Dev gets it. “The spec describes the wire format. Not the behavior.”
THREE LAYERS OF SHAPES LAYER 1: SYNTAX ✓ WELL COVERED ─────────────────────────────────────────────────── │ OpenAPI, Protobuf, TypeScript, JSON Schema │ → Request/response format, data structure, types ─────────────────────────────────────────────────── LAYER 2: SEMANTIC ⚠️ PARTIAL ─────────────────────────────────────────────────── │ Value Objects, Branded Types, Zod/Yup │ → "amount is cents", "returns null not throws" ─────────────────────────────────────────────────── LAYER 3: BEHAVIORAL ❌ RARELY DOCUMENTED ─────────────────────────────────────────────────── │ Retry contract │ "idempotent, caller may retry" │ Timing guarantee │ "cache updates within 500ms" │ Timeout budget │ "we allow 30s, caller needs 35s" │ Failure philosophy │ "fail-fast, no degradation" │ Consistency model │ "eventually consistent reads" ─────────────────────────────────────────────────── ↑ This is where Maya's bugs lived
Syntax shapes are well-tooled. Semantic shapes are partially covered by patterns like Value Objects. But behavioral shapes — the contracts about retry, timing, failure, consistency — live in tribal knowledge, scattered configs, or nowhere at all.
Maya’s team had OpenAPI specs. They had TypeScript. The bugs still happened.
What Maya’s Bugs Were Actually Missing
| Bug | Syntax Contract | Semantic Contract | Behavioral Contract |
|---|---|---|---|
| Double charge | ✓ POST /charge | ✓ amount: Money | ❌ “Idempotent?” |
| Timing bug | ✓ GET /status | ✓ paid: boolean | ❌ “Stale for how long?” |
| Error wrapping | ✓ 4xx/5xx | ✓ PaymentError | ❌ “Wrapped or raw?” |
Every bug had syntax coverage. Most had semantic coverage. None had behavioral coverage.
The test: Can you swap out the implementation and the system still works? If yes, the shape was clear. If no, you never had a shape — you had an accident.
CODE SHAPE
──── ─────
┌─────────────────┐ ┌─────────────────┐
│ retry logic │ │ "idempotent: │
│ error handling │ │ yes" │
│ timeout config │ │ │
│ cache strategy │ │ "timeout: 30s" │
│ null checks │ │ │
│ ... 500 lines │ │ "returns null │
└─────────────────┘ │ on not-found" │
└─────────────────┘
│ │
▼ ▼
Can be rewritten Survives rewrite
by AI tomorrow Shared across team
Verified by gates
The code is disposable. The shape is the product.
The gates don’t review code. They verify shapes:
- Contracts gate: “What’s the shape at this boundary? Show me.”
- Understanding gate: “Can you explain the shape from memory?”
- Ownership gate: “Do you know why this shape, not another?”
When the shape is right, mediocre code becomes sufficient. When the shape is wrong, perfect code still fails at integration.
Beyond Process: Making the Machine Enforce It
Gates catch it at review. But you can also catch it at compile time or runtime:
// Make idempotency explicit in the type
type IdempotentEndpoint<T> = T & { __idempotent: true };
// Force callers to acknowledge the contract
async function charge(
request: ChargeRequest,
idempotencyKey: IdempotencyKey // Can't call without this
): Promise<ChargeResult>
// Runtime assertion at the boundary
function assertShape(service: PaymentService) {
if (!service.metadata.idempotent) {
throw new ContractViolation("Caller expects idempotent endpoint");
}
}
Process is fragile. Types and assertions make the contract explicit. The machine catches what humans forget.
Try It
Not prescriptions. Just starting points.
This week:
- Pick one service boundary
- Write down: retry policy, failure handling, timeout budget
- Ask: does the other team know this?
This sprint:
- Before your next AI implementation, try the understanding check: close the docs, explain from memory
- Notice: what can’t you explain?
This month:
- Ask your team: “Can we explain our architecture to a new hire?”
- The answer might be uncomfortable
We’ll Update This
This document will change.
Maya, Dev, and Kai are still learning. So are we.
What’s working will get promoted. What’s failing will get documented. New patterns will get added.
The field is moving. So is our understanding.
Check back.
Last updated: December 2025
Related reading:
- The Alien Tool — the perception gap and cognitive science behind AI-assisted development
- The Death and Rebirth of Programming — Chad Fowler on code as disposable artifact
- The Gradient of Trust — Chad Fowler on why shapes matter more than prompts