MoreRSS

site iconIrrational ExuberanceModify

By Will Larson. CTO at Carta, writes about software engineering and has authored several books including "An Elegant Puzzle."
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Irrational Exuberance

Self-reinforcing quality / adopting verification pattern

2026-10-30 20:00:00

After implementing the small-change pattern, Imprint Engineering’s next focus in improving our development loop has been implementing a verification loop for our software development. This is an increasingly common pattern, particularly popularized by poteto’s pstack, but you’ll find similar ideas emerging in other places as well, e.g. Niko Matsakis’ post on the correctness ratchet, among others.

This captures some of the confusion I’ve seen in our initial rollout, and the particulars of how we are implementing the approach.

What verification pattern is, and what it isn’t

The verification pattern suffers from confusing naming: when you tell a team that you’re planning to roll it out, they’ll often ask you what mechanism you’re using to verify the codebase, don’t we already have tests, linters and so on?

However, the verification pattern itself is more similar to the chain-of-thought pattern, which aims to steer the harness towards your desired outcome in a clear way. For example, we require that each pull request generate a verifications/{pr_num}.md file that describes each step the harness took to verify the changes, and also what it was unable to verify.

You can argue that your harness was already performing these steps, but it’s actually hard to say whether it was or wasn’t. My sense is that it often wasn’t happening: I’ve seen too many pull requests go to CICD that had failed basic lints that could have been run locally to believe the harness was always running them. Now the harness has to explicitly document that it’s done those steps, or acknowledge it hasn’t. This allows the harness to self-drive more effectively.

Equally importantly, this generates a renewing source of data about where your harness is struggling to verify its work. That list is your roadmap for improving the ability of your harness to self-drive work more reliably over time.

Our verification loop

Verification loop from creating changes through verifying changes

The core of our verification loop is five steps:

  1. Implement something that you’ve identified or that has been identified by an automated system to resolve. Let’s say addressing a new error that’s shown up in the logs.
  2. Create a pull request containing that work after your harness has verified it locally. Pull requests cannot be moved from draft to ready until they have a verifications/{pr}.md verification file available
  3. Provide feedback on the pull request, at minimum from our ai-code-review tool that automatically reviews all pull requests, but potentially also from humans
  4. Incorporate and reject feedback on the pull request. For low quality feedback, we explicitly dismiss it using a /dismiss {reason} syntax in the pull request comment responses
  5. Maintain our systems on a daily basis by pulling in the above steps to improve our software:
    1. Use pstack’s /maintain-verification-skill skill to update the feature maps and tools used based on what has changed and where verifications have run into issues. Automatically generate a pull request with these changes for review
    2. Review all verification files to identify where they struggled to verify, and generate issues for review that would reduce those gaps
    3. Review which feedback from ai-code-review was incorporated and which was rejected, tightening up its guidance

This is working pretty well for us so far, typically each of our major code areas (frontend, backend, data, and native) wakes up with 4-6 pull requests to review as output of this process, improving our codebase’s correctness while we sleep.

feedback tool

The above feedback loop still misses meta-issues in our CICD and testing patterns. To address that, we’ve implemented the feedback tool pattern described by Teddy Riker in Designing for Agents, which has worked very well for us with respect to addressing places where CICD and harnesses themselves are failing above the level of verifying a specific pull request.

feedback and verifications are complementary, not competing, techniques. For example, if the container’s runner crashed before completing, the feedback tool would often report on this issue, whereas the verification file wouldn’t even get generated.

Explore (raise ceiling) vs exploit (raise floor)

One of the fundamental tradeoffs in how organizations approach AI adoption comes down to an explore vs exploit tradeoff. When you explore by having the full team do its own thing, you explore for ideas which can raise the ceiling, but when you centralize you raise the floor by ensuring everyone gets better PR review and better tooling.

The other related–and extremely open–question is whether exploration tends to over-optimize for the particulars of a given individual or team’s preferences, which often reduces cohesion across a codebase. At this point, I think of misaligned guidance as “context poison”, and am pushing hard to eliminate duplicate guidance on any topic, to ensure we have consistent, cohesive direction.

So far, our approach is running this as a centralized process for our repositories, building a single, continually improving, corpus for each area using the self-reinforcing loop built around the verification pattern. I think it’s working well for us, although certainly I’ll admit that there’s some skepticism about how far we go towards centralizing, and I think that skepticism is warranted: we do want to continue exploring, even if we are primarily exploiting. The best techniques for AI-enhanced development in 2027 will be ideas that are mostly unknown today.

