<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Notes on Systems</title>
    <description>Writing on systems, AI, data, and software for teams trying to reduce operational drag without losing clarity.</description>
    <link>https://brimtech.co/notes/</link>
    <atom:link href="https://brimtech.co/feed.xml" rel="self" type="application/rss+xml" />
    <language>en-us</language>
    <lastBuildDate>Thu, 21 May 2026 12:00:00 GMT</lastBuildDate>
    
    <item>
      <title>What I Optimize for Now</title>
      <description>What I optimize for has shifted from capability, elegance, and speed toward concreteness, reversibility, and earlier clarity — the system that will exist after me, not just the one I am building.</description>
      <link>https://brimtech.co/notes/what-i-optimize-for-now/</link>
      <guid>https://brimtech.co/notes/what-i-optimize-for-now/</guid>
      <pubDate>Thu, 21 May 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;What I optimize for has shifted, and the older preferences were defensible at the time.&lt;/p&gt;
&lt;p&gt;I optimized for capability, because capability is the easiest thing to demonstrate. I optimized for elegance, because elegance reads well in review. I optimized for speed, because speed is the only metric a project tracks before it ships. Each was reasonable in the room where it was decided. None of them were quite what the system needed a few years in.&lt;/p&gt;
&lt;p&gt;What changed was not a framework or a principle. It was rereading code I had written a few months earlier and noticing the cost of choices I had already half-forgotten.&lt;/p&gt;
&lt;h2&gt;Leave more things concrete&lt;/h2&gt;
&lt;p&gt;The first thing I would tell my younger self is to leave more things concrete.&lt;/p&gt;
&lt;p&gt;The instinct to generalize is strong in engineering culture. It is rewarded in interviews. It is rewarded in code review. It is sometimes the difference between a junior and a senior label. But generalization paid up front draws against a future you have not yet seen.&lt;/p&gt;
&lt;p&gt;Most of the abstractions I am embarrassed by were correct generalizations of the cases I knew at the time. They were not careless. They were premature.&lt;/p&gt;
&lt;p&gt;A concrete piece of code can be generalized later, when the second and third use cases reveal what they actually share. A generalization written before those cases exist tends to fit the first case and constrain the next two.&lt;/p&gt;
&lt;p&gt;I now prefer to copy three times before extracting once. The copies look ugly during review. They survive change better than the abstraction that would have replaced them.&lt;/p&gt;
&lt;h2&gt;Reversibility over correctness&lt;/h2&gt;
&lt;p&gt;The decisions that have hurt me most were not bad decisions. They were good decisions that could not be undone when context shifted.&lt;/p&gt;
&lt;p&gt;A vendor lock-in chosen for speed. A schema choice that propagated through a hundred call sites before anyone noticed. A platform commitment that made sense for the team at the time and outlived three reorganizations.&lt;/p&gt;
&lt;p&gt;Each was reasonable. Each became expensive in a way that had nothing to do with the original analysis.&lt;/p&gt;
&lt;p&gt;I now apply a different filter before committing. Not “is this the best decision,” which is rarely answerable, but “if this turns out to be wrong, can the people who inherit it actually undo it.” That second question has changed more of my choices than any architectural principle.&lt;/p&gt;
&lt;p&gt;The honest version of this filter is uncomfortable. It treats most of my own decisions as hypotheses I expect to be partly wrong about. It builds for a team that will know things I do not. It accepts that the present moment is rarely a good vantage point for long decisions.&lt;/p&gt;
&lt;h2&gt;Ambiguity is a defect&lt;/h2&gt;
&lt;p&gt;One of the largest unforced costs in long-lived systems is deferred clarity.&lt;/p&gt;
&lt;p&gt;A team disagrees about what something means. The disagreement is uncomfortable. The conversation is postponed. The system is built. The disagreement settles into the code as ambiguity. A year later, two parts of the system operate on incompatible interpretations of the same word. Five years later, a new engineer is told that nobody really knows why it is this way.&lt;/p&gt;
&lt;p&gt;The cost of clarifying early is small. It is one awkward meeting, one written paragraph, one explicit decision. The cost of clarifying late is structural. It is rewrites, migrations, and an accumulating tax on every new contributor.&lt;/p&gt;
&lt;p&gt;I now treat unresolved ambiguity as a defect with the same weight as a bug. Not because clarity is virtuous, but because ambiguity compounds quietly and is invisible until it is structural.&lt;/p&gt;
&lt;p&gt;The most useful question I ask in design conversations now is not “what should we build.” It is “what would we have to agree on for this to make sense in three years.” If the answer requires a long pause, the design is not ready.&lt;/p&gt;
&lt;h2&gt;Not a retreat into caution&lt;/h2&gt;
&lt;p&gt;This is not a retreat into caution.&lt;/p&gt;
&lt;p&gt;I still build things. I still ship. I still make choices I will not be able to defend a decade from now. The change is not in tempo. It is in what I am willing to spend the future’s optionality on.&lt;/p&gt;
&lt;p&gt;I am more willing to spend it on concrete things that work and can be replaced. I am less willing to spend it on abstractions that promise reuse. I am more willing to spend it on small commitments that can be undone. I am less willing to spend it on architectural bets that lock in a reading of a problem I do not yet fully understand.&lt;/p&gt;
&lt;p&gt;The three preferences pull against each other often enough that this remains a posture, not a procedure. The work is in deciding, case by case, which one is paying more than it is asking.&lt;/p&gt;
&lt;p&gt;The net effect is fewer impressive moments and more boring weeks. I have come to read that ratio as a leading indicator, with a caveat I have earned the hard way: boring is not the same as healthy. Sometimes uneventful weeks are a system whose quiet failure modes have not yet surfaced. The most durable systems I know look uninteresting in their day-to-day. The prior is not proof.&lt;/p&gt;
&lt;h2&gt;The posture I want to avoid&lt;/h2&gt;
&lt;p&gt;There is a posture I want to avoid here.&lt;/p&gt;
&lt;p&gt;It would be easy to claim that what I optimize for now is what I should have always optimized for, and that the earlier versions of these preferences were errors. That framing is unfair to the engineer I was.&lt;/p&gt;
&lt;p&gt;I could not have arrived here without the years of optimizing for the wrong things. The judgment I have now is the residue of a hundred small surprises, each of which would have read as exaggerated caution if it had been advice.&lt;/p&gt;
&lt;p&gt;What I optimize for now is shaped by what I have already done. The same will be true a decade from now. The next version of these preferences will read back on this version as incomplete. That is the expected case.&lt;/p&gt;
&lt;h2&gt;The system that will exist after me&lt;/h2&gt;
&lt;p&gt;If I had to compress this into one frame, it would be this.&lt;/p&gt;
&lt;p&gt;Earlier in my career I optimized for the system I was building. I now optimize for the system that will exist after me.&lt;/p&gt;
&lt;p&gt;Those are not the same system. The first one only has to ship. The second one has to be lived in by people whose names I do not know, against constraints I cannot predict, using context that has eroded.&lt;/p&gt;
&lt;p&gt;Optimizing for the second system looks slower in the short run. It looks indistinguishable from over-caution to anyone whose horizon is the next release. It is rarely the thing that gets celebrated.&lt;/p&gt;
&lt;p&gt;It is the optimization that compounds in the direction I now choose.&lt;/p&gt;
&lt;p&gt;The first eleven weeks of this arc were about the patterns underneath these preferences — time as a constraint, simplicity as a posture, judgment over imported rules, the cost of cleverness, the weight of irreversible choices, the demands of ownership, the regret of decisions that aged badly, the information in systems that surprised me.&lt;/p&gt;
&lt;p&gt;Fewer abstractions. Fewer irreversible decisions. Earlier clarity.&lt;/p&gt;
&lt;p&gt;That is the trade. I have chosen it knowing what it costs, and I expect to choose it again knowing more.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Systems That Surprised You</title>
      <description>The most useful audits of long-lived software come from the moments your assumptions about durability turned out to be wrong.</description>
      <link>https://brimtech.co/notes/systems-that-surprised-you/</link>
      <guid>https://brimtech.co/notes/systems-that-surprised-you/</guid>
      <pubDate>Fri, 15 May 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;The most useful audits of long-lived software are the ones where you were wrong about what would last.&lt;/p&gt;
