Context Engineering as Narration

User profiles are stories compressed into spreadsheets. LLMs let us uncompress them. This is both a context engineering challenge and a storytelling one.

We’ve been building user profiles for a long time.

A resume is a narrative of a career. Law enforcement builds behavioral profiles to predict what suspects might do next. Shows like Mindhunter and Criminal Minds are entirely predicated on investigators constructing psychological profiles and using them to anticipate behavior (or not).

All of these profiles are narratives. They tell stories about people, with motivations, histories, and trajectories.

When we started building software, we had to flatten user profiles into rows in a database. Computers couldn’t reason about prose. They didn’t understand narrative. So we used structured data: fields, values, things they could query and aggregate. 

But now we have systems that can make sense of narrative. The question is how we make the most of these capabilities.

A user profile – both as a set of attributes and as a narrative

The user profile as we know it

We are conditioned to think of a user profile as a set of attributes. 

It might be simple: last purchase date, email preferences, customer tier. Or it might be complex: ML feature vectors, propensity scores, behavioral clusters. User profiles are usually structured, quantitative, and designed for database queries and analytics. They answer questions like “who should we target with this promotion?” or “what products should we recommend?”

This approach has been the foundation of the software ecosystem. Recommendation engines need feature vectors. Segmentation tools need categorical labels. Analytics dashboards need numbers they can aggregate and chart.

But these profiles are snapshots, not stories.

A traditional profile captures what happened, not why. Consider: your profile says a user hasn’t bought ice cream in three months. But it can’t tell you why. Did they go on a diet? Develop lactose intolerance? Move to a colder climate? Simply got bored of it?

Traditional ML can try to infer causality through revealed preferences. Maybe the user has been buying lactose-free milk, or medication for lactose intolerance. A clever feature engineer might connect those dots. But this approach may not scale. 

It’s like looking at a single frame in a comic book. You can see what’s happening in that moment, but you don’t have the panels before and after that give it meaning. 

A graphic novel would show the doctor’s visit, the dietary change, the moment of temptation at the frozen aisle. A database row just shows:

{last_ice_cream_purchase: null, days_since_purchase: 90}.
The difference between a single frame – “I won’t buy ice cream” and a more coherent narrative

LLMs thrive on narratives

Building conversational and personalized AI experiences with large language models (LLMs) allow us to switch from a stats-based user profile to a narrative-based profile. This requirement reshapes how we think about user data.

Let’s address the obvious question: why can’t we just serialize our JSON user object into the prompt? LLMs can parse structured data, after all.

They can. But narrative representation offers something structured data doesn’t.

Narrative captures causality and motivation. Compare:

“She bought the standing desk because she mentioned back pain last month”

To:

{purchased: "standing_desk", category: "furniture"}.

Narrative could also handle ambiguity gracefully. “She seems to prefer premium brands, though she’ll opt for value options when buying in bulk” reads naturally. Encoding the same nuance in structured data requires awkward confidence scores and conditional logic.

And narrative is closer to how humans think about other humans, which is precisely what we’re asking the LLM to do. The profile needs to become something the LLM can reason about, not just reference.

The UX problem

There is a significant catch. We assume that the user would be willing to tell us what they are thinking when they ignore the ice cream and buy lactose-free milk.

This implies a UX shift, not just a data representation shift. 

Clickstream data tells you what users did. Conversational UX lets them tell you why. You need to build an experience where a user feels comfortable saying “I’m looking for a standing desk because I’ve been having back pain” rather than just clicking through product pages.

Look, I know that maybe 10-20% of people might provide further context on their decisions. Nobody likes talking to bots after all. But that 10-20% is gold. And the conversational interface creates the possibility of asking follow-up questions and perhaps building understanding over multiple interactions.

This is a significant departure from traditional eCommerce, which follows a browse → click → buy pattern. We’re moving toward something more like a conversation with a knowledgeable salesperson who remembers you.

There’s a chicken-and-egg challenge here. Users share more when they see value in sharing, but the experience only improves if they share. Solving this is a design problem, not just a technical one.

Looking ahead, this shift toward narrative has implications beyond human-to-AI interactions. As we move toward agentic commerce, users’ agents will decide what parts of their narrative to share with merchant agents. A narrative-based approach opens the door to richer agent-to-agent interactions. But that’s a topic for another post.

From attributes to character descriptions

What does the shift from attributes to narrative actually look like in practice?

Instead of:

{loyalty_tier: "gold", avg_order_value: 142, preferred_category: "electronics"}

You might have:

“Sara is a loyal customer who tends to make considered purchases, typically in the $100-200 range. She gravitates toward electronics and has shown growing interest in home automation over the past six months. Her recent browsing suggests she’s researching smart thermostats, possibly triggered by a move to a new home.”

The narrative captures nuance, temporality, and implied motivation. It allows the LLM to make decisions on a richer set of data.

There is a potential short-cut here: the LLM itself can generate this narrative.

You can use an LLM to transform database rows into prose, and the same LLM (or another) to consume that narrative for personalization. The narrative becomes a kind of intermediate representation, optimized for reasoning rather than storage.