Speeding up code review with `small-change`

2026-10-17 20:00:00

Imprint’s average engineer is merging 4x more PRs than 2 years ago. Yay. Aggregated behind the average is something even more interesting: we have two teams that are merging 10x their PRs over that period. One engineer is merging 25x the PRs per week of our average engineer two years ago, and a number of others are closing in on that number. That’s pretty neat, and the slope of the line suggests our average in a year is going to look much closer to today’s p90 than today’s p50. Even more importantly, we aren’t just shipping more, our quality has improved: incidents are down 42% half-over-half, and critical incidents are down 78% half-over-half. I think we’ve always known the quality/velocity tradeoff wasn’t a particularly useful construct, but that’s undeniable today.

As we’ve walked this path, we–like everyone else progressing down it–have run into the problem of pull request review. The p50 pull request review took about 33 minutes to complete 2 years ago, but now takes us about 77 minutes. The slope here correlates tightly with the number of pull requests getting merged. Tightly enough that addressing it became one of my priorities for the week.

We solved it! Just kidding, we’ve absolutely not solved it, but I think our current approach is an interesting one, which we call the small-change.


The work discussed here was shaped and done by many folks at Imprint including Dan, Joy, Daniel, Lenny, Taylor, Ted, and Annie, among many others.

Why review pull requests?

There are four reasons we review pull requests:

  1. Knowledge sharing between author and reviewer – software engineering is generally learned via apprenticeship, and reviews are a primary mechanism of experienced engineers teaching others what good software looks like. We believe the current volume means that this knowledge sharing will have to move towards operating on clusters of pull requests, rather than individual pull requests
  2. Preventing errors – review has always been an expensive time to catch errors, and we believe that effective CICD along with harness verification techniques like poteto’s pstack are more effective mechanisms for preventing errors than review
  3. Preventing malicious behavior – my experience is that even careful code review will generally miss competent malicious behavior, although fortunately most malicious behavior is incompetent. We believe both sorts of behavior are best identified using a combination of fixed rules (modifying these specific objects/classes/files requires review), anti-abuse mechanisms that we’ve refined in our LLM-based member support experiences, auditing, and audit log inspection
  4. Compliance obligations in our documented controls – these can be changed, but today they require that an independent human reviews changes in a subset of our repositories

In the modern development paradigm, we think there is a better solution for each of these problems. But, we also want to make these changes very cautiously.

Small steps with small-change

We believe we move fast by finding narrow, contained ways to validate new practices, so to iterate our way into an effective solution, we’ve developed the concept of a small-change. Here’s how it works:

  1. When a pull request is marked ready, CICD determines whether it is eligible to be a small-change. This is represented as a passing Github check. There is also a label for visibility, but it is the check itself, managed by the build and not human modifiable, that determines eligibility. (The label is used for initial filtering, but the check is still independently verified.)
  2. Something is a small-change if it meets all of these criteria:
    1. It is less than 1,000 lines of code
    2. There are no p1 or p2 findings from our ai-code-review automated review bot
    3. The author has “experience” in the service or application, defined as 10+ prior merged pull requests into that service or application’s directories. For changes working across multiple services or applications, the author must have experience in all of them to qualify
    4. Impacts fewer than N distinct services or applications. N is 1 on our frontend monorepo, and N is 3 in our backend monorepo, where typical changes will modify both a gRPC service and a common HTTP gateway
    5. Shared libraries and utilities files are not eligible for small-change
    6. Each repository has a .small_change file which allows per-path customization while we continue to tune the behaviors. For example, a service dealing with money movement can be excluded
  3. Our Slack automation tags every reference to a pull request with reacji labeling the state of that pull request, for example if it has failing tests, if it has been approved but tests are still running, or if it is ready to merge. We added a small-change reacji that indicates which of those pull requests meet the small-change criteria
  4. In our internal skills repository, we created two skills that have been distributed across harnesses:
    1. review-small-change which takes a list of repositories and reviews all open, non-draft, small-changes, including providing approval if they meet the review’s criteria (all checks pass including small-change check, and not touching on sensitive topics such as payments and security).

      Importantly, this skill has only two options: approve or ignore. It never comments on the PR to avoid the proliferation of “meat proxy review” which has become a bottleneck-pretending-to-be-a-solution on some teams

    2. review-pr-cluster which reviews batches of pull requests to identify common themes that came up, and suggest opportunities for discussion and adding verifications

  5. We set the social expectation that any error caused by a small-change is a failure in our system, and has to be solved at the system level: improvements to ai-code-review, to our verification mechanisms, to our CICD, to our harness, etc.

