Skip to content

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.

THE FIRST BUG
  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.

THE TIMING BUG
  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.

THE ERROR BUG
  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.

WHERE AI-ASSISTED DEVELOPMENT DIVERGES
                   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.
THE RETROSPECTIVE
  
                      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
                      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:

BugGateQuestion That Would Have Surfaced It
Double chargeContracts”Who handles retries? Show me evidence.”
Timing bugContracts”What’s the consistency window? Verified?”
Error wrappingContracts”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

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
                  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

BugSyntax ContractSemantic ContractBehavioral Contract
Double chargePOST /chargeamount: Money❌ “Idempotent?”
Timing bugGET /statuspaid: boolean❌ “Stale for how long?”
Error wrapping4xx/5xxPaymentError❌ “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 VS SHAPE
       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

Appendix: More Production Failure Patterns 5 patterns beyond multi-service orchestration Expand Collapse

The bugs above all involve distributed services talking past each other. But AI-assisted development creates similar gaps in other dimensions.

Pattern 1: The Race Condition

Week 9. Black Friday sale. Two customers click “Buy” on the last unit at the same millisecond.

THE INVENTORY RACE
  Customer A                 Database                Customer B
                                                        
  10:00:00.001                                      10:00:00.001
                                                        
          SELECT quantity          SELECT quantity      
          WHERE item='X'           WHERE item='X'       
       
                                                        
        returns: 1                          
                                                        
          "1 available!"            "1 available!"      
                                                        
                                         
   CHARGE                                          CHARGE  
                                         
          UPDATE quantity=0       UPDATE quantity=0     
       
                                                        

  Both charged. One item. Customer support nightmare.

Kai’s AI suggested the pattern: check availability, then charge. Clean separation of concerns. “Does this need locking?” The AI said: “For most use cases, the check-then-charge pattern is sufficient.”

Black Friday wasn’t in the training data’s context.

Gate check: “What happens with concurrent access?” / “Is this atomic?”


Pattern 2: The Slow Leak

Week 10. Maya’s invoice service runs fine. Then Thursday at 4pm: connection pool exhausted. Service down.

THE CONNECTION LEAK
  Monday     Tuesday    Wednesday    Thursday
  

              
  ░░░░░    ▓▓░░░    ▓▓▓▓░    █████  FULL
  ░░░░░    ░░░░░    ▓░░░░    █████
              
   2/50       12/50      28/50      50/50

  The error path:
  
     try {                                  
       const result = await query(conn);    
       return result;                       
     } catch (e) {                          
       throw e;  //  connection never      
     }           //   released on error     
  

Maya’s AI generated try/catch. The happy path releases connections. The error path doesn’t.

“The tests pass,” Maya says. “We even have integration tests.”

Dev: “How many errors do your integration tests produce? Maybe three? You have 50 connections. You need 51 errors to see the leak.”

Gate check: “What happens when this throws?” / “Trace the connection lifecycle.”


Pattern 3: The Config Gap

Week 11. Push to production. Immediate 500s. Roll back. It worked in staging.

THE CONFIG DRIFT
            DEV                        PROD
      
   TIMEOUT=30000            TIMEOUT=5000        
   REDIS_HOST=localhost     REDIS_HOST=redis-1  
   MAX_RETRIES=10           MAX_RETRIES=2       
      
                                      
                                      
          WORKS                   TIMEOUTS

  The AI defaulted to generous dev values.
  Prod has tighter constraints.

“What’s a reasonable timeout?” Kai asked the AI. “30 seconds is a common default.”

In dev, 30 seconds is fine. In prod, the load balancer kills requests at 5 seconds.

Gate check: “What are the prod constraints?” / “Are any values hardcoded?”


Pattern 4: The Cascade

Week 12. Recommendation service is slow. Then product service times out. Then checkout fails. Entire platform down.

THE CASCADE FAILURE
  Normal flow:
          
   Checkout  Product    Recs    
                              (optional)
          

  When Recs is slow:
          
   Checkout  Product    Recs    
   WAITING       WAITING        SLOW    
   ...       ...        10s     
          
                                     
                                     
    TIMEOUT        TIMEOUT         "Working..."

Dev’s AI parallelized API calls for performance. “Should recommendations be required or optional?” Dev didn’t ask.

When recommendations got slow, every call waited. No circuit breaker. An optional feature became a critical dependency.

Gate check: “Which dependencies are optional?” / “What if this times out?”


Pattern 5: The Blind Spot

Week 13. 3am. PagerDuty. Payment failures spiking 40%. But where?

THE OBSERVABILITY GAP
  What Kai sees at 3am:
  
    ERROR RATE: 40%                                      
    P99 LATENCY: 2.3s                                    
                                                          
    Recent logs:                                          
    [ERROR] PaymentError: something went wrong            
    [ERROR] PaymentError: something went wrong            
    [ERROR] PaymentError: something went wrong            
                                                          
    No stack trace. No request ID. No correlation.        
  

The AI wrote functional code. Error handling exists. Logs exist. But no context — no request IDs, no “what was the user trying to do?”

Kai can’t debug it. They can’t even figure out which code path is failing.

Gate check: “How would you debug this at 3am?” / “What’s in the logs when this fails?”


The Common Thread

All five patterns: the AI optimized for the happy path in the small.

PatternWhat AI Got RightWhat AI Missed
Race conditionClean separationConcurrent access
Resource leakError handling existsError path cleanup
Config driftReasonable defaultsProd constraints
CascadeParallelizationDependency classification
ObservabilityLogging existsDebugging at scale

Each passed tests. Each looked correct in isolation. Each failed when assumptions met reality.


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: