Category: Software

It’s been approximately 1 million years since I last wrote about how I use and think about LLMs. I figure I should document the state of things these days; if nothing else, it will be a time capsule.

Coding vs Writing

Something like 99% of my coding is done by agents now. If I’m in a text editor or IDE, it’s almost always to write prose (documentation, comments, etc.) because:

  1. Writing is an act of thinking; it helps me gather and focus my thoughts
  2. I want to give my coworkers a positive signal that I have put thought+effort into whatever I ask them to read. Text that was obviously generated by an LLM is a negative signal.

Agent harnesses

The Codex and Claude desktop apps are my daily drivers, I mostly stopped using the TUIs a few months ago. They have nicer UI with lots of bells and whistles, and they can both connect to remote servers over SSH. There isn’t as much reason to build or use sophisticated TUIs these days, and that’s a little bittersweet for me as someone who spent a lot of time getting good at building terminal applications.

I spend about half of my time in auto mode and half in YOLO mode. I’d like to use auto mode 100% of the time, but in Codex I find it a little buggy.

Models

Model-wise, I’ve been impressed (like everyone else) by gpt-5.6-sol and Claude Fable. I threw them both at some problems that previous agents struggled with (multiplayer text editing in a very idiosyncratic web app, remote connections in my systemd UI) and they nailed them. I’m also very impressed by the price/performance of gpt-5.6-luna and have switched to it for lower-end-higher-volume use cases. I’m using Anthropic models a bit less than OpenAI ones these days, partially due to cost/value and partially because Claude-isms are wearing on me.

I’ve also played around with OpenCode and some of the top-tier Chinese models; GLM 5.2 is quite capable and can be served very very fast. The speed makes more of a difference than I expected, I think because it helps with deep focus. When the agent returns back to me ~immediately, I’m far less likely to get distracted and context-switch.

Cheap LLMs open up new use cases

The price of decent models has dropped a lot lately, and that opens up some interesting new use cases. Bluesky supports custom feeds, and now I consume most of my social media through a custom feed which scores+filters every post by people I follow through Luna for about $5/mo. Nobody’s immune to rage-bait and doom-scrolling (least of all me), and this is a remarkably effective way to curate my social media diet. I spent a bit of time experimenting with even cheaper models on OpenRouter, but wasn’t happy with the performance compared to Luna.

“The future is already here – it’s just not evenly distributed”

This old Gibson quote comes to mind nearly every day. I have access to better budgeting and financial tracking than anyone using bank and fintech websites, because coding agents are great when given a bunch of CSVs. I’m sure fintech companies will catch up eventually, but right now we’re in an interesting in-between period.

Likewise, the custom filtering I do with Bluesky seems like a natural feature for any social media network once the cost comes down a bit more.

And this isn’t even touching on the huge numbers of non-programmer domain experts who haven’t yet realized that they can speak their own custom software into existence. That will change, and it’s going to be interesting to watch.

Intentional learning

Back when we were writing code “by hand”, it was easy to develop a deep understanding of your code because you had to think real hard about how to write it! That’s no longer the case.

I’ve tried a few different approaches for intentional learning and this skill by Cat Hicks has been the only one that’s stuck. It prompts an agent into quizzing you on the code it just wrote; I find that engaging in that dialogue is useful because it forces me to meaningfully engage with the material (as opposed to documentation that is easy to skim over).

Local voice transcription is really good now

I make heavy use of voice transcription (mostly via Handy and one of the Parakeet transcription models). Agents do better the more (relevant) context you give them, and rambling into a microphone is a very fast way to dump context. This would be very annoying to my coworkers if I worked in an office, so I’ll enjoy this little perk of remote work while I can.

More vs Better

Sometimes I use these tools to ship something fast, quality be damned. And other times I use them to ship better software than I’d otherwise be able to. As a example, systemctl-tui now has a sophisticated test suite that runs across a large number of systemd versions; this is something I could have built on my own but simply didn’t have the time for, and it caught real bugs the first time I ran it.

David Crespo’s “I’m not cutting corners” post is a good read on this topic.

It’s been quite a year. Here’s a grab bag of what stuck in my mind from 2025 and what’s coming next.

Personal Stuff

I started 2025 with a New Year’s resolution to deadlift 300lb. I hit 300 in the middle of the year, upped my resolution to 350lb, but only made it to 310lb. I’d like to hit 350 in 2026, but if I plateau and stay healthy I’m fine with that too.

My big resolution for the new year is to host a gathering at least once a month. Doesn’t have to be anything fancy, just an excuse to see people. My plan to make this happen: even if I’m busy, it’s easy to make a big batch of pasta and have guests bring side dishes and wine.

2025 was the first year I really noticed my dog getting old. He’s 11 and doing well for his age, but I’m more aware that I probably only have a few more years left with him. Retired racing greyhounds make great pets:

We spent a lot of time in late 2025 looking at real estate. After 7 years we’re a little bored of our current place and it’d be nice to have a bedroom for guests. It’s been an emotional roller-coaster; in November we had an offer accepted for a gorgeous condo downtown, only to back out after the inspection found some issues. But hey, it could be worse - when we bought our current place the market was so hot that people didn’t even get inspections done 😬.

Work Stuff

I got promoted to Staff Engineer in December. This required a ton of effort plus some luck, and I’m very proud of it; feels like I finally made it, y’know? The promotion felt especially good because during the tech job rout of late 2022 I accepted a Staff offer from another mid-sized tech company, and then after 3 weeks of delay they retracted it.

Overall it was a very good year for work. I shipped a product, worked on a high-profile keynote demo with OpenAI, and changed teams to launch a new product that’s attracting a lot of interest. I also flew down to SF twice to give talks for work; here’s one I’m particularly proud of.

Software

It is an incredibly crazy time to be working in software.

It feels like 2025 was the year where agents blew up. 1 year ago, I was occasionally using Aider to make commit-sized changes to software projects, and I felt like I was ahead of the curve. Today I tend to use Claude Code (sometimes Codex CLI) to make more ambitious changes, and they are far more capable of iterating on a change until they get it right.

My day to day now involves less “hands-on” coding and more high-level management of coding agents. It’s become incredibly cheap to try things out, and Opus 4.5 is remarkably capable.

I’m spending a lot of time with these new tools and I still feel quite a bit of FOMO. It helps to know that I’m not the only one.

Among people who do a lot of agent-assisted software development, there is some skepticism about whether MCP is useful:

A quick experiment makes this clear: try completing a GitHub task with the GitHub MCP, then repeat it with the gh CLI tool. You’ll almost certainly find the latter uses context far more efficiently and you get to your intended results quicker.

This is a fair criticism; in some scenarios an agent does better if we let it go wild with bash (which is effectively giving it the ability to write+run code) and a CLI tool. I also agree with Armin’s assertion we need better ways to compose MCP tool results. But I still think MCP is useful as-is, and I’d like to sketch out why I believe that.

MCP is simple

To connect an AI agent to an MCP server, I don’t need to download anything; I just provide a URL. Authentication is taken care of as part of the connection (more on this later). Tools are annotated with info that indicates whether they are safe to run. My agent doesn’t need to be able to execute code, and it doesn’t even need a filesystem.

It’s true that this agent might be less flexible or powerful than one with the ability to run arbitrary code. But that’s a tradeoff, and people are exploring ways to combine MCP with code execution - this is something to keep an eye on in 2026!

Programmers are weird

I’m a programmer who spends a lot of time with coding agents like Claude Code and Codex CLI. You could think of me as a power user driving an agent semi-interactively, and most people discussing MCP are in the same boat.

CLI tools are often a viable alternative to MCP for us, but a big part of that is that we can evaluate whether any given call to bash looks safe. That is not a skill that most people have.

think
OK, sure, we can’t expect most users to validate bash commands. But can’t we just sandbox their agents?
Reilly
Maybe someday! But sandboxing is hard, and I don’t think anyone’s fully solved the UX around it yet. Are you sandboxing all of your agents today? The answer is “no, it’s too much of a hassle,” right?

Toward Autonomous Agents

Let’s step away from the well-trodden path of Claude Code. Say you’re building an agent that operates autonomously based on untrusted data. To make this more concrete, let’s say it’s an incident investigator agent; when a monitor goes off, it tries to find the root cause using data from your favourite observability provider. How do you give that agent access to your observability data?

If your observability provider has a CLI available, the agent could use that. But using a CLI means:

  1. Your agent will need access to a filesystem (provisioned with a copy of the CLI)
  2. Your agent will need a sandbox to stop malicious code execution and resource exhaustion
  3. You’re opening yourself up to credential exfiltration attacks. The CLI needs credentials to talk to the observability provider; if the agent can execute arbitrary code, it can almost certainly read those credentials.

All of these problems go away if you connect your agent to an MCP server instead. MCP can get a production-ready agent off the ground almost immediately.

Putting it all together

MCP is a dead-simple way to give agents access to tools safely, and it works today. For some agents, that simplicity is extremely valuable; for others it is not. As you move away from expert oversight and toward fully automated agents, the case for MCP grows stronger.

Tool Calls Are Expensive And Finite

Design your agents accordingly

Giving LLMs access to tools (which turns them into ✨agents✨) is an incredibly powerful way to give LLMs capabilities that go beyond generating text. But it’s important to think clearly about the costs and limitations of tool calling, and in particular, people should understand that calling a tool is many orders of magnitude more costly than calling a plain old function from code. There is and probably always will be a limit on how many tool calls an agent can effectively make, and people should design their agentic systems accordingly.

Wait, why?

For this to make sense, you have to consider what a tool call is “under the hood.” LLMs are typically used as very fancy text generation machines. And the way they do tool calls is by generating text, although that’s typically abstracted away from us.

Let’s say you have an agent with one tool, add, for adding 2 numbers together. A user asks the agent a question that’s easy to answer with the add tool:

What’s 15 + 27?

To actually call the add tool, the model generates a message like this (simplified):

{
  "tool_call_id": "call_abc123",
  "tool_name": "add",
  "tool_arguments": "{\"a\": 15, \"b\": 27}"
}

At this point the model stops generating tokens. The thing that’s driving the model (the agentic loop?) parses that message, passes those arguments to some function like add(15, 27), and then puts the output of that into chat history as a new message:

{
  "tool_call_id": "call_abc123", 
  "tool_call_result": "42"
}

Inference resumes, and the LLM now has everything it needs to tell the user that the answer is 42. This works! It’s the foundation of some really incredible software systems! But it wasn’t free:

  1. The model had to generate a bunch of tokens.
  2. We used up precious context window for the 2 messages.

But why does that matter?

If you’re adding 2 numbers once, it probably doesn’t matter. If you’re summing up 1,000 numbers… you’re going to be waiting a very long time for those 999 tool calls to finish, and you might blow through your entire context window.

This might seem like an academic point, but calling a function many times in a loop is one of the most common ways to solve a problem with code. To give a contrived example, say we have 1,000 user IDs and we want to list the users whose name starts with ‘R’:

  1. A programmer with a get_user_info(id) function can write+run a simple for loop. Easy peasy.
  2. An agent with a get_user_info(id) tool can try to make 1,000 tool calls, but it will probably run out of context window long before it finishes
    1. Remember, the entire result of every tool call ends up in the context window

Designing agentic tools that are flexible enough for every use case (or even most use cases) is hard, and I don’t think enough people are talking about that.

So what do we do instead?

As always, it depends. Maybe your agent is solving problems where it will never need to make large numbers of tool calls. Maybe you’re clever and you can design your tools to be very flexible+powerful so an agent can do a lot in a small number of tool calls. Maybe you can sidestep this problem by letting your agent write+run code (keeping in mind all of the necessary security precautions).

The Model Context Protocol (MCP) is a pretty big deal these days. It’s become the de facto standard for giving LLMs access to tools that someone else wrote, which, of course, turns them into agents. But writing tools for a new MCP server is hard, and so people often propose auto-converting existing APIs into MCP tools; typically using OpenAPI metadata (1, 2).

In my experience, this can work but it doesn’t work well. Here are a few reasons why:

Agents don’t do well with large numbers of tools

Infamously, VS Code has a hard limit of 128 tools - but many models struggle with accurate tool calling well before that number. Also, each tool and its description takes up valuable context window space.

Most web APIs weren’t designed with these constraints in mind! It’s fine to have umpteen APIs for a single product area when those APIs are called from code, but if each of those APIs is mapped to an MCP tool the results might not be great.

MCP tools designed from the ground up are typically much more flexible than individual web APIs, with each tool being able to do the work of several individual APIs.

APIs can blow through context windows quickly

Imagine an API that returns 100 records at a time, and each record is very wide (say, 50 fields). Sending those results to an agent as-is will use up a lot of tokens; even if a query can be satisfied with only a few fields, every field ends up in the context window.

APIs are typically paginated by the number of records, but records can vary a lot in size. One record might contain a large text field that takes up 100,000 tokens, while another might contain 10. Putting these API results directly into an agent’s context window is a gamble; sometimes it works, sometimes it will blow up.

The format of the data can also be an issue. Most web APIs these days return JSON, but JSON is a very token-inefficient format. Take this:

[
  {
    "firstName": "Alice",
    "lastName": "Johnson",
    "age": 28
  },
  {
    "firstName": "Bob",
    "lastName": "Smith",
    "age": 35
  }
]

Compare to the same data in CSV format:

firstName,lastName,age
Alice,Johnson,28
Bob,Smith,35

The CSV data is much more succinct - it uses up half as many tokens per record. Typically CSV, TSV, or YAML (for nested data) are better choices than JSON.

None of these issues are insurmountable. You could imagine automatically adding tool arguments that let agents project fields, automatically truncating or summarizing large results, and automatically converting JSON results to CSV (or YAML for nested data). But most servers I’ve seen do none of those things.

APIs don’t make the most of agents’ unique capabilities

APIs return structured data for programmatic consumption. That’s often what agents want from tool calls… but agents can also handle other, more free-form instructions.

For example an ask_question tool could perform a RAG query over some documentation, then return information in plain text that is used to inform the next tool call - skipping structured data entirely.

Or, a call to a search_cities tool could return a structured list of cities and a suggestion of what to call next:

city_name,population,country,region
Tokyo,37194000,Japan,Asia
Delhi,32941000,India,Asia
Shanghai,28517000,China,Asia

Suggestion: To get more specific information (weather, attractions, demographics), try calling get_city_details with the city_name parameter.

That sort of layering and tool chaining can be very effective in MCP servers, and it’s something you’ll miss out on completely if auto-converting APIs to tools.

If an agent needs to call an API, it could just do that

Agents like Claude Code are remarkably capable of writing+executing code these days, including scripts that call web APIs. Some people take this so far as to argue that MCP isn’t needed at all!

I disagree with that conclusion, but I do think we should skate to where the puck is going. Sandboxing of agents is improving rapidly, and if it’s easy+safe for an agent to call APIs directly then we might as well do that and cut out the middleman.

Conclusion

Agents are fundamentally different from the typical consumers of APIs. It’s possible to automatically create MCP tools from existing APIs, but doing that is unlikely to work well. Agents do best when given tools that are designed for their unique capabilities and limitations.

headshot

Cities & Code

Top Categories

View all categories