This took about two days of infrequent attention to implement and rollout, although I imagine we will be tuning the knobs here for a long time. Maybe forever.

Meat proxy review

I mentioned this in 4.1 above, but meat proxy review has become an interesting bottleneck for many teams. It’s a particularly difficult bottleneck because it initially looks like a solution, but it makes things worse. The pattern is roughly:

  1. Team is overwhelmed with reviews
  2. Team decides to review pull requests with their local harness
  3. Pull requests get a large number of comments, some of which make sense, and some of which don’t. It’s unclear which is which without study, and you don’t know which comments reflect the reviewer’s point of view. So you end up having to do substantial work to disprove comments that the reviewer may not even agree with
  4. Those teams then start using harnesses to respond to the harness-generated comments, making the entire review process an illusion that doesn’t facilitate learning, and most importantly could be done without the involvement of the other team members

In every case that meat proxy review is performed, the team would get better outcomes by investing into the centralized review mechanism, the harness’ loop for addressing the centralized review’s feedback, and simply stamping approval.

fleet: centrally orchestrated harness

I’ll briefly mention our other approach to reducing code review volume, which anchors on our centrally-orchestrated harness, fleet. (Which is similar to Stripe’s Minions although not following the one-shot approach in particular). We rely on fleet to implement and verify software in a highly controlled harness running in the cloud, with independent evaluation mechanisms to identify the correct number of independent reviewers for any given change. That number can be as low as one (e.g. fleet writes, one human reviews) for changes evaluated as safe.

Results / next steps / reception

I’ll be honest, it’s too early to give a useful result here. From early results, about 10% of changes qualify as a small-change. Those are now eligible for very fast approval, and generally get it unless they trip a flag, generally due to touching a security or money adjacent topic.

While 10% is a small percentage, the core concept here is working, and ultimately will succeed or fail based on our ability to expand that percentage over the next few months in tandem with expanding our verification mechanisms to be increasingly comprehensive. This is ultimately going to fail or succeed depending on our ability to improve our verifications, which does require more difficult sorts of techniques to accomplish in low scale-complexity, high domain-complexity software than in the high scale-complexity, low domain-complexity products where many techniques like incremental rollouts are exceptionally effective. (Don’t get me wrong, they still work, they just provide less coverage when most users have different configuration and behavior.)

Folks have not been uniformly excited about this, it’s a substantial change from how we’ve learned to be good engineers, and it’s uncomfortable to reevaluate whether a long-held best practice has become merely dogma. The most energizing part of the current cycle is that we get to reevaluate these practices we grew up on every week; the most painful part of the current cycle is that we’re likely to fall behind if we ever stop reevaluating them.

Trying the Software Factory pattern.

2026-09-20 20:00:00

One of the interesting challenges of the AI ecosystem in 2026 is that new, effective patterns emerge faster than I can adopt them. I’ll find a handful, get back to work, and realize a month later that I’d missed four or five more. The adoption cycle for Imprint this year has been something like:

  1. January: get every engineer onto Claude Code every single day
  2. March: ok, let’s also get everyone else onto Claude Code or Claude Cowork every single day
  3. April: local development is bottlenecked on checkout and worktree model, instead create ~10 local workspaces which each have an independent checkout of every repository, and operate at the workspace level, not at the repository level, so it can generate cross-repository pull requests across frontend, backend, infrastructure and data monorepos
  4. June: oh boy, agent-driven development is heavily constrained by lack of a common task management system with higher visibility and less permission complexity than Jira, so let’s migrate the entire company over to Linear and hard stop on Jira
  5. July: yikes, now we have visibility into all these tickets, many of them are trivial but managing them through local development isn’t scaling, let’s roll out an orchestrated harness which internally we call “Agent Fleet”, along the lines of Stripe’s Minions

The most recent question for me has been figuring out how to adopt the software factory pattern. (After some light research, the specific AI-context origin of this term is slightly messy to attribute, but I think it might be Justin McCarthy in February 2026’s Software Factories And The Agentic Moment.)