This isn’t free, of course. The LLM generating the narrative needs guidance about what matters, which means you’re still encoding domain knowledge somewhere. You’re moving complexity from feature engineering to prompt and context engineering. But the output is more flexible, and the reasoning that consumes it is more powerful.

The context engineering challenge

Creating the narrative is only the beginning. The harder problems come after.

  • Selection: Which data points matter? A user’s purchase history might have thousands of entries. What belongs in the profile right now? One direction is to distinguish between what’s relevant to the current task versus what belongs in a general standing profile. Using embedding based retrievals, graph representations of user profiles, and other techniques could all be useful here.
  • Prominence: Not all facts are equally important. How do you weight recency versus frequency versus magnitude? Do you apply some sort of “salience algorithm”? Or do you let the LLM itself determine what to emphasize based on the current context.
  • Temporality: If a user mentioned they like chocolate ice cream two years ago, is that still relevant? What if they’ve since developed lactose intolerance? Approaches here include forgetting curves inspired by memory research, explicit decay functions, and contradiction detection that notices when new information conflicts with old.
  • Evolution: As users interact with your application, how do you update their profile? What triggers an update? This might involve event-driven updates, periodic summarization, or distinguishing between explicit signals (the user told you something) and inferred ones (you noticed a pattern).

This is where context engineering comes in. Engineers and designers must determine what’s important for the LLM to know in order to serve the user well. General-purpose tools like ChatGPT, Gemini, and Claude are introducing memory features, but these are designed for general use. Domain-specific applications can do better by encoding domain-specific knowledge about what matters.

Tools like Mem0, Zep, and LangMem are beginning to address these challenges, building memory layers that handle persistence, retrieval, and evolution. I’ll explore these memory architectures in more depth in an upcoming post.

It’s worth noting that dynamic generation adds latency and cost. For high-volume applications, you’ll likely cache narratives and update them periodically or on significant events, which reintroduces some of the “snapshot” quality we’re trying to escape. LLMs may be amazing, but they still demand that we think carefully about tradeoffs.

A different kind of engineering

I believe that building personalized AI experiences is fundamentally a narrative challenge.

We need to think about users the way novelists think about characters. Not as collections of attributes, but as people with stories that are still being written.

This isn’t a trivial problem. Research shows that even frontier models struggle to track dynamic user profiles accurately. The PersonaMem benchmark, which tests whether models can maintain and update user profiles across long conversations, found roughly 50% accuracy on profile evolution tasks.

Narrative profiles aren’t universally superior. For high-frequency, low-latency decisions (fraud detection, real-time bidding), structured profiles remain the right tool. And for brand-new users, you need some data before you can narrate anything. The cold start problem doesn’t disappear. But for the growing category of conversational, personalized experiences, the shift towards a narrative and profile driven approach will be inevitable.

Footnote:
This post is part of a series of posts on building with LLMs. You might find the following posts interesting:

The Lego Model: How Tool Calling Changes the Way We Build AI Applications

“What should I read next?” seems like a simple question. But answering it well requires knowing what the user is currently reading, what they’ve finished recently, and what’s already on their list. When I built QuietReads, a book tracking app with an AI assistant, I faced a choice: pre-load all this context on every request (expensive), build complex routing logic to fetch the right data (brittle), or find a different approach entirely.

In those post, I talk about why I chose this option, the architectural implications, and the tradeoffs involved. 

Conversational Interfaces Are Not Deterministic

A lot of mid-career technologists like me still think deterministically. We reach for decision trees, map out every probable permutation, and write test cases to cover each branch. This works well for forms and structured APIs where you control the inputs.

But QuietReads has a chat interface. Users might ask:

  • “What should I read next?”
  • “I’m in the mood for something like the last book I finished, but shorter”
  • “What were my thoughts on that dystopian novel from last month?”
Asking the QuietReads assistant to recommend some books

Each query requires different context. The first needs the user’s want-to-read list. The second needs their recently finished books plus some understanding of “shorter.” The third requires searching through their notes. I couldn’t predict which context any given question would need, and I didn’t want to fetch everything every time.

The traditional approach would be routing logic. For just the first query, you might write something like:

def get_context_for_recommendation(message, user_id):
    context = {}

    if contains_recommendation_intent(message):
        context['want_to_read'] = get_want_to_read_books(user_id)
        context['recently_finished'] = get_recently_finished(user_id)

        if mentions_specific_book(message):
            book = extract_book_reference(message)
            context['book_details'] = get_book_details(book)

    # ... and this continues for every intent type
    return context

This gets unwieldy fast. Each new question type requires new routing rules. The intent detection functions themselves need maintenance. And you’re constantly guessing what context the model will need.

LLMs allow us to use a different mental model. 

Think of LLMs as expert Lego assemblers. You provide a curated set of bricks (tools), an instruction manual (your system prompt), and let the assembler determine which bricks to use and in what order. You don’t hand them every brick in existence. You give them the right pieces for the task and clear guidance on when to use each one.

Tools as Building Blocks

In QuietReads, I define “context tools” that let the AI retrieve user data as needed:

CONTEXT_TOOLS = [
    {
        "name": "get_user_profile",
        "description": (
            "Get the user's name and reading preferences. Use this when you need to "
            "personalize your response or discuss their reading interests."
        ),
        "input_schema": {"type": "object", "properties": {}}
    },
    {
        "name": "get_want_to_read",
        "description": (
            "Get books on the user's want-to-read list. IMPORTANT: Always call this "
            "BEFORE recommending any books to avoid suggesting books they already have."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {"type": "integer", "description": "Max books to return (default: 10)"}
            }
        }
    }
]


Each tool has a clear description of when to use it. The model reads these descriptions and decides which tools to call based on the user’s question.

The Agentic Loop

When the model decides to use a tool, we handle that request, execute the tool, and feed the results back. This creates a loop (simplified code below):

async def execute(self, system, messages, tools, tool_handlers, max_tokens=2048):
    response = self.client.messages.create(
        model=self.model,
        max_tokens=max_tokens,
        system=system,
        messages=messages,
        tools=tools
    )

    while response.stop_reason == "tool_use":
        tool_results = []

        for block in response.content:
            if block.type == "tool_use":
                tool_name = block.name
                tool_input = block.input

                # Execute the tool and capture result
                result = await tool_handlers[tool_name](tool_input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })

        # Feed results back and get next response
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})
        response = self.client.messages.create(...)

    return response

The model might call multiple tools, or call the same tool with different parameters, or decide it has enough context after the first call. 

The loop continues until the model has everything it needs to answer. In practice, you should also enforce a maximum iteration count as a guardrail against runaway loops or unexpectedly expensive queries. When the model requests multiple tools in a single response, you can execute them in parallel for performance gains.

This code handles the happy path. Production implementations need additional safeguards: error handling when tools fail or timeout, validation of tool inputs before execution, and graceful handling when the model hallucinates a tool name that doesn’t exist.

Client and Server-side Tools

QuietReads uses two categories of tools. Client-side tools are functions I implement: when the model calls get_want_to_read, my code queries the database and returns formatted results.

Server-side tools are capabilities the AI provider offers (like the web_search tool used below). 

I enable web search so the assistant can look up recent book releases or author news. Anthropic’s infrastructure handles the search; I just control when and how it’s available:

tools:
  web_search:
    type: "web_search_20250305"
    name: "web_search"
    server_side: true
    enabled: true
    config:
      max_uses: 5  # Limit searches per request

With some prompt engineering, the model can integrate custom database queries with real-time web searches, producing responses that feel coherent to the user. 

The system prompt guides how the model synthesizes information from different sources, when to cite web results versus personal reading history, and how to maintain a consistent voice across tool-augmented responses.

Architectural Implications

It is important to recognize where determinism matters and where it doesn’t. Each tool is a testable piece of code. I can unit test get_want_to_read in isolation, verify it returns the right data, and trust it to behave consistently. What I can’t fully predict is which tools the model will call or in what order. In my work, I have found that even cheaper models like the Haiku family of models do a decent job at tool use.

This separation has practical implications. Tool descriptions are instructions the model uses to decide when to call each tool. Writing clear, specific descriptions is as important as the implementation itself. Instead of pre-loading everything a user might need, I provide minimal context upfront and let the model request more, keeping initial requests fast and reducing token costs.

And while the model chooses its tools, I still control the boundaries. QuietReads runs input guardrails before messages reach the assistant and can validate outputs before returning them to the user.

The Tradeoffs

Tool calling introduces real costs that you should weigh against your specific requirements.

  • Predictability. With static context, you know exactly what data the model sees on every request. With tool calling, the model decides what to retrieve. This makes cost and performance harder to predict. A simple question might resolve in one API call; a complex one might trigger four tool calls and five round-trips.
  • Prompt caching. Static context can benefit significantly from prompt caching, where repeated system prompts are stored and reused. Dynamic tool results change with each request, which can reduce or eliminate caching benefits. Depending on your usage patterns, this could meaningfully impact both latency and cost.
  • Quality assurance. Unit testing individual tools is straightforward, but testing the system end-to-end becomes harder. The model might call tools in unexpected combinations, or skip tools you expected it to use. Comprehensive evaluations become essential because tool calling adds non-determinism to the critical path. I’ll write more about evaluation strategies in a future post.
  • Refactoring risk. IDE tooling can automatically update function signatures across a codebase. Tool definitions live in JSON objects that describe behavior and parameters in natural language. If you change a tool’s behavior or modify its parameters, automated refactoring won’t catch the JSON definitions, and the mismatch may not surface until production. LLM-based coding agents like Claude Code handle this well, and adding tool-specific checks to code review agents helps catch these issues.
  • Latency. Each iteration of the agentic loop requires a round-trip to the API. For QuietReads, this is acceptable. For applications where response time is critical, the additional latency may be a dealbreaker.

That said, tool calling offers real advantages beyond flexibility. Token costs can decrease because the model only retrieves data it actually needs. Direct tool calls avoid the protocol overhead of intermediary layers like MCP servers. And tools create a clean separation of concerns: database migrations, API upgrades, or new data providers can happen without touching the prompt.

This pattern fits QuietReads: read-only tools, flexible latency, and context costs that exceed API overhead. Applications with side effects, strict latency, or predictable context needs may want different approaches.

Navigating a Mindset Shift

Building with tool calling requires accepting the risk of non-deterministic code execution. You cannot predict every code path. Instead of mapping out decision trees, you’re designing capabilities and constraints. You’re giving the model a well-stocked toolbox and clear guidance, then trusting it to assemble the right response.

You control what tools exist, what data they access, what the model knows about when to use them, and what guardrails prevent misuse. The model handles the dynamic orchestration that would otherwise require hundreds of lines of if/else chains.

For those of us who’ve spent years thinking in flowcharts, this shift takes practice. But once it clicks, you start asking different questions: not “what are all the paths a user might take?” but “what capabilities does the model need, and how do I describe when to use them?”

Building at the Speed of Thought

On building QuietReads, Claude Code, and the inversion in software economics

I have wanted to build QuietReads for two decades.

The idea is simple: a book tracking application that treats reading as a reflective practice rather than a social performance. Just you, your books, and an AI companion that remembers what you’ve read and can discuss it with you.

Every few years, I would sketch out the features, maybe prototype a database schema, and then abandon the project when the scope overwhelmed the time I could spare. The economics never worked. Building a full-stack application with authentication, third-party integrations, and a sophisticated AI layer would take months of focused effort. I had a day job. I had a family. QuietReads stayed in the drawer.

Then came Thanksgiving 2025. Anthropic rolled out $1000 in API credits for Max subscribers to use with Claude Code, their agentic coding tool. The Opus 4.5 model had just launched. I decided to try again.

Two weeks later, QuietReads was live.

QuietReads organizes books in a Library View

What Changed?

This wasn’t my first attempt at building QuietReads with AI tools. Last summer, I tried vibe-coding the same application using Replit, Cursor, and an early version of Claude Code running Sonnet 4. The results were miserable. The AI agents went off the rails constantly, making changes I didn’t ask for, getting stuck in loops, producing code that looked plausible but broke in subtle ways. I gave up after burning through credits and several frustrating weekends.

The difference with Opus 4.5 was stark. 

Where Sonnet 4 required constant hand-holding, Opus 4.5 understood what I was trying to build. It made architectural decisions that made sense. When I pointed it at a bug, it found the root cause rather than applying band-aids. It demonstrated genuine systems thinking: analyzing trade-offs, proposing multiple approaches with honest assessments of pros and cons, thinking through downstream implications.

Claude Code making suggestions on how to refactor the AI Assistant to make tool calling more consistent

The screenshot above shows Claude Code reasoning through three different implementation approaches for a feature, weighing simplicity against performance against pattern consistency. This is a discussion I would expect to have with a senior developer. The options made sense, and I was able to make an informed decision on how I wanted to structure a key component of the application.

I shipped a full-stack, mobile-responsive application with social login, Google Books integration, and an AI reading companion that maintains persistent memory of your reading history. The companion can engage in literary discussion, reference your previous notes, and connect themes across books you’ve read months apart. You can @mention it in any note or journal entry, and it responds with context. Everything flows into a unified timeline that weaves together notes, reading sessions, and AI conversations.

Eko, QuietReads’s default AI Assistant helps me understand Neil Postman’s work. Books in AI responses are interactive components, allowing me to add them straight to the library.

The Inversion

My experience with QuietReads is a small data point in a larger pattern. We are navigating an inversion in how software gets built.

For decades, every decision about software development has been constrained by the scarcity of labor. Whether to buy or build. Whether to refactor legacy code or start fresh. Whether to ship now or wait for more resources. 

The person has always been the limiting factor, the most expensive line item, the ever-present bottleneck. 

The language of our industry reflects this: we estimate in person-days, bill in person-hours, staff projects in person-months.

That bottleneck is fading faster than I expected.

Boris Cherny, the creator of Claude Code at Anthropic, recently revealed that he didn’t open an IDE for an entire month. Every line of code he shipped during that period (259 pull requests, 497 commits, 40,000 lines added, 38,000 removed) was written entirely by Claude Code powered by Opus 4.5.

“Software engineering is radically changing,” Cherny wrote, “and the hardest part even for early adopters and practitioners like us is to continue to re-adjust our expectations.”

I am not Boris Cherny. I don’t work at Anthropic. I don’t have access to internal builds or institutional knowledge. But my experience over the holidays rhymes with his. And I’m not alone. Developers across the industry spent the holiday break shipping projects that had languished for years, finally made tractable by the combination of Opus 4.5 and Claude Code’s improved agentic capabilities.

Lots of people had “Claude Code” moments over the holidays..

So What Does This Mean for People Like Us?

In March 2025, Anthropic CEO Dario Amodei told the Council on Foreign Relations that he expected AI to be writing 90% of code within three to six months, and essentially all code within twelve months. The industry-wide numbers haven’t hit those marks. But within certain teams and workflows, his timeline looks prescient. Cherny’s experience is evidence. So is mine.

Software engineering as we know it is going to change faster than many of us imagined. The fundamental assumptions that drive economic decisions around building software (hiring models, training investments, vendor selection, build-versus-buy calculations) will all need revisiting. The implications extend beyond individual productivity gains into questions about how power accrues to capital and compute, and what happens to labor markets when a significant category of knowledge work becomes dramatically cheaper to produce (See footnote).

