2026-03-11 22:30:00
When I joined Imprint a little less than a year ago, our deploys were manual, requiring close human attention to complete. Our database migrations were run manually, too. Developing good software is very possible in those circumstances, but it takes a remarkable attention to detail to do it. It was also possible to develop good software using Subversion and developing by ssh’ing into a remote server to edit PHP files, but the goal is making things easy rather than possible.
Ten months later, the vast majority of our changes, including database migrations, continuously deploy to production without human involvement after the initial pull request is reviewed and merged. Reading aloud the relevant pages from the mandated gospel of continuous deployment, deploying changes this way doesn’t make them less reliable, but more so. Each step of validation a human might do, is now consistently done on every deploy, including many steps that are just onerous enough to drop off the standard operating steps like meticulously checking the post-launch health on a production canary every minute for half an hour after each deploy.
This migration has reminded me a lot of the Uber service migration, which prompted me to write Migrations: the only scalable solution for technical debt back in 2018, and in particular how different this sort of migration feels in the age of coding agents. The more I’ve thought about how these two migrations compared, the more it’s solidified my thinking a bit about how this technology is going to impact software development over the next few years.
Although I really want to talk about how coding agents are changing software development, I want to start by expanding a bit on this recent migration at Imprint and how it compared with the migration at Uber.
The Uber migration was:
The Imprint migration felt fairly differently:
We were building on substantially more powerful infrastructure, with Kubernetes, ArgoCD, etc. Our problem statement was composing our software and workflows with these platforms, rather than building the platforms from scratch.
We migrated all our services and databases to a continuous deployment setup, with the majority of the work occurring over 3 months. Once again, the significant majority of it was done by a team of ~3 engineers.
In 2014, we spent the vast majority of our time implementing decisions: how the scheduler worked, how the UX for provisioning services worked, etc. In 2026, we spent almost our entire time designing our approach, reviewing coding agent pull requests, and revising our approach when designs and reality didn’t come together as cleanly as we hoped.
The frenzied sprint was replaced by substantially more time on designing our approach.
All the fundamental challenges of migrations remained true, but in 2026 we got to solely work on solving those challenges, rather than on the essential but mundane minutiae of implementing those decisions. (Ok, I’ll be honest, we also had to keep iterating on our approach to using coding agents to get longer working cycles out of them without human involvement, but we’re telling a story here, let’s not get distracted.)
What this migration highlighted for me, is that coding agents have already generally solved the problem of time for our team. We have, effectively, an unlimited amount of time, at a very affordable price, to complete our work.
They have also made substantial progress on the problem of attention. After I go beyond five or so concurrent projects, I tend to lose track of the necessary work to shepherd those projects to completion, but increasingly I believe that this, as the LLM community would charmingly frame it, is a skill issue in how I am composing the tools. I’m fairly confident that I will evolve my approach to these problems such that the bottleneck on my attention is less important. I don’t think this will go to zero, a reality of working on teams is that the work has to be coordinated, but it will go down.
The next constraint, which I think is the biggest issue today when it comes to building genuinely important software, is judgment. With unlimited time, and with attention increasingly constrained on my personal workflow rather than an inherent limit, I can do anything. But how do I do it in a way that is maintainable, secure, and reliable? How do I do it in a way where it keeps running after a key engineer leaves the company?
I developed the idea of datapacks in What is the competitive advantage of authors in the age of LLMs?, and this still rings true to me as the core mechanism for scaling judgment in how we approach software: we can supplement judgment by introducing expert context for the task at hand. Today this is defacto happening within the coding agent development layer, in the wider community developing shared agent skills, and internally within companies developing their own skills. My guess is that the industry will develop an ecosystem for high-quality skills, e.g. detailed and maintained skills for security engineering, product engineering, and so on. You can easily imagine O’Reilly, or another technology publisher, developing a package manager for blessed skills, which is the first stop for injecting judgment into tasks. (This is the idea I experimented with in creating LLM-optimized edition of my latest book, but it’s really the distribution platform that’s going to be most valuable here.)
Once we solve judgment, and I do imagine that we will using a variety of open-source and commercially managed skill package managers that are tightly integrated with coding agents, then the last constraint ahead of us is creativity. This is a problem far enough ahead that I’m not too worried about it, but I feel like it’s a classic entrepreneurship problem that will be amenable to the same solutions as it is today.
I’ll admit I’m ignoring financial constraints here, but relative to how much companies are spending on software engineering budgets today, this isn’t a particularly interesting constraint today. Maybe the financial constraints will get more interesting over time as engineering conceivably gets cheaper, but as we think about injecting judgment, things will get more expensive as well, so the outcomes remain to be seen.
2026-02-05 22:00:00
In our latest developer productivity survey, our documentation was the area with the second most comments. This is a writeup of the concrete steps I took to see how much progress one person could make on improving the organization’s documentation while holding myself to a high standard for making changes that actually worked instead of optically sounding impressive.
There were a handful of issues we were running into:
We migrated from Confluence to Notion in January, 2025, which had left around a bunch of old pages that were “obviously wrong.”
These files created a bad smell around our other docs, as folks felt like things weren’t well maintained.
We had inconsistent approach to what we documented in Git-managed files versus managing in Notion. This led to duplication.
Duplication meant that it felt safer to create an N+1th version,
rather than debugging why N versions already existed.
We’ve had a bunch of new folks join over the past year, who weren’t sure if they were empowered to update documentation or if someone else was managing any given file
We started using Notion AI as the primary mechanism for exposing content, which meant that hierarchical organization was less important, and that having inaccurate snippets was harmful even if they were tucked away into a quiet corner
This was combined with a handful of interesting limitations in Notion itself:
The policy we adopted for addressing the above diagnosis was:
Then the specifics of implementing that policy were:
Create Scheduled to Archive and Archive teamspaces.
The Archive teamspace is a private teamspace, such that documents added there don’t pollute the search index.
Conversely, Scheduled to Archive is public, where anyone can add documents to its root document.
We have a weekly script that migrates everything from Scheduled to Archive to Archive.
This was the most effective mechanism we could find to implement archiving within Notion’s constraints.
Prune expired pages. Created a script which recursively builds hierarchy from a root page,
enriches each page with the last_edited_date for each child, and then prunes all pages
where it and all children were last edited more than N days ago.
Using this script on 3-4 most relevant top-level pages, we archived about 1,500 pages of expired documentation.
Compact stale hierarchies. Created a second script which identifies current pages deep in stale hierarchies,
e.g. the one updated page among 15 inaccurate docs. After finding a “buried current page”, promotes it to the grandparent page,
and move the parent page (and its stale children) to Scheduled to Archive.
This ended up as a script that found all the candidates, and then I worked through approving/rejecting each suggestion. The biggest issue being the lack of “verification” status within the API, such that there’s no way to bless given pages and their descendants.
Stale link finder. Created a third script which recursively works through a hierarchy and finds 404s.
It’s essential that this script does not have access to the Archive so those scripts show up as 404s,
otherwise you would have to scan through Archived to find things there. Both approaches would work,
just a bit of a matter of preference.
Ran this after the mass migrations to ensure we didn’t leave a “haunted forest” of links into archived documents that folks can’t see, which would make the documentation still feel bad even though much of the bad content was removed.
Manual review of key pages. After running all of the above steps, I then worked through all new-hire documentation to ensure it was linked to top-level onboarding guide, stated clear prerequisites, indicated the Slack channel to get help if folks ran into trouble, and ensured that instructions did not duplicate our Git-managed READMEs, instead linking to them where appropriate.
I did a lighter pass of this approach for our top-level engineering and technology pages, although those were generally in a good place.
Altogether, I think this was about eight hours of my time, but required zero hours of anyone else’s, and will have hopefully significantly improved the quality of our documentation. There’s still a lot more to be done in specific areas, but I’m optimistic that having far fewer duplicates, and more evidence that we’re actively maintaining the documentation, will make that easier as well.
2026-01-26 04:00:00
While Staff Engineer was first and foremost an attempt to pull the industry towards my perspective on staff-plus engineering roles, writing it also changed my opinions in a number of ways. Foremost, it solidified my belief that the industry too often treats engineers like children to be managed, rather than adults to be involved, and that I needed to change some of my own leadership practices that I’d inadvertently adopted.
When I started writing it, I had already shifted my opinion about reporting hierarchy, believing that it was important to have engineers reporting directly to senior leaders rather than reporting to leaf managers. But I hadn’t gone the whole distance to include them in my core leadership meeting. However, for the last six years I have had active engineers in my senior-most leadership group, I’m glad that I’ve adopted this practice, and I intend to continue it going forward.
The core approach here is:
This is a simple formula, but has worked well for me the past decade. There absolutely are topics that some people don’t care about too much, but I try to push my leadership team to care widely, even if things don’t impact them directly.
It’s easy for managers to get into a mode where they are managing the stuff around reality, without managing reality itself. That is much harder when engineers who write and maintain the company’s software are in the room, which is the biggest benefit of including them. Similarly, there are many decisions and discussions that would have to be punted to another forum without effective engineering representation in the room. You might not be able to finalize the technical approach to a complex problem with only a few engineers in the room, but you can at least evaluate the options much further.
Another major benefit is that these engineers become a second propagation mechanism for important context. Sometimes you’ll have a manager who isn’t communicating effectively with their team, and while long-term that has to be solved directly, having these engineers means that information can flow down to their teams through the engineers instead.
Finally, this sets an expectation for the managers in the room that they should be in the details of the technical work, just as the engineers in the room should understand the managerial work.
There are relatively few downsides to this approach, but it does serve as a bit of a filter for folks who have a misguided view of what senior leadership entails. For example, there are some engineers who think senior leadership is having veto power over others without being accountable for the consequences of that veto. Those folks don’t survive long in this sort of meeting. Some would argue that’s a downside, but I wouldn’t.
While I can conceive of working in some future role where I am simply not allowed to implement this pattern, I really can’t otherwise imagine not following it. It’s just been transformative in better anchoring me in the actual work we do as engineers, rather than the “work around work” that is so much of management.
2026-01-26 03:00:00
Remotion is having a bit of a moment at the moment, and I decided to play around with the Claude Code integration. Here are a couple videos I was able to make in <10 minutes summarizring data on my blog.
First, here is published posts over time. I had Claude write some scripts to generate this dataset, and then did a series of prompts to get the right visual. It was pretty straightforward, worked well, and I imagine I could have gotten to the right video much faster if I’d had a clearer destination when I started.
Second, here’s a video of showing the published blog posts over time, my most frequently used tags at that point in time, and how many posts I published at each employer along the way.
Altogether, this was a really fascinating to see how effectively Claude and Remotion together were able to generate fairly complex videos. This is definitely something I could imagine using again.
2026-01-26 01:00:00
Despite my best efforts, I have been wrong a lot over the years. I’ve been wrong about technology patterns (in 2014, I thought microservices would take over the world), I’ve been wrong about management techniques (I used to think systems thinking was the ultimate technique, but I’ve seen so many mistakes rooted in over-reliance on systems thinking), and a bunch of other stuff as well.
Early on, I spent a lot of time thinking about how to be wrong less frequently. That’s a noble endeavor, and one I still aim to improve at today. However, a lot of the problems you encounter later in your career are deeply ambiguous, and it simply isn’t possible to eliminate bad outcomes. Some examples of this are:
In every one of those examples, you know upfront that you simply don’t have all the information you’d like to have, and still need to make a decision to move forward. As a result, these days I spend far more time thinking about how to make being wrong cheap rather than how to avoid being wrong.
Of everything I’ve tried, demonstrating curiosity is consistently the best technique I’ve found to reduce the cost of being wrong. These days, if I regret being wrong about something, it’s almost always because I engaged in problem solving before exercising curiosity. I feel this so strongly that “curiosity is the first step of problem solving” has become a steadfast engineering value in the organizations that I lead.
Some examples of demonstrating curiosity well and poorly:
Someone thinks we shouldn’t hire someone that I’ve worked with closely.
(Bad) Assume they are wrong.
(Good) Explain your mental model of why you think the candidate would work well, and ask them where they do or don’t agree with that mental model.
(Best) Spend time upfront aligning with interviewers on the specific fit you’re focused on for this specific role.
Someone is asking for help logging into an internal dashboard where the internal login steps are extensively documented in your internal wiki.
(Bad) Get slightly snippy with them about not reading the documentation.
(Good) Ask them if they are running into something that isn’t covered by the documentation.
(Best) Replace this with a chat bot that uses the (Good) approach automatically instead of having a human do this.
Someone proposes introducing a new programming language for your internal stack.
(Bad) Tell them that they need to follow the existing architecture document.
(Good) Mention that this seems in conflict with the current architecture doc, and ask them how they’re thinking about that conflict.
(Best) Make sure new-hire onboarding includes links to those materials, and create an LLM-driven RFC reviewer that directs RFC writers to existing materials they should reference in their RFC.
Someone doesn’t show up for an incident that they are on-call for.
(Bad) Tell them they failed to meet on-call expectations.
(Good) Ask them what happened that led them to missing their on-call expectations.
(Best) Create automation to ensure folks going on-call are notified ahead of time, and to detect anyone going on-call whose notification mechanisms aren’t appropriately configured.
In each of these cases, showing curiosity is not about being unwilling to hold folks accountable, and it’s not about consensus-based decision making. Instead, it’s starting each discussion by leaving space for the chance that you’re missing important information. Often you’re not missing information, and then the next step is to hold folks accountable, but demonstrating curiosity helps you avoid applying accountability without context, which damages relationships without providing any benefit.
2026-01-26 00:00:00
I did a lot of hiring at Uber, some days I would be doing back-to-back 30 minute phone screens for several hours in a row. That said, while Uber taught me how to hire at scale, it was Stripe that taught me how to hire creatively.
Some of that was learning the fundamental mechanics like how to cold source, optimizing hiring funnels, and designing interview loops, but Stripe had some fairly unique ideas that I haven’t heard discussed much elsewhere. One of those was Stripe’s Bring Your Own Team (BYOT) concept, which invited teams to apply together as a sort of light-weight acquihire approach.

The BYOT approach captured folks’ attention, but it was ultimately more effective as an idea showing Stripe’s originality rather than as a hiring practice. By the point that I left, zero teams had been hired through the BYOT mechanism, although we did talk to some. On the other hand, one of the hiring ideas that Stripe didn’t blog about publicly, but worked extremely well in practice, is the idea of Lighthouse Hiring.
Lighthouse Hiring is the idea that hiring well-connected folks makes subsequent hires both easier and higher quality. Sure, hiring those well-known folks is difficult, but if you’re trying to hire ten great people, then spending more time hiring one “lighthouse hire” can improve the quality and velocity of the overall hiring push.
A few examples of Stripe’s lighthouse hiring:
There are many other examples you could pick from, and importantly not all of them are widely-known, just widely-connected. Julia is a tech internet mainstay, but Raylene operated more from personal networks rather than social media networks. Any sort of high quality network can be the underpinning of a successful lighthouse hire.
This mechanism worked exceptionally well, but there is a complex underside to Lighthouse Hires: strong networks, particularly publicly visible networks, create a complex power dynamic that some managers can struggle to navigate. If you’re relying on a public personality, and they get frustrated at work, then your lighthouse hiring strategy is going to implode on you. Similarly, hiring them to begin with can be a challenge if you don’t have an interesting role, but carving out a uniquely interesting role for one hire will come across as biased internally, undermining your relationship with the broader team.
These are all navigable, and I think Stripe would have been less successful if it hadn’t used this pattern, but it takes some nuance to deploy effectively.