The software factory pattern is looping on a broad goal, and then relying on the harness to drive progress towards that goal. Our first pass at implementation is fairly basic:

  1. An agent skill /linear-project-loop which reads in a Linear project and starts by auditing that project’s goal definition on these dimensions:

    1. An RFC in Notion that describes the project’s goals, how those goals are measured, and the general approach
    2. A Datadog dashboard or Snowflake queries that measure progress against those goals

    If those are missing, or the Linear project is missing in its entirety, it iterates with you on creating those missing tools.

  2. Then it reviews the state of the metrics and issues for the project. If new work is identified, it adds those issues to the project. It updates the state of issues that have moved.

  3. It works on the non-blocked tasks based on the project’s current state. This is often writing a pull request, updating a pull request, pinging for review, asking a clarifying question, etc.

  4. When a task completes, if the project description is fresh, it takes on the next task. If the description hasn’t been updated in a while, it reruns the loop starting with the first step.

Right now I am running this locally in a local harness, but it’s working well enough that I anticipate moving the behavior to be driven by the same orchestrated harness that we assign one-off tasks to.

What I particularly like about the factory pattern is that it parallels very closely how I’ve been working locally, while forcing me to recognize the places where I was accidentally hoarding parts of the state for myself regarding the goals of the project. I was already asking agents to iterate on specific Linear projects, but they didn’t have the ability to evaluate if they were going in the right direction, or if it was missing necessary tasks. Now it does. The other place this has been extremely helpful for me is checking in on projects post-release. For example, I shipped our passkeys implementation earlier this year, but some months go by without my checking in on how it’s going. If we saw adoption spike, or error rates start to turn, I might miss it, but running the factory in a less frequent post-release mode would catch it immediately.

The final thought that’s been interesting to me is how much all of the pieces here compound only to the extent that you have the other pieces. For example, this factory pattern depends on having Datadog MCP and Snowflake access available to manage goal-tracking, but it also depends on Linear being the single source of state for the company’s work, and an orchestrated harness that can perform work independently from your laptop. Keeping up with this many migrations is a fascinating industry moment.

Roadmap decisions rather than dates.

2026-08-11 22:00:00

One thing that bothered me about Imprint’s product after joining was our lack of passkey support. Passkey support is a rare opportunity to increase resiliency to phishing attacks while simultaneously reducing login friction. If it’s good for our members, our partners, and our product, it felt like something we should have already shipped. Nonetheless, it was hard to get it onto the roadmap alongside everything else we were working on.

To dig into passkeys, I started sketching out the implementation as a side quest. Some iterations later, I had something implemented behind a disabled feature flag for team review. At that point, most problems had a concrete solution implemented, and the remaining issues were messy intersections between passkey implementation and user experience. Issues remained, but the tangible implementation made tradeoffs explicit, and we were able to work through them. Soon thereafter, we launched passkeys to a small group in our web experience, iterated on feedback, finalized the details, and brought those details forward to our native mobile experiences as well. It never got onto the roadmap, but it did ship.

Our passkey release planted a seed for me, but it required another experience to fully germinate. We had a discussion about hitting a date for a product extension we’re developing. Our conversation kept anchoring on the idea that pulling in a date was dependent on pushing out dates for other projects. Presenting two conflicting projects as requiring timeline tradeoffs wouldn’t have caused me to blink an eye five years ago, but in this conversation it inspired a sort of instinctual revolt: with modern development techniques, I believe very few projects are essentially constrained by execution bandwidth. Some are constrained by approvals, others are constrained by cross-team and cross-functional handoffs, and many are constrained by missing decisions, but almost none should be constrained purely on time. Shifting blocks of time across projects to speed up delivery felt wholly divorced from the reality I work in.

Grappling with my frustration for some time didn’t lead to a profound awakening, but it did help me bring together my thoughts about how effective modern organizations operate. The modern organization operates most effectively by orienting itself around highly empowered teams who broaden their capabilities to absorb cross-team and cross-functional handoffs, can rely on automated harnesses to address most remaining cross-functional approvals, and accelerate high-quality decision making through rapid prototyping. The premise here is an old one, but AI-tooling has shifted what’s possible in remarkable ways.

Reducing handoffs with team and individual accountability