&lt;p&gt;Not wrong about a feature or a deadline. Wrong about durability — the property that is hardest to evaluate at the moment of choice and the most expensive to misjudge.&lt;/p&gt;
&lt;p&gt;Every system I have stayed with long enough has eventually surprised me. Something I expected to break held. Something I expected to last quietly stopped working in a way I did not notice for months. The lesson was rarely about the part. It was about the assumption underneath it.&lt;/p&gt;
&lt;p&gt;These surprises are worth more than most retrospectives.&lt;/p&gt;
&lt;h2&gt;Unexpected durability&lt;/h2&gt;
&lt;p&gt;There is always a part of the system that should not have survived this long. An old script. A library someone wrote in a week. A schema that predates the team. A queue choice made over a lunch conversation.&lt;/p&gt;
&lt;p&gt;On any clean evaluation, those pieces should have been replaced years ago. They were not. They quietly kept working, often through changes that should have broken them.&lt;/p&gt;
&lt;p&gt;What those parts share is rarely cleverness. It is narrow scope. They did one thing. They had few callers. They made no assumptions about the rest of the system, and the rest of the system made few assumptions about them.&lt;/p&gt;
&lt;p&gt;The components that surprised me by lasting were almost always the ones that refused to grow. Not impressive in isolation. Impressive in retention. They survived because nothing depended on them more than necessary, and because nothing about them invited expansion.&lt;/p&gt;
&lt;p&gt;This is not protection from every failure mode. Narrow scope can carry its own buried assumptions — date formats, character encodings, integer widths — that one day get tested by a condition the original author never imagined. A small component is not a safe one. It is one whose failure modes have a smaller surface to find. Both can be true.&lt;/p&gt;
&lt;p&gt;The honest reading is still uncomfortable. Most of the engineering I have spent worrying about would have been better spent ensuring the same restraint elsewhere. The durable parts were durable because somebody — sometimes accidentally — kept them small.&lt;/p&gt;
&lt;h2&gt;Quiet failure modes&lt;/h2&gt;
&lt;p&gt;The opposite surprise is more common. A system stops working correctly, but not in a way that triggers alarms. A scheduled job has been failing silently for weeks. A cache has been serving stale data since a deploy nobody remembers.&lt;/p&gt;
&lt;p&gt;These are not the failures the team rehearses for. The team rehearses for the loud ones — the outage, the spike, the 5xx wall. The quiet failures are the ones the system was never instrumented to notice.&lt;/p&gt;
&lt;p&gt;What is exposed in those moments is not a missing alert. It is a missing belief. The team did not believe this particular thing could fail. So no one wrote the check. So no one wired the alarm. The failure mode lived in plain sight for as long as the assumption held.&lt;/p&gt;
&lt;p&gt;Every quiet failure mode I have found mapped back to an assumption nobody articulated. The fix was rarely the alarm. It was the conversation that surfaced the assumption.&lt;/p&gt;
&lt;p&gt;This is the harder kind of stewardship: not catching failure when it happens, but noticing the categories of failure your monitoring is shaped to ignore.&lt;/p&gt;
&lt;h2&gt;Assumptions exposed by time&lt;/h2&gt;
&lt;p&gt;Time is the cheapest test the system has. It runs continuously. It exercises every assumption implicit in the original design, including the ones nobody wrote down.&lt;/p&gt;
&lt;p&gt;A system in production for five years has been audited by reality more thoroughly than any review process could manage. The parts still standing have passed tests nobody designed.&lt;/p&gt;
&lt;p&gt;What surprises me in those audits is not which assumptions failed. It is which assumptions turned out to have been load-bearing in the first place.&lt;/p&gt;
&lt;p&gt;Assumptions about traffic volume that quietly shaped the data model.
Assumptions about team size that determined how operations were structured.
Assumptions about deployment cadence that fixed the release pipeline.
Assumptions about the trustworthiness of upstream services that defined the error paths.&lt;/p&gt;
&lt;p&gt;Most were never debated. They were the conditions of the moment. They became architecture without ever being decided.&lt;/p&gt;
&lt;p&gt;When a long-lived system surprises you, the surprise is usually about one of these. A condition you treated as background turned out to be foreground.&lt;/p&gt;
&lt;h2&gt;What surprised me about surprise itself&lt;/h2&gt;
&lt;p&gt;The first few times a system surprised me, I read each surprise individually. A specific job that should not have lasted. A specific failure that should have been caught.&lt;/p&gt;
&lt;p&gt;Eventually a pattern emerged. The surprises clustered around two recurring shapes.&lt;/p&gt;
&lt;p&gt;Things that turned out to be more durable than expected almost always had less surface area than I remembered.&lt;/p&gt;
&lt;p&gt;Things that turned out to be more fragile than expected almost always rested on an assumption I had stopped seeing.&lt;/p&gt;
&lt;p&gt;This is not a satisfying lesson. It would have been more useful if certain technologies, patterns, or disciplines reliably produced durable systems. They did not. What produced durability was usually that something stayed small. What produced fragility was usually that something stayed unexamined.&lt;/p&gt;
&lt;p&gt;Both findings resist optimization. You cannot decide to keep a system small from the outside; that decision has to survive years of pressure. You cannot list assumptions you have stopped seeing; by definition they are invisible. The best technique available is to assume some are there and invite people who did not build the system to point at them. They have not yet learned which assumptions to overlook.&lt;/p&gt;
&lt;h2&gt;What I do with the surprises now&lt;/h2&gt;
&lt;p&gt;Two adjustments have followed from this, both modest.&lt;/p&gt;
&lt;p&gt;I treat any old component still in production as evidence of something worth preserving. Not the technology. The conditions that kept it small. Before replacing it, I try to understand whether the replacement will preserve those conditions or quietly remove them.&lt;/p&gt;
&lt;p&gt;I treat anything that has not failed for a long time with mild suspicion. Especially anything that processes data, retries on errors, or handles silent edge cases. The absence of visible failure is not the same as correctness. It often just means the monitoring matches the same assumptions the code does.&lt;/p&gt;
&lt;p&gt;Neither adjustment is dramatic. Neither shows up in a roadmap. Both have changed more decisions than I expected.&lt;/p&gt;
&lt;h2&gt;What the surprises were really about&lt;/h2&gt;
&lt;p&gt;Systems that surprised me did so on a single axis: my model of them was wrong in a specific, locatable way. The durable parts taught me I had over-valued sophistication. The fragile parts taught me I had under-valued the act of naming what the system was relying on.&lt;/p&gt;
&lt;p&gt;There is no clean rule that follows. Long-lived systems do not reward rules.&lt;/p&gt;
&lt;p&gt;What they reward is the habit of asking, regularly: which parts of this should not still be working — and why are they. Which parts have not failed recently — and what would it look like if they were failing in ways my instruments do not show.&lt;/p&gt;
&lt;p&gt;Surprise is information. It is the system telling you which parts of your understanding have not been updated in a while.&lt;/p&gt;
&lt;p&gt;That is worth listening to. Even when it means accepting that the parts of the system you invested the least in may have taught you more than the ones you spent years on.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Decisions That Aged Badly</title>
      <description>Some decisions were wrong. Others were defensible at the time and aged badly. The discipline is keeping the categories separate, and making it cheaper to be wrong.</description>
      <link>https://brimtech.co/notes/decisions-that-aged-badly/</link>
      <guid>https://brimtech.co/notes/decisions-that-aged-badly/</guid>
      <pubDate>Thu, 07 May 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;They were defensible at the time. Reviewed. Discussed. Approved. Made by capable people with the information they had.&lt;/p&gt;
&lt;p&gt;The cost arrived years later. Often after the people involved had moved on. Including, in some cases, me.&lt;/p&gt;
&lt;p&gt;That gap is the whole problem.&lt;/p&gt;
&lt;h2&gt;Wrong decisions and decisions that aged badly&lt;/h2&gt;
&lt;p&gt;A wrong decision is easy to point to. You can name the missing data, the ignored warning, the sloppy reasoning. You can fairly assign blame. Sometimes you should.&lt;/p&gt;
&lt;p&gt;A decision that aged badly is harder to find fault with. The reasoning was correct given what was known. The context simply moved past it.&lt;/p&gt;
&lt;p&gt;What was once a reasonable trade became a load-bearing constraint nobody chose.&lt;/p&gt;
&lt;p&gt;These two failure modes feel similar in retrospect. They are not the same thing.&lt;/p&gt;
&lt;p&gt;Most engineering hindsight collapses them. Either everything in the past was a mistake — which is unfair and useless. Or nothing was — which prevents any learning at all.&lt;/p&gt;
&lt;p&gt;The honest middle is harder to hold.&lt;/p&gt;
&lt;h2&gt;Regret without blame&lt;/h2&gt;
&lt;p&gt;A decision can be defensible at the time and still age badly. The useful posture toward this category is regret without blame.&lt;/p&gt;
&lt;p&gt;Regret, because the system would be better if it had gone differently.&lt;/p&gt;
&lt;p&gt;Without blame, because the people who made it were paying attention. They simply could not see the ten years that came after.&lt;/p&gt;
&lt;h2&gt;The risk in this posture&lt;/h2&gt;
&lt;p&gt;There is a real risk in this posture.&lt;/p&gt;
&lt;p&gt;&amp;quot;Regret without blame&amp;quot; can become evasion. It can become the language a team uses to avoid examining actual misjudgments — analyses that were wrong on their own terms, warnings that were available and ignored.&lt;/p&gt;
&lt;p&gt;The discipline is to keep the categories separate. Some decisions were wrong. Some aged badly. Some were both.&lt;/p&gt;
&lt;p&gt;Treating every bad outcome as a victim of changed context is its own failure of judgment.&lt;/p&gt;
&lt;h2&gt;Patterns that recur&lt;/h2&gt;
&lt;p&gt;A few patterns recur, looking back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Premature abstractions.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The shape of the problem was not yet clear. Someone wrote a generic interface for the cases they could anticipate. The cases that actually arrived were not those. The abstraction now obscures more than it reveals. But it is depended on widely, so it stays.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optionality preserved just in case.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A configuration flag. A plugin point. A strategy interface for one strategy. Each was nearly free at the moment it was added. Cumulatively they form the bulk of the system&#39;s surface area. Removing them is now a quarter of work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tooling chosen for early productivity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The framework that made the first six months fast. The build system that fit the original architecture. None of these were wrong. All of them eventually became things future maintainers had to work around rather than with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coupling between things that did not need to know each other.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The shared database two services briefly used. The library that grew to import from the application. The deploy pipeline that assumed one team&#39;s release cadence. Each coupling was a small saving. Each is now a constraint.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best practices imported without context.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The pattern that worked at a previous company. The architecture style that fit a different problem at a different scale. The decision was anchored in authority rather than fit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decisions made under deadline that became permanent.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The temporary table. The hardcoded value. The &amp;quot;we will fix this next sprint.&amp;quot; There is no next sprint. The shortcut becomes the shape.&lt;/p&gt;
&lt;h2&gt;The shared property is reversibility&lt;/h2&gt;
&lt;p&gt;The shared property of all of these is not effort or skill.&lt;/p&gt;
&lt;p&gt;It is reversibility.&lt;/p&gt;
&lt;p&gt;Each one reduced the cost of the present by spending the future&#39;s optionality. Each made the system easier to write and harder to change.&lt;/p&gt;
&lt;p&gt;The asymmetry was invisible because the future maintainers were not in the room.&lt;/p&gt;
&lt;h2&gt;Reversibility is contextual&lt;/h2&gt;
&lt;p&gt;Reversibility is itself contextual.&lt;/p&gt;
&lt;p&gt;What is reversible for a small team can be effectively permanent for a large one, because the cost of coordination scales faster than the cost of the change.&lt;/p&gt;
&lt;p&gt;A two-line config swap in a startup is a multi-quarter migration in a public company.&lt;/p&gt;
&lt;p&gt;The honest test is not &amp;quot;could this be undone in principle.&amp;quot; It is &amp;quot;is the team that would have to undo it likely to be able to.&amp;quot;&lt;/p&gt;
&lt;h2&gt;The strongest filter&lt;/h2&gt;
&lt;p&gt;This is why the strongest single filter on long-lived decisions is not &amp;quot;is this correct.&amp;quot;&lt;/p&gt;
&lt;p&gt;It is &amp;quot;is this reversible by the people who will inherit it.&amp;quot;&lt;/p&gt;
&lt;p&gt;A reversible decision can be wrong and still survive. The team adjusts as understanding improves.&lt;/p&gt;
&lt;p&gt;An irreversible decision must be defended against every change in context that comes later. Usually by people who do not know why it was made.&lt;/p&gt;
&lt;p&gt;Most decisions that aged badly were locally optimal and globally irreversible.&lt;/p&gt;
&lt;p&gt;That is the shape to watch for.&lt;/p&gt;
&lt;h2&gt;Commit later and lighter&lt;/h2&gt;
&lt;p&gt;Looking back, the change I would make is not a particular technical choice.&lt;/p&gt;
&lt;p&gt;It is to commit later and lighter. To treat decisions as hypotheses. To assume I will be partly wrong, and to build so that being wrong is survivable.&lt;/p&gt;
&lt;p&gt;The decisions that aged best in the systems I have known were rarely the cleverest. They were the smallest commitments that addressed the actual problem.&lt;/p&gt;
&lt;p&gt;Smaller surfaces.&lt;/p&gt;
&lt;p&gt;Fewer dependencies.&lt;/p&gt;
&lt;p&gt;Shorter assumptions.&lt;/p&gt;
&lt;p&gt;Things that could be undone in an afternoon.&lt;/p&gt;
&lt;p&gt;That is not a guarantee against aging. Nothing is.&lt;/p&gt;
&lt;p&gt;It is the best available defense.&lt;/p&gt;
&lt;h2&gt;The lesson&lt;/h2&gt;
&lt;p&gt;Regret without blame ends in the same lesson.&lt;/p&gt;
&lt;p&gt;The judgment is not &amp;quot;I should have known.&amp;quot;&lt;/p&gt;
&lt;p&gt;It is &amp;quot;I should have made it cheaper to be wrong.&amp;quot;&lt;/p&gt;
&lt;p&gt;That posture is the one I try to carry into the next decision.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Stewardship Begins Where Ownership Ends</title>
      <description>Ownership makes responsibility visible in the present. Stewardship makes systems survivable across time.</description>
      <link>https://brimtech.co/notes/stewardship-begins-where-ownership-ends/</link>
      <guid>https://brimtech.co/notes/stewardship-begins-where-ownership-ends/</guid>
      <pubDate>Wed, 29 Apr 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Most systems are built as if the original builder will always be around to explain them.&lt;/p&gt;
&lt;p&gt;That assumption fails early.
People change teams. Priorities shift. Memory thins out. The system remains.&lt;/p&gt;
&lt;p&gt;This is why ownership is not a sufficient concept for long-lived software.
Ownership describes responsibility in the present.
Stewardship describes responsibility across time.&lt;/p&gt;
&lt;p&gt;A person can own a service for a quarter.
They cannot own the future conditions under which it will be changed.&lt;/p&gt;
&lt;p&gt;That future belongs to other people.
Usually people who did not choose the original trade-offs, do not share the original context, and will meet the system under pressure.&lt;/p&gt;
&lt;h2&gt;Ownership is local. Stewardship is temporal&lt;/h2&gt;
&lt;p&gt;Ownership is useful because it makes accountability visible.
Someone is on call. Someone approves changes. Someone is expected to know where the sharp edges are.&lt;/p&gt;
&lt;p&gt;But ownership has a built-in weakness.
It encourages a model of software where knowledge lives inside the current owner.&lt;/p&gt;
&lt;p&gt;That works until the owner is unavailable.&lt;/p&gt;
&lt;p&gt;Many systems appear healthy only because the same people keep carrying them. The moment they step away, the real condition of the system becomes visible. Documentation is missing. Operational assumptions were never written down. Naming decisions only make sense if you remember a migration from two years ago. A change that looked routine becomes risky because nobody can tell what is load-bearing.&lt;/p&gt;
&lt;p&gt;This is the practical difference between ownership and stewardship.&lt;/p&gt;
&lt;p&gt;Ownership says: I know how this works.&lt;/p&gt;
&lt;p&gt;Stewardship says: someone else should be able to know how this works without me.&lt;/p&gt;
&lt;p&gt;That second standard is harder.
It requires a builder to spend effort on people who are not present yet.
It asks for work whose benefit arrives later, often after the builder has moved on.&lt;/p&gt;
&lt;p&gt;It is still part of the job.&lt;/p&gt;
&lt;h2&gt;Handoff friction is a design signal&lt;/h2&gt;
&lt;p&gt;Teams often treat painful handoffs as a documentation problem.
Sometimes they are. Often they are a system design problem that documentation cannot rescue.&lt;/p&gt;
&lt;p&gt;If a new engineer needs three weeks of oral history before they can safely change a service, the issue is not only that the history was undocumented. The system depends on history too heavily.&lt;/p&gt;
&lt;p&gt;If a service requires knowing which exception to ignore, which alarm is noisy on purpose, which table can never be backfilled, and which endpoint is &amp;quot;legacy&amp;quot; but still critical, then the system is communicating through folklore.&lt;/p&gt;
&lt;p&gt;Folklore scales badly.&lt;/p&gt;
&lt;p&gt;It also creates a hidden tax on teams.
The current maintainers spend time answering the same orientation questions. New maintainers learn caution instead of clarity. Risk accumulates around every undocumented edge because nobody wants to be the person who discovers, in production, that a strange rule mattered.&lt;/p&gt;
&lt;p&gt;Handoff friction should be read as a structural signal.
It means knowledge is stored in the wrong place.&lt;/p&gt;
&lt;p&gt;Some of that knowledge belongs in documents.
Some belongs in runbooks.
Some belongs in tests.
Some belongs in the code itself, through naming, boundaries, and fewer surprising paths.&lt;/p&gt;
&lt;p&gt;The point is not to write more words.
The point is to move critical understanding out of private memory.&lt;/p&gt;
&lt;h2&gt;Documentation is part of the interface&lt;/h2&gt;
&lt;p&gt;Teams often speak about documentation as if it were secondary work, adjacent to the real system.
For long-lived systems, it is part of the system.&lt;/p&gt;
&lt;p&gt;An undocumented deploy process is not a complete deploy process.
An undocumented recovery path is not an operational capability.
A dependency nobody can explain is not just messy. It is a blind spot with maintenance attached.&lt;/p&gt;
&lt;p&gt;This is why &amp;quot;the code is the documentation&amp;quot; is rarely true in practice.
Code can show what the system does.
It usually cannot explain which constraints matter, which decisions were temporary, which failures are acceptable, and which simplifications must not be undone.&lt;/p&gt;
&lt;p&gt;Those are design facts.
If they exist only in memory, the design is incomplete.&lt;/p&gt;
&lt;p&gt;Good documentation does not try to narrate every file.
It reduces ambiguity where ambiguity becomes expensive.&lt;/p&gt;
&lt;p&gt;Why does this service exist.
What does it own.
What does it deliberately not own.
How is it safely changed.
How is it safely operated.
What assumptions would surprise a reasonable new maintainer.&lt;/p&gt;
&lt;p&gt;These are not administrative details.
They are continuity mechanisms.&lt;/p&gt;
&lt;h2&gt;Stewardship changes what &amp;quot;done&amp;quot; means&lt;/h2&gt;
&lt;p&gt;A task can be complete and still be poorly stewarded.&lt;/p&gt;
&lt;p&gt;The feature works.
The ticket is closed.
The owner understands the trade-off.
Nobody else does.&lt;/p&gt;
&lt;p&gt;That is not a finished piece of engineering.
It is a working private arrangement.&lt;/p&gt;
&lt;p&gt;Stewardship raises the standard meaningfully.
It asks whether the system became easier or harder for the next person to carry.&lt;/p&gt;
&lt;p&gt;Did the change introduce a new hidden dependency.
Did it expand the set of things that must be remembered.
Did it create another special case with no visible explanation.
Did it leave behind a migration state that only makes sense to the person who performed it.&lt;/p&gt;
&lt;p&gt;These are ordinary questions.
But they are often skipped because they do not affect the immediate deliverable.&lt;/p&gt;
&lt;p&gt;Time is where the bill arrives.&lt;/p&gt;
&lt;p&gt;This is also why stewardship often looks unglamorous.
Clear naming is stewardship.
Removing dead paths is stewardship.
Explaining why a constraint exists is stewardship.
Writing a small runbook before an incident forces one is stewardship.
Declining a clever shortcut because nobody else will trust it later is stewardship.&lt;/p&gt;
&lt;p&gt;None of this looks impressive in the week it is done.
All of it matters in the year after.&lt;/p&gt;
&lt;h2&gt;Build for the next honest reader&lt;/h2&gt;
&lt;p&gt;There is a useful standard for long-lived systems:
build for the next honest reader.&lt;/p&gt;
&lt;p&gt;Not the ideal maintainer.
Not the person who sat in the meeting.
Not the original author on a well-rested day.&lt;/p&gt;
&lt;p&gt;The next honest reader is competent, busy, and missing context.
They are trying to make a safe change without breaking something important.
They do not need elegance.
They need legibility.&lt;/p&gt;
&lt;p&gt;This standard improves both code and communication.&lt;/p&gt;
&lt;p&gt;It discourages hidden invariants.
It exposes names that only make sense historically.
It makes one-off exceptions feel expensive.
It pushes knowledge toward artifacts that survive turnover.&lt;/p&gt;
&lt;p&gt;Most importantly, it resists a common failure mode in experienced teams: mistaking familiarity for clarity.&lt;/p&gt;
&lt;p&gt;A team that has lived with a system for years can stop seeing what is opaque about it.
Everything feels obvious because the explanations are carried socially.&lt;/p&gt;
&lt;p&gt;The next honest reader is the corrective.
If they cannot safely understand the thing, the thing is not yet clear enough.&lt;/p&gt;
&lt;h2&gt;Systems outlive confidence&lt;/h2&gt;
&lt;p&gt;One reason stewardship matters is that confidence is temporary.&lt;/p&gt;
&lt;p&gt;The certainty present during implementation decays fast.
Six months later, even the original author may remember the decision but not its edges. Two years later, a surviving comment or note may be the only thing separating an intentional constraint from a superstition.&lt;/p&gt;
&lt;p&gt;Long-lived systems should not depend on durable confidence.
They should depend on durable traces.&lt;/p&gt;
&lt;p&gt;This is not a call for exhaustive process.
It is a call to leave behind enough structure that the system can be changed by someone who did not witness its formation.&lt;/p&gt;
&lt;p&gt;That is what stewardship is.&lt;/p&gt;
&lt;p&gt;Not permanent ownership.
Not total foresight.&lt;/p&gt;
&lt;p&gt;A quieter responsibility:
to build so the system can survive your absence without becoming dangerous.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>What You Refuse to Do Is Architecture</title>
      <description>A system without explicit constraints is not flexible. It is undecided. The refusals you write down are the load-bearing structure.</description>
      <link>https://brimtech.co/notes/what-you-refuse-to-do-is-architecture/</link>
      <guid>https://brimtech.co/notes/what-you-refuse-to-do-is-architecture/</guid>
      <pubDate>Fri, 24 Apr 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Most architecture conversations focus on what a system can do.
The harder, more useful conversation is about what it won&#39;t.&lt;/p&gt;
&lt;p&gt;A system without explicit constraints is not flexible. It is undecided.
And undecided systems force every contributor to re-make the same decisions, in private, every week.&lt;/p&gt;
&lt;p&gt;This is how complexity arrives. Not through bad code. Through deferred choice.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Constraints reduce the surface area of disagreement&lt;/h2&gt;
&lt;p&gt;When a team says &amp;quot;we don&#39;t run background jobs in this service,&amp;quot; a hundred future debates disappear. When it says &amp;quot;we never call the database directly from controllers,&amp;quot; code review becomes shorter and clearer. When it says &amp;quot;we don&#39;t accept new dependencies without a documented reason,&amp;quot; dependency drift slows.&lt;/p&gt;
&lt;p&gt;None of these are restrictions on capability.
They are restrictions on ambiguity.&lt;/p&gt;
&lt;p&gt;Capability without constraint becomes a request to negotiate every decision twice — once when the code is written, and again when someone tries to understand it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Optionality is sold as freedom. It behaves as debt.&lt;/h2&gt;
&lt;p&gt;Every option you preserve is something a teammate must consider, document, test, and eventually maintain.&lt;/p&gt;
&lt;p&gt;When the original author moves on — and they will — the option remains. No one can quite say whether it&#39;s holding something up or just sitting there. So nobody touches it. So it stays.&lt;/p&gt;
&lt;p&gt;A team can lose a year to options nobody ever chose to use.&lt;/p&gt;
&lt;p&gt;The cost of optionality is rarely visible at the moment it is added.
It compounds in the form of caution, hesitation, and the slow accumulation of &amp;quot;I&#39;m not sure if we can change this.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Explicit constraints calm teams&lt;/h2&gt;
&lt;p&gt;This is the part most people underestimate.&lt;/p&gt;
&lt;p&gt;A large share of engineering energy goes not to writing code but to adjudicating what is acceptable. The smaller the allowed set, the less energy is consumed there.&lt;/p&gt;
&lt;p&gt;A clear constraint says: don&#39;t think about this. Don&#39;t relitigate this. Spend your judgment elsewhere.&lt;/p&gt;
&lt;p&gt;Teams that operate inside well-defined bounds tend to ship more, argue less, and produce systems that age predictably. Not because they have better engineers. Because they have fewer open questions per square meter.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The useful constraints are voluntary&lt;/h2&gt;
&lt;p&gt;The most useful constraints are the ones a team chooses before they are forced to.&lt;/p&gt;
&lt;p&gt;A budget for latency.
A ceiling on service count.
A rule that no service owns more than one database.
A list of dependencies that require written justification.&lt;/p&gt;
&lt;p&gt;These are not best practices. They are local agreements with future-you.&lt;/p&gt;
&lt;p&gt;When the constraint is explicit, the trade-off becomes visible. You can see what you are giving up. You can argue against it on its actual terms.&lt;/p&gt;
&lt;p&gt;When the constraint is implicit — &amp;quot;we just don&#39;t tend to do that here&amp;quot; — it operates as folklore. Folklore decays with turnover. Documents survive turnover better.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Constraints clarify trade-offs&lt;/h2&gt;
&lt;p&gt;Without a stated limit, a team optimizes for a moving target. With one, the trade-off is legible: we accept slower iteration here because we want fewer surprises later. We accept higher memory use because we want simpler code paths. We accept fewer features because we want smaller error surfaces.&lt;/p&gt;
&lt;p&gt;These are not heroic decisions. They are ordinary ones, made legible.&lt;/p&gt;
&lt;p&gt;The legibility is the point.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The cost is paid early. The benefit arrives late.&lt;/h2&gt;
&lt;p&gt;A system designed under explicit constraints tends to look smaller, slower to start, and disappointing on the day it ships.&lt;/p&gt;
&lt;p&gt;It also tends to look reasonable five years later.&lt;/p&gt;
&lt;p&gt;That is the trade — and it is the trade most teams are reluctant to take, because the cost of restraint is paid up front, and the benefit accrues to people who may not be in the room when it is felt.&lt;/p&gt;
&lt;p&gt;This is also why constraints are often added under duress, after a system has grown beyond comprehension. By then, the constraints feel punitive instead of clarifying. They are perceived as taking something away, rather than preserving something.&lt;/p&gt;
&lt;p&gt;It is much cheaper to start with constraints than to retrofit them.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The wrong constraint is also expensive&lt;/h2&gt;
&lt;p&gt;A constraint chosen before the team understands the problem can ossify a misreading. Writing down &amp;quot;we never do X&amp;quot; too early locks in a decision the team does not yet have the evidence to make.&lt;/p&gt;
&lt;p&gt;The defense is modest: prefer constraints that can be relaxed, and treat any rule older than the system itself with suspicion.&lt;/p&gt;
&lt;p&gt;A team can relax a rule in an afternoon if it proves wrong.
Reintroducing a rule once a hundred files quietly violate it is a quarter of work, sometimes more.&lt;/p&gt;
&lt;p&gt;The asymmetry runs one direction.
When in doubt: start narrower than feels natural.
You can always widen.
You will rarely need to.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Constraints are not the opposite of design&lt;/h2&gt;
&lt;p&gt;They are its load-bearing structure.&lt;/p&gt;
&lt;p&gt;The systems that age well are not the most powerful or the most general.
They are the ones whose authors were willing, early on, to write down what the system would not be.&lt;/p&gt;
&lt;p&gt;That decision — to refuse, in writing, on the record — is the part that carries.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>AI in Long-Lived Systems</title>
      <description>Every AI integration is a bet that you can monitor something you do not fully control.</description>
      <link>https://brimtech.co/notes/ai-in-long-lived-systems/</link>
      <guid>https://brimtech.co/notes/ai-in-long-lived-systems/</guid>
      <pubDate>Wed, 15 Apr 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Every AI integration is a bet that you can monitor something you do not fully control.&lt;/p&gt;
&lt;p&gt;The conversation about AI in software systems is mostly about capability. What it can do. What it will do next.&lt;/p&gt;
&lt;p&gt;The harder question is what happens when you depend on it. Not for a demo. Not for a prototype. For a system that needs to work reliably for years, maintained by people who did not build it, under conditions that shifted since the original design.&lt;/p&gt;
&lt;h2&gt;Probabilistic output, deterministic expectations&lt;/h2&gt;
&lt;p&gt;AI introduces a category of behavior that most engineering practices were not designed for: probabilistic output embedded in deterministic expectations. A function that returns a different result for the same input is not a function in the way most systems assume. When that behavior is buried inside a pipeline — making decisions, filtering data, generating content, routing requests — the system becomes harder to reason about in ways that do not show up in testing.&lt;/p&gt;
&lt;p&gt;This is not a reason to avoid AI. It is a reason to treat it differently than other dependencies.&lt;/p&gt;
&lt;h2&gt;Observability&lt;/h2&gt;
&lt;p&gt;The first problem is observability. Most systems log what happened. AI components require logging enough context to reconstruct why. A model that rejects a transaction, summarizes a document, or classifies a support ticket is making a judgment. If you cannot inspect that judgment after the fact, you cannot debug it, audit it, or explain it to the applicant who was denied.&lt;/p&gt;
&lt;p&gt;Traditional observability assumes deterministic behavior. You trace a request, you see the path it took, and you understand the outcome. With AI, the path includes a decision that may not be reproducible. The same input tomorrow might produce a different output. Logging the input and output is necessary but not sufficient. You need the model version, the prompt template version, the context window — logged alongside each call, not reconstructed after the fact. This tooling tends to arrive late.&lt;/p&gt;
&lt;h2&gt;Irreversibility&lt;/h2&gt;
&lt;p&gt;The second problem is irreversibility. AI is increasingly used to make routing, filtering, and approval decisions that are difficult or impossible to undo. Approving or denying applications. Prioritizing work. Filtering what a user sees. Generating communications sent to real people. Each of these has downstream consequences that compound. A wrongly filtered email is not just a missed message — it is a missed message that influenced a decision that influenced a timeline no one can trace back to the filter.&lt;/p&gt;
&lt;p&gt;The systems that handle this well treat AI decisions as proposals, not conclusions. A model suggests; a human confirms. A model classifies; a review queue catches edge cases. A model generates; a validation layer checks constraints. This is slower. It is also more durable. The speed advantage of AI is real, but it is not free. The cost is paid in the infrastructure required to keep the system accountable.&lt;/p&gt;
&lt;h2&gt;Containment&lt;/h2&gt;
&lt;p&gt;The third problem is containment. AI capabilities are general-purpose by nature, which makes them easy to spread across a system. A team integrates a language model for one use case. Another team sees it working and adopts it for a different use case. The model becomes a shared dependency with no clear owner, no consistent evaluation criteria, and no unified understanding of its failure modes. This is tool sprawl, but with a dependency that changes behavior when the provider updates it.&lt;/p&gt;
&lt;p&gt;Containment means treating AI as a bounded component with explicit interfaces. What goes in. What comes out. What the acceptable range of behavior is. What happens when the behavior falls outside that range. These are the same questions you would ask about any critical dependency, but AI makes them harder to answer because the behavior is not specified in code. It is learned, and it shifts.&lt;/p&gt;
&lt;h2&gt;The pattern that holds&lt;/h2&gt;
&lt;p&gt;The pattern that holds up is the same one that holds up for most long-lived system decisions: make it observable, make it reversible, make it replaceable.&lt;/p&gt;
&lt;p&gt;These constraints feel like they slow things down. They do. That is the point. The speed of integration is not the bottleneck in long-lived systems. The bottleneck is the speed of understanding — how quickly someone unfamiliar with the system can figure out what it does and why.&lt;/p&gt;
&lt;p&gt;AI does not make systems fragile by itself. Systems become fragile when AI is embedded without the same discipline applied to every other critical component: clear boundaries, monitoring that captures why and not just what, and the assumption that it will eventually be wrong in a way no one predicted.&lt;/p&gt;
&lt;p&gt;The question is not whether to use AI. It is whether you are building the infrastructure to live with it.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Tooling and Accidental Complexity</title>
      <description>No one decides to make a system heavy. It happens one reasonable choice at a time.</description>
      <link>https://brimtech.co/notes/tooling-and-accidental-complexity/</link>
      <guid>https://brimtech.co/notes/tooling-and-accidental-complexity/</guid>
      <pubDate>Fri, 10 Apr 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;No one decides to make a system heavy. It happens one reasonable choice at a time.&lt;/p&gt;
&lt;p&gt;A team adds a caching layer because latency matters. A monitoring tool because visibility matters. A build plugin because developer experience matters. A message broker because decoupling matters. Each addition solves a real problem. Each is justified on its own terms. The weight is not in any single decision. It is in the aggregate.&lt;/p&gt;
&lt;h2&gt;Accidental complexity&lt;/h2&gt;
&lt;p&gt;This is accidental complexity — not complexity born from the problem, but complexity accumulated from the solutions. The system grows heavier than the problem requires, and the weight arrives so gradually that no one notices until a change that should take a day takes a week.&lt;/p&gt;
&lt;h2&gt;Stack drift&lt;/h2&gt;
&lt;p&gt;Stack drift is the quiet version of this. The stack you run today is not the stack you chose. It is the stack you chose plus everything that was added to keep it working, plus the things those additions required, plus the compatibility layers that hold the seams together. Nobody designed the full picture. It emerged.&lt;/p&gt;
&lt;h2&gt;The asymmetry of adding and removing&lt;/h2&gt;
&lt;p&gt;The asymmetry is what makes this hard. Adding a tool is a local decision. One team evaluates it, one team adopts it, and the integration cost seems small. Removing a tool is a global decision. It requires understanding every system that touches it, every pipeline that depends on it, every configuration that references it. Adding takes a week. Removing takes a quarter. So things accumulate.&lt;/p&gt;
&lt;h2&gt;Configuration is code&lt;/h2&gt;
&lt;p&gt;Configuration is part of the weight, and it is the part that gets the least scrutiny. A YAML file that controls deployment behavior is code. An environment variable that changes runtime behavior is code. A feature flag that alters control flow is code. But none of these go through the same review process as code. They accumulate in places that are harder to search, harder to test, and harder to reason about.&lt;/p&gt;
&lt;h2&gt;Hidden dependencies&lt;/h2&gt;
&lt;p&gt;Hidden dependencies are the most expensive form of this. A service that calls another service through an intermediary. A build step that depends on a global state set by a previous step. A test suite that passes only because an unrelated process is running. These dependencies are invisible in architecture diagrams. They show up at 2 AM when an on-call engineer is trying to understand why something that has not changed is suddenly broken.&lt;/p&gt;
&lt;h2&gt;The actual system&lt;/h2&gt;
&lt;p&gt;The system is less resilient than it appears. The diagram shows five services. The reality is five services, three shared libraries, a custom build plugin, two configuration management tools, an internal CLI that nobody remembers writing, and a Makefile that references a Docker image tagged &amp;quot;latest&amp;quot; from 2024. That is the actual system. The weight is real even when it is not visible.&lt;/p&gt;
&lt;h2&gt;The consolidation instinct&lt;/h2&gt;
&lt;p&gt;The instinct when this becomes painful is to consolidate. Replace five tools with one platform. Build an internal developer platform. Standardize everything. This sometimes works. But it often introduces a different kind of weight — the weight of the platform itself, which becomes the new thing that is hard to change. The second system is frequently heavier than the first, built to solve every problem the first system had, and a few it did not.&lt;/p&gt;
&lt;h2&gt;Audit over consolidation&lt;/h2&gt;
&lt;p&gt;The better response is not consolidation but audit. What is here? Why was it added? Is it still solving the problem it was introduced for? If not, what would it take to remove it? These questions are simple. Answering them honestly is not.&lt;/p&gt;
&lt;h2&gt;Carrying cost&lt;/h2&gt;
&lt;p&gt;Removal requires understanding that adding was not a mistake. The caching layer was correct when it was introduced. The monitoring tool was the right choice at the time. The message broker solved a real coordination problem. The question is not whether it was a good decision then. The question is whether it is still earning its carrying cost now. Every tool has a carrying cost — in maintenance, in cognitive load, in the time it takes a new team member to understand the system. That cost is never on the invoice.&lt;/p&gt;
&lt;h2&gt;Subtraction as operations&lt;/h2&gt;
&lt;p&gt;The systems that stay manageable are not the ones that avoid adding tools. They are the ones that treat removal as a normal part of operations, not as a special project. They notice when something stops earning its cost. They budget time for subtraction, not just addition.&lt;/p&gt;
&lt;h2&gt;Intentionality over lightness&lt;/h2&gt;
&lt;p&gt;Lightness is not the goal. Intentionality is. A heavy system where every component is justified and understood is better than a light system held together by assumptions. The problem is not weight. It is weight that nobody chose and nobody maintains.&lt;/p&gt;
&lt;p&gt;Systems grow heavier than intended because adding is easy and removing is hard. The discipline is not in choosing fewer tools. It is in continuing to ask whether the tools you have are still the right ones.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Abstractions That Age Poorly</title>
      <description>Good abstractions compress complexity. Bad abstractions relocate it.</description>
      <link>https://brimtech.co/notes/abstractions-that-age-poorly/</link>
      <guid>https://brimtech.co/notes/abstractions-that-age-poorly/</guid>
      <pubDate>Thu, 02 Apr 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Good abstractions compress complexity. Bad abstractions relocate it.&lt;/p&gt;