Remember: the models you’re using today are the worst they will ever be.

A User Base of One

On a more personal note: I’ve enjoyed using QuietReads. It is very much an application I built for myself, though perhaps you might enjoy it too (sign up here – let me know what you think). I have a backlog of features I want to add (voice notes, OCR for capturing physical book passages, maybe a Kindle integration to sync highlights) and I’m confident I can build them quickly.

I look forward to building more applications. Maybe something to help train my recalcitrant hound dog to stop stealing food. Maybe something to help my daughters learn Gujarati.

What I know for certain is that my view of how software gets built has shifted faster than I expected. Coming to terms with the pace of improvement has required repeated recalibration.

I am equal parts excited and terrified about what comes next.


Footnote:

There has been a lot of discussion about the macro-economic implications of broad AI adoption. From Dwarkesh Patel and Philip Trammell talking about Capital in the 22nd Century, to many, many posts that swing from breathless excitement to abject terror. Maybe we may even see an acute version of Baumol’s cost disease – where a significant bump in software productivity drives up costs and inflation as lower productivity sectors raise wages to compete leading to a hyper-inflationary spiral? Or perhaps Jevon’s paradox will reign supreme and we will end up with an absolute explosion of software tools. 

2025: The Year I Became A Cyborg

In chemistry, activation energy is the minimum energy required to start a reaction. It’s the barrier between potential and action, between “I could” and “I did.”

This year, AI collapsed that barrier for me.

As foundation models became better, and the tools built on top of them became more useful, the gap between having an idea and acting on it shrank to almost nothing. And for someone whose natural disposition is to try things, to experiment, to see what happens, this has been transformative. I have embraced using AI for work and play and for much else. 

Image generated using Gemini Pro

From Thought to Artifact

I’ve written more consistently this year than ever before. 

For many years, I used to maintain a list of ideas that I wanted to explore. Bookmarked sites, academic papers, and spicy social media takes. These ideas often were just abandoned or ignored until I forgot why I wrote them down in the first place.

Now, the time between having an idea, doing the research, writing an outline and then publishing it online has shrunk significantly because of AI. I use skills, deep research agents, and a set of prompts that have let me express myself faster and more coherently than ever before. 

My recent post “The Same Window For Everything” exists because I noticed something interesting while reading Kiran Desai, opened Claude to think it through, and found myself with the skeleton of an essay. A year ago, that observation might have stayed in my head, filed away with all the other thoughts that never quite made it to the page.

There’s a passion project I’ve been building, a reading companion I’ll be launching soon. It exists because, in 2025, the distance from “what if I built this?” to “let me try” became trivially small. Just like my writing ideas, I have a huge list of side projects and experiments that I wanted to try but never got off the ground. This year, I did.

But, it’s not all serious stuff! I vibe-coded (built quickly with AI assistance) a tool to help me journal regularly. I use AI to help plan dinner for my kids. I have setup my phone so it launches ChatGPT in “search mode” at the press of a button. And, I ask it all kinds of questions! From dealing with my dog’s flatulence to figuring out why the minivan doors won’t open. I now look up things where before I would have just shrugged and moved on.

I have a different relationship now with making things; one where the cost of trying something has dropped low enough that I actually try it.

The Professional Stakes

Looking up recipes for spaghetti carbonara is all well and good, but AI has had a significant impact on my work as well. 

At Jeavio, we’ve taken on more ambitious, outcome-oriented projects. Internal initiatives I sponsor, like our campus programs, have become more ambitious and aggressive because I believe we can get them done. That belief comes from now having enough experience with using AI tools to be confident on what my teams can and should be able to deliver.

We ran a company-wide hackathon late last year and a product-focused one in 2025. The hackathons shifted how Jeavio thinks about and uses AI tooling. They encouraged experimentation and built collective confidence about what capabilities these tools could unlock.

It’s not all fun and cheap inference though. Some projects have been challenging. We’re working at the frontier of what’s possible, and frontiers can be uncomfortable places. 

But my risk appetite has increased. I understand the tools better now. I know what Cursor and Claude Code can do and, equally important, where they fall short. I have a clearer understanding of what guardrails should be in place and how to evaluate the performance of inherently probabilistic systems. That understanding translates into confidence: confidence to take on projects with ambiguity, and confidence to deliver clearer projects faster and more predictably.

The throughline is the same as the personal examples: lower activation energy. Faster exploration, quicker iteration, more willingness to try things that might not work than ever before. For my work at Jeavio, this is an energizing change. 

Cyborgs and Foxes

Two frameworks have helped me make sense of what’s changed.

Ethan Mollick, in his research on AI and knowledge work, distinguishes between Centaurs and Cyborgs. Centaurs maintain a clear division of labor between human and machine, handing off discrete tasks to AI. Cyborgs blend the two. As Mollick puts it:

“Cyborgs don’t just delegate tasks; they intertwine their efforts with AI, moving back and forth over the jagged frontier.”

I’ve become a Cyborg.

AI is woven into how I think, write, and build. When I’m reading and want to explore an idea, I open Claude. When I’m coding and hit a wall, I think through the problem with an AI collaborator. The boundaries between what is truly my work and what is AI-mediated have become somewhat meaningless.