In 1975, Fred Brooks’s The Mythical Man-Month warned us that adding individuals to a late project makes that project even later rather than speeding it up. However well-understood this is, it remains extremely common for leaders to repeat this mistake, even today. My personal theory is that most leaders performing the “ask for more staffing” gambit intuitively know they are optimizing to “get stuck” in a way that shifts responsibility off them because they didn’t get the requested funding, rather than that there is no way to accomplish the task at hand.

Even if teams avoid the simplest versions of the mythical man-month’s trap, organizations that operate through strict ownership of defined boundaries introduce the same problem. Well, of course your team can’t launch that without approval from the architecture team and the security team and performing a user research study. That’s not really your team’s ownership area, you’ll need approval from the membership accounts team, at a minimum! Agile, with the idea of collective code ownership, rejected this idea in principle, although certainly it remains messy in practice.

Even with strong AI-enhanced workflows, I still find human review of load-bearing technical decisions (e.g. a decision that will meaningfully impact subsequent decisions) to be extremely valuable. This is why the highly-empowered team model is an effective compromise: there needs to be an experienced engineer whose perspective is infused into the architecture, who shares the team’s context, and who is aligned with the team’s priorities. This creates space for earlier career folks to be highly effective, while also allowing teams to largely operate independently across complex, intersecting goals.

The final piece of driving accountability is rejecting the argument that a problem is too confusing or too hard for someone to know the consequences of their work. There are a lot of hard problems, and that’s what makes creating software such an interesting job. However, in a world with infinite feedback readily available, even if that feedback is of inconsistent quality, getting surprised or being unable to unpack a problem is an algorithmic gap in how work is being approached. Learning how to manage that gap is a core part of being an engineer, and something we are accountable for learning.

To be explicit, the argument isn’t that folks should be flawless: mistakes are fine, even laudable in some cases. Laudable mistakes are almost always couched in a web of derisking, often feature flags, to make them inexpensive. If someone isn’t increasingly effective at derisking their decisions throughout their career, then they’re not being accountable for their impact as an engineer.


The above section has been fairly standard advice for decades, but I believe AI-tooling has substantially changed three dimensions of this approach:

  1. Maintained context in e.g. AGENTS.md files can improve the overall approach of the company working on a given area. It’s very hard to train an entire company, but today you can get many of the outcomes of training by manipulating centralized context. This makes it far easier to operate decentralized teams than in prior eras.

  2. Inconsistent patterns in a codebase will “poison” an LLM’s context window with bad patterns, but it’s never been easier to simply remove those bad patterns from the codebase. Bad patterns within your codebase are largely a problem you can choose not to have at this point. This further simplifies operating decentralized teams, especially if you love introducing tighter typing and linting. Which I do.

  3. One high context engineer can scale themselves across a team’s scope much more easily than before by using LLM-tooling to investigate, debug, monitor and influence agent development.

    The broader this engineer’s judgment extends, the more effective they’ll become. I don’t think that any of design or product or engineering are going away, but individuals whose judgment spans across multiple of these fields are increasingly impactful on their teams because they can breeze through the easy cross-domain problems rather than get hung up on them.

In aggregate, I believe that these changes in degree become a change in kind for running an empowered team.

Minimize cross-functional dependencies with relationships or escalations

Even if code ownership is shared, it can become impossible to move forward if there’s enough cross-functional permission required to make changes. To a small extent this is due to inherent misalignment in incentives across functions, but it’s much more driven by incompatible priorities. Where possible, it’s ideal to embed functions into a common pod to align priorities, but in an era of lean teams, that’s not a particularly accessible solution.

Instead your two most likely solutions are the same ones that have always been there. First, building relationships across functions to help get your project unblocked by a helping hand from within that function. Second, an effective executive team is able to make collective tradeoffs–and share some of their treasury of cross-functional relationship merit–to unblock a given project.

Now, however, I think we finally have a playbook for speeding up cross-functional approval in a meaningful way, which is providing function-specific harnesses that can perform elements of their function’s work, especially of the approval variety. I don’t see substantial adoption of this technique yet, but I see it coming, and Imprint has spun up an internal AI team to iterate our way through deploying this approach. Hopefully we’ll be writing about our learning there in a month or two.