&lt;p&gt;The best ones disappear. You stop noticing them. The worst ones announce themselves on every change, every new requirement exposing another edge case the abstraction was not designed for. The team spends more time working around the abstraction than working through it.&lt;/p&gt;
&lt;p&gt;Most abstractions that age poorly were not bad ideas. They were good ideas applied too early, before the problem was understood well enough to know what should be hidden and what should remain visible.&lt;/p&gt;
&lt;h2&gt;Early abstraction is seductive&lt;/h2&gt;
&lt;p&gt;You see a pattern forming — two or three things that look similar — and the instinct is to unify them. Extract a base class. Create a shared interface. Build a generic handler. The code becomes shorter. The duplication disappears. It feels like progress.&lt;/p&gt;
&lt;p&gt;The cost arrives later. The things that looked similar turn out to be similar only at the surface. As requirements diverge, the shared abstraction accumulates conditionals. It grows parameters. It develops modes. The generic handler now has seven configuration options, four of which exist to accommodate a single consumer. The abstraction that reduced complexity has become the primary source of it.&lt;/p&gt;
&lt;h2&gt;The cleverness tax&lt;/h2&gt;
&lt;p&gt;The initial abstraction was elegant. Maintaining it is not. Every modification requires understanding the full surface area of the abstraction, not just the part you are changing. New team members cannot reason about one use case without understanding all of them. The cost is no longer paid once. It is paid on every change.&lt;/p&gt;
&lt;p&gt;Local elegance creates this problem more often than carelessness does. A developer writes a beautiful generic solution. It works. It is well-tested. It handles the current use cases with minimal duplication. Within its own scope, it is correct. But zoom out, and the system now has a coupling point that resists change. Two features that should evolve independently are bound together by a shared interface that neither fully fits.&lt;/p&gt;
&lt;p&gt;The failure is not in the code. It is in the assumption that the things being unified will continue to evolve together. That assumption is rarely tested at the time the abstraction is introduced. It is tested later, when one use case needs to move in a direction the abstraction does not support.&lt;/p&gt;
&lt;h2&gt;When the generic bus breaks&lt;/h2&gt;
&lt;p&gt;I have watched this happen with event systems. A team builds a generic event bus early in a project&#39;s life. Every domain event flows through the same pipeline. It works well for the first year. Then one domain needs strict ordering because payment reconciliation depends on event sequence. Another needs transactional boundaries. A third needs fire-and-forget because latency matters more than delivery guarantees. The generic bus, originally a clean solution, becomes a negotiation layer where every team must accommodate every other team&#39;s requirements.&lt;/p&gt;
&lt;p&gt;The event bus was not a mistake. It was a reasonable choice given what was known at the time.&lt;/p&gt;
&lt;p&gt;The mistake was not revisiting that choice when the domains diverged. The abstraction became structurally necessary before anyone noticed, and by the time the cost was visible, replacing it required coordinated effort across multiple teams.&lt;/p&gt;
&lt;h2&gt;The pattern&lt;/h2&gt;
&lt;p&gt;Abstractions that age poorly tend to share a few traits. They unify things that are similar now but will diverge later. They hide differences that turn out to matter. They are introduced before the problem space is stable. And they become harder to remove the longer they exist, because more code depends on them.&lt;/p&gt;
&lt;p&gt;The alternative is not to avoid abstraction. It is to delay it. Write the duplication. Let the pattern prove itself over three or four instances, not two. Wait until you understand not just what is similar, but why it is similar. The shape of a good abstraction comes from understanding the forces that will act on it, not from the code that currently exists.&lt;/p&gt;
&lt;h2&gt;Duplication is cheaper than the wrong abstraction&lt;/h2&gt;
&lt;p&gt;Duplicated code can be changed independently. A bad abstraction forces coordinated change. Duplicated code is obvious. A bad abstraction hides its cost behind a clean interface that no longer reflects reality.&lt;/p&gt;
&lt;p&gt;The hardest part is resisting the urge to clean things up too early. Duplication feels like a problem. It looks unfinished. It triggers the same instinct that makes refactoring attractive — the desire for order. But premature order can be more expensive than temporary disorder.&lt;/p&gt;
&lt;p&gt;The abstractions that last are the ones introduced after the problem was understood, not before. They are smaller than you would expect. They hide less than you would think necessary. They leave room for the cases that have not appeared yet, not by being generic, but by being narrow enough that they do not get in the way.&lt;/p&gt;
&lt;p&gt;Simplicity is not the absence of abstraction. It is the presence of the right ones, introduced at the right time, hiding the right things.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Refactoring as a Signal</title>
      <description>The code that needs refactoring most often is rarely the worst code. It is the code sitting on top of a structural assumption that no longer holds.</description>
      <link>https://brimtech.co/notes/refactoring-as-a-signal/</link>
      <guid>https://brimtech.co/notes/refactoring-as-a-signal/</guid>
      <pubDate>Fri, 27 Mar 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;The code that needs refactoring most often is rarely the worst code. It is the code sitting on top of a structural assumption that no longer holds.&lt;/p&gt;
&lt;p&gt;Refactoring is framed as improvement. You take something that works and make it more modular, more readable, easier to extend. That framing is not wrong. But it hides the more interesting question: why does the code need restructuring in the first place?&lt;/p&gt;
&lt;h2&gt;The need to refactor is a signal&lt;/h2&gt;
&lt;p&gt;It tells you something about the gap between what the system was designed for and what it is now being asked to do. That gap is worth understanding before you begin restructuring.&lt;/p&gt;
&lt;p&gt;Most refactoring conversations start with the code. The function is too long. The module has too many responsibilities. The naming is inconsistent. These describe the code. They do not explain why it drifted. The deeper question is: what changed? What shift in requirements, team structure, or understanding created the misalignment you are now trying to fix?&lt;/p&gt;
&lt;p&gt;When you skip that question, refactoring becomes cosmetic. You improve the shape of the code without addressing the structural mismatch that created the problem. Six months later, the same area needs restructuring again — not because the first refactor was bad, but because it treated the symptom.&lt;/p&gt;
&lt;h2&gt;The pattern of repeated cleanup&lt;/h2&gt;
&lt;p&gt;A module gets refactored, stabilizes briefly, then drifts again. Each pass feels productive. Each pass addresses real issues. But the cycle continues because the underlying cause — a shifting domain boundary, an unclear ownership model, a feature that outgrew its original scope — was never confronted.&lt;/p&gt;
&lt;p&gt;Repeated cleanup is not a sign of careless engineering. It is a sign that the system&#39;s organizational shape no longer matches its purpose. The refactoring is correct at the code level and insufficient at the design level.&lt;/p&gt;
&lt;p&gt;Consider a payments module originally designed as a simple pass-through — validate, forward, log. Over two years, it becomes the system&#39;s primary orchestration layer. It coordinates retries, manages state, triggers downstream workflows. It gets refactored three times, each time to accommodate a responsibility it was never meant to own. Each refactor improves the code. None questions whether the module should still exist in this form.&lt;/p&gt;
&lt;h2&gt;Refactoring as avoidance&lt;/h2&gt;
&lt;p&gt;There is a subtler failure mode. Ambiguity makes refactoring attractive. The team faces a question it cannot cleanly answer — whether a feature belongs in this service at all, how to scope a requirement that keeps shifting, a technical decision with no clear winner. Refactoring offers a productive alternative. You spend a week improving the codebase, and the code is genuinely better afterward. But the hard question remains untouched.&lt;/p&gt;
&lt;p&gt;This is not laziness. Refactoring has clear inputs and measurable outputs. The hard question does not. Given a choice between visible progress and uncertain exploration, most teams choose progress. The cost is that the architectural question gets deferred again.&lt;/p&gt;
&lt;p&gt;I have seen this play out in systems where the same area gets refactored by successive teams. Each team improves the code according to their understanding. Each refactor is defensible. But the cumulative effect is a module that has been reshaped multiple times without anyone asking whether it should exist in its current form at all.&lt;/p&gt;
&lt;h2&gt;&amp;quot;This code keeps becoming messy&amp;quot;&lt;/h2&gt;
&lt;p&gt;The signal worth paying attention to is not &amp;quot;this code is messy.&amp;quot; It is &amp;quot;this code keeps becoming messy.&amp;quot; The first is a maintenance task. The second is a design problem.&lt;/p&gt;
&lt;p&gt;The misalignment has a specific shape. The code is organized around one model of the domain, but actual usage has shifted to another. Features that were once separate now overlap. Boundaries that made sense at one scale create friction at the current one. The refactoring impulse is correct — something is wrong. But the fix is not reorganizing the code within the existing structure. It is questioning the structure itself.&lt;/p&gt;
&lt;p&gt;This is harder work. It requires understanding not just the code but the forces acting on it. Why do these two modules keep needing to change together? Why does every new feature touch the same three files? Why does onboarding to this area take twice as long as it should?&lt;/p&gt;
&lt;p&gt;These questions do not have clean answers. They require conversations about product direction, team boundaries, and debt that accumulated from decisions that made sense when the team was smaller and the product was simpler.&lt;/p&gt;
&lt;h2&gt;What a refactor should produce&lt;/h2&gt;
&lt;p&gt;The most useful thing a refactoring can produce is not cleaner code. It is a clearer understanding of why the code looked the way it did. If you finish a refactor and can articulate what assumption was wrong, you have learned something that will inform the decisions that follow. If you finish a refactor and can only say the code is better now, you have improved one file and learned nothing about the system.&lt;/p&gt;
&lt;p&gt;Refactoring is maintenance when it addresses known issues in stable code. It is a signal when it keeps happening in the same places. And it is avoidance when it substitutes for the harder work of confronting what actually needs to change.&lt;/p&gt;
&lt;p&gt;The discipline is not in knowing how to refactor. It is in asking what the need to refactor is trying to tell you.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Defaults Nobody Revisits</title>
      <description>Best practices are useful until they become borrowed conclusions that no longer match the system’s actual constraints.</description>
      <link>https://brimtech.co/notes/defaults-nobody-revisits/</link>
      <guid>https://brimtech.co/notes/defaults-nobody-revisits/</guid>
      <pubDate>Wed, 18 Mar 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Best practices are borrowed conclusions. They compress someone else&#39;s context into a rule you can follow without thinking. That is their value, and that is their cost.&lt;/p&gt;