The second framework comes from David Epstein’s book Range: Why Generalists Triumph in a Specialized World. Drawing on Isaiah Berlin’s famous distinction and Philip Tetlock’s research on forecasting, Epstein contrasts hedgehogs, who know one big thing deeply, with foxes, who know many things and integrate broadly. 

Hedgehogs thrive in stable, rule-bound environments. Foxes thrive in ambiguous, rapidly-changing ones. As Epstein writes:

“Foxes see complexity in what others mistake for simple cause and effect. They understand that most cause-and-effect relationships are probabilistic, not deterministic.”

I’ve always been a fox.

Broad curiosity, comfort with ambiguity, a tendency to wander across domains. But being a fox is expensive and risky. Every new domain requires starting from scratch. The activation energy to explore something unfamiliar is high.

AI subsidizes that cost. Becoming a Cyborg helps make my fox-like tendencies viable in ways they weren’t before. I can move into an unfamiliar domain, quickly get oriented, experiment, and learn, all without the friction that used to make such exploration feel indulgent.

These frameworks work on orthogonal dimensions. Cyborg describes how I work. Fox describes who I am. Becoming a Cyborg made me more comfortable leaning into my fox-ness.

Looking Forward

I’m aware this could sound like boosterism. The AI discourse is full of inflated predictions and productivity theater.

So here’s what 2025 has taught me. Becoming a Cyborg works for me. This may not always be true.

Maybe I am just a frog slowly boiling to irrelevance as AI takes away my agency and creativity. 

Maybe the AI bubble might burst, and Anthropic and OpenAI will raise prices making vibe-coding a passion project or having a long conversations about literary fiction non-viable.

But until then: the distance between curiosity and creation has collapsed. I intend to exploit that gap.

More experiments. More wacky things. More small wins and instructive failures. The activation energy is low, and I have a lot of ideas. Bring on 2026.

Do agentic coding tools make us less productive?

Are agentic coding tools like Cursor making us less productive?

An interesting study from METR (HT The Pragmatic Engineer newsletter) found that use of LLM-assisted coding tools like Cursor made some experienced developers *less* productive than before. The catch? Most of these developers had used Cursor for less than 50 hours.

I’ve been vibe coding a webapp as my summer project using Claude Code and Cursor, and this finding resonates with my experience.

Here’s what I think is happening:

➡️ IDEs are incredibly complex tools with steep learning curves
Fifty hours isn’t nearly enough time to develop the muscle memory needed for effective coding with any new IDE. I’ve stuck with IntelliJ for years precisely because of this learning curve investment, and even now when I’m not evaluating Cursor, I use Claude Code with IntelliJ.

➡️ AI tools are black boxes in ways that aren’t obvious
You might assume Cursor simply passes what you type to the underlying LLM, but these tools use code search, context compaction, and LLM routing behind the scenes. What you type gets significantly modified before reaching the AI, making it nearly impossible to develop a mental model of what’s happening. Cursor updates far more frequently than any IDE I’ve used, and those “optimizations” can change behavior without warning.

➡️ Agentic workflows break the flow state
It’s easy to get distracted when your AI agent is off deciding what file to change next. By the time the agent is done, I’ve often forgotten what I was working on. I honestly don’t understand how people running multiple instances of Claude Code stay productive.

📝 My experience so far
The magic moments when Claude Code one-shots a complex solution are incredible. But like I discovered while switching from Replit’s authentication to Supabase, it can be a grind. The auth switch seemed to work perfectly at first, but broke other parts of my app in subtle ways. Later, migrating the database to Supabase was a disaster – I forgot to update my CLAUDE.md file with the new libraries, so Claude Code got confused and burned through tokens trying to figure out my setup. Technically user error, but exactly the kind of mistake you only learn to avoid through experience.

I expect my productivity will improve significantly as I develop better workflows around these tools. Until then, vibe coding feels like being a new parent – periods of genuine frustration interspersed with moments of sheer amazement.

Google, Microsoft and the Search Wars

A demo cost Google’s shareholders $100bn dollars last week. Why?

Google’s Share Price after the Bard event

Google has dominated search and online advertising for the last twenty years. And yet, it seems badly shaken by Microsoft’s moves to include a ChatGPT-like model in Bing search results. 

Why is this a threat to Google?

1️⃣ Advertising: Google’s revenues are driven by the advertisements it displays next to search results. The integration of language models allows users to get answers – removing the need to navigate to websites or view ads for a significant subset of queries.

2️⃣ Capital Expenditure: Search queries on Google cost around $0.01 (see link in the comments for some analysis). Integrating an LLM like ChatGPT *could* cost an additional 4/10th of a cent per query since the costs of training and inference are high. Even with optimization, integrating LLMs into Google search will increase costs in running search queries. According to some estimates, this directly impacts the bottom line to almost $40bn. 

3️⃣ Microsoft’s Position: Bing (and, more broadly, search) represents a small portion of Microsoft’s total revenues. Microsoft can afford to make search expensive and disrupt Google’s near-monopoly. Indeed Satya Nadella, in his interviews last week, said as much (see comments). 