Our goal is that every function has a centrally managed harness that can perform an increasingly large portion of their team’s work, prioritizing the sorts of things that require cross-team approvals. They would have a collection of general tools–e.g. search indexes of their work corpus–and also very function specific tools–e.g. access to a tool like Zip for financial approvals on the Finance team–along with the ability to provide skills and direction to the harness. Assuming this idea works, this harness will become a shim on top of functions to further allow teams to focus on the highest value work rather than getting caught up in the least interesting approvals.

Eliminate decisions through the iterative application of judgment

With less accidental complexity slowing down our organizations from cross-team and cross-functional dependencies, we still have one very large remaining: deciding what software to write. Software is not, as some argue today, free, but it’s no longer constrained on implementation weeks but instead on correct decisions.

Grounding this in an example, for an upcoming release we found ourselves grappling with implications of changing how applicants applied for a new credit card. There were concerns that we didn’t have enough weeks to implement the change. However, the actual work to be done was roughly two weeks, and we were talking about the initiative taking two months. The gap between the two weeks and the two months was a dozen ambiguous decisions. Those proposing the longer timeline assumed we’d continue making decisions at the current decision-making pace, resulting in the longer schedule.

There was some discussion around how we might find that number of months to complete the project. Treating decision-making speed as a fixed constant oriented the discussion around a complex, low-value workstream of deciding the relative priority of other tasks we were currently working on and then reranking them to free up more calendar time. But we were never constrained on calendar time, what mattered was accelerating the pace of high-quality decision-making.

In my experience, calendar discussions are another way that folks deliberately get stuck, because no company ever has a central list of the true priority of every task. Asking for that non-existent list shifts accountability for forward progress off the asker, but it’s a fundamentally unreasonable thing to ask for. Thinking about things from a CPU scheduling perspective: any scheduler that tried to centrally orchestrate priority within every thread would be extraordinarily slow and stuck all the time; it’s only by locally determining priority–often through extremely crude methods–that things move quickly overall.


Decision-making at a fixed degree of complexity hasn’t gotten easier with AI-tooling but AI-tooling has dramatically reduced the complexity of most decisions I see in practice. Going back to the passkey problem, trying to make those decisions from a blank slate was very difficult. I was simply too ignorant of the handshake’s constraints to correctly decide the UX details. However, I was able to iterate through the open decisions as a side project, until the answers became obvious. The answers became obvious not because I got smarter–I didn’t get smarter–but because iterative exploration discarded many of the initial options until the inherent constraints of passkeys and our user experience simplified the problem.

Prototyping–e.g. the iterative application of judgment–can turn most complex problems into a series of simple problems, and the rate of prototyping today makes it a substantially more accessible strategy than in the past. Yes, prototyping has always been a well-received solution to validate ideas, but it used to be expensive enough that many teams didn’t take advantage of it, but these days I think it is the most accessible way to find high-quality decisions within an ambiguous problem space.

A roadmap of decisions, not on dates

Combining all of these ideas, the world we live in is increasingly constrained by access to high-quality decisions. Rather than talk about scheduling software around software, we should be fixated on the idea of accelerating making high-quality decisions. Every hour we spend deciding priorities or timelines is a distraction from making the next decision.

As an ending caveat, this isn’t an argument that we shouldn’t have dates on projects. Dates are the external interface for coordinating across companies, so you still need dates. However, I think teams are better picking a plausible, slightly conservative external date and then entirely ignoring that date internally. Instead, they should focus on the list of decisions to be made, and work through them directly. Dates, global priorities, stack ranked items… An engineer who doesn’t know the release date can still ship.

Middle management roles are also a trap.

2026-08-08 22:00:00

Six years ago, I wrote Tech Lead Management roles are a trap. My argument then was that TLM roles present themselves as easier than moving into a full management role, but the tension between doing the software engineering and engineering management aspects of the role made being a TLM a much harder first management role than a pure engineering management role. I still agree with that post, and I have some additional bad news to share: middle management roles are mostly a trap as well, if you goal is to become an executive.

The core aspects of middle management roles are:

  1. Balancing between top-down executive, lateral stakeholder, and bottom-up team pressure, e.g. keeping morale up as an organization deprioritizes last year’s big initiative
  2. Defining and operating an organization’s process, e.g. creating career ladders and interview loops
  3. Competing for a share of fixed organizational resources (e.g. budget) and allocating acquired resources