&lt;p&gt;The rule says: keep functions short. So you split a coherent operation into five pieces, each with a name that explains what it does but not why it exists. The code is clean. It follows the linter. It also resists change, because changing the behavior now means understanding the relationship between fragments that were never designed to be separate.&lt;/p&gt;
&lt;p&gt;This is what blind rule-following produces. Not bad code, compliant code. Code that passes review but quietly increases the cost of the next decision.&lt;/p&gt;
&lt;p&gt;Best practices originate in specific environments. Extract a service when your deployment boundaries require it. Write unit tests when your domain logic is complex enough to justify the maintenance cost. These are reasonable conclusions, inside their original constraints. Detach them from those constraints, and they become defaults nobody revisits.&lt;/p&gt;
&lt;p&gt;The problem is not that best practices are wrong. It is that they are incomplete. They encode the what without the why. And when the why changes, a new team, a different scale, a shifted business model, the what keeps running on inertia. Nobody questions it, because questioning a best practice feels like questioning competence.&lt;/p&gt;
&lt;p&gt;This is authority-based decision-making. The practice carries weight not because it fits the current situation, but because it was endorsed somewhere credible. A conference talk. A well-known post. A team lead who used it at their last company. The reasoning gets replaced by the reference.&lt;/p&gt;
&lt;p&gt;Senior engineers are not the ones who know more best practices. They are the ones who know when to stop following them. Judgment is not the rejection of rules. It is the ability to evaluate whether a rule still applies in the current context, and to accept the cost if it does not.&lt;/p&gt;
&lt;p&gt;There is a specific failure mode I have seen repeatedly. A team adopts clean architecture and applies it uniformly. Every service gets the same layered structure. Simple CRUD endpoints get use-case classes, repository interfaces, domain models, and DTOs, a five-layer ceremony around a single database insert. The architecture is consistent. It is also expensive. New features take longer than they should because every change must pass through layers that exist for structural purity, not because the domain demands them.&lt;/p&gt;
&lt;p&gt;The cost is not visible in any single file. It is distributed across the entire system as friction. And friction does not trigger alerts. It shows up as slower velocity, longer onboarding, a growing sense that something is heavier than it needs to be.&lt;/p&gt;
&lt;p&gt;Clean code that resists change is not clean. It is rigid. Cleanliness is not a property of the code alone, it includes how the code responds to the next requirement. If every modification requires reshuffling abstractions, the structure is serving itself, not the system.&lt;/p&gt;
&lt;p&gt;The alternative is not chaos. It is context-sensitive reasoning. You evaluate a practice against the actual constraints: team size, expected lifetime, rate of change, domain complexity. Sometimes the best practice applies perfectly. Sometimes it applies partially. Sometimes it actively gets in the way.&lt;/p&gt;
&lt;p&gt;This requires something harder than knowledge. It requires the willingness to make a judgment call and own the consequences. Best practices offer safety, if the outcome is bad, the decision was defensible. Judgment offers no such cover. You chose, and the result is yours.&lt;/p&gt;
&lt;p&gt;That is why organizations default to best practices. They reduce variance. They make decisions reviewable. They create a shared vocabulary. These are real benefits. But they come at a cost: they suppress the reasoning that distinguishes systems that work from systems that last.&lt;/p&gt;
&lt;p&gt;Judgment has its own failure mode. Without shared constraints, it becomes preference. I have seen senior engineers override team conventions not because the context demanded it, but because they had the authority to do so. The resulting system is legible only to its author. This is not judgment, it is taste mistaken for reasoning. The difference matters: judgment accounts for the team that inherits the decision. Taste accounts only for the person making it.&lt;/p&gt;
&lt;p&gt;Medicine has a useful model here. Clinical guidelines are best practices, standardized treatment protocols backed by research. Experienced clinicians deviate from them routinely, based on patient-specific context. But the field does not treat deviation as rebellion. It treats it as a skill that must be taught, practiced, and documented. The guideline provides the default. Clinical judgment handles the exception. And when a clinician deviates, the reasoning goes in the chart. Software engineering rarely has an equivalent. The decision is made, but the reasoning stays in someone&#39;s head, or in a Slack thread that no one will search six months later.&lt;/p&gt;
&lt;p&gt;This points to what is actually missing. Not more best practices, and not more individual judgment, but a culture that treats the override as a first-class event. When someone deviates from a convention, the deviation should be visible and the reasoning should be recorded. Not as bureaucracy, as design context for the next person who encounters the same decision point.&lt;/p&gt;
&lt;p&gt;The systems that hold up over time are not the ones built with the most discipline. They are the ones where someone understood the constraints well enough to know which rules to follow and which to set aside. That understanding cannot be codified. It is earned through decisions that went wrong and the willingness to examine why.&lt;/p&gt;
&lt;p&gt;Judgment is not a replacement for best practices.&lt;/p&gt;
&lt;p&gt;It is the thing that makes them useful.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>The Carrying Cost of Flexibility</title>
      <description>Speculative flexibility often looks responsible at first, then quietly raises the cost of understanding, testing, and changing the system.</description>
      <link>https://brimtech.co/notes/the-carrying-cost-of-flexibility/</link>
      <guid>https://brimtech.co/notes/the-carrying-cost-of-flexibility/</guid>
      <pubDate>Thu, 12 Mar 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Flexibility is one of the most intuitive design goals in software. Make it configurable. Make it extensible. Build the abstraction so it handles future cases. These sound like good engineering. Often, they are. But flexibility has a carrying cost, and that cost does not announce itself early.&lt;/p&gt;
&lt;p&gt;Early in a system&#39;s life, flexibility feels free. A generic interface, a plugin architecture, a config-driven behavior, these seem like responsible choices. They signal foresight. They reduce the fear of being wrong. And in some cases, they genuinely pay off.&lt;/p&gt;
&lt;p&gt;The problem is that most flexibility is speculative. It solves a problem that has not arrived yet, and may never arrive. Meanwhile, it is already shaping the system, adding indirection, widening interfaces, and distributing logic across layers that could have been one.&lt;/p&gt;
&lt;p&gt;There is a specific pattern I keep seeing. A team builds a module that handles one case well. Someone raises the question: what if we need to support a second case later? So the module gets generalized. An interface is extracted. A strategy pattern is introduced. The original behavior is now one implementation of a broader contract.&lt;/p&gt;
&lt;p&gt;Six months later, there is still only one implementation. But the abstraction remains, and every new developer reads it as if it matters. They study the interface. They wonder what the other implementations are. They hesitate to change it, because it looks load-bearing. The flexibility that was meant to reduce future cost is now increasing present cost, not through failure, but through indirection that no one needs.&lt;/p&gt;
&lt;p&gt;This is not a failure of engineering skill. It is a failure of timing. The abstraction was introduced before the pressure that would justify it. And without that pressure, there is no feedback loop to tell you whether the abstraction is right.&lt;/p&gt;
&lt;p&gt;Premature flexibility and premature optimization share the same root. Both try to solve a future problem with present effort, using incomplete information about what the future actually requires.&lt;/p&gt;
&lt;p&gt;The difference is that premature optimization is widely understood as a risk. Premature flexibility often is not. It passes code review. It gets praised as forward-thinking. It survives because it looks like the kind of thing a senior engineer would do.&lt;/p&gt;
&lt;p&gt;But the cost is real. Every unused extension point is a question mark in the codebase. Every config flag that controls behavior no one toggles is a branch that still has to be understood, tested, and maintained. Every generic interface that only has one implementation is a contract that constrains without earning its keep.&lt;/p&gt;
&lt;p&gt;Optionality is not free. It defers the cost of a decision, but it does not eliminate it. Keeping your options open means keeping the cognitive surface area wide. Someone has to hold all those possibilities in their head, or discover them the hard way when they intersect in production.&lt;/p&gt;
&lt;p&gt;The systems I trust most are not the most flexible ones. They are the ones where the constraints are visible and the boundaries are tight. Where a module does one thing and the interface reflects that one thing. Where someone made a decision instead of deferring one.&lt;/p&gt;
&lt;p&gt;Simplicity is not the absence of thought. It is the result of making hard choices early, choosing what the system will not do, what cases it will not handle, what abstractions it does not need yet. That takes more judgment than generalization, because you have to be willing to be wrong about a specific bet rather than hedge across all of them.&lt;/p&gt;
&lt;p&gt;There is a phrase that sounds like a compliment but often signals a problem: &amp;quot;It can handle anything.&amp;quot; A system that can handle anything usually handles nothing particularly well. The cost of generality is diffusion. The behavior is spread across configurations, strategies, and runtime switches. Understanding what the system actually does requires tracing through layers that exist for hypothetical cases.&lt;/p&gt;
&lt;p&gt;The more useful question is not can this handle future cases, but does this make the current case clear? A system that is clear about what it does now is easier to change later than one that is vague about what it might do eventually.&lt;/p&gt;
&lt;p&gt;This is counterintuitive. It feels like rigidity. But rigidity and clarity are not the same thing. A rigid system resists change. A clear system makes change legible, you can see what exists, understand why, and decide what to replace.&lt;/p&gt;
&lt;p&gt;Flexibility helps early when it addresses a known, concrete axis of variation. If you know from real usage that the system must support multiple authentication providers, an interface for that is earned. If you are guessing that someday someone might want to swap the database, that interface is speculative, and you will likely guess the abstraction boundary wrong anyway.&lt;/p&gt;
&lt;p&gt;The signals are usually visible if you look for them. Over-generalization shows up as interfaces with one implementation, config flags that are always set to the same value, and extension points that no one has extended. Optionality without purpose shows up as code that is harder to read than the problem it solves.&lt;/p&gt;
&lt;p&gt;Simplicity as a strategy means accepting that you will sometimes need to change a system that was not designed for that specific change. That is a real cost. But it is a cost you pay once, with full context, when the need is concrete. The alternative, paying a continuous tax on flexibility you never use, is more expensive. You just do not see it on any single line item.&lt;/p&gt;
&lt;p&gt;The hardest part of simplicity is that it requires confidence in a decision you cannot fully validate at the time you make it. Flexibility feels safe because it avoids commitment. But avoidance has its own cost, and it compounds quietly.&lt;/p&gt;
&lt;p&gt;Build for what you know. Make the current case clear. Let the future problem arrive before you solve it.&lt;/p&gt;
&lt;p&gt;The systems that age well are not the ones that anticipated everything. They are the ones that were easy to change when the unanticipated thing finally showed up.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Time As A Design Constraint</title>
      <description>Systems age whether we plan for it or not, and the best design decisions are often the ones that remain legible long after their original context is gone.</description>
      <link>https://brimtech.co/notes/time-as-a-design-constraint/</link>
      <guid>https://brimtech.co/notes/time-as-a-design-constraint/</guid>
      <pubDate>Wed, 04 Mar 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;Most systems are designed against requirements. Features, deadlines, throughput targets. These are real inputs, but they share an omission: they describe the system at one point in time.&lt;/p&gt;