4️⃣ Google’s Cautious AI Strategy: Google remains a pioneer in AI research. After all, the “T” in GPT stands for Transformer – a type of ML model created at Google! Google’s strategy has to sprinkle AI in products such as Assistant, Gmail, Google Docs, etc. While they probably have sophisticated LLMs (see LaMDA, for example) on hand, Google seems to have held off releasing an AI-first product to avoid disrupting their search monopoly. 

5️⃣ Curse of the demo: Google’s AI presentation seemed rushed and a clear reaction to Microsoft’s moves. LLMs are known to generate inaccurate results, but they didn’t catch a seemingly obvious error made by their BARD LLM in a recorded video. This further reinforced the market sentiment that Google seems to have lost its way.

References and Further Reading

Ben Thomson’s “4 Horsemen of the Tech Recession”

In the last month, we have had huge layoffs across technology, yet the “real economy” seems robust. What is going on?

Meta is making 2023 ‘a year of efficiency’. Microsoft, Alphabet, and many other companies have stated economic headwinds as the reason for letting thousands of people go. 

However, last week, the US posted the lowest unemployment numbers in 50 years(!) while adding half a million jobs. 

Ben Thomson discusses this in this week’s excellent Stratechery article. 

He points to 4 factors that are causing this disconnect:

1️⃣ 😷 The COVID Hangover -> Companies assumed COVID meant a permanent acceleration of eCommerce spending. Customer behavior has reverted (to a certain extent) to pre-pandemic patterns

2️⃣ 💻 The Hardware Cycle -> Hardware spending is cyclical. After bringing forward spending due to the pandemic, customers are unlikely to buy new hardware for a while.

3️⃣ 📈 Rising interest rates -> The era of free money is over. Investing in loss-making technology companies in anticipation of a future payout is no longer attractive.

4️⃣ 🛑 Apple’s Application Tracking Transparency (ATT) -> ATT has made it difficult to track the effectiveness of advertising spending. This caused enormous problems for companies like Meta, Snap, etc. that rely on advertising.

From Elden Ring to Hades: What Video Game Design Taught Me About Management

Introduction – Exploring the Lands Between

I have played video games for thirty years. With two kids and a busy job, I don’t get as much time to play as I would like, but I pick up my Xbox controller whenever I get a chance. Over the last few months, this has meant playing Elden Ring, a role-playing game where you explore and adventure in a fantastical realm called the Lands Between.

Elden Ring – Bandai Namco

I am terrible at Elden Ring, yet I have spent hours playing it over the last six months. I am in awe of the game’s scale, beauty, and challenge.

When not playing video games, I support software development teams. Over the last ten years, I have worked as an Engineering Manager and, more recently, as a CTO at early-stage startups.

Managing and supporting teams is hard. You have to balance competing priorities and make decisions under conditions of ambiguity. Periods of stability can be interrupted by crises. It’s not that different from playing a game like Elden Ring!

As I reflected on why I enjoyed playing some video games more than others, I realized there are parallels between excellent video game design and supporting high-performance teams.

In this post, I explore what makes a video game great and what lessons we can apply from video game design to become better managers.


What Makes a Good Video Game?

Video game design is a vast and evolving topic. However, there are three critical elements to a good video game.

Good vs. Bad Video Design

The Story: What is this video game about, and why should I bother playing it?

A good video game story makes players want to invest their time in learning more about the world and the story. Games like Elden Ring, Horizon Zero Dawn, and God of War have stories that push players to do all sorts of side quests and missions. Exploring the world helps fill out the story, and each task moves the character and the story forward.

Hades – Supergiant Games

The Setting: Where am I going to be spending my time?

Seasoned gamers are familiar with the “one more turn” phenomenon. You want to keep playing because the game world is so darn fun. Dungeons filled with loot (and traps), exciting side missions, and beautiful scenery make the game’s exploration and progression fun. Games like the Mass Effect series make you care not just about the characters but also the broader game world and lore.

Gameplay Loop: How do I play the game?

Elden Ring is brutally difficult, yet I keep returning to the game. The reason is that while challenging, the gameplay is fair and predictable. And I get a real sense of accomplishment after clearing a particularly tricky dungeon or boss encounter. Hades is another game that has wonderfully compelling gameplay. Great video games have a simple yet addictive core gameplay loop. These are the actions that a player is expected to perform most often to make progress in the game. These must be balanced to avoid tedium while presenting fun and challenging experience.


From Video Games to High-Performance Teams

What do video games have to teach us about supporting high-performance teams?

We will approach this by looking at the same attributes that we explored for successful video games:

  • Story → Vision
  • Setting → Workplace
  • Gameplay → The Day-to-Day Work

Vision: Why am I being asked to do this?

A compelling narrative is about selling a vision – what will the player gain at the end of this quest line, boss battle, or challenging project? An honest, well-articulated vision helps give direction to a team. In his viral talk, “Start with Why,” Simon Sinek talks in detail about this “inside out” approach.
Having a vision contradictory or inconsistent with the day-to-day work could lead to frustration and a lack of trust.
The narrative must be straightforward and backed up with action aligned with the company’s stated values.

Workplace: Where do I spend my time?