These are all extremely important skills to be an effective executive, and they make up the bulk of The Engineering Executive’s Primer, but they are insufficient to make you a great executive. If you don’t have them, you will be a deeply flawed executive, but even if you’re an expert at them, you can still be a terrible executive.

That’s because the most important skills of an effective executive are the same exact skills that make an excellent line manager: developing domain expertise, driving execution (including setting pace), and translating both of those into an organizational culture that extends beyond you (in any of innumerable different ways). All of them are more easily practiced and mastered as a line manager than as a middle manager. Most middle management roles make practicing those skills difficult, and sometimes negatively select against developing them.

As a middle manager, if you drive execution too closely, you might get told off as a micromanager. As a middle manager, if you go too deep on domain expertise, you might get told that you’re not focusing enough on your internal stakeholders. That’s undoubtedly valid feedback in many middle management roles, but it’s the perfectly wrong feedback to someone who is trying to become an effective executive. As a result, I’ve come to believe that the filters for good middle managers inadvertently negatively select out the “challenging” line managers who actually have the best chance to be excellent executives. That’s not even necessarily irrational: most companies are developing middle managers to take on more complex middle management roles; very rarely do they worry about growing future executives from within.

If you’re willing to embrace this fact that a little bit of time in middle management roles is important preparation to become an executive, but spending a great deal of time in middle management only prepares you for further middle management roles–and makes you less effective as a potential executive–then the AI-driven shift in managerial fads might be a threat to your current role, but it’s likely to improve your chances to succeed as an executive.

Generated and suppressed demand.

2026-07-12 05:00:00

Stages of a performing team, revised for 2026

Eight years ago, I wrote about my theory of restoring struggling teams, which came down to four steps:

  1. A team is falling behind if each week their backlog is longer than the week before. Solve by hiring more.
  2. A team is treading water if they’re able to get their critical work done, but are not able to start paying down technical debt or start major new projects. Solve by reducing work-in-progress.
  3. A team is repaying debt when they’re able to start paying down technical debt, but progress still feels slow. Solve by staying the course: it’s actually working, you just have to keep the faith until you finish digging out.
  4. A team is innovating when their technical debt is sustainably low, morale is high, and the majority of work is satisfying new user needs. There’s nothing left to solve, at this point.

Even now, I find this mental model extremely valuable, but I do think it is missing one interesting nuance that I’ve seen many teams run into in high-growth environments: suppressed and generated demand. Suppressed demand is the idea of incoming work that isn’t incoming, because teams stop asking you for help. Generated demand is when an increasingly effective team’s progress is noticed, and the previously suppressed demand is converted into actual demand.

Generated demand

The consequence of generated demand is that a team that was struggling can successfully recover, work through much of its backlog, and then shortly thereafter be just as far underwater as they were at their worst. This is a very disorienting experience, and even a demoralizing one. The team has done everything right, shipped a bunch of genuinely valuable work, and are nonetheless just as far underwater as they were before.

To give a concrete example, our Customer Operations Engineering team didn’t exist a year ago, and instead we invested in customer operations engineering tasks by prioritizing them into a larger team’s tasks. This often meant we had very valuable projects that didn’t get staffed. We then split it out into its own team, launching a number of projects like reworking our internal customer operations tooling and integrating Sierra for our IVR, both of which worked out quite well. As a result of working out well, there are far more requests for work. Despite accomplishing so much, the team is even further behind on the incoming requests than they were a year ago, when they had shipped relatively little and had relatively little capacity to ship more.

Unfortunately, the solution here is not particularly novel: you have to run through the cycle again. And potentially a third time. And potentially a fourth time. You just have to keep running through it until you’ve surfaced the entire backlog of suppressed demand. This is very similar to the problem of latent incidents which cause effective reliability programs to look like they’re failing as they drain the stock of previously created latent incidents. Sometimes you’re doing the right thing, and it just takes a while to work. Your challenge in that moment is building conviction that you are indeed doing the right thing, and convincing your team and leadership of that as well.

Finally, it’s interesting to attempt to predict which teams are, and which aren’t, sitting on top of a backlog of suppressed demand. Some teams run through the recovery cycle, and find that there simply isn’t much else to do. These tend to be teams with very narrow interfaces, for example a team whose job is providing internal queues probably won’t have much generated demand after clearing the initial backlog. Teams with broad interfaces, like customer operations or developer experience, are generally sitting on an incredibly large, albeit currently invisible, backlog of suppressed work.