&lt;p&gt;Treating time as a design input changes what you notice, and what you defer.&lt;/p&gt;
&lt;p&gt;Software ages whether you plan for it or not. The team that built it rotates. The domain shifts. The libraries update or stop updating. The person who understood the naming convention leaves, and no one asks why things are named that way. They just keep going.&lt;/p&gt;
&lt;p&gt;None of this is failure. It is the default behavior of systems under time. The question is whether your design accounts for it.&lt;/p&gt;
&lt;p&gt;There is a common pattern in engineering decisions: something works well today, so it ships. The trade-off is deferred, not avoided. You know the naming is inconsistent. You know the module boundary is wrong. You know the config layer is doing too much. But it works, and there is pressure to move forward.&lt;/p&gt;
&lt;p&gt;This is not laziness. It is rational under short-term constraints. The problem is that short-term rationality compounds. Each small deferral is reasonable on its own. Together, they form a system that no one fully understands, not because it is complex, but because its structure no longer reflects its intent.&lt;/p&gt;
&lt;p&gt;The config layer that parses YAML, validates environment variables, and also handles feature flags, because at one point those were the same concern. They are no longer the same concern, but the module does not know that.&lt;/p&gt;
&lt;p&gt;That gap between structure and intent is where maintenance cost lives.&lt;/p&gt;
&lt;p&gt;When I say &amp;quot;treat time as a design input,&amp;quot; I do not mean plan for every future scenario. That leads to over-abstraction, another cost that compounds. I mean something simpler: when making a decision, ask what this will look like in two years if no one touches it.&lt;/p&gt;
&lt;p&gt;Not what if requirements change. Not what if traffic doubles. Just: what happens if this decision sits, untouched, while the context around it keeps shifting?&lt;/p&gt;
&lt;p&gt;Some decisions hold up. A well-named module, a clear boundary, a simple contract between services, these age well because they carry their own context. You can read them later without needing the original author to explain.&lt;/p&gt;
&lt;p&gt;Others do not. A clever shortcut, a shared mutable structure, an implicit dependency between teams, these become opaque. Not because they were wrong, but because they required context that no longer exists.&lt;/p&gt;
&lt;p&gt;There is a phrase I keep returning to: deferred clarity. It shows up everywhere. In code that works but cannot be read. In architecture that functions but cannot be explained. In decisions that were made for good reasons that no one wrote down.&lt;/p&gt;
&lt;p&gt;Deferred clarity is not technical debt. Debt implies someone borrowed deliberately. Deferred clarity is quieter, the slow accumulation of decisions where understanding was present but never made explicit. The cost does not show up as a bug. It shows up as slower onboarding, longer review cycles, more meetings to re-establish context that should have been in the code.&lt;/p&gt;
&lt;p&gt;Context erosion is the mechanism behind most of this. Every system carries implicit knowledge: why this service exists separately, why that field is nullable, why the deployment order matters.&lt;/p&gt;
&lt;p&gt;When the people who hold that knowledge move on, the system does not change, but its readability does. This is not a documentation problem. It is a design problem. Systems that depend on external context to be understood are fragile, they work until someone needs to change them.&lt;/p&gt;
&lt;p&gt;The fix is not more documentation. It is designing for reduced context. Naming that explains intent. Boundaries that match real domain splits. Contracts that are explicit rather than implied.&lt;/p&gt;
&lt;p&gt;This does not require more effort. It requires effort at a different point, at the moment of decision rather than after the fact.&lt;/p&gt;
&lt;p&gt;The systems I am most proud of are not the ones that were technically impressive at launch. They are the ones that someone else changed successfully two years later without calling me.&lt;/p&gt;
&lt;p&gt;That is the real test. Not whether the system works on day one, whether it remains legible on day seven hundred.&lt;/p&gt;
&lt;p&gt;Treating time as a constraint changes what you optimize for. You favor clarity over cleverness. Explicit contracts over implicit ones. Fewer abstractions rather than more. You become skeptical of flexibility that no one has asked for, because you have seen how unused flexibility becomes unused complexity.&lt;/p&gt;
&lt;p&gt;You do not stop making trade-offs. You just weigh them differently. The question shifts from does this solve the problem to will this still make sense when the context around it has changed?&lt;/p&gt;
&lt;p&gt;That second question does not have a clean answer.&lt;/p&gt;
&lt;p&gt;But asking it changes the decisions you make.&lt;/p&gt;
&lt;p&gt;Decisions, more than code, are what age.&lt;/p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>When Code Is No Longer Scarce</title>
      <description>As code generation gets cheaper, the real bottleneck shifts from production to coordination, comprehension, and judgment.</description>
      <link>https://brimtech.co/notes/when-code-is-no-longer-scarce/</link>
      <guid>https://brimtech.co/notes/when-code-is-no-longer-scarce/</guid>
      <pubDate>Tue, 17 Feb 2026 12:00:00 GMT</pubDate>
      <content:encoded>&lt;p&gt;For most of software history, change was expensive.&lt;/p&gt;
&lt;p&gt;Humans wrote the code. Humans hesitated. Humans coordinated.&lt;/p&gt;
&lt;p&gt;That friction was not a flaw.&lt;/p&gt;
&lt;p&gt;It was the stabilizing force.&lt;/p&gt;
&lt;p&gt;Most of our architecture patterns, monorepos, microservices, CI pipelines, code review, ownership models, were built around a quiet assumption:&lt;/p&gt;
&lt;p&gt;Change would be limited by human effort.&lt;/p&gt;
&lt;p&gt;That assumption is dissolving.&lt;/p&gt;
&lt;h2&gt;The hidden constraint&lt;/h2&gt;
&lt;p&gt;Software systems work because change is naturally constrained.&lt;/p&gt;
&lt;p&gt;Not by rules.&lt;/p&gt;
&lt;p&gt;By people.&lt;/p&gt;
&lt;p&gt;A developer considers the blast radius before refactoring. A team weighs whether touching a stable subsystem is worth it. A reviewer pushes back when something feels unnecessary.&lt;/p&gt;
&lt;p&gt;Those pauses shape the system.&lt;/p&gt;
&lt;p&gt;They create stability not by policy, but by friction.&lt;/p&gt;
&lt;h2&gt;When change becomes cheap&lt;/h2&gt;
&lt;p&gt;AI systems can now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generate entire modules&lt;/li&gt;
&lt;li&gt;refactor thousands of files&lt;/li&gt;
&lt;li&gt;standardize patterns instantly&lt;/li&gt;
&lt;li&gt;propose improvements continuously&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When production cost drops toward zero, the limiting factor shifts.&lt;/p&gt;
&lt;p&gt;It is no longer writing code.&lt;/p&gt;
&lt;p&gt;It is coordinating it. Understanding it. Trusting it.&lt;/p&gt;
&lt;p&gt;And those costs do not shrink at the same rate.&lt;/p&gt;
&lt;h2&gt;The new bottleneck: comprehension&lt;/h2&gt;
&lt;p&gt;What happens when 30% of your codebase changes in a week?&lt;/p&gt;
&lt;p&gt;Not because strategy changed, but because optimization became automatic.&lt;/p&gt;
&lt;p&gt;Reviews become procedural. Ownership becomes symbolic. Tests validate motion more than intent.&lt;/p&gt;
&lt;p&gt;The system becomes fluid.&lt;/p&gt;
&lt;p&gt;But not necessarily clearer.&lt;/p&gt;
&lt;p&gt;We are entering a world where the cost of producing code collapses while the cost of understanding it remains stubbornly human.&lt;/p&gt;
&lt;p&gt;That asymmetry is the real shift.&lt;/p&gt;
&lt;h2&gt;The reflex to add more structure&lt;/h2&gt;
&lt;p&gt;When coordination becomes painful, we add structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;more metadata&lt;/li&gt;
&lt;li&gt;stricter boundaries&lt;/li&gt;
&lt;li&gt;deeper dependency graphs&lt;/li&gt;
&lt;li&gt;more automation to manage automation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We call this scaling.&lt;/p&gt;
&lt;p&gt;Often, it is complexity redistribution.&lt;/p&gt;
&lt;p&gt;The system becomes easier for machines to operate on.&lt;/p&gt;
&lt;p&gt;Harder for humans to reason about.&lt;/p&gt;
&lt;p&gt;At some point, the coordination layer consumes more energy than the product itself.&lt;/p&gt;
&lt;p&gt;The system becomes the product.&lt;/p&gt;
&lt;h2&gt;Abundance is not automatically good&lt;/h2&gt;
&lt;p&gt;Software has never been hard because we could not produce enough code.&lt;/p&gt;
&lt;p&gt;It is hard because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;deciding what to build requires judgment&lt;/li&gt;
&lt;li&gt;deciding what not to touch requires restraint&lt;/li&gt;
&lt;li&gt;removing code requires confidence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A system optimized for constant motion loses the ability to say:&lt;/p&gt;
&lt;p&gt;“This is done.”&lt;/p&gt;
&lt;p&gt;Without that boundary, everything becomes provisional.&lt;/p&gt;
&lt;p&gt;That is not agility.&lt;/p&gt;
&lt;p&gt;It is restlessness.&lt;/p&gt;
&lt;h2&gt;What survives in a world of abundant change&lt;/h2&gt;
&lt;p&gt;When change is cheap, the scarce resources become:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;attention&lt;/li&gt;
&lt;li&gt;context&lt;/li&gt;
&lt;li&gt;taste&lt;/li&gt;
&lt;li&gt;long-term memory&lt;/li&gt;
&lt;li&gt;judgment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These do not scale linearly with automation.&lt;/p&gt;
&lt;p&gt;They become more valuable as production accelerates.&lt;/p&gt;
&lt;p&gt;Not less.&lt;/p&gt;
&lt;h2&gt;Protect the real constraint&lt;/h2&gt;
&lt;p&gt;AI expands what we can generate.&lt;/p&gt;
&lt;p&gt;It does not automatically improve what we should generate.&lt;/p&gt;
&lt;p&gt;Architecture was never just about managing code.&lt;/p&gt;
&lt;p&gt;It was about managing change.&lt;/p&gt;
&lt;p&gt;And in a world where change is abundant, the role of restraint becomes structural, not optional.&lt;/p&gt;
&lt;p&gt;Protect the real bottleneck: human judgment.&lt;/p&gt;
&lt;p&gt;Because once judgment becomes secondary, complexity becomes inevitable.&lt;/p&gt;
</content:encoded>
    </item>
    
  </channel>
</rss>