A leader must create a workplace that maximizes productivity while allowing creativity, serendipity, and exploration. This is true both for in-person and remote work. Encouraging (reasonable) risk-taking and exploration enable more engaged and motivated teams.
A video game with a predictable and tired setting (post-apocalyptic zombie infestations, for example..) is boring. Similarly, an environment that is dull or unpleasant is a drag on motivation and productivity.
Psychological safety is also essential. As any player of online games knows, dealing with abuse and cheating makes for a miserable experience. A workplace perceived as hostile and a leader unwilling to support and protect their team will lead to people walking out of the door.

The Day to Day: How I do my work… 

A manager must focus on the “gameplay” loop for their team. What are the challenges that stop them from doing their work? For software engineering teams, this could be the ease of making changes, getting pull requests approved, and getting changes into production.
I have rage-quit lots of games because “life is too short.” Online games where I keep getting taken out by snarky teenagers with fast twitch reflexes are a particular bug-bear. Elden Ring can also veer into frustrating territory until I realized I could avoid most difficult encounters until I was leveled up and ready.
When supporting a team, you need to consider what can be done to remove obstacles for your team. It may mean picking the right battles and knowing when to compromise.
Making the workday loop engaging for your team is a critical leadership skill.


Conclusion – Gaming and Learning

Video games are the dominant entertainment and artistic form of our time. Oscar Wilde opined, “Life Imitates Art far more often than Art Imitates Life.” I agree.

Video games have been around far longer than modern software engineering tools such as Agile, DevOps, and other current paradigms. The art of video game design has been refined through decades of experimentation and many, many failures.

Indeed, as managers, most of us will be supporting teams that grew up playing video games. As a medium, video games create interactive, compelling worlds where people enjoy spending their time.

Taking cues from how video games are designed could help us become more effective supporters and advocates for our teams.


George Saunders on Feedback

Feedback is an integral part of working in a team and managing people.

Code reviews, architectural reviews, 1-1s, and Sprint Retrospectives, are all situations that involve giving (and receiving) feedback as software engineers, product managers, and engineering managers. Yet, giving critical feedback can be a difficult and stressful experience. So how best to navigate these potentially adversarial situations?

George Saunders is one of my favorite contemporary writers. He has an excellent Substack called Story Club. In this week’s post, Saunders talks about giving feedback to other writers. While his advice is in the context of a writers workshop, I found it quite applicable to my work.

Saunders advices us to give specific yet kind feedback:

.. as we learn to analyze and diagnose with increased specificity and precision, the potential for hurt feelings diminishes, because we are offering specific, actionable ways (easy ways, often, ways that excite the writer, once she’s made aware of them) to make the story better. And who doesn’t want some of that?

George Saunders

Giving constructive or critical feedback is integral to working as a software engineer. Yet, these conversations can become challenging. 

One might be tempted not to say anything or speak in the most generic and broad terms to avoid offense. Instead, as Saunders suggests, the focus should be on giving thoughtful, specific, precise, and actionable feedback:

In this [giving feedback], we indicate that we are on the writer’s side, we are rooting for her and are glad to have found these small but definite ways to make her story better. There’s no snark, no competition, no dismissiveness, nothing negative or accusatory about it; just the feeling that we, her readers, are coming together with her, the writer, by way of craft. We’re all on the same team, the team of art.

George Saunders

Not much more to add is there?

Between Rock and a.. podcast?

Just because you can do it doesn’t make it a great business model. Take music streaming, for example.

Image by Chloe Ridgeway on Unsplash

Spotify, the world’s most popular streaming service, has been the target of some Internet ire in the last week or so. Neil Young, the creator of the legendary Pono digital media player (apparently he made some music too?), decided he didn’t want anything to do with Spotify. 

Why all the righteous indignation?

Spotify pays Joe Rogan, a media personality / MMA commentator / master of “doing his own research,” over $100m to have exclusive rights to his wildly popular podcast. 

Apparently, Mr. Rogan has some interesting ideas around COVID, vaccinations, and horse de-worming medication. Not particularly controversial topics 😬. 

Why is this a big deal for Spotify?

Music streaming is a terrible business. Spotify has been bleeding cash for years and only recently turned a meager profit. The company had an operating margin of 1.4% in the first nine months of last year. No hockey sticks in sight.

The reason? It has to pay royalties to music labels for each music stream. The value from streaming accrues to the music companies, not to the streamers or artists.

Spotify makes its money not from streaming but from selling subscriptions and advertising. 

This is where podcasts come in. Spotify pays millions to Joe Rogan because he brings in a massive audience in the highly desirable 18-34 demographic. Spotify offers targeted advertising on podcasts to its most important customers, advertisers. This makes much more economic sense than making tiny margins on each stream of, let’s say, “Rockin’ in the Free World.” 

The risk to Spotify in this, slightly ridiculous, situation is not losing access to rock & roll; its not being able to monetize their investments in podcasting. 

Spotify would rather you come for the music and stay for Elon Musk smoking some fine herb  with his buddy Joe Rogan. 

They have set up expectations for their users that they can stream any song at any time. So they have to double down on more economically viable content like the Joe Rogan Experience. 

I am sure there is a Neil Young song about rocks and hard places..