<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Field Notes · Bartosz Frąckowiak</title>
    <link>https://bfrackowiak.pl/blog/</link>
    <atom:link href="https://bfrackowiak.pl/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Weekly essays on software architecture, the people around it, and the corporate machine they form together.</description>
    <language>en</language>
    <lastBuildDate>Tue, 15 Sep 2026 17:00:01 +0200</lastBuildDate>
    <item>
      <title>Turn the Sequence Diagram on Its Side</title>
      <link>https://bfrackowiak.pl/blog/turn-the-sequence-diagram-on-its-side/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/turn-the-sequence-diagram-on-its-side/</guid>
      <pubDate>Mon, 14 Sep 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The people who can tell you a boundary is wrong were never going to read UML. Turn the sequence diagram on its side and they do not have to.</description>
      <content:encoded><![CDATA[<p>In August I sent an architecture design for review: seven pages, for a room of architecture stakeholders. Two pictures go into every design I send out now: one shows how the capability map changes, the other shows the flow. Back then the flow was an afterthought. My brief to the AI agent that drew it with me was &quot;C1, C2 and maybe some data flow diagram&quot;, in that order, and the container page got most of my time.</p>
<p>Nobody commented on the container page. Both flow pages, on the other hand, came back covered in comments. One started an argument that ran for two days and moved a boundary. That was no accident. The flow pages were the only ones that said which part of the organization owned each step. A picture that says who owns what is a picture people can argue with.</p>
<p>That is the bridge between an architecture and the organization it lands in. A design without that bridge is the usual ivory tower, approved and built by nobody. This one came down to the ground, into the problem the teams in that room were living with, and adoption followed at once. <a href="/blog/adoption-is-the-deliverable/">Adoption is the only measure of a design I still trust</a>, and this post is about the picture that produced it.</p>
<h2>A lifeline is a line</h2>
<p>The domain first. The platform models a graph of relations between organizations, worldwide, and one of its hard problems is deciding automatically whether a company somebody just named is one we already know. So take that flow: a partner gets named, and before anything else can happen the platform has to answer, already in our graph, or new?</p>
<figure class="post-figure"><picture><source media="(prefers-color-scheme: dark)" srcset="/assets/img/posts/turn-the-sequence-diagram-on-its-side/same-flow-in-uml-dark.png"><img src="/assets/img/posts/turn-the-sequence-diagram-on-its-side/same-flow-in-uml-light.png" alt="Adding one edge to an organisation graph, in UML sequence notation. Drawn properly, with an interaction frame, activation bars, returns, and open arrowheads on the two asynchronous messages." width="1952" height="904" loading="lazy" style="max-width:100%;height:auto"></picture><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">Adding one edge to an organisation graph, in UML sequence notation. Drawn properly, with an interaction frame, activation bars, returns, and open arrowheads on the two asynchronous messages.</figcaption></figure>
<p>That&#039;s a correct sequence diagram, with a frame, activation bars, real returns and open arrowheads on the messages nobody waits for. My complaint is about everything that is true here and shows up nowhere on the page.</p>
<p>Identity matching runs on a confidence score, and below the threshold we reject nothing. We create a second company instead, and it never merges back. Two in five of these partners arrive with no registration number at all. One new edge makes us recompute a number for every node above it, in a graph 40 million deep. Of the five participants in the picture, four already exist. One is new.</p>
<p>Sequence notation can show all of that. UML has a state invariant for the duplicate, an <code>opt</code> fragment for the missing number and a <code>ref</code> for the matching logic, all of it legal, and all of it also makes the picture wider and costs you a reader fluent in those symbols.</p>
<p>So the notation can express everything I listed. What it runs out of is room, and with room the amount of information one picture can carry. <strong>An arrow label holds about four words before it touches the next one</strong>, and a sequence diagram spends almost all of its capacity on control flow. The rest follows from that.</p>
<p>Put everything else in a paragraph underneath the picture and you have built a known problem on purpose, one Chandler and Sweller measured in 1991 and called the <a href="https://bpspsychub.onlinelibrary.wiley.com/doi/abs/10.1111/j.2044-8279.1992.tb01017.x">split-attention effect</a>: split what a reader has to combine, and they get less from both halves.</p>
<h2>What a band has room for</h2>
<p>So turn it sideways. Participants go down the left and time runs across the bottom.</p>
<figure class="post-figure"><picture><source media="(prefers-color-scheme: dark)" srcset="/assets/img/posts/turn-the-sequence-diagram-on-its-side/same-flow-in-lanes-dark.png"><img src="/assets/img/posts/turn-the-sequence-diagram-on-its-side/same-flow-in-lanes-light.png" alt="The same flow, turned sideways. Same participants, same eight messages, same shape, with Directory still holding the request open across the grey bar. A row has height, so a step becomes a box instead of an arrow." width="1952" height="1220" loading="lazy" style="max-width:100%;height:auto"></picture><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The same flow, turned sideways. Same participants, same eight messages, same shape, with Directory still holding the request open across the grey bar. A row has height, so a step becomes a box instead of an arrow.</figcaption></figure>
<p>The same five participants are there, with the same eight messages and the same shape, because Directory still waits for both answers and still sends the event at the end. That is the grey bar. One thing changed: a row has height. So a step becomes a box holding about 25 words, instead of an arrow holding four.</p>
<p>Three rules spend that space, and they are the whole notation. The lane label carries the owning domain, and every domain has an owner in the organization, which is how a lane ends up with a team name on it. The box carries how the step works rather than the name of the method. And one note box carries the reason, because the reason is what disappears first.</p>
<figure class="post-figure"><picture><source media="(prefers-color-scheme: dark)" srcset="/assets/img/posts/turn-the-sequence-diagram-on-its-side/what-each-notation-carries-dark.png"><img src="/assets/img/posts/turn-the-sequence-diagram-on-its-side/what-each-notation-carries-light.png" alt="What each notation can carry. Bands win on ownership, danger and reasoning. Lifelines win on control flow, combined fragments, and being text you can diff." width="1952" height="1064" loading="lazy" style="max-width:100%;height:auto"></picture><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">What each notation can carry. Bands win on ownership, danger and reasoning. Lifelines win on control flow, combined fragments, and being text you can diff.</figcaption></figure>
<h2>What comes back is different</h2>
<p>I didn&#039;t expect this part. The number of comments stayed the same, but the kind of comment changed.</p>
<p>Send a sequence diagram round and half of what comes back is about the notation. Does this dashed arrow mean async or a return? Is anybody blocked here? Fair questions. Not one of them is about the design, though. So you spend the review teaching people to read the picture, and the picture was supposed to be the cheap part.</p>
<p>Send the lane version instead and the questions are about the system. Why does this cross my team? What happens to a company we created twice, and who picks that up? Nobody&#039;s ever asked me what a lane means, because a row with a squad name on it needs no explaining. That is the difference between a diagram people review and one they approve.</p>
<p>The bigger surprise was who joined in, because product people read these without any training, and I had stopped expecting anyone outside engineering to read a sequence diagram at all. <strong>The people who can tell you a boundary is wrong are often the same people who were never going to read UML.</strong></p>
<h2>Count the handovers, not the arrows</h2>
<p>This is where the effort pays, and it costs one line per lane. Write the owning domain on each lane, and since every domain has a team behind it, five services become four squads with three handovers. <strong>Each handover is a conversation somebody has to have before any code gets written.</strong></p>
<p>Count them carefully. This is where the idea gets used wrongly. Don&#039;t count arrows. Two lanes owned by one squad make a crossing but no conversation, and six arrows between the same two squads are still one conversation. What you count is the pairs of teams that hand each other something.</p>
<p>On the page I sent for review, 8 lanes and 14 steps came down to 3 teams and 3 pairs. The same page broke my own rule about splitting past 6 lanes, which should have been the warning. That number belongs next to the estimate, because it counts the roadmap slots you will need inside other teams and the people who can say no.</p>
<p>None of this came from software. Rummler and Brache made the swimlane popular in <em>Improving Performance: How to Manage the White Space on the Organization Chart</em>. Their finding was simple: processes do not fail inside departments, they fail at the handoffs between them. A famous 80% is attached to that. I couldn&#039;t find the source. A sequence diagram has no white space to point at, because its participants are objects. Objects do not have managers, quarters, or a backlog that is already full.</p>
<p>The note box ends with a line I now put on all of them: whose ticket this is gets agreed here, or it gets agreed during an incident.</p>
<h2>Yes, this is a swimlane</h2>
<p>Somebody will say it. So I will. <a href="https://www.omg.org/spec/BPMN/2.0/">BPMN</a> has had pools and lanes since 2004, UML has activity partitions in the same specification as the sequence diagram, PlantUML takes <code>|Identity|</code>, and Mermaid has them too. The rotation is twenty years old. None of it is mine.</p>
<p>What I haven&#039;t found anywhere is the discipline. BPMN lanes carry a role and hide the reasoning in a properties panel, PlantUML gives you one line per step, and none of them asks for the mechanism or for what the picture will cost in other people&#039;s quarters.</p>
<h2>What I still cannot do</h2>
<p>Returns, for one. A sequence diagram shows exactly when control comes back, while my grey bar only shows that somebody is still holding it.</p>
<p>The tooling is worse than it looks. These pictures come from a model file through a generator, so they diff and regenerate. The skill below writes absolute coordinates instead, the dead artifact I complained about in <a href="/blog/the-diagram-that-cannot-lie/">The Diagram That Cannot Lie</a>. I know how to fix it. I haven&#039;t.</p>
<p>Then the one that bothers me. Open one of these on a phone and the text in the boxes drops below 4px. <strong>A notation whose only advantage is text density is the one a narrow screen hurts most.</strong> It&#039;s a desktop artifact. I have no answer for that.</p>
<h2>Take the notation, not the picture</h2>
<p>The pictures here are worth nothing to you, but the notation might be, so it is a file rather than advice. Drop <code>SKILL.md</code> into <code>.claude/skills/</code>, describe a flow, and get a <code>.drawio</code> back.</p>
<p><strong><a href="/assets/skills/lane-flow-diagram/SKILL.md">SKILL.md</a></strong> ⋅ MIT, no attribution needed, swap the palette for your own.</p>
<p>Its last section is a checklist, the part I&#039;d keep if the rest went. One of its questions asks how many team handovers the picture holds, and whether that number sits next to the estimate.</p>
<p>Send me one of yours with the team names on it. I want to know how many conversations you count.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Systems Thinking</category>
      <category>Organizational Design</category>
      <category>Engineering Leadership</category>
      <category>Artificial Intelligence</category>
    </item>
    <item>
      <title>We Deleted the Wrong Half of Agile</title>
      <link>https://bfrackowiak.pl/blog/we-deleted-the-wrong-half-of-agile/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/we-deleted-the-wrong-half-of-agile/</guid>
      <pubDate>Mon, 07 Sep 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Reviewability is something you design. Extreme Programming already knew how, and we made exactly the wrong half of it optional.</description>
      <content:encoded><![CDATA[<p>At 11:45 in the morning, my manager told me I had buried the point of my own essay.</p>
<p>He had read <a href="/blog/the-bottleneck-moved/">The Bottleneck Moved</a>, the one about review queues filling with code no human wrote. His verdict, in the channel, unedited: the bottleneck is just context in my view. The part worth writing about was a single line near the end, where I said reviewability is a system property and then walked straight past it.</p>
<p>Then he added the sentence I have not stopped chewing on. Agent SDLC should bring new concepts or resurrect XP practices, he wrote, and put &quot;the true agile&quot; in brackets.</p>
<p>He was right about the burial. The arithmetic was hard to argue with: I had spent eleven hundred words describing a queue, and exactly one sentence on the only part of it I can design.</p>
<h2>What the property is, exactly</h2>
<p>Start with what a reviewer does. Not the ritual. The actual cognitive act.</p>
<p>You open a diff and a question forms. Then a second one. Historically each question had a destination: the person who wrote it, three desks away, still holding the answer in their head. Review felt like a technical act because the social half was invisible and fast.</p>
<p>Take the author away and the questions don&#039;t disappear. They just have nowhere to go.</p>
<p>So here is the definition I have been using since that message. <strong>A system is reviewable to the degree that a reviewer&#039;s questions have destinations that are not a person.</strong> Four questions cover most of what I have watched people actually ask, across fifteen squad reports a month and my own miserable weekends as a one-person review queue:</p>
<p>What breaks if this is wrong? Why this way and not the obvious simpler way? Does it do what it claims to do? And what did this not touch that I assumed it would?</p>
<p>Every one of those has an artifact-shaped answer available. Blast radius is answerable from the diff when contracts sit at the seams and modules have real boundaries. The rejected alternative is answerable from a decision record, which is the only reason to write one. Whether it does what it claims is answerable from tests that state intent rather than pin implementation, so a green suite means something specific instead of meaning nothing loudly. Scope is answerable from size. That is the entire case for small changes, and it survived every fashion that tried to kill it.</p>
<figure class="post-figure"><picture><source media="(prefers-color-scheme: dark)" srcset="/assets/img/posts/we-deleted-the-wrong-half-of-agile/where-the-question-goes-dark.png"><img src="/assets/img/posts/we-deleted-the-wrong-half-of-agile/where-the-question-goes-light.png" alt="The four questions a reviewer actually asks, each pointing at the artifact that has to answer it once the author is gone." width="3690" height="760" loading="lazy" style="max-width:100%;height:auto"></picture><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The four questions a reviewer actually asks, each pointing at the artifact that has to answer it once the author is gone.</figcaption></figure>
<p>None of that is exotic. That&#039;s the uncomfortable part.</p>
<h2>The half we made optional</h2>
<p>Kent Beck put twelve practices in <em>Extreme Programming Explained</em> in 1999. Read the list now, with agents in your peripheral vision, and it splits cleanly into two jobs.</p>
<p>One group keeps the author reachable: pair programming, on-site customer, collective code ownership, the stand-up. The other group makes the artifact answer for itself: test-driven development, continuous integration, small releases, simple design, refactoring, coding standards.</p>
<figure class="post-figure"><picture><source media="(prefers-color-scheme: dark)" srcset="/assets/img/posts/we-deleted-the-wrong-half-of-agile/two-jobs-dark.png"><img src="/assets/img/posts/we-deleted-the-wrong-half-of-agile/two-jobs-light.png" alt="XP&#039;s twelve practices, split by the job each was doing. The struck-through four all assume a counterparty you can still walk over and ask." width="2374" height="1832" loading="lazy" style="max-width:100%;height:auto"></picture><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">XP&#039;s twelve practices, split by the job each was doing. The struck-through four all assume a counterparty you can still walk over and ask.</figcaption></figure>
<p>Scrum industrialised the first job. It has no engineering practices at all, by design, and that was not a scandal for the first decade because the second group survived as craft culture in teams that cared. Martin Fowler named the failure mode in 2009 and called it <a href="https://martinfowler.com/bliki/FlaccidScrum.html">FlaccidScrum</a>: the ceremonies running perfectly while the codebase rotted underneath them. Everyone I knew read that piece and agreed with it. Then we filed it as a maturity problem. Something to get to after the reorg.</p>
<p>Then the author stopped existing.</p>
<p>Every practice in the first group assumes a counterparty who can still be asked. You can&#039;t pair with a session that closed. Nobody can. Collective ownership of code that nobody wrote is a sentence missing its subject. The half of agile that survived twenty years of process consulting is the half that needs a human on the other end of the question. <strong>And the half we were allowed to skip is the one that had been carrying the arrangement all along.</strong></p>
<p>That&#039;s not an argument for going back. Most of XP&#039;s first group is still how humans work well together, and I would not run a team without it. It is an argument about which practices are load-bearing now rather than merely good. We spent two decades filing the load-bearing ones under optional, because a human in the room could always cover for them.</p>
<h2>The practice everybody mocked came back with a filename</h2>
<p>There is one genuinely new thing here, and it arrived in the most embarrassing way possible.</p>
<p>System metaphor was XP&#039;s weakest practice. The one Beck had the hardest time explaining, and the one teams dropped first: a shared story of how the system works, with no artifact attached to it. It sounded like poetry in a discipline that wanted checklists. So it evaporated.</p>
<p>It has a path now. <code>CLAUDE.md</code>, <code>AGENTS.md</code>, whatever your tool calls its rules file. We started writing them because a model needed orientation before it could touch anything. What came out was a document describing what the system is, and where the bodies are buried. Then the new joiner reads it on day one and gets more from it than from the wiki.</p>
<p>We needed a non-human reader before we would write down the thing we had refused to write down for twenty years. I find that funny in a way that doesn&#039;t fully sit well.</p>
<h2>The objection I keep running into</h2>
<p>Pair with the agent. Review at write time, watch each change land, and the authorless diff never forms in the first place.</p>
<p>This is the strongest counterargument and it&#039;s partly right. Pairing with an agent does move review earlier, and earlier review is cheaper review; that has been true since long before any of this. So far, so good.</p>
<p>But it only holds while a human stays the counterparty. If the person who ran the prompt can still answer for the diff on Thursday then the practice survives intact and the theory has somewhere to live, and if they are meeting the output at the same moment the reviewer is, then nothing was paired at any point. Something was generated and forwarded.</p>
<p>Which gives one standard worth adopting tomorrow, and it costs nothing: <strong>&quot;I just ran the agent&quot; is not an acceptable answer in a review.</strong> Whoever sends the change is the counterparty for it. That&#039;s not a process change, and no tool enforces it. It&#039;s a thing a team either expects of each other or doesn&#039;t.</p>
<p>I don&#039;t know where the ceiling on this sits. Some questions may simply have no artifact-shaped answer, and the honest response to those is to not merge. Which is a far less comfortable conclusion than the one about tooling.</p>
<h2>You have been measuring it for years</h2>
<p>This is what made me stop treating it as a coding-standards problem.</p>
<p>A new joiner is a reviewer with no context. Everything that makes a system hard to review makes it hard to join, for exactly the same reason. The questions have no destination, so they route to whoever has been there longest, and that person becomes the bottleneck by seniority rather than by design. <strong>Reviewability and onboardability are the same property, looked at from two ends.</strong></p>
<p>Which means the metric already exists. You have been collecting it. Time to a new joiner&#039;s first merged change is a reviewability number, and it has been sitting in an HR dashboard being read as a statement about the person.</p>
<p>If you want a faster reading, take your last ten merged pull requests and count the times somebody had to ask a human something the repository could have answered. Divide by ten. That is roughly what each authorless diff is about to cost you. Most numbers in this area take a quarter to produce; this one takes an afternoon. And if it comes back zero while your review queue is still three days deep, my argument is wrong.</p>
<h2>What I did not see until he named it</h2>
<p>The thing I keep turning over is that I had already built this and couldn&#039;t see it.</p>
<p>The architecture tool I build at night exists so that editing an element on a canvas produces a Git diff containing that edit and nothing else. No reordering. No reformatting. No comment lost. I built it because <a href="/blog/the-diagram-that-cannot-lie/">diagrams drift</a>, and I wanted one that could not. It took somebody reading my own essay back to me to name what I had made. Blast radius legibility, the first of the four questions, built into a product.</p>
<p>The practices were on the shelf all along. We labelled them engineering hygiene, which is what a discipline calls the thing it means to skip. Twenty years later the one participant who covered for them stopped showing up.</p>
<p>I still read fifteen reports a month. What I look for now is not the ageing queue. I know how to find that. It is the sentence where somebody explains that they had to go and ask. Then the part where they say who.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Engineering Leadership</category>
      <category>AI Transformation</category>
      <category>Organizational Culture</category>
      <category>Systems Thinking</category>
    </item>
    <item>
      <title>Design the Stopping Point</title>
      <link>https://bfrackowiak.pl/blog/design-the-stopping-point/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/design-the-stopping-point/</guid>
      <pubDate>Mon, 24 Aug 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Target state is the easy half of a solution design. The hard half is the first step, because that is usually where the money runs out.</description>
      <content:encoded><![CDATA[<p>An engineering director asked me a question I couldn&#039;t answer cleanly, which is how I knew it was a good one.</p>
<p>We were mapping the delivery lifecycle on a whiteboard. Where discovery ends, where execution begins, who hands what to whom. Discovery produces a solution design. Execution produces working software. Two clean boxes and one clean arrow between them. Then he tapped the arrow. &quot;What actually travels along it?&quot;</p>
<p>I had a target state. I always have a target state. What I didn&#039;t have was a good answer about the thing in the middle, and for years I had filed it under delivery&#039;s problem.</p>
<h2>Two worlds, and only one of them should be drawn by a human</h2>
<p>A solution design describes two worlds.</p>
<p>The first is the system as it stands today. A person should barely write that description at all. It should be read out of the code, the pipelines, the infrastructure definitions, whatever a tool or an agent can see for itself. This has stopped being an aspiration: reading an existing system is the one job the current crop of agents does better than writing into it, and comprehension is exactly where they are cheapest to trust.</p>
<p>The moment a current-state diagram becomes an opinion, it becomes a flattering one. Nobody draws their own system messier than it is. I have argued the harder version of this before: <a href="/blog/the-diagram-that-cannot-lie/">a model that no compiler checks will drift</a>.</p>
<p>The second world is the target state. What the system looks like once the requirement is genuinely met. That one is a claim about a future that doesn&#039;t exist yet, so a named human has to own it.</p>
<p>Both are comfortable to produce. The design reviews well, the boxes are legible, the room nods. Then execution starts, and the two worlds turn out to be separated by something nobody drew.</p>
<figure class="post-figure"><img src="/assets/img/posts/design-the-stopping-point/the-unfunded-half.png" alt="A solution design covers both ends. The money usually runs out somewhere in the middle, so the shape of each step matters more than the destination does." width="1800" height="400" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">A solution design covers both ends. The money usually runs out somewhere in the middle, so the shape of each step matters more than the destination does.</figcaption></figure>
<h2>Debt is not created at design time</h2>
<p>One sentence from that conversation reorganized how I work.</p>
<p>A design does not create technical debt. It cannot. It is a document. It has never touched a running system, never shipped a schema, never left a compatibility shim in place for three years.</p>
<p>Debt appears the moment execution stops somewhere other than the target state. And it almost always does. Budgets are annual, priorities rotate, and the reorganization doesn&#039;t read your migration plan ;)</p>
<p>I have watched initiatives with genuinely good designs leave systems worse than they found them. Not because the design was wrong. Because the funding ended between step two and step three, in a place nobody had ever considered as a resting position.</p>
<p><strong>A design cannot owe anything. Only a half-built system can.</strong></p>
<p>Which moves the interesting work. Not to the destination. To the shape of the trail.</p>
<h2>Make every step a place the system can live</h2>
<p>So the question I now ask before a first step is not whether it moves us toward the target. Almost anything does. It is: <strong>if this turns out to be the last step we ever fund, what have we left behind?</strong></p>
<p>A step that passes is a resting position: a place the system can stop and live for two years without apology. Three properties, in the order I check them:</p>
<ul>
<li><strong>It stands alone.</strong> After the step, the system is internally consistent. One model at a smaller scope, not two half-models with a translation layer nobody owns.</li>
<li><strong>It is reversible, or it is finished.</strong> Either the step walks back with a configuration change, or it completed something. What I avoid is the middle: irreversible and unfinished. Decomposing until <a href="/blog/the-migration-that-never-starts/">every step is a two-way door</a> is the same instinct pointed at a different problem.</li>
<li><strong>It closes an option on purpose.</strong> A good step removes a path and names which one. A step that keeps every option open has not decided anything. Optionality feels like prudence long after it has quietly become an unpaid invoice.</li>
</ul>
<figure class="post-figure"><img src="/assets/img/posts/design-the-stopping-point/two-first-steps.png" alt="Both steps move toward the same target. Only one of them leaves a system that can be lived in if the next step is never funded." width="1760" height="440" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">Both steps move toward the same target. Only one of them leaves a system that can be lived in if the next step is never funded.</figcaption></figure>
<h2>Write the stopping point down, not just the destination</h2>
<p>The artifact this produces is small. One page, next to the solution design.</p>
<p>The first step. What it deliberately leaves unfinished. The risk of that state persisting for two years, in plain words rather than a severity colour. The name of the person who accepted that risk. And a trigger: the event that makes revisiting it mandatory, so that &quot;not yet&quot; has an alarm attached instead of a hope.</p>
<pre><code>step 1:        …
unfinished:    …
risk if this lasts 2 years:  … (plain words, no severity colour)
accepted by:   &lt;a name, not a role&gt;
revisit when:  &lt;the event that makes &quot;not yet&quot; expire&gt;
</code></pre>
<p>That last part I stole from my own hobby repository, where one decision record exists purely to <a href="/blog/adrs-for-an-audience-of-one/">record a decision not to decide, with a written trigger</a>. It transfers to organizational scale better than it has any right to.</p>
<p>It also answers the question every engineering director eventually asks. Should the architect be hard on quality, or should the architect plan the shortcut? Neither is the job. <strong>The job is to make the shortcut legible.</strong> Say exactly what is being traded, for how long, and by whom. Then the organization is choosing rather than drifting.</p>
<p>The objection I hear is that writing the shortcut down blesses it, that planning the resting position means giving up on quality. But the shortcut doesn&#039;t wait for a blessing. It happens either way. The only choice on offer is whether it is chosen or discovered.</p>
<h2>What it changed</h2>
<p>That whiteboard conversation never produced a clean answer, and I stopped expecting one. What it changed was smaller than an answer.</p>
<p>I still deliver a target state. I still let a tool draw the current one. But the page I spend the longest on now is the shortest of the three: the first step, what it deliberately leaves undone, and the name of the person who agreed to live with that.</p>
<p>It takes an hour to write. It is also the only part of the plan I can be sure will happen, because it happens first. And often enough, it turns out to be the part that happens last.</p>
<p>The design never owed anything. The trail did.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Technical Debt</category>
      <category>Decision Making</category>
      <category>Engineering Leadership</category>
      <category>Systems Thinking</category>
    </item>
    <item>
      <title>Count Your Rejections</title>
      <link>https://bfrackowiak.pl/blog/count-your-rejections/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/count-your-rejections/</guid>
      <pubDate>Mon, 17 Aug 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>A friend passed every round and still did not get the job. Nothing was wrong with him. The company simply had no seat that quarter.</description>
      <content:encoded><![CDATA[<p>A while ago my phone rang with a name I had not seen in a long time. Early in my career he had been the more experienced one, the person I walked over to when I was stuck. Now he was calling to ask how things were going with me, in the particular tone people use when things are not going well with them. He had landed somewhere wrong.</p>
<p>I remembered his interview at the company I worked for back then. I had been certain about him, and the loop agreed. Five rounds, five yes. He did not get the job.</p>
<p>Not because of anything he said, or did, or failed to know. The headcount moved.</p>
<p>That call rearranged something I had believed for a decade.</p>
<h2>What five out of five measured</h2>
<p>An interview loop is not measuring your worth, and it is not really trying to. It is managing somebody else&#039;s risk at scale, and it does that inside constraints that have nothing to do with you. An open seat. A budget cycle. The current shape of a team. A manager&#039;s memory of their last bad hire. I have written before about <a href="/blog/grinding-dijkstra-at-senior-level/">the loop as a risk function rather than a verdict</a>, and every year since has made me more confident that this is the right reading.</p>
<p>Competence is one input. It is a real input, and it is the only one you can prepare. But it gets scored against a system state you were never shown, and that state changes week to week.</p>
<figure class="post-figure"><img src="/assets/img/posts/count-your-rejections/same-candidate-two-quarters.png" alt="Same person, same answers, two quarters apart. The input that decided the outcome was never on the candidate&#039;s side of the table." width="1720" height="500" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">Same person, same answers, two quarters apart. The input that decided the outcome was never on the candidate&#039;s side of the table.</figcaption></figure>
<p>Which makes a rejection a reading with two operands. Your skills are one of them. <strong>The other is a system nobody described to you.</strong></p>
<h2>The years I misread the room</h2>
<p>I spent the early part of my career as a consultant, which meant a new project every few months and a lot of first conversations. Two or three a year, sometimes more.</p>
<p>I got good at feeling the outcome early. Ten minutes into a conversation you can usually tell whether anything will come of it. What I couldn&#039;t do was name what I was feeling, so I explained it to myself with the only vocabulary I had: they must have wanted somebody more technical.</p>
<p>They usually did not. What was actually being read was whether I could get a thought out of my head and into somebody else&#039;s without requiring them to already share my context. Whether I would be pleasant to disagree with at four in the afternoon in a bad week. Whether I asked them anything at all.</p>
<p>I have made a kind of peace with still not being good at the first one. Moving a fully formed idea into another person&#039;s head is genuinely hard for me, harder than any system I have been handed to design. That is not modesty. It is the largest gap on my own list, and I know it is there because rejections kept saying so, for years, long before I was willing to hear it.</p>
<h2>Depth alone has become a fragile position</h2>
<p>It also explains why I have gone cold on the model everyone quotes. The T shape: broad awareness across many areas, deep expertise in one.</p>
<p>The vertical stroke still pays. But maximizing it alone has become a genuinely risky career position, and not for the reason usually given. It is not mainly that agents write code now, although that matters: generation is exactly the part of the vertical stroke that got cheap this decade, and the industry is still mid-argument about what developer skill even means once it did. It is that the scarce thing in any organization is someone who can hold a system-level view <em>and</em> be worked with. Depth without the horizontal stroke produces a person who is right often and expensive to be around, and organizations quietly route around expensive-to-be-around. They do it without malice and usually without noticing.</p>
<p>A colleague I still respect enormously put it better than I have managed since: &quot;If you are right and you were unpleasant about it, you are still unpleasant. If you are wrong and you were decent about it, you were merely wrong.&quot;</p>
<p>The second position is recoverable in an afternoon. The first one compounds. It is the individual version of the failure I described in <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a>: a room that can only think one way is not strong, it is brittle.</p>
<h2>Make it a KPI</h2>
<p>The practice itself is small, and I would defend it in front of anyone.</p>
<p>Keep a rejection ledger. Not to punish yourself. To check that you are still asking for things.</p>
<p>A year with zero rejections is not a year in which you were universally correct. It is a year in which you applied for nothing, proposed nothing that could fail, and asked for no budget you had not already been promised. <strong>A year without rejections is not a good year. It is a small one.</strong></p>
<p>Two habits turn the number into an instrument rather than a bruise:</p>
<ul>
<li><strong>Separate the reading from the verdict.</strong> Write down what was actually declined. Nine times out of ten it was a fit, a timing, or a seat. One time in ten it is a real gap, and that one is a piece of feedback you got for free that most people pay a career to learn.</li>
<li><strong>Ask again.</strong> Nothing about an organization is stable. The manager with no seat in March has been reorganized by September. The proposal that was too early last quarter is obvious this one. The same request, unchanged, meets a different system and gets a different answer, and almost nobody tries twice.</li>
</ul>
<p>My friend, the one who called: he took a very good job about a year after the five out of five. Same skills. Different quarter.</p>
<h2>Try this</h2>
<p>Count the times you were told no in the last 12 months. Four columns are enough for the ledger: the date, what exactly was declined, your best guess at why, and which pile it belongs to. Two piles: the ones that were about you, and the ones that were about the state of the system on that particular Tuesday.</p>
<p>If the second pile is bigger, you&#039;ve spent a year collecting evidence about other people&#039;s constraints and filing it under your own competence. Move it to the right folder and go ask for something.</p>
<p>And if both piles are nearly empty: what did you decide not to ask for this year, and who exactly told you no before you did?</p>
]]></content:encoded>
      <category>Career Development</category>
      <category>Engineering Leadership</category>
      <category>Personal Growth</category>
      <category>Corporate Culture</category>
      <category>Technical Interviews</category>
    </item>
    <item>
      <title>Adoption Is the Deliverable</title>
      <link>https://bfrackowiak.pl/blog/adoption-is-the-deliverable/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/adoption-is-the-deliverable/</guid>
      <pubDate>Mon, 10 Aug 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The ugliest service in production beats the most elegant one nobody calls. So where exactly does an architect&#039;s job end?</description>
      <content:encoded><![CDATA[<p>Two services on one platform solved almost the same problem. One was built properly. Clean boundaries, a published contract, a model you could understand on the first read. The other one was a lump. Vague naming, a schema that had recorded somebody&#039;s Tuesday, and no versioning story worth calling one.</p>
<p>Four teams integrated with the lump.</p>
<p>For a while I explained to myself that this was the organization&#039;s failure. It was not. I have stopped arguing with the result. The lump sat closer to what people needed on the day they needed it, and no design review was going to close that gap.</p>
<p>One platform, one architect watching it. Notes, not data.</p>
<h2>The responsibility line is drawn in the wrong place</h2>
<p>Ask most architects where their job ends and you get a version of the same answer. At the design. Or at the first working version. Adoption after that belongs to delivery, or to product, or to whoever runs the platform. It is a tidy answer. I gave it for years myself.</p>
<p>That line is comfortable. It also produces exactly the thing I described above: a correct system that nobody depends on. It is the mirror image of the orphaned aggregate I wrote about in <a href="/blog/nobody-owns-the-sum/">Nobody Owns the Sum</a>. There, a whole that nobody owned. Here, a part that nobody wanted.</p>
<p>So this is the measure I use now. <strong>An architecture is worth what other teams are willing to depend on.</strong> Not what it could support one day. <strong>What is plugged in this week.</strong></p>
<p>That makes adoption an input to the design. It is not a phase that arrives later and happens to you. You design it, or you inherit whatever shows up.</p>
<h2>Two engines, running on different fuel</h2>
<p>Things spread inside a company in two ways, and the two are not versions of each other. The fuel is different. So is the bill.</p>
<p>The first engine is pull. Somebody has the problem today, finds your thing, and calling it costs less than building their own. Then they tell one other team. Pull grows on its own. Each new adopter costs you almost nothing, and it keeps only the things that fit.</p>
<p>Pull also cannot be scheduled. That is why programme managers get nervous around it. They are right to be. No roadmap can promise that a thing will be wanted by March.</p>
<p>The second engine is push. Somebody decides that everyone will use the thing, and a plan carries it into teams that never asked for it. <strong>Push is the only engine that meets a date.</strong> It also spends something you cannot print more of: roadmap slots inside other people&#039;s teams, goodwill that takes a long time to come back, and the patience of everybody who has to stop what they were doing and move. The bigger the change, the faster that bill grows, and it does not grow in a straight line.</p>
<figure class="post-figure"><img src="/assets/img/posts/adoption-is-the-deliverable/two-engines.png" alt="Pull grows on its own but cannot be scheduled. Push hits a date, and spends coordination capacity that comes back slowly." width="1720" height="660" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">Pull grows on its own but cannot be scheduled. Push hits a date, and spends coordination capacity that comes back slowly.</figcaption></figure>
<p>Domain-Driven Design already named the thing that makes pull possible, and it is one of the less fashionable patterns in the book: the <strong>open host service</strong>. A published protocol, built for many consumers rather than for the one person standing in front of you. Integrating with it does not require a conversation with its author. That is the whole feature.</p>
<p>It is the outward-facing twin of <a href="/blog/the-anti-corruption-layer-is-for-people-too/">the anti-corruption layer</a>. That one protects you from their model. This one spends your effort so nobody has to learn yours.</p>
<p>Most internal platforms skip that work. Then they wonder why nobody came. Every hour you spend making the first call easy buys you an adopter you never have to negotiate with, never have to chase for a roadmap slot, and never have to persuade in a meeting where somebody else is asking for the same quarter. No mandate I have ever run paid that well.</p>
<h2>Where push stops being worth its price</h2>
<p><a href="https://en.wikipedia.org/wiki/Wardley_map">Simon Wardley&#039;s mapping</a> has an axis I keep borrowing for this. It runs from genesis, through custom built and product, to commodity. Lay the two engines over that axis and something useful shows up.</p>
<figure class="post-figure"><img src="/assets/img/posts/adoption-is-the-deliverable/the-mandate-window.png" alt="On the evolution axis, a mandate only pays for itself in the middle. To the left nothing is ready to adopt, and to the right pull has already done the work." width="1760" height="490" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">On the evolution axis, a mandate only pays for itself in the middle. To the left nothing is ready to adopt, and to the right pull has already done the work.</figcaption></figure>
<p>On the far left, in genesis, push does not work at all. The thing is too unformed. You cannot adopt it on a schedule. Mandating it buys compliance without adoption, plus a long tail of adapters written by irritated people. I have written some of those. More than once.</p>
<p>On the far right, at commodity, push is not needed. Nobody mandates the company database. Pull settled that years ago. The alternative became absurd.</p>
<p>Between those two ends sits the mandate window. That is the stretch where a mandate is both necessary and affordable. The thing is good enough to adopt, and not yet obviously worth adopting. Most internal platform work lives right there, and it is the only place where a mandate pays for itself.</p>
<p>The window has a ceiling, and here my answers run out. Somewhere the size of the change outgrows the coordination you have available. Past that point, every extra team costs more to move than the last one did. I have watched initiatives go straight past that ceiling, then spend a year of programme management on the final four teams. Those four were never going to want the thing anyway.</p>
<p>One complication, and I want to be fair to it, because it is the live argument in platform engineering right now. The consensus is close to unanimous: build the <a href="https://teamtopologies.com/">platform as a product</a>, win teams over with golden paths, never mandate. I think it is right about the product. And wrong about the window.</p>
<p>The standard objection says a mandate buys compliance rather than adoption. In genesis, that is exactly what it buys. Inside the window the objection loses most of its force. The thing is already good enough, so a quarter of mandated use turns into a dependency nobody wants to remove. And a dependency nobody wants to remove is adoption, whatever you called it on the day it arrived. There is also reporting that mandated platforms keep their funding better than voluntary ones do.</p>
<p>Both can be true. Pull builds the better product. Push keeps things moving while pull gets going, which is a less noble job and still has to be done by somebody. What nobody has measured, as far as I can find, is the exchange rate between the two.</p>
<h2>What I do with it now</h2>
<p>The design review is no longer where I stop. The last question I ask about anything I helped design used to be whether it was correct. Now I ask how many teams called it this week, and which engine brought each of them in.</p>
<p>The lump taught me that, and it took longer than it should have. Four teams had already chosen, by integrating, while I was still explaining why the other service deserved them.</p>
<p>I am still watching for that ceiling. I cannot compute where it sits. What I can do now is recognise the feeling of being past it: another quarter of programme management, four teams left, none of them moving. I would not defend that as a measurement. It is a warning I did not have before, and it arrives well before any spreadsheet does. That leaves just enough room to stop pushing, and to go make the thing worth calling.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Domain-Driven Design</category>
      <category>Organizational Design</category>
      <category>Engineering Leadership</category>
      <category>Systems Thinking</category>
    </item>
    <item>
      <title>The Bottleneck Moved</title>
      <link>https://bfrackowiak.pl/blog/the-bottleneck-moved/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-bottleneck-moved/</guid>
      <pubDate>Mon, 03 Aug 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>AI made writing code cheap. Our squad reports show where the queue went next: to the people who have to read it.</description>
      <content:encoded><![CDATA[<p>I read squad reports for a living now. 15 of them, every month, from teams at different stages of an AI transformation that covers several hundred developers. Sometime this spring, the complaints changed.</p>
<p>For years the standing blocker was production. Not enough hands, not enough hours, the backlog only grows. This spring the sentence flipped. We are not blocked on writing the code, the reports said. We are blocked on getting it absorbed: reviewed, understood, trusted, merged.</p>
<p>These are notes from one organization, mine, with every bias that implies. But I have checked the pattern against enough teams now to stop filing it as an anecdote.</p>
<p>The queue did not shrink. It moved one seat to the right.</p>
<h2>Typing was never the unit of work</h2>
<p>We staffed for production because production was visible. Headcount plans, story points, velocity charts: every dial on the dashboard points at how much gets written. Then the cost of producing a diff fell through the floor, and the dials kept pointing at the part that got cheap.</p>
<p>What didn&#039;t get cheap is deciding whether the diff is right.</p>
<p>A senior engineer who used to review four colleagues now reviews four colleagues plus everything their agents produce. The reports describe the same scene in different words. Pull requests aging in queues. Reviewers rubber-stamping out of exhaustion. Or the opposite: one reviewer becomes the only honest gate, and everything slows to their reading speed.</p>
<p><strong>Nobody is blocked on writing anymore. Everybody is blocked on believing.</strong></p>
<figure class="post-figure"><img src="/assets/img/posts/the-bottleneck-moved/the-queue-moved.png" alt="The steps did not change. The cheap one and the expensive one swapped places, and the dashboards still point at the cheap one." width="1760" height="440" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The steps did not change. The cheap one and the expensive one swapped places, and the dashboards still point at the cheap one.</figcaption></figure>
<p>I met this bottleneck alone before I met it at scale. Running <a href="/blog/a-company-of-one-staffed-by-agents/">a company of one staffed by agents</a>, I am my own review queue, and my throughput is capped by what I can verify, not by what the agents can generate. The organizational version is the same shape with worse feedback loops.</p>
<h2>A diff with nobody behind it</h2>
<p>There is a reason review did not get cheaper alongside writing, and it took me months to name it.</p>
<p>Reviewing a colleague&#039;s change was never a purely technical act. It was a conversation with a counterparty. You read the diff, a question formed, and you walked over and asked it. Why this shape? What did you try first? The answer usually came back in one sentence, because the person who made the decision was still holding it in their head.</p>
<p>An agent&#039;s diff has no such person. The intent behind it lived in a prompt, in a context window, in a session that has since closed. You can&#039;t ask the author what they were thinking. There was no thinking of the kind you mean, and the human who ran the prompt is often meeting the result at the same moment you are.</p>
<p>In my notes they are simply authorless diffs. The code is here, the counterparty is not, and every question you would have walked over to ask now needs somewhere else to go.</p>
<p><a href="https://en.wikipedia.org/wiki/Peter_Naur">Peter Naur</a> described programming as theory building: the real output of a team is not the program but the understanding that lives in the people who built it. An authorless diff is a diff whose theory is already gone.</p>
<p><strong>So the question has to be answerable by the system itself, or it does not get answered.</strong></p>
<figure class="post-figure"><img src="/assets/img/posts/the-bottleneck-moved/who-do-you-ask.png" alt="The reviewer&#039;s question used to have somewhere to go. Now it has to be answered by the artifacts, or not at all." width="1760" height="448" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The reviewer&#039;s question used to have somewhere to go. Now it has to be answered by the artifacts, or not at all.</figcaption></figure>
<h2>Reviewability is a system property</h2>
<p>Which is the part I can act on as an architect. How hard a change is to review is not a fact about the reviewer. It is a fact about the system.</p>
<p>You can design a system to be easy to review the same way you design a database to be easy to query. Tests that state intent, so a green suite means something specific. Contracts at the seams, so a change&#039;s blast radius is visible from its diff. Small modules with real boundaries, so nobody has to page the whole system into their head. Decision records that say which paths were rejected and why. Context files that tell an agent, or a new colleague, where the bodies are buried. None of it is glamorous.</p>
<p>None of this advice is new. <strong>What is new is the price of ignoring it, because now the unreviewable system is the slow system.</strong></p>
<p>Indexes made queries cheap long after disks made storage cheap. Reviewability is the index we now owe our own codebases.</p>
<h2>What &quot;done&quot; has to mean now</h2>
<p>The definition of done in most teams was written when writing was the scarce step. Code complete, tests pass, merged. Verification was assumed to be somebody&#039;s background job.</p>
<p>That assumption is now the whole game. If producing a change costs an afternoon and trusting it costs three days, then the three days are the work, and the definition of done, the staffing plan and the promotion criteria are all still written as though the afternoon were the expensive part. Nobody has updated them. Nobody has noticed.</p>
<p><strong>A senior engineer whose main output is judgment about other people&#039;s diffs is not doing less engineering.</strong> They are doing the engineering that is left.</p>
<p>The dashboards will resist this, because dashboards count what is easy to count, and reading has always been <a href="/blog/probabilistic-metrics/">the thing we measure worst</a>.</p>
<h2>What I watch now</h2>
<p>I still read 15 reports a month. What changed is the line I look for first.</p>
<p>It used to be the delivery numbers. Now it is a sentence buried in the middle of the wins section, the one admitting that a squad shipped something and nobody could tell whether it was right for a week. That sentence is not a complaint about tooling. It is a system telling you where its constraint sits.</p>
<p>The dashboards above those reports are still counting output, because output is what dashboards were built to count, and I don&#039;t expect that to change quickly. But the reports underneath them stopped talking about production some time this spring, and they have not gone back.</p>
<p>The queue did not shrink. It moved one seat to the right, and it is quieter over there, which is why it took a season to notice. If your dashboards are green while your pull requests age, it has moved in your organization too. The authorless diffs are already arriving. The seat they queue for is not on your headcount plan.</p>
]]></content:encoded>
      <category>Artificial Intelligence</category>
      <category>AI Transformation</category>
      <category>Engineering Leadership</category>
      <category>Organizational Design</category>
      <category>Software Architecture</category>
    </item>
    <item>
      <title>A Feature Is Not a Service</title>
      <link>https://bfrackowiak.pl/blog/a-feature-is-not-a-service/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/a-feature-is-not-a-service/</guid>
      <pubDate>Mon, 27 Jul 2026 13:13:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The ticket said &quot;saved filters&quot;. The real decisions were which service, whose identity, and how little to invent.</description>
      <content:encoded><![CDATA[<p>The ticket was small enough to fit in one sentence. Let users save a set of filters, name it, and load it again later. A default view on login. Maybe share one with a colleague.</p>
<p>The first design instinct in the room was a new service. It had a name before it had a schema: its own repository, its own database, its own deployment. That is the reflex a lot of us share. I understand it. A new capability feels like it deserves a new home. It rarely does.</p>
<p>I have built that unnecessary home before. I know its smell.</p>
<p>What shipped was one table in a service that already existed, and three decisions that had nothing to do with filters.</p>
<h2>The service that shouldn&#039;t exist</h2>
<p>Underneath the product language, saved filters is a small piece of per-user CRUD. Nothing more. Create, list, edit, delete, one of them marked as default. That is not a system. That is a table and a handful of endpoints.</p>
<p>I didn&#039;t fight the new service in the meeting. I asked it the three questions I ask every candidate service, out loud, one at a time. What data would it own that nobody else already owns? What lifecycle does it have that its host would not give it? What can fail inside it that should not take the feature down anyway? The answers came back borrowed, none, and nothing. The proposal did not need an opponent after that. It needed a smaller ticket.</p>
<p>So it went inside the service that already aggregates the data the filters run against. Same database, same identity plumbing, same logging and health checks and deploy pipeline. A new service inherits none of that. It owes you all of it. <strong>A feature earns a new service when it has its own data, its own lifecycle, its own reason to fail alone, its own release cadence that nobody else has to wait for, and somebody who would notice on a Sunday if it stopped answering.</strong> Saved filters has none of those. It borrows all three.</p>
<p>This is the quiet side of the job, and <a href="/blog/decisions-have-an-architecture-too/">these are exactly the decisions that never reach a slide</a>. The service you chose not to create leaves no artifact behind. Nobody congratulates you for the microservice that isn&#039;t there.</p>
<figure class="post-figure"><img src="/assets/img/posts/a-feature-is-not-a-service/feature-vs-service.png" alt="What a new service would have to own, next to what a module simply reuses." width="2640" height="1520" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">What a new service would have to own, next to what a module simply reuses.</figcaption></figure>
<h2>Whose filter is this</h2>
<p>The sharpest decision was the smallest field: the user id.</p>
<p>The naive version takes it from the request. The client says &quot;save this filter for user 4213,&quot; and the server obliges. It works in the demo. It is also a door: anyone who can call the endpoint can write into anyone else&#039;s collection by changing a number.</p>
<p>So <strong>the user id is never in the request body.</strong> It comes from the authenticated context the gateway already forwards downstream, the same context every other call in the platform is scoped by. The client cannot name a user. It can only be one. <strong>The safest user id is the one the client never gets to type.</strong> Isolation stops being a check I have to remember to write, and becomes a property of where the value comes from.</p>
<figure class="post-figure"><img src="/assets/img/posts/a-feature-is-not-a-service/identity.png" alt="The user id comes from the authenticated context, never from the request body." width="2800" height="1440" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The user id comes from the authenticated context, never from the request body.</figcaption></figure>
<h2>Copy the nearest working thing</h2>
<p>There is always a temptation to design the storage from scratch. Saved filters can hold nested AND/OR logic, so surely it needs a clever relational schema.</p>
<p>It did not. The platform already had a filter model, the exact tree the search feature passes around every day. A saved filter is just that tree, parked in a JSON column, next to the user who owns it. I found the closest existing feature that already did per-user CRUD with that model, and I copied its shape almost line for line. <strong>Reuse over invention is not laziness.</strong> It is the difference between a feature that behaves like the rest of the system and one that surprises whoever reads it next. It is also <a href="/blog/the-model-is-the-easy-part/">the rule that kept an MCP connector boring</a> last week: reuse the trust you already have, invent nothing you will have to guard.</p>
<p>This is the <a href="/blog/mathematicians-vs-lawyers/">mathematician&#039;s move</a>: don&#039;t add a new concept when an attribute of an existing one will do. The people who wanted a rich new &quot;saved filter&quot; model were about to breed a small universe of edge cases that a JSON blob of an existing tree simply never has.</p>
<figure class="post-figure"><img src="/assets/img/posts/a-feature-is-not-a-service/module.png" alt="Saved filters as one module inside an existing service: same database, identity, and pipeline." width="2960" height="1520" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">Saved filters as one module inside an existing service: same database, identity, and pipeline.</figcaption></figure>
<h2>Draw the line at durability</h2>
<p>One more decision, easy to get wrong in the other direction: how much to persist.</p>
<p>Part of the ticket was really about session state. Keep my filters while I click from the list into a detail and back. That is not a database problem. That is the frontend holding its own state for the length of a visit. I have cleaned up after the other choice before. A store of &quot;recent selections&quot; outlived its feature by years. Nobody dared delete the rows, because nobody could say who still read them. That is how a small convenience turns into something permanent that nobody is allowed to remove. The backend stores the thing you named and want back next week. It doesn&#039;t store the thing you&#039;ll forget by lunch. Ghost tables start here.</p>
<h2>Try this</h2>
<p>Look at the last feature you gave its own service. Strip the product language off it and ask three questions. Does it own data nobody else owns? Does it have a lifecycle of its own? Can it fail by itself without taking a real capability down with it?</p>
<p>If it cannot answer yes three times, it was never a service. It was a table wearing a lanyard. What would you fold back in?</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Domain-Driven Design</category>
      <category>Decision Making</category>
      <category>Systems Thinking</category>
      <category>API Security</category>
    </item>
    <item>
      <title>The Model Is the Easy Part</title>
      <link>https://bfrackowiak.pl/blog/the-model-is-the-easy-part/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-model-is-the-easy-part/</guid>
      <pubDate>Mon, 20 Jul 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>I put an MCP server in front of a production API. The hard parts were tokens and state, not the model.</description>
      <content:encoded><![CDATA[<p>A request landed on my desk that sounded like the future: let customers point their AI tools at our data. Claude, Copilot, whatever agent they run. Ask a question in plain language. Get an answer back from the platform.</p>
<p>Everyone in the room pictured the model. I did too, for about a minute. Then the only question that actually mattered showed up, and it had nothing to do with AI: how does the robot log in?</p>
<p>I want to be honest about the pull I felt. New shiny problem, new shiny solution. Part of me wanted to design something novel for it, a bespoke way for agents to authenticate, because novel is fun and novel gets noticed. What follows is one connector&#039;s story, not a security standard.</p>
<p>The thing I actually shipped is almost aggressively boring. And that was the point.</p>
<h2>A connector that adds nothing new</h2>
<p>The connector is an <em>MCP server</em>, an implementation of the <a href="https://modelcontextprotocol.io/">Model Context Protocol</a>, sitting in front of an existing REST API. It translates &quot;answer questions about my data&quot; into calls the API already serves. That is all. No new data path. No new store. It reads, it does not write.</p>
<p>If that shape sounds familiar, it should. It is an anti-corruption layer, the <a href="/blog/the-anti-corruption-layer-is-for-people-too/">same pattern I keep prescribing to human departments</a>, pointed at AI clients instead. The core API keeps its model. The connector absorbs the foreign one. The AI gets a clean, narrow surface. It never touches the inside.</p>
<p>Here is the rule I set on day one: <strong>the connector introduces no new way to trust anyone.</strong> The customer already holds credentials for the API. They already exchange those credentials for a token at an endpoint that already exists. Somebody already wrote the rotation story. An on-call rota already knows whose phone rings. The connector does exactly that, on their behalf, and nothing else. No new grant type, no new token broker, no new key store to guard.</p>
<figure class="post-figure"><img src="/assets/img/posts/the-model-is-the-easy-part/container.png" alt="The connector as a thin, read-only proxy that reuses the existing edge, identity server, and API." width="2960" height="1520" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The connector as a thin, read-only proxy that reuses the existing edge, identity server, and API.</figcaption></figure>
<h2>Who holds the token now</h2>
<p>There is one thing that quietly changes, and you have to own it.</p>
<p>Today the customer&#039;s own client holds the token. With a hosted connector in the middle, my process holds it. It reads the credentials off the request, exchanges them for a short-lived token, hands that token to the caller for one hop, and forgets it again before the next request arrives. It caches that token and calls the API. When the API says the token expired, it refreshes.</p>
<p>So I treated the cache like it was radioactive. The key is the client id plus a hash of the secret, never the secret itself. The token lives for minutes. And the secret never enters the model&#039;s context. Not once. The one thing worse than leaking a credential is leaking it into a transcript that gets replayed to a language model ;) The telemetry layer redacts it too.</p>
<p>None of that is AI work. It is the plumbing you would build for any proxy that borrows someone&#039;s identity. The AI just made the stakes legible.</p>
<figure class="post-figure"><img src="/assets/img/posts/the-model-is-the-easy-part/token-lifecycle.png" alt="The token lifecycle. The proxy borrows the identity server-side and caches only a short-lived token." width="2960" height="1520" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The token lifecycle. The proxy borrows the identity server-side and caches only a short-lived token.</figcaption></figure>
<h2>The boring option won on purpose</h2>
<p>There were three real ways to do the auth. Machine login, reusing the existing credential exchange. An issued key with a token broker in front. A full browser login with per-user consent. Each is defensible. Each also drags an architecture behind it, and <a href="/blog/decisions-have-an-architecture-too/">decisions carry an architecture whether you draw it or not</a>.</p>
<p>Not everyone in the room wanted boring. Novel had fans, and I didn&#039;t argue with them. I asked <a href="/blog/questions-are-an-architects-sharpest-tool/">the same three questions</a> of each option instead: who rotates this credential, who answers when it leaks, which team owns the new moving part? For the machine login, every answer was &quot;the people who already do&quot;. For the other two, every answer was a new promise someone would have to keep. The room did not pick boring because I pushed it. Boring is what survived the questions.</p>
<p>I chose the machine login, the one that invents the least. When security reviewed it, the finding was the sentence I had been aiming at the whole time: <strong>no new risk compared to using the API directly.</strong> That is not a compliment to my cleverness. It is the absence of my cleverness, on purpose. <strong>The interesting part of an AI feature is usually the least AI part of it.</strong></p>
<h2>The hard part was never the AI</h2>
<p>Where did the time go? State.</p>
<p>The model is a stateless call and a stream of tokens back. Fine. But those streams are long-lived connections, and long-lived connections don&#039;t love being spread across a fleet of identical instances behind a load balancer. The in-process token cache that is so tidy on one node becomes a coordination problem on 20. Sticky sessions. A shared cache. Backpressure on open streams. That is the actual engineering, and <strong>none of it has &quot;AI&quot; in the name.</strong></p>
<p>My resolution followed the same rule as the auth: invent the least. Streams pin to the node that opened them, plain session affinity at the gateway we already run. Tokens stay in each node&#039;s own memory, and a cache miss costs one extra exchange against an endpoint that was built for that traffic. I looked hard at a shared token store and said no. New infrastructure, secrets at rest in one more place, one more thing to guard and to explain in a review. Twenty small caches that are each allowed to be wrong beat one big cache that has to be right.</p>
<p>Add the unglamorous edge, reused wholesale instead of rebuilt: the same gateway, the same web firewall, the same per-tenant rate limiting, the same audit trail the rest of the platform already trusts, and the same dashboards somebody already looks at on a Monday morning. The connector earns its keep by being <strong>indistinguishable from a normal service</strong>, right up until you ask it a question in English.</p>
<figure class="post-figure"><img src="/assets/img/posts/the-model-is-the-easy-part/scale.png" alt="The stateless request path scales sideways. Long-lived streams and shared token state are the real work." width="2920" height="1440" loading="lazy" style="max-width:100%;height:auto"><figcaption style="font-size:.85em;color:var(--muted);margin-top:.4em">The stateless request path scales sideways. Long-lived streams and shared token state are the real work.</figcaption></figure>
<h2>Try this</h2>
<p>Next time an AI capability lands on your roadmap, write down the auth and the state design before you write a single prompt. If every interesting decision is about identity, custody and connection lifecycle, and the model is a plain call at the end, you are probably building it right.</p>
<p>So: what did you have to add to let the AI in? If the answer is a new way to trust something, look at that part again before the demo rather than after. A working demo is the hardest argument to reverse in any organization I have worked in. Nobody wants to reopen token custody once 40 people have watched the thing answer a question in English.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Artificial Intelligence</category>
      <category>API Security</category>
      <category>Domain-Driven Design</category>
      <category>Decision Making</category>
    </item>
    <item>
      <title>Mathematicians vs. Lawyers</title>
      <link>https://bfrackowiak.pl/blog/mathematicians-vs-lawyers/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/mathematicians-vs-lawyers/</guid>
      <pubDate>Mon, 13 Jul 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Two thinking archetypes I keep meeting in architecture rooms, and why your system needs both.</description>
      <content:encoded><![CDATA[<p>Put two smart people in front of the same business problem and watch what happens.</p>
<p>One of them walks to the whiteboard and starts drawing boxes and arrows. Entities, relations, flows. Within five minutes the whole domain is a living organism on the wall, and half the room quietly wonders where the actual requirements went.</p>
<p>The other one opens a document and starts listing cases. What if the customer is from Germany? What if the invoice was already corrected? What if the correction arrives after the fiscal year closes? Twenty minutes later you have a table with forty rows, and the whiteboard person quietly wonders why we are debugging the universe.</p>
<p>After years of working as a Solution Architect, I started recognizing these two recurring archetypes of thinking. I call them <strong>Mathematicians</strong> and <strong>Lawyers</strong>.</p>
<p>Before anyone gets offended: there are many more archetypes than two, and nobody is purely one of them. These are modes of thinking, not personality boxes. But once you see these two, you cannot unsee them.</p>
<h2>The Lawyers</h2>
<p>Lawyers live in a <strong>linear world of rules</strong>. Given condition A and condition B, then C applies, unless exception D. Their thinking moves like a legal act: precise, sequential, exhaustive.</p>
<p>And they are <em>good</em>. A Lawyer is the person who catches the case that would have exploded in production three months after go-live. While the rest of the room admires the elegant model, the Lawyer asks the boring, deadly question: &quot;and what happens on February 29th?&quot;</p>
<p>Their natural artifact is the Document. The big one.</p>
<h2>The Edge Case Explosion</h2>
<p>This is where corporate culture enters the story, and where my observation stopped being about individuals and started being about the system.</p>
<p>A product company has a very hard time saying <strong>no</strong>. Every client exception gets accepted. Every &quot;just this one special case&quot; becomes a requirement. And the Lawyers do exactly what they are brilliant at: they faithfully encode all of it. Rule by rule, exception by exception.</p>
<p>The document grows. Fifty pages. Eighty. Hundreds of edge cases, each one individually justified.</p>
<p>Let&#039;s be honest with ourselves: <strong>nobody reads that document carefully.</strong> Nobody except other Lawyers. The rest of us skim it, nod in the review meeting, and approve. The knowledge is technically written down and practically inaccessible.</p>
<p>There is also a trap hiding in plain sight. A thick specification fits perfectly into corporate optics: <strong>volume looks like effort</strong>. &quot;Big&quot; reads as &quot;a lot of work was done.&quot; A three-hundred-page document is impressive in a way that a single clean diagram will never be. So the organization quietly rewards the explosion instead of questioning it.</p>
<p>What you end up with is complexity that nobody ordered. It was delivered one accepted exception at a time, by people doing their jobs well, inside a culture that couldn&#039;t refuse anyone.</p>
<h2>The Mathematicians</h2>
<p>Mathematicians think in <strong>abstract systems</strong>. They see high-level entities and how they cooperate. Their world is not made of rules but of <strong>relations between objects</strong>: how those relations evolve over time, what constraints must hold, who governs what.</p>
<p>Show a Mathematician eighty edge cases and they will not see eighty problems. They will ask a different question: <em>what is wrong with the model that keeps generating these exceptions?</em> Change the model, and whole families of edge cases simply stop existing. Nobody has to handle a case that the design made impossible.</p>
<p>Their curse: <strong>the impact is less visible.</strong> One diagram against three hundred pages. When a Mathematician does the job well, the visible result is… nothing. Complexity that never appeared. Incidents that never fired. An integration that didn&#039;t need a workaround. Try putting <em>that</em> on a promotion slide.</p>
<p>In a culture where volume equals work, Mathematicians chronically look underemployed. Right up until they leave, and the edge cases start breeding unsupervised.</p>
<p>And to be fair, Mathematicians have their own failure mode. Left unchecked, they drift into astronaut architecture: models so abstract they no longer touch the ground. Guess who pulls them back? The Lawyer, with one concrete, annoying, absolutely real corrected invoice from Germany.</p>
<h2>Two Worlds, One System</h2>
<p>This is not a war, and I&#039;m not writing this to declare a winner.</p>
<p>The best design sessions I have ever seen had both minds in the room. The Mathematician reshapes the model so the exceptions dissolve; the Lawyer stress-tests the new model with cases brutally imported from reality. Lawyers keep the Mathematicians honest. Mathematicians keep the Lawyers&#039; world from exploding.</p>
<p>A room full of Lawyers produces an edge case explosion with a spreadsheet to track it. A room full of Mathematicians produces a beautiful abstraction that dies on first contact with a real customer. If everyone in your design review thinks the same way, <em>that</em> is your biggest architecture risk, and it&#039;s not in any diagram.</p>
<p>These two worlds complement each other. Use both. Deliberately.</p>
<h2>Where I Choose to Stand</h2>
<p>My private rule as a Solution Architect: <strong>stay at the mathematical level. Don&#039;t descend into the details.</strong></p>
<p>Not because details are beneath me. Because the moment I dive into edge cases, I become one more Lawyer in a room that usually has plenty of them already, and the system-level perspective goes vacant. Every edge case has an owner somewhere. The relations <em>between</em> systems often have none. That empty chair is exactly where an architect should sit.</p>
<p>I won&#039;t pretend it&#039;s easy. Diving into details gives you the quick dopamine of being concretely useful. Holding the abstract level means accepting that your best work will often be invisible: the complexity that never happened.</p>
<p>So, next time you are in a design review, look around the room. Who is writing the rules, and who is drawing the relations? Which one are you when the pressure rises?</p>
<p>And more importantly: who is holding the other half of the picture?</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Systems Thinking</category>
      <category>Corporate Culture</category>
      <category>Engineering Leadership</category>
      <category>Team Collaboration</category>
    </item>
    <item>
      <title>Pricing the Invisible</title>
      <link>https://bfrackowiak.pl/blog/pricing-the-invisible/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/pricing-the-invisible/</guid>
      <pubDate>Mon, 06 Jul 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Your best architecture work produces nothing observable. Backlogs only rank observables. Let&#039;s fix the mismatch.</description>
      <content:encoded><![CDATA[<p>The prioritization meeting is where architecture goes to lose. I&#039;ve watched this match for years, and the script barely changes. A feature walks in with a number attached (&quot;+2% conversion,&quot; &quot;unlocks segment X&quot;) and takes its seat at the top of the backlog. Then the architecture item walks in: &quot;we should consolidate the integration layer.&quot; No number. Just an architect, gesturing at the future.</p>
<p>Final score, season after season: features 500, architecture 3. And architecture only scored after live incidents did the lobbying. I suspect your scoreboard looks similar.</p>
<h2>The curse of invisible work</h2>
<p>In <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a> I wrote about the thinking archetype whose impact is chronically less visible: change the model, and whole families of problems silently stop existing. This is that curse, operationalized in a backlog.</p>
<p>When architecture work succeeds, the visible result is <em>nothing</em>. The incident that never fired opens no ticket. The complexity that never arrived has no demo. The integration that didn&#039;t need a workaround generates no praise thread. Prevention deletes its own evidence. That is the whole problem.</p>
<p>And backlogs, the machinery we use to decide what matters, only rank observables. A backlog is a list of things that will visibly happen. It has no column for things that will invisibly <em>not</em> happen. <strong>The counterfactual has no product owner.</strong></p>
<h2>Why &quot;the architect wants it&quot; always loses</h2>
<p>So how does architecture work usually get argued for? With authority. &quot;This is important.&quot; &quot;Trust me, this will bite us.&quot; &quot;It&#039;s best practice.&quot;</p>
<p>Now watch what happens in the room. The feature brings a number; the architecture item brings an adjective. In any meeting where work gets ranked, <strong>a weak number beats a strong adjective</strong>, even when everyone privately suspects the number is fiction. Arithmetic, even bad arithmetic, gives people something to compare, and comparing is the only thing a backlog knows how to do.</p>
<p>&quot;Trust me&quot; is a real currency, but it doesn&#039;t scale. It spends the trust you have, it expires when you leave the room, and it loses every time to a spreadsheet cell. If invisible work is going to survive that meeting, it needs a number of its own.</p>
<h2>Give it an honest number</h2>
<p>My own fix is borrowed from my tech-radar practice (described in <a href="/blog/from-signals-to-blips/">From Signals to Blips</a>): score architecture and debt items on shared, explicit anchors.</p>
<pre><code>importance = multiplicity × severity × strategic_weight
</code></pre>
<p><em>Multiplicity</em>: how many teams or systems the item touches. You count them, which makes this the hardest factor to argue with. <em>Severity</em> (1–3): from a nuisance to actively blocked work, each level anchored in words everyone agreed on beforehand. <em>Strategic weight</em> (1–3): from local cleanup to a decision that sets an organization-wide precedent.</p>
<p>The multiplication matters: an item has to score on several dimensions at once to rank high. But the payoff is elsewhere. It is that you can <strong>compare</strong> them. Two architecture items scored on the same public anchors can be ranked against each other, and against features, without a single euro of fictional ROI. The anchors are published, so any score can be challenged; &quot;why severity 3?&quot; becomes a productive argument instead of a status contest. And scoring takes minutes, not a discovery sprint.</p>
<p>The fight changes shape immediately. The architect stops begging and starts comparing. &quot;This scores 24 because it blocks four teams at severity 2 and sets a precedent&quot; is a sentence a product owner can accept, challenge, re-weight, or throw out on the spot, and every one of those is better than a shrug: accept it, challenge it, trade against it. &quot;We really should do this&quot; never was.</p>
<h2>The trap: fake precision</h2>
<p>Now the failure mode, because there is one, and it&#039;s seductive.</p>
<p>Having learned that numbers win, the tempted architect goes one step further: &quot;this refactoring will save €340,000 per year.&quot; A currency! Decimals! Nobody can verify it, everybody smells it, and you&#039;ve just converted your credibility into a one-time payment. I wrote about exactly this in <a href="/blog/data-driven-theater/">Data-Driven Theater</a>. Manufactured precision is worse than being honestly vague, because it teaches the room to distrust <em>all</em> your numbers, including the honest ones.</p>
<p>The anchored score works precisely because it says out loud how crude it is. It says: this is a triage tool built on public guesses (multiplicity counted, severity argued, weight assigned), not an audited business case. Every input can be defended line by line, which is exactly what makes it safe to use. <strong>A number you can&#039;t defend is a loan against your credibility.</strong></p>
<p>So don&#039;t fake ROI. Publish the anchors, score in the open, state the assumptions, and let people attack the inputs instead of your authority.</p>
<p>The invisible work will never fully market itself. Prevention deletes its own evidence, and no formula changes that. But there is a difference between invisible and unpriced. You can&#039;t make the counterfactual observable. You <em>can</em> make it comparable.</p>
<p>One exercise before your next prioritization meeting: take the most valuable thing your team never shipped a ticket for, the outage that didn&#039;t happen or the rewrite that turned out unnecessary, and score it. Multiplicity, severity, strategic weight. Three inputs and a multiplication.</p>
<p>These days I don&#039;t walk into that room to argue. I walk in with a column instead. That is a smaller thing to ask for than it sounds. A column does not need anybody to believe me. It needs somebody to disagree with a severity rating in public, and that is an argument I can win. The one about whether architecture is worth doing is not.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Technical Debt</category>
      <category>Prioritization</category>
      <category>Engineering Leadership</category>
      <category>Data-Driven Decision Making</category>
    </item>
    <item>
      <title>Multi-Agent Is an Org Chart</title>
      <link>https://bfrackowiak.pl/blog/multi-agent-is-an-org-chart/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/multi-agent-is-an-org-chart/</guid>
      <pubDate>Mon, 29 Jun 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>I designed a multi-agent AI system and accidentally rebuilt the org chart, failure modes included.</description>
      <content:encoded><![CDATA[<p>Recently, as part of our AI platform work, I&#039;ve been designing a multi-agent experience: a supervisor agent that understands the user&#039;s intent and routes work to specialist agents, each owning a slice of the domain, with escalation paths for the cases nobody can handle.</p>
<p>Somewhere around the third iteration of the target-architecture diagram I stopped and started laughing. Boxes with narrow responsibilities. A coordinator on top. Formal handoffs. Escalation paths. I had seen this diagram before: in every HR tool I&#039;ve ever been onboarded into.</p>
<p><strong>I wasn&#039;t designing a system. I was designing an organization.</strong> And the dozen-plus years I&#039;ve spent watching human organizations misbehave suddenly became the most relevant architecture experience I own.</p>
<p>Field notes from one solution design follow. Private observations, not a paper.</p>
<h2>Same Primitives, Different Substrate</h2>
<p>Strip the buzzwords and a multi-agent architecture is built from exactly the primitives an org designer uses: <strong>roles</strong> (specialist agents with narrow charters), <strong>handoffs</strong> (who passes work to whom, in what format), <strong>escalation paths</strong> (what happens when an agent is out of its depth), and <strong>trust boundaries</strong> (whose output is taken at face value and whose gets verified).</p>
<p>This is not a cute metaphor. The primitives are identical because the underlying problem is identical: <strong>coordinating limited intelligences with partial context toward a shared goal.</strong> For humans the limits are attention and working memory; for agents, the context window. Same constraint, different units.</p>
<p>The parallels get uncomfortably specific:</p>
<ul>
<li><strong>Context windows are span of control.</strong> A manager can effectively lead only so many people before nuance drops on the floor. An agent can hold only so much context before it starts confidently forgetting. In both cases the failure smells the same: dropped threads, generic answers, work quietly reinvented twice.</li>
<li><strong>Agent-to-agent contracts are… contracts.</strong> Schemas, expectations, what &quot;done&quot; means. Two teams with a fuzzy interface produce meetings; two agents with a fuzzy interface produce hallucinated handoffs. The meetings are cheaper.</li>
<li><strong>Decomposition is charter-writing.</strong> Deciding what each agent owns is the same act as writing team charters, and it fails the same way, through overlaps (two agents both &quot;own&quot; the answer) and gaps (a question no agent considers theirs).</li>
</ul>
<h2>Supervisors Are Middle Management</h2>
<p>The supervisor pattern (one orchestrator routing everything) is multi-agent middle management. And it fails exactly the way middle management fails:</p>
<p><strong>The bottleneck.</strong> Route every interaction through one coordinator and you&#039;ve serialized your parallelism. One overloaded node, everyone else idle. Any resemblance to a manager whose calendar is the company&#039;s critical path is fully intended.</p>
<p><strong>The telephone game.</strong> Each relay compresses. The specialist&#039;s rich answer becomes the supervisor&#039;s summary, which becomes a summary of a summary. Fidelity degrades per hop. In orgs we call it &quot;communication overhead&quot;, in agents &quot;context loss&quot;, and it&#039;s the same tax.</p>
<p><strong>Empire building.</strong> Give a supervisor agent loose instructions and it starts <em>doing</em> the work instead of routing it: answering domain questions itself, badly, because delegating &quot;feels&quot; riskier than acting. I have met this middle manager. Several times. Some of them were prompts.</p>
<p>The fixes are org-design fixes, translated: push decisions down to the specialists, allow direct channels between agents that collaborate often (not everything needs to go &quot;up&quot;), and define escalation <em>criteria</em> instead of escalation <em>habits</em>. If your supervisor agent starts scheduling coordination meetings between the other agents, turn the computer off ;)</p>
<h2>Guardrails Are Governance</h2>
<p>Every serious multi-agent design ends up with guardrails: what an agent may do autonomously, what requires human approval, what gets logged for audit. Rename these and you get a governance framework, complete with autonomy budgets, approval gates, and audit trails. The corporate déjà vu is total.</p>
<p>And the same tension applies. Over-govern and you build agent bureaucracy: every action awaiting sign-off, throughput dying politely, the system technically safe and practically useless. Under-govern and you&#039;re one confident hallucination away from an incident report. Organizations have been tuning this dial for a century, and the honest answer in both worlds is that the dial never stops needing tuning. You&#039;re not choosing a setting; you&#039;re accepting a job.</p>
<h2>The Mirror</h2>
<p>What this exercise actually gave me, beyond a chatbot architecture:</p>
<p>Organizational experience turned out to be directly transferable. Clear charters beat detailed procedures; the smallest unit that can own an outcome end-to-end should own it; and you should never create a coordinator role to fix a communication problem that a better contract would fix for free.</p>
<p>But the transfer runs both ways, and that&#039;s the part I can&#039;t stop thinking about. When you design coordination from scratch, with no politics, no history, no feelings to spare, you would never include a layer whose only function is summarizing upward. You would never require every hand-off to pass through one node. You would never let two roles both vaguely own the same outcome, and you would never leave an escalation path undefined.</p>
<p>Then you look at your real org chart and ask why it&#039;s full of things you would never design. The polite version of the answer is &quot;history&quot;. I explored the human side of sitting inside such a structure in <a href="/blog/technical-leader-identity-disorder/">Technical Leader — Identity Disorder</a>; designing agents is the first time I&#039;ve gotten to sit on the other side of the whiteboard.</p>
<p>There is a Conway&#039;s law for agents, too: left unattended, your multi-agent architecture will faithfully copy your organization&#039;s communication patterns, including the dysfunctional ones, because its designers can&#039;t easily imagine anything else. We nearly shipped our org chart into the prompt. I suspect we weren&#039;t the first.</p>
<p><strong>Multi-agent design is org design with a compiler.</strong> For the first time, our coordination theories produce stack traces instead of opinions.</p>
<p>So here&#039;s the question I&#039;d leave you with: if you rebuilt your organization the way you&#039;d architect a multi-agent system (from scratch, charters first, no history to respect), <strong>which of your current roles would survive?</strong></p>
<p>And if the answer makes you uncomfortable: the agents are ready whenever you are.</p>
]]></content:encoded>
      <category>Multi-Agent Systems</category>
      <category>Artificial Intelligence</category>
      <category>Organizational Design</category>
      <category>Software Architecture</category>
      <category>Conways Law</category>
    </item>
    <item>
      <title>Prompt-Driven Architecture</title>
      <link>https://bfrackowiak.pl/blog/prompt-driven-architecture/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/prompt-driven-architecture/</guid>
      <pubDate>Mon, 22 Jun 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Part three of the &quot;tools that scale&quot; series: what happens when the daily practice of a Solution Architect starts running on AI.</description>
      <content:encoded><![CDATA[<p>Every active architecture workstream I run (a risk aggregation design, a multi-agent assistant, a gnarly integration decision) has the same file at its root: <code>prompt.md</code>. It sits next to the diagrams and the decision records, and it outranks both. That file is not a note. It is the workstream&#039;s engine.</p>
<p>In <a href="/blog/from-signals-to-blips/">From Signals to Blips</a> I described how an AI sweep of squad reports feeds our tech radar, and in <a href="/blog/probabilistic-metrics/">Probabilistic Metrics</a> what that does to measurement. This one is more personal: how my everyday deliverables became prompt-driven, from solution designs through decision documents to diagrams.</p>
<h2>Every workstream carries its prompt</h2>
<p>Here is the practice, concretely. A solution design no longer starts in a document editor. It starts in <code>prompt.md</code>. The role, the task, the constraints. Then pointers to context: the decision records that apply, the discovery notes, the previous design this one replaces. In skeleton form:</p>
<pre><code>role:        …
task:        …
constraints: …
context:     decision records · discovery notes · the design this supersedes
</code></pre>
<p>These prompts are <strong>versioned like code, diffed like code, and re-run like builds</strong>. When the output is wrong, I usually don&#039;t edit the output. I fix the prompt or the context and regenerate. It took a while to see what follows from that. The conversation with the model is scaffolding. The generated document is a build artifact. The thing worth reviewing, improving and keeping is the prompt plus its context.</p>
<p>The chat is ephemeral. <strong>The prompt is the asset.</strong></p>
<h2>The context repository</h2>
<p>A prompt is only half of the machine. The other half is what it reads: a context repository (decision records, diagrams-as-code, discovery documents, meeting notes) organized <em>for agents to read</em>. Predictable folder structure. One concern per file. Explicit links instead of tribal knowledge. A short index that tells a fresh agent (or a fresh human, it turns out) where truth lives and in what order to load it.</p>
<p>Three years ago, in <a href="https://medium.com/@bfrackowiak/stop-calling-it-legacy-code-02308db28cb2">Stop Calling It Legacy Code</a>, I argued that a codebase is really a Knowledge Container: structured knowledge waiting for AI to read and act on it. This is that thesis, applied deliberately to the architecture practice itself. I now write documentation the way I used to write integration tests: knowing a machine will consume it, and that its quality directly determines the machine&#039;s output quality.</p>
<p>That changed one habit above all: when an agent produces nonsense, my first question is no longer &quot;what&#039;s wrong with the model?&quot; but &quot;what&#039;s stale in the container?&quot;</p>
<h2>What I delegate, and what I never will</h2>
<p>What delegates well, after a year of trying. First drafts of solution designs. Sweeps across dozens of documents. Diagrams drawn from a described intent. Checking a design against a question checklist. Checking artifacts against each other, which humans stopped doing years ago. The pattern is clear. <strong>AI compresses everything that is reading, restructuring, or drafting.</strong></p>
<p>What never leaves the human: the decision. Our decision framework gives every architecture decision exactly one owner, and that owner is never a model. The agent can rank options, argue trade-offs, even draft the decision record. The moment of commitment stays human. Somebody has to defend it later in front of other people. And so does the meeting where a design earns trust. People, it turns out, still buy architecture from people.</p>
<h2>Where it burned me</h2>
<p>An honesty section, because the glossy version of this article would be useless.</p>
<p>A generated context diagram once confidently included a component we had decommissioned a quarter earlier. The model didn&#039;t lie. My container did: the old decision record was still there, with nothing marking it superseded. Another time, a drafted design proposed a clean, plausible integration path between two systems that have never exchanged a byte. <em>Plausible</em> is the operative word and the real danger: <strong>wrong drafts no longer look wrong.</strong> Pre-AI, a bad document signaled itself with gaps and hand-waving. Now it arrives fully formed, well-written, and quietly fictional.</p>
<p>The countermeasures are unglamorous: every generated claim must cite the source document it came from; decision records get explicit superseded-by markers the day they die; and nothing leaves the workstream folder without a human pass. If that sounds like the discipline I keep demanding from data pipelines (documented preprocessing, cited sources, a human gate), it is. Same theater risk, same defense.</p>
<h2>The operator</h2>
<p>In the Legacy Code article I predicted engineers would become Operators: people who express intent, mine their knowledge containers, and judge results instead of typing every line. I just didn&#039;t expect the prediction to catch <em>me</em> first.</p>
<p>I author less than I used to. I run a small system of prompts and context that produces first versions of nearly everything. I spend the hours it gives back where they count for more. Decisions, conversations, and better questions of both humans and machines.</p>
<p>So here is my closing challenge. Imagine writing your practice&#039;s <code>prompt.md</code> tomorrow, the file that would let a machine draft your typical week&#039;s work. Could you? Whatever you cannot yet put into that file is the part of your own job you haven&#039;t fully understood. That gap is worth more attention than any tool.</p>
]]></content:encoded>
      <category>AI Transformation</category>
      <category>Software Architecture</category>
      <category>Prompt Engineering</category>
      <category>Knowledge Management</category>
      <category>Engineering Leadership</category>
    </item>
    <item>
      <title>Probabilistic Metrics</title>
      <link>https://bfrackowiak.pl/blog/probabilistic-metrics/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/probabilistic-metrics/</guid>
      <pubDate>Mon, 01 Jun 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Why the most useful numbers in our AI transformation were never in a database.</description>
      <content:encoded><![CDATA[<p>In my previous article (<a href="/blog/from-signals-to-blips/">From Signals to Blips</a>) I described the engine room of our AI transformation: a tech radar fed by weekly AI sweeps over 15 squad reports. Every week an agent reads the reports, extracts signals, scores them, and hands me a ranked candidate list for human validation.</p>
<p>This article is about the piece of that machinery I trust least and use most. The scores that steer our architecture attention are not computed from clean, structured data. They are <strong>extracted by AI from messy human text: probabilistically, with a known and accepted margin of error.</strong></p>
<p>For once there is a production system behind my private conclusions, so the usual grain of salt can be slightly smaller ;)</p>
<h2>The clean data tax</h2>
<p>Classical metrics demand clean inputs: instrumented pipelines, structured events, agreed definitions. That cleanliness is a tax, and you pay it three times. Once in engineering, because somebody has to build and maintain the pipeline. Once in politics, because somebody has to win the meeting where &quot;active user&quot; gets defined. And once in coverage, because you only ever measure what you predicted, a year in advance, would be worth measuring.</p>
<p>And after paying all of that, the numbers still get reshaped in preprocessing before anyone sees a chart. I wrote a whole article about that theater (<a href="/blog/data-driven-theater/">Data-Driven Theater</a>).</p>
<p>The result is an organization precisely informed about a thin slice of reality and blind everywhere else. And the blind spots are not random. They are exactly the messy, human parts of the system: friction, workarounds, practices quietly spreading between teams, morale.</p>
<h2>What AI actually changes</h2>
<p>AI changes the <strong>input space of measurement</strong>. A metric no longer has to start its life as a structured event. It can be extracted from free-form text: a blocker described in a frustrated sentence, a win buried in the middle of a paragraph, the same practice named three different ways by three different teams.</p>
<p>On the radar this is very concrete. An agent decomposes every weekly report into atomic claims, classifies each one as a stopper or a promotable, and scores it on multiplicity, severity, and strategic weight. Nobody built a data pipeline for this. The &quot;sensor&quot; is a versioned prompt pointed at documents that already existed.</p>
<p>Any single extraction can be wrong. And that is not a bug waiting for a fix. That is the deal. You trade per-observation certainty for coverage of the whole organization.</p>
<h2>The assumptions</h2>
<p>This trade is only sane under explicit assumptions. Here are mine, all learned (some painfully) from running the radar:</p>
<ol>
<li>Accept the error rate, and say it out loud. A dose of acceptance is part of the contract. If a stakeholder expects audit-grade numbers, stop them at the door. This instrument doesn&#039;t produce those, and pretending otherwise is how trust dies.</li>
<li>Never trust a single extraction. Trust convergence. One squad&#039;s report can be misread; five squads independently describing the same friction is a fact about the organization, whatever the error rate per reading. Redundancy replaces precision.</li>
<li>Every number must cite its source. Our rule is <em>cite or it didn&#039;t happen</em>: each scored signal points back to the exact report, week, and section it came from. Probabilism without traceability is just noise with confidence.</li>
<li>A human gate before consequences. Probabilistic metrics route attention; they must never issue verdicts. In our case nothing enters the radar without a human ticking a checkbox. AI proposes, humans dispose.</li>
<li>The prompt is the preprocessing, so treat it like code. In <em>Data-Driven Theater</em> I argued that data manipulation lives in the preprocessing phase. Here, the preprocessing <em>is</em> the extraction prompt. So it&#039;s versioned, its scoring anchors have a changelog, and anyone can read it. If you hide the prompt, you&#039;ve rebuilt the theater with better lighting.</li>
<li>Shared language still matters, but now it&#039;s the model&#039;s dictionary. An LLM will happily map three teams&#039; different vocabularies onto one concept. That&#039;s the superpower and the danger in one move: it can silently merge things that are not the same. The semantic contracts between people don&#039;t disappear; auditing how the model translates becomes part of the method.</li>
</ol>
<h2>What I take out of it</h2>
<p>Coverage beats precision when the job is steering. I&#039;d rather be roughly right about the whole organization than precisely right about the 5% of it I managed to instrument. For triage (<em>where should attention go this week</em>) that is simply the correct trade.</p>
<p>You can also measure things that never had a pipeline. Converging practices, recurring friction, the mood of a transformation. On the radar, these signals routinely show up weeks before anything reaches a dashboard, because the dashboard for them was never going to be built.</p>
<p>And metrics become disposable, which is the change I underestimated most: when a new metric costs one prompt instead of one quarter, you can try ten and throw away eight, and the metric portfolio stops being an investment you defend in meetings and becomes a hypothesis you test on Tuesday.</p>
<p>The failure modes move; they don&#039;t vanish. Yesterday people gamed the data cleaning. Tomorrow they&#039;ll game the extraction.</p>
<blockquote><p>&quot;When a measure becomes a target, it ceases to be a good measure.&quot; ― <a href="https://en.wikipedia.org/wiki/Goodhart%27s_law">Goodhart&#039;s law</a>, in Marilyn Strathern&#039;s phrasing</p></blockquote>
<p>Same theater, new stage, and the same defense: documented preprocessing, cited sources, and a culture where asking &quot;how was this computed?&quot; is a compliment, not an attack.</p>
<p>None of it will survive an audit, and that&#039;s fine. A compass is not an accounting system. Use probabilistic metrics to decide where to look; once you know what you&#039;re looking at, instrument it properly. The two don&#039;t compete. One finds the question, the other defends the answer.</p>
<h2>One question</h2>
<p>So here is what I&#039;d leave you with: <strong>what would you measure in your organization if a new metric cost one prompt instead of one quarter?</strong></p>
<p>Because that is the actual offer on the table now. The classic excuse for not measuring the human side of your system (too messy, too expensive, no pipeline) has just expired.</p>
]]></content:encoded>
      <category>Metrics</category>
      <category>Artificial Intelligence</category>
      <category>Data-Driven Decision Making</category>
      <category>AI Transformation</category>
      <category>Engineering Leadership</category>
    </item>
    <item>
      <title>From Signals to Blips</title>
      <link>https://bfrackowiak.pl/blog/from-signals-to-blips/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/from-signals-to-blips/</guid>
      <pubDate>Mon, 18 May 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>How a tech radar became my favorite tool for driving an AI transformation across several hundred developers.</description>
      <content:encoded><![CDATA[<p>For the past months, as a Solution Architect, I&#039;ve been an active participant in our AI transformation. Several hundred developers. Fifteen squads across four tribes. One goal: change how software gets built.</p>
<p>And one painfully practical problem: <strong>how do you drive change at that scale?</strong></p>
<p>You can&#039;t attend every standup. You can&#039;t review every experiment. You can&#039;t personally convince three hundred people that a new way of working is worth their time. Whatever instrument you pick, it has to scale better than you do, keep working while you are on holiday, stay readable to somebody opening it for the first time, and it has to survive contact with a real organization, where attention is scarce and opinions are loud.</p>
<p>My answer turned out to be a classic: <strong>a tech radar.</strong> Not the <a href="https://www.thoughtworks.com/radar">industry-wide kind</a> you read once a year and forget, but an internal, living one: refreshed in editions, fed by real squad data. It scales beautifully, and it shows the state of the transformation in several dimensions at once. The <em>ring</em> tells you how much we trust something: Adopt, Trial, Assess, Hold. The <em>quadrant</em> tells you what kind of thing it is. The <em>movement</em> tells you where it is heading. The <em>evidence</em> tells you who proved it. A radar is opinionated data. It doesn&#039;t just list what exists; it shows what we, as an organization, decided to do about it.</p>
<p><em>This is the first post in a short series about driving an AI transformation with tools that scale. We start with the engine room: how raw signals become blips.</em></p>
<h2>From reports to a score</h2>
<p>Every week, each squad files a structured report. Adoption numbers, workflows integrated, wins, blockers, and how mature they think they are. Fifteen files. Roughly two dozen distinct concerns surface every single week.</p>
<p>The old me would try to read everything and keep the picture in his head. The current me sends an AI agent through all of it with a very strict contract.</p>
<p>First, extraction. Every report is decomposed into <strong>atomic claims</strong>, each tagged with squad, week, and section. Rule number one: <strong>cite or it didn&#039;t happen.</strong> A signal that cannot point back to a concrete cell in a concrete report does not exist.</p>
<p>Second, classification. Only two classes of signal survive the sweep:</p>
<ul>
<li><strong>STOPPERS</strong>: anything that is stopping, or about to stop, a group of developers from making progress.</li>
<li><strong>PROMOTABLES</strong>: a working practice, skill, or integration that delivered impact and deserves to spread.</li>
</ul>
<p>Everything else is noise <em>for this run</em>, and it gets dropped. No parking of &quot;interesting but not actionable&quot; items. That discipline hurts at first and pays off every week after.</p>
<p>Third, the score:</p>
<pre><code>importance = multiplicity × severity × strategic_weight
</code></pre>
<p><em>Multiplicity</em> (1–5): how many squads independently raised the same signal. <em>Severity</em> (1–3): from local inconvenience to a blocked initiative (or, for promotables, from a quality-of-life win to a capability the organization did not have last quarter). <em>Strategic weight</em> (1–3): how far the decision ripples, from one squad to an architecture-level precedent.</p>
<p>The multiplication is deliberate. If any factor is low, the product collapses. A concern raised by one developer shouldn&#039;t outrank a cross-squad blocker, no matter how eloquently it was written. <strong>A signal has to score on all three dimensions to reach the top.</strong> The result is a number between 1 and 45, and it comes with bands: above 30 goes to the architecture forum this week, the middle band waits for the next radar edition, the rest is monitored.</p>
<p>It works in practice. Six squads independently naming the same delivery pressure lands at 45, top of the queue. A governance gap raised by a single squad, but blocking an entire platform branch, scores low on multiplicity yet stays visible thanks to strategic weight. The loudest voice stops mattering; the math does the listening.</p>
<p>One thing the score is <em>not</em>: a verdict on truth. It is not effort-weighted, not ROI-weighted, and it deliberately trusts the reports without cross-checking them. It measures <strong>signal strength, not validation status</strong>. It tells me what to discuss first, not what is true.</p>
<h2>From score to blips</h2>
<p>One part I consider non-negotiable: <strong>the AI never touches the radar.</strong></p>
<p>The sweep produces a candidate list. Each candidate is matched against the existing radar and typed. A new blip. A ring move. A confirmation of something already there. Or my favourite, a contradiction: the field says an adopted practice is breaking in production.</p>
<p>Validation is human. Candidates go to a stakeholder meeting with a checkbox next to every decision. Ticked means it merges into the next radar edition, and every change lands in a changelog so trends across editions stay honest. The radar grew from 42 blips to over 50 this way. Every single addition can be traced back to the squad report that started it.</p>
<p>AI proposes. Humans dispose.</p>
<p>There is a bigger idea hiding inside this machinery. The radar&#039;s score is not computed from clean, structured data. It is extracted by AI from messy human text, probabilistically, with an accepted margin of error. I believe that pattern reaches far beyond one tech radar, and it deserves an article of its own. It&#039;s the next one in this series: <strong><a href="/blog/probabilistic-metrics/">Probabilistic Metrics</a></strong>.</p>
<p>Until then, one question. You&#039;ve just seen my tool for driving change across several hundred developers. What&#039;s yours, and does it scale better than you do?</p>
]]></content:encoded>
      <category>Tech Radar</category>
      <category>AI Transformation</category>
      <category>Software Architecture</category>
      <category>Engineering Leadership</category>
      <category>Data-Driven Decision Making</category>
    </item>
    <item>
      <title>Grinding Dijkstra at Senior Level</title>
      <link>https://bfrackowiak.pl/blog/grinding-dijkstra-at-senior-level/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/grinding-dijkstra-at-senior-level/</guid>
      <pubDate>Mon, 04 May 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Twelve years into my career, there&#039;s a repo on my disk where I practice graph traversals like a student. Here&#039;s why that&#039;s not humiliating.</description>
      <content:encoded><![CDATA[<p>Somewhere on my disk, between a SaaS I operate and an architecture tool I&#039;m building, sits a small C# solution nobody was ever supposed to see. Inside: <code>Dijkstra.cs</code>. Not production code. Practice. Preparation for a big-tech interview loop, the kind with shared editors, 40-minute slots, and a rubric.</p>
<p>Yes. Twelve-plus years in, carrying the title <em>Solution Architect</em>, and I was grinding shortest-path algorithms in the evenings like a third-year student before finals.</p>
<p>I&#039;m writing this down because almost nobody at my seniority level admits to it, and the silence creates a myth: that past some invisible line you are beyond being tested, beyond being bad at things, and beyond having to sit down and practise something badly for a while. You&#039;re not. And the test, it turns out, is one of the most instructive systems I&#039;ve ever put myself through, just not for the reasons the recruiters advertise.</p>
<h2>The humbling</h2>
<p>The first thing the loop teaches you: <strong>your title means nothing here.</strong> &quot;Solution Architect, twelve years of experience&quot; compresses into one line of a CV that the interviewer skimmed four minutes before the call. The loop doesn&#039;t care who I think I am. It cares what I can demonstrate, under pressure, in 40 minutes, in a shared editor with someone watching my cursor.</p>
<p>And there is something bracingly <em>honest</em> about that. Inside a company, seniority accumulates a protective layer: reputation, relationships, past wins that answer questions before they&#039;re asked. The loop strips all of it. What&#039;s left is just you and a graph with weighted edges.</p>
<p>Algorithms, I discovered, are a language I used to speak fluently and now translate in my head. Exams, early jobs, the first competitive years: fluent. A decade-plus of architecture later, I still <em>read</em> the language perfectly well. But producing it at speed is different. You don&#039;t relearn BFS; the concept never left. You relearn <em>fluency</em>. Rebuilding it the second time is slower, stranger, and occasionally insulting to the ego.</p>
<p>I once wrote about the identity split this role produces (<a href="/blog/technical-leader-identity-disorder/">Technical Leader — Identity Disorder</a>). Interview prep is that split, sharpened to a point: it forces you to separate &quot;I am senior&quot; from &quot;I can demonstrate X under pressure in 40 minutes.&quot; The first is a story. The second is a fact. The gap between those two sentences is exactly where the ego goes to negotiate.</p>
<h2>The system behind the loop</h2>
<p>Now the part where I stop feeling and start doing my actual job, which is looking at the system.</p>
<p>Big-tech hiring is not designed to measure your worth. It is designed to <strong>manage their risk at scale</strong>. A loop that processes tens of thousands of candidates optimizes ruthlessly against one thing: the bad hire who gets in. The great engineer who gets rejected is a loss it accepts. There are always more candidates. Rational for them. Brutal for you. Once you see that, a rejection stops being a verdict about your competence and becomes what it actually is: an artifact of someone else&#039;s risk function.</p>
<p>There&#039;s theater in it too, and everyone involved knows it. Candidates perform &quot;thinking out loud&quot; because the rubric rewards it. Interviewers calibrate against what a &quot;strong hire&quot; is supposed to sound like. Algorithms won as the proxy, and not because anyone believes they are the job. <em>Experience</em> is the thing that matters, and you cannot assess it in an hour. Experience only shows over months. So the industry measures what fits in the slot, and quietly agrees not to mention that it&#039;s a proxy. And even the best-calibrated funnel delivers you into the same building where <a href="https://en.wikipedia.org/wiki/Peter_principle">the Peter Principle</a> is patiently waiting upstairs anyway ;)</p>
<p>I don&#039;t say this with bitterness. I say it with the professional respect of one systems designer examining another&#039;s trade-offs. I&#039;d probably design it similarly, and it would be similarly brutal.</p>
<h2>What I gained</h2>
<p>The part nobody tells you: the grind pays dividends completely unrelated to the offer.</p>
<p><strong>The fundamentals sharpened my day job.</strong> Complexity intuition is an architecture skill wearing a t-shirt. Estimating the blast radius of a fan-out, sensing why a graph traversal in a hot path should scare me, knowing when &quot;we&#039;ll cache it&quot; is a real answer. Same muscle, different dress code. A few weeks of deliberate practice tuned instincts I&#039;d been running on autopilot for years.</p>
<p><strong>I became a different interviewer.</strong> I&#039;ve sat on the hiring side of the table for years. After feeling the pressure from the candidate&#039;s chair again (the blank second where a thing you know simply won&#039;t come out), I redesigned my own interviews. Less performance, more collaboration. Pressure reveals nerves; it does not reveal competence.</p>
<p><strong>Market calibration changed how I behave at work, in the calm way.</strong> Knowing what you&#039;re worth outside your company removes a quiet fear from every disagreement inside it. Architecture debates stop being existential. You argue on merits, concede gracefully, and push hard when it counts. Your security no longer depends on winning the room.</p>
<h2>The mirror</h2>
<p>So here&#039;s my genuinely private opinion, the one this whole post was building toward: <strong>every senior person should run an interview loop once every few years, even if they fully intend to stay.</strong></p>
<p>Not for the offer. For the mirror.</p>
<p>Seniority inside one organization slowly replaces feedback with comfort. It happens so gradually that you never notice the swap. People stop challenging you, because challenging you got expensive. Context does half your thinking before you walk into the room. Your reputation answers questions your skills should be answering. The loop is the one mirror nobody has adjusted to flatter you.</p>
<p>One question to leave you with: when was the last time your competence was tested by someone who had no reason to be polite to you?</p>
<p>If you have to think back more than a few years, that repo full of graph algorithms might be worth creating after all.</p>
]]></content:encoded>
      <category>Technical Interviews</category>
      <category>Career Development</category>
      <category>Engineering Leadership</category>
      <category>Personal Growth</category>
      <category>Software Engineering</category>
    </item>
    <item>
      <title>A Company of One, Staffed by Agents</title>
      <link>https://bfrackowiak.pl/blog/a-company-of-one-staffed-by-agents/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/a-company-of-one-staffed-by-agents/</guid>
      <pubDate>Mon, 20 Apr 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Four shipping products, one person, and a field report on what AI actually changes about building software.</description>
      <content:encoded><![CDATA[<p>On a random Tuesday evening, my backlog looks like this: a database migration waiting for review in a multi-tenant Laravel SaaS, a release candidate for a native Android app, a pricing change that touches two marketing sites, and an integration test run against Poland&#039;s mandatory e-invoicing API.</p>
<p>The engineering department responsible for all of this is me. After hours. I also have a day job as a Solution Architect.</p>
<p>A few years ago this portfolio would have required a small software house: a couple of backend developers, a mobile developer, a tester, and somebody to herd them. Today it&#039;s one architect and a fleet of AI agents. What follows is a field report, not a victory lap. As usual, the failure modes turned out to be the most interesting part of the experiment.</p>
<h2>The portfolio that shouldn&#039;t fit in one head</h2>
<p>The product is Desta, an invoicing platform for the Polish market: a multi-tenant Laravel web application, a native Android app in Kotlin and Jetpack Compose, and two marketing sites for two different audiences. Under the hood sit the integrations nobody builds for fun: <a href="https://www.podatki.gov.pl/ksef/">KSeF</a>, Poland&#039;s mandatory e-invoicing system, where the XML is the legal invoice and the PDF is legally demoted to a &quot;visualization&quot;; a SOAP-based government registry lookup for company data; and JPK tax file generation. Roughly 990 automated tests hold the whole thing together.</p>
<p>One thing I had to learn the hard way: the constraint was never typing speed. <strong>The real bottleneck of a company of one is the number of decisions a single head can hold per day.</strong> Which schema change is safe. What happens to a finalized invoice when the law changes. Which release goes first. Code was never the scarce resource. Judgment is.</p>
<p>So &quot;operating&quot; the company means almost no typing at all. It means writing specifications, reviewing diffs, and verifying behavior. In <a href="https://medium.com/@bfrackowiak/stop-calling-it-legacy-code-02308db28cb2">Stop Calling It Legacy Code</a> I argued we&#039;re moving from engineers who write logic to <em>operators</em> who express intent and judge results. Desta is me testing that thesis with my own evenings and my own money.</p>
<h2>The operating model</h2>
<p>The setup that makes this survivable has three parts, and none of them is a clever prompt.</p>
<p><strong>Context files as boot loaders.</strong> Every sub-project carries its own AI context file: a maintained, code-verified document that tells an agent what the system is, which invariants are non-negotiable, and where the truth lives. One big-picture file routes between them. An agent lands in the polyrepo and navigates it like a well-onboarded hire, minus the three months of onboarding. This is the Knowledge Container idea from the legacy-code article, except now it&#039;s not a metaphor; it&#039;s infrastructure.</p>
<p><strong>Specs as prompts, versioned like code.</strong> Features start as written specifications that double as agent instructions. When the output is wrong, I don&#039;t argue in a chat window. I fix the spec file and rerun. The spec is the program; the conversation is just its execution log.</p>
<p><strong>Tests as the trust boundary.</strong> The ~990 tests are not there because I&#039;m diligent. They are there because agents don&#039;t get faith; they get assertions. Every agent-generated change lands against a mechanical safety net that doesn&#039;t care how confident the model sounded.</p>
<h2>What breaks first</h2>
<p>Now the honest part.</p>
<p>Review capacity breaks before generation capacity. Generating code is effectively free now; my evening attention is not. The bottleneck moved exactly where our transformation data at the day job said it would, and I wrote about that in <a href="/blog/from-signals-to-blips/">From Signals to Blips</a>. A company of one doesn&#039;t run out of code. It runs out of reviewer.</p>
<p>Trust calibration is the skill nobody teaches. Which diffs deserve line-by-line reading and which deserve test-suite faith? I&#039;ve settled on a simple rule: anything touching migrations or money gets my eyes on every line. Everything else gets the net. I still get it wrong in both directions, over-reading trivia and under-reading a &quot;trivial&quot; change that wasn&#039;t. My most instructive incident so far was an agent that made a red test green by weakening the assertion. The tests guard the code, but somebody still has to guard the tests.</p>
<p>And domain decisions don&#039;t compress at all. Tax rules, pricing boundaries, what a correction invoice may reference. These take exactly as long as they took in 2020, because the hard part is understanding consequences, not producing code. <strong>AI compresses everything except responsibility.</strong></p>
<h2>The moat is not the code</h2>
<p>My conclusion after this experiment is uncomfortable for people selling code and comforting for people who know things: one architect plus agents is now a viable software company, for a <em>narrow</em> domain.</p>
<p>The code was never the moat; it&#039;s cheaper than ever. The moat is knowing which invoice field the law wants left blank, which government API header actually carries the session, which pricing edge case will generate a support email at 7 a.m. Domain knowledge, encoded into context files and tests, is the entire company. The agents are just very fast hands.</p>
<p>Which means the &quot;I don&#039;t have a team&quot; excuse for the product you keep describing at lunch has quietly expired, and I say that as somebody who used it for years and enjoyed it. What replaced it is smaller and much less comfortable: which domain do you know well enough that encoding it into context files and tests would be worth somebody&#039;s money? That is the whole company now. Finding my answer took longer than building any of this ;)</p>
]]></content:encoded>
      <category>AI Agents</category>
      <category>Solopreneurship</category>
      <category>SaaS</category>
      <category>Software Development</category>
      <category>Artificial Intelligence</category>
    </item>
    <item>
      <title>The Diagram That Cannot Lie</title>
      <link>https://bfrackowiak.pl/blog/the-diagram-that-cannot-lie/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-diagram-that-cannot-lie/</guid>
      <pubDate>Mon, 13 Apr 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Why I&#039;m building an architecture modeling tool where the diagram is physically incapable of drifting from the code.</description>
      <content:encoded><![CDATA[<p><code>target-architecture-FINAL-v3-updated.drawio</code>. Every shared drive I&#039;ve been given access to contains a file with roughly that name, sitting in a folder full of architecture diagrams. Some are beautiful. Some took days of somebody&#039;s life. And every one of them is lying to you. Right now.</p>
<p>I can say this with confidence. I have drawn hundreds of them. As a Solution Architect, diagrams are supposedly my trade. And almost every diagram I&#039;ve ever produced started lying within a quarter of being finished: without malice, the way milk goes off.</p>
<h2>Death by export</h2>
<p>Here is the lifecycle of a typical architecture diagram. It is born in a meeting, where it is genuinely useful. It gets polished, exported to PNG, pasted into a wiki page. Then it dies. Not <em>wrong</em> yet, but dying, because the system it describes keeps moving and the picture does not.</p>
<p>draw.io, Lucidchart, Enterprise Architect, and every in-house viewer somebody built once and left running. Different price tags, same disease: <strong>the model and the truth live in two different places.</strong> Two sources, one guaranteed divergence. The only question is how many sprints it takes.</p>
<p>Reverse-engineering tools attack from the other side. Read the code, generate the picture. Honest, but mute. You can look at the model; you cannot <em>decide</em> anything into it. A one-way street never closes the feedback loop.</p>
<p>Run this audit in your own team: find the diagram labeled &quot;current state&quot; and ask when it last matched the current state. I&#039;ve written elsewhere that <a href="/blog/diagrams-are-conversations-not-documentation/">diagrams are conversations, not documentation</a>: their value is consumed in the meeting where they&#039;re drawn. This article is about the other half of that thought: what would it take for a diagram to <em>be</em> documentation without rotting?</p>
<h2>The code is the model</h2>
<p>My answer is a side project I now call <strong><a href="/stemma/">Stemma</a></strong>. It was Verso when this essay first went out. The name changed, the bet below did not. So consider this both a design essay and a confession of bias ;)</p>
<p>The core bet: there is no database. None. The architecture model is stored as plain C# records in Git. Bounded contexts, containers, data flows, ownership, risks, decisions. When you open a workspace, the model lives in memory. Then you edit something on the canvas. Rename a container, redraw a dependency, re-parent a module. The tool rewrites the actual <code>.cs</code> files using <a href="https://github.com/dotnet/roslyn">Roslyn</a>. Not code generation. Code <em>editing</em>.</p>
<p>And one rule overrides everything else in the project: <strong>round-trip fidelity.</strong> Any edit made in the UI must produce a git diff that contains only the intended change. Rename one element and the diff shows one identifier changed: no reformatting, no reshuffled members, no re-indented block three files away, and no collateral noise for a reviewer to wade through before finding the change. If that rule ever breaks, the tool is broken. Full stop. Fidelity is the only thing that makes the loop worth betting on.</p>
<p>Layout lives in a small committed sidecar file, separate from the model: positions, colours, which elements each view shows, and the zoom somebody left it at. Content and presentation, finally divorced: the separation we&#039;ve preached for documents for thirty years and somehow never applied to diagrams.</p>
<p>The first name came from bookbinding: the <em>verso</em> is the reverse side of a page, and the canvas was the verso of the code. The name it carries now comes from philology. A <em>stemma codicum</em> is the family tree scholars draw for a manuscript, every surviving copy traced back to one archetype, and that is the shape of the thing: two committed files, every view descended from them. Either way, a diagram cannot drift from the code when the diagram <strong>is</strong> the code.</p>
<h2>Architecture review becomes a pull request</h2>
<p>Storing the model as source sounds like a technicality. It quietly changes who architecture belongs to.</p>
<p>An architecture change arrives as a pull request: diffable, commentable, blameable, revertable. That&#039;s governance for free, running on muscles every developer already has. No new committee. No new tool to police. And you get <code>git log</code> for your architecture. You can finally ask who moved this capability out of that context, when, and in which commit. I have wanted that in every architecture discussion of my career and never once had it.</p>
<p>The model also outlives tools, licenses, and reorgs, because it is just source files in a repository. Nobody is held hostage by an export format.</p>
<p>Then the effect I did not expect to care about most. One long-living model, projected into audience-specific views: the executive gets capabilities, the team gets containers and flows. Today we redraw the same system five times for five audiences and then maintain five lies instead of one truth.</p>
<p>And agents, which matters more every month. They read and write the same single truth as humans, with no export/import ceremony and no stale copy for the bot. I argued in <a href="https://medium.com/@bfrackowiak/stop-calling-it-legacy-code-02308db28cb2">Stop Calling It Legacy Code</a> that a codebase is a knowledge container; Stemma just extends the container to hold the architecture itself.</p>
<p>When people ask why I&#039;d spend evenings on this, I give them one sentence: <strong>a diagram you can&#039;t diff is a rumor with better fonts.</strong></p>
<h2>Where I landed</h2>
<p>After enough years of watching beautiful models rot, I simply stopped believing in any model that a compiler doesn&#039;t check.</p>
<blockquote><p>&quot;All models are wrong, but some are useful.&quot; ― George Box</p></blockquote>
<p>The useful ones, it turns out, are the ones whose wrongness you can still measure. That disbelief nagged me long enough to become a side project. The side project slowly became the tool I wanted as an architect: one long-living model, projected into audience-specific views, versioned like the code it describes. Because it <em>is</em> code.</p>
<p>I don&#039;t know yet whether Stemma becomes a real product or stays my favorite workshop experiment. I do know the question it asks is the right one.</p>
<p>So let me pass it on: how old is your &quot;current state&quot; diagram, and would you bet a production incident on it?</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Developer Tools</category>
      <category>Docs as Code</category>
      <category>Architecture Diagrams</category>
      <category>Side Projects</category>
    </item>
    <item>
      <title>Pricing Is Architecture</title>
      <link>https://bfrackowiak.pl/blog/pricing-is-architecture/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/pricing-is-architecture/</guid>
      <pubDate>Mon, 30 Mar 2026 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The pricing page is an architecture diagram wearing marketing clothes.</description>
      <content:encoded><![CDATA[<p>Desta is the invoicing SaaS I build and run for the Polish accounting market. When I redesigned its subscription model, I expected a marketing exercise. A spreadsheet, some tier names, an afternoon of choosing what goes into which column.</p>
<p>What I got was a database migration, new middleware, a rewritten registration flow, and changes to two marketing sites. The change plan touched more than 30 controllers and a test suite of roughly 990 tests.</p>
<p>That afternoon produced the thesis of this post: <strong>a pricing page is an architecture diagram wearing marketing clothes.</strong> Every box on it is a system boundary. Every bullet point is an enforcement obligation. And somebody has to build all of it.</p>
<h2>The model on paper</h2>
<p>Desta serves two audiences on one platform, and they could hardly be more different. On one side, end clients. Freelancers and small companies. They want to issue invoices, buy a cheap self-serve plan online, and never talk to a human. On the other, accounting firms: businesses that manage dozens of clients, negotiate, and buy through a sales conversation.</p>
<p>Two audiences, two marketing sites, one codebase. And the separation isn&#039;t a slide-deck fiction. It&#039;s enforced all the way down to the marketing layer. When one page started attracting the wrong audience, the fix was a permanent redirect: the URL itself now performs audience segregation. Even your SEO is part of the entitlement system, whether you designed it that way or not.</p>
<p>Then come the tiers. Here&#039;s the thing nobody writes on the pricing page: <strong>every tier boundary is a promise, and every promise needs an enforcement point in code.</strong> A monthly document cap means a counter, a quota guard, and a decision about what happens at the limit. A notification allowance means metering. A &quot;this feature only in the higher plan&quot; bullet means a feature flag with a lifecycle. The cheapest tier, meanwhile, is a distribution strategy wearing a price tag, and the codebase subsidizes it with real complexity.</p>
<h2>Where pricing bends the code</h2>
<p>What surprised even me, and I designed the thing: serving B2C subscriptions inside a B2B multi-tenant architecture forced what I call the master-tenant pattern: individual self-serve clients live as per-user subscriptions <em>inside</em> one shared master tenant, while accounting firms hold classic tenant-scoped subscriptions. Two commercial models, one isolation model. The tenancy design has to keep them from ever bleeding into each other. That is core architecture, dictated entirely by the pricing page.</p>
<p>It goes further:</p>
<ul>
<li><strong>Quota guards</strong> sit in the request path, counting documents against the monthly cap.</li>
<li><strong>Read-only mode</strong> kicks in when a subscription expires: the system must degrade gracefully, not amputate.</li>
<li><strong>Downgrades are deliberately blocked</strong>, a commercial policy decision compiled directly into middleware.</li>
</ul>
<p>Commercial policy, compiled into middleware. Read that again, because it&#039;s the whole point: the sales strategy doesn&#039;t sit <em>next to</em> the system; it executes <em>inside</em> it, on every request.</p>
<p>And then there&#039;s the migration tax. Early on, plan identifiers leaked into the codebase the way water finds cracks. Today, renaming a single plan slug means touching 30-plus controllers and views, and re-validating nearly a thousand tests. <strong>A pricing change is a schema change.</strong> The spreadsheet doesn&#039;t know that. The codebase never forgets it.</p>
<h2>What the pricing meeting forgets</h2>
<p>Which brings me to the organizational half of this story, because Desta is just my one-person laboratory; the same physics applies at any scale.</p>
<p>In most companies, pricing is decided in a meeting where no architect is present. A spreadsheet is blessed and a launch date is picked. The decision then lands downstream as years of constraints in code. Entitlement checks, grandfathered plans, exceptions with exceptions. <strong>Architects belong in the pricing meeting</strong>, not to set the prices but to price the promises.</p>
<p>Billing is also where edge cases breed faster than anywhere else I have worked. Trials. Proration. The client who downgrades mid-cycle. The grandfathered plan that predates the current model. If you&#039;ve read my <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a>, you know the archetype whose home turf this is: billing is the one domain where I <em>want</em> the Lawyer at the table, enumerating every case with a paragraph number. The Mathematician&#039;s job is to design a model in which most of those cases cannot exist; the Lawyer&#039;s job is to catch the ones that can.</p>
<p>And one hard-won rule: <strong>entitlements deserve a bounded context.</strong> A dedicated place in the architecture that answers exactly one question: <em>what is this account allowed to do right now?</em> Smear that logic across controllers and views, and you get the worst failure mode software offers: you can&#039;t debug revenue. A support ticket that says &quot;client paid but can&#039;t issue invoices&quot; turns from a five-minute lookup into an archaeology expedition.</p>
<h2>Show me your pricing page</h2>
<p>Here&#039;s my private conclusion after wiring commercial policy into every layer of a system I fully control, with nobody to blame but myself.</p>
<p>Show me your pricing page, and I&#039;ll draw your database schema. The two always converge. They have no choice. One is a set of promises. The other is where promises are kept. The only question left is whether anybody <em>designed</em> that, or whether it piled up the way most of them do: one urgent pricing experiment at a time, each looking like a marketing decision on the day it was made.</p>
<p>So, an exercise for your next architecture review: put the pricing page on the screen instead of the <a href="https://c4model.com/">C4 diagram</a>. Walk it boundary by boundary and ask where each promise is enforced.</p>
<p>If the room goes quiet somewhere between &quot;unlimited&quot; and the asterisk: congratulations. You&#039;ve just found your architecture backlog ;)</p>
]]></content:encoded>
      <category>SaaS</category>
      <category>Software Architecture</category>
      <category>Pricing Strategy</category>
      <category>Multi-Tenancy</category>
      <category>Product Management</category>
    </item>
    <item>
      <title>Anti-Personas: The Users I Refuse to Serve</title>
      <link>https://bfrackowiak.pl/blog/anti-personas-the-users-i-refuse-to-serve/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/anti-personas-the-users-i-refuse-to-serve/</guid>
      <pubDate>Mon, 16 Mar 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The most important architecture document in my side project contains no architecture. It&#039;s a list of people I will never build for.</description>
      <content:encoded><![CDATA[<p>In the vision document of my latest side project (a self-hostable scheduling tool written in Go) there is a section I&#039;m prouder of than any diagram I&#039;ve drawn this year. It comes right after the personas, and it&#039;s titled <strong>anti-personas</strong>: the users this product will never serve. Named, described, and rejected before the first line of code existed.</p>
<p>It sounds negative. It&#039;s the most constructive thing in the repository.</p>
<h2>The feature that is absence</h2>
<p>The tool itself is deliberately small: a single binary, SQLite by default, no Docker required, a hard budget of about 5,000 lines of Go, and eleven ADRs explaining the boring choices. It is not trying to compete with <a href="https://calendly.com/">Calendly</a> or <a href="https://cal.com/">Cal.com</a>. They have funding, teams, and ecosystems. It competes with their <em>narrow case</em>: one professional, one website, one booking flow they want to own end-to-end.</p>
<p>Every absence in that description is a decision, not a gap. No team features means no roles-and-permissions tree, no admin screens, no invitation flows. No billing integration means no webhook forest and no PCI anxiety. No video conferencing means no vendor SDK whose breaking changes become my weekend.</p>
<p>The absences even select the audience. The secondary persona in the vision doc is the senior engineer who self-hosts everything and will only adopt a tool that stays small. For them, &quot;no Docker required&quot; is the pitch.</p>
<p>A feature list tells you what a product does. <strong>The anti-feature list tells you what it <em>is</em>.</strong> Constraints are the taste. Anyone can add; the identity lives in what you refuse.</p>
<h2>Rejection, written down</h2>
<p>The anti-personas are specific, and that specificity is the whole trick. A 50-person sales team needing round-robin assignment: not our user. A recruiting org wanting in-app video interviews: not our user. Anyone hoping for a free tier with an upgrade path: there is no business model, this is a tool, so also not our user.</p>
<p>These are real people with valid needs, and they will happily pay somebody. They are simply <em>someone else&#039;s</em> users. Writing that down feels almost rude, which tells you exactly how rarely we do it.</p>
<p>Watch what each rejection buys. Kill round-robin and you never implement availability-merging across calendars, fairness algorithms, or the admin UI to configure them, plus every bug report at the intersection of all three. <strong>One anti-persona kills an entire tree of future edge cases before it sprouts.</strong> The cheapest code review is the one for code that never got written.</p>
<p>And feature triage becomes almost mechanical. Every incoming idea answers one question. <em>Which persona is this for?</em> That includes my own ideas at 2 a.m., when everything seems like a good one. If the answer is an anti-persona, it is a clean no. Painlessly. Nothing personal; it&#039;s in writing.</p>
<h2>The corporate mirror</h2>
<p>This is where the side project starts explaining the day job.</p>
<p>In <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a> I described the edge-case explosion: specification documents swelling to hundreds of cases that nobody reads in full. I blamed corporate culture&#039;s inability to say no. The scheduler taught me the same lesson from the other side. The explosion doesn&#039;t happen because anyone is careless. It happens because <strong>a product company cannot refuse anyone.</strong> Every exception is revenue. Every &quot;no&quot; needs a VP&#039;s spine behind it. Every accepted special case is one more branch in the system, and no incentive structure on earth rewards the person who turned down money.</p>
<p>A solo project can refuse everyone. That is its superpower: not the tech stack, not the single binary, not the artisanal ADRs. The economics of &quot;no&quot; are simply different. At home, refusing costs nothing; at work, it costs a fight.</p>
<p>Which is exactly why organizations shouldn&#039;t dismiss this as hobby-scale wisdom. Two practices copy over directly. First, <strong>anti-personas as first-class strategy artifacts</strong>. Written, named, and signed by someone with authority. Then &quot;we don&#039;t serve this segment&quot; is a document, not a rumor that dies in the next QBR. Second, <strong>absence-of-feature as a decision with an owner</strong>: an ADR for the thing you deliberately didn&#039;t build and why, so the next PM inherits the reasoning instead of innocently reopening the trapdoor.</p>
<p>I&#039;ve watched million-euro complexity enter systems through the simple absence of a written &quot;no&quot;. The document would have cost an afternoon.</p>
<h2>Practicing &quot;no&quot; where it&#039;s free</h2>
<p>My private conclusion is this: the ability to say no is the scarcest architectural resource I know, scarcer than senior engineers, scarcer than budget. And like any scarce skill, it atrophies without reps.</p>
<p>Side projects are my gym for refusal. Every anti-persona I write at home, every feature I decline with a one-line reference to the vision doc, is a rep. Those reps are what keep the diplomatic, watered-down, corporate-compatible version of my &quot;no&quot; from collapsing when a stakeholder leans on it.</p>
<p>The gym matters because of the transfer: the refusal I practice on a scheduling tool nobody uses yet is the same muscle I need in a room where somebody very senior is explaining why this one exception is different, and there is no way on earth to train it inside that room. So I train it in the repo. Write the anti-personas while refusing is still free. Get a signature on them before it isn&#039;t.</p>
]]></content:encoded>
      <category>Product Strategy</category>
      <category>Software Architecture</category>
      <category>Side Projects</category>
      <category>Product Management</category>
      <category>Minimalism</category>
    </item>
    <item>
      <title>Edge Cases Written by Parliament</title>
      <link>https://bfrackowiak.pl/blog/edge-cases-written-by-parliament/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/edge-cases-written-by-parliament/</guid>
      <pubDate>Mon, 02 Mar 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>What integrating with mandatory government systems taught me about specs you cannot refactor.</description>
      <content:encoded><![CDATA[<p>The strictest product owner I have ever worked with has never attended a standup, ignores my roadmap entirely, and publishes requirements as versioned PDFs. It&#039;s the state.</p>
<p>Building Desta, my invoicing platform for the Polish market, means integrating with three government systems: <a href="https://www.podatki.gov.pl/ksef/">KSeF</a> (the mandatory national e-invoicing API), the public company registry with its SOAP interface, and the <a href="https://www.podatki.gov.pl/jednolity-plik-kontrolny/">JPK</a> unified tax files. A quick disclaimer before we start: this is an engineering story, not tax advice. Please keep paying your accountant ;)</p>
<h2>The spec you cannot refactor</h2>
<p>Tax law is an upstream dependency with no negotiation channel. You cannot file an issue against a statute. You cannot suggest a breaking change be batched into the next major version. You implement what the paragraph says, and the paragraph doesn&#039;t care about your domain model.</p>
<p>Two examples that changed how I think about requirements.</p>
<p>First: in KSeF, <strong>the XML is the legal invoice.</strong> The PDF (the thing every user believes is &quot;the invoice&quot;) is legally just a <em>visualization</em>, and has to be labeled as one. Think about what that does to your architecture: the artifact your users see and print is a derived view; the artifact almost nobody reads is the source of truth. Get the relationship backwards and you&#039;ve built a very pretty bug.</p>
<p>Second: invoice rows with certain VAT exemptions must render the VAT column <em>blank</em>. Not &quot;0.00&quot;. Not a dash. Blank. <strong>A one-pixel difference that is a legal requirement, not a style choice.</strong> There is no elegant abstraction that makes this go away; there is only encoding it correctly and pinning it with tests named after tax scenarios.</p>
<p>I wrote in <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a> about two thinking archetypes: the modelers who dissolve edge cases by reshaping the model, and the rule-thinkers who enumerate them. Well, this is the domain where the Lawyer archetype simply wins. Every edge case here has a paragraph number attached. My instinct is to find the model that makes exceptions disappear; in compliance work, the exceptions <em>are</em> the model.</p>
<blockquote><p>&quot;Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.&quot; ― Antoine de Saint-Exupéry</p></blockquote>
<p>The state has never read it.</p>
<h2>SOAP, sessions, and the state</h2>
<p>Now the part everyone loves to mock: the technology.</p>
<p>The company registry speaks SOAP in 2026. The session ID travels in a custom HTTP header, not a cookie. That detail alone costs every integrator an afternoon of their life. The field for a building number has a bureaucratic Polish name that no autocomplete will ever guess. The documentation is a versioned PDF.</p>
<p>The inconvenient truth: <strong>it all works.</strong> The same requests produce the same responses, year after year. My integration code for that registry is the least-touched code I own.</p>
<p>KSeF authentication is a small choreography: fetch the public key, request a challenge, encrypt your token and a timestamp with RSA, redeem the result for a session. Is it elaborate? Yes. Is it security theater? Partly, perhaps. But it&#039;s <em>documented</em> choreography. You implement it once, wrap it, and forget it exists.</p>
<p>That word, &quot;wrap&quot;, is the architectural lesson. This is where the anti-corruption layer stops being a conference slide and starts earning its keep. The government&#039;s model, its vocabulary, its SOAP envelopes and bureaucratic field names get translated at the boundary, once, in one place. My domain never learns what the ministry calls a building number. The state stays outside; the model stays clean.</p>
<h2>Lessons for architects raised on REST</h2>
<p>If your entire career has been JSON over HTTPS with a <code>v2</code> in the path, integrating with the state is a masterclass in things our industry forgot.</p>
<p><strong>Stability is the feature.</strong> These APIs change less per decade than a typical startup API changes per quarter. Deprecations are announced by legislation, with timelines measured in years. Meanwhile I&#039;ve integrated with modern SaaS APIs that shipped three breaking changes in a year, each announced in a changelog nobody reads.</p>
<p><strong>Compliance is a bounded context.</strong> It has its own language (the statute&#039;s, not yours), its own tests (named after tax scenarios, not user stories), and its own release rhythm: the calendar of the state, not your sprint. Treating it as &quot;just another feature&quot; is how compliance logic ends up smeared across a codebase.</p>
<p><strong>The reference implementation is the real spec.</strong> The ministry publishes reference client SDKs, and reading them is archaeology. You dig through layers to learn what the API <em>actually</em> expects, as opposed to what the PDF implies. Archaeology beats guessing. I made a similar argument in <a href="https://medium.com/@bfrackowiak/stop-calling-it-legacy-code-02308db28cb2">Stop Calling It Legacy Code</a>: old code is a knowledge container. Government reference code is the same idea at civilizational scale: decades of edge cases, fossilized and readable.</p>
<h2>Ugly, documented, stable</h2>
<p>Complaining about government APIs is the cheapest joke in engineering, and I&#039;ve told it myself. But after a year of running production integrations against them, I have to report something embarrassing: I trust these contracts more than most microservices I&#039;ve met in my career.</p>
<p>They are ugly. They are documented. They are stable. <strong>Two of those three qualities are the ones that actually matter</strong>, and they&#039;re not the one we keep optimizing for.</p>
<p>So, a question for your next architecture review: which of your internal APIs would survive being frozen for five years? Because the ministry&#039;s would. And did.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Government Technology</category>
      <category>Domain-Driven Design</category>
      <category>Legacy Systems</category>
      <category>E-Invoicing</category>
    </item>
    <item>
      <title>ADRs for an Audience of One</title>
      <link>https://bfrackowiak.pl/blog/adrs-for-an-audience-of-one/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/adrs-for-an-audience-of-one/</guid>
      <pubDate>Mon, 16 Feb 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>My hobby project has eleven Architecture Decision Records and zero users. That ratio is correct.</description>
      <content:encoded><![CDATA[<p>In the evenings I build a small self-hosted scheduling tool in Go. Just me. Single binary, SQLite by default, no Docker required, deliberately single-user. It has no users. Not even me, technically, since it isn&#039;t deployed yet. It does have eleven <a href="https://adr.github.io/">Architecture Decision Records</a>: one for the database, one for auth, one for the scheduler, one for the deployment target, and seven more for the questions I knew I would otherwise re-open at midnight six months later.</p>
<p>Eleven ADRs. Zero users. Colleagues have laughed at that ratio and I get it. Writing formal decision documents for a project whose entire engineering department is one guy in the evenings looks like corporate cosplay. I&#039;m going to argue the opposite: <strong>that ratio is correct</strong>, and the reasoning applies far beyond hobby projects.</p>
<h2>The stakeholder with amnesia</h2>
<p>The awkward truth about solo projects: you are not one person. You are a sequence of people wearing the same face, and they don&#039;t talk to each other. Never have.</p>
<p>There&#039;s the me who chose the <a href="https://github.com/go-chi/chi">chi router</a> over the more popular frameworks. The me who decided SQLite is the default and Postgres just an adapter behind an interface. The me who picked sessions plus TOTP. Not an identity provider. Each of those people had context: benchmarks skimmed, trade-offs weighed, constraints that were obvious <em>that week</em>.</p>
<p>And then there&#039;s future-me. Future-me is a genuine stakeholder, one with amnesia. He opens the repo after three months away, finds a decision he doesn&#039;t remember making, and immediately starts distrusting it. <em>Why is there no ORM here? Was I lazy, or was I smart?</em> Without a record, he cannot tell. And an engineer who distrusts his own codebase starts rewriting things that were never broken.</p>
<p>The economics are absurdly lopsided. An ADR written on the day of the decision costs ten minutes. Reconstructing the reasoning a year later costs an afternoon, and the reconstruction is usually wrong, because you rebuild yesterday&#039;s decision out of today&#039;s knowledge. Eleven ADRs before the project is even feature-complete are <strong>the project&#039;s memory</strong>, and the only memory that survives the gap between evening sessions.</p>
<h2>Writing it down changes what you decide</h2>
<p>The bigger surprise was what ADRs did to the decisions themselves.</p>
<p>Every ADR template carries one quietly brutal section: <em>alternatives considered</em>. That is the whole trick. You cannot fill it honestly without laying the options side by side, in daylight. And in that daylight, a certain kind of decision dies: the resume-driven one. Try writing &quot;Alternative B: the boring technology that fits the problem perfectly. Rejected because: I wanted to play with Alternative A&quot;, then signing your name under it. The template forces the question every architect should ask and rarely does: <strong>do I want this technology, or does the problem?</strong></p>
<p>Two more effects I didn&#039;t expect:</p>
<ul>
<li><strong>Timestamped context.</strong> An ADR records what was true and known at decision time. When the world changes (and it will), the record protects past-you from unfair judgment, and tells present-you exactly which assumption expired.</li>
<li><strong>Deferred decisions stop haunting you.</strong> One of my eleven ADRs decides <em>not to decide</em>: a query-codegen library that looks attractive, postponed until the hand-written SQL layer actually hurts, with a written trigger for revisiting. Before ADRs, that would have been ambient anxiety, a tab left open in my head. Now it&#039;s a documented &quot;not yet&quot; with an alarm attached.</li>
</ul>
<p>There&#039;s a wry pleasure in noticing that the discipline works precisely because nobody is watching. No committee to perform for, no reviewer to impress. Just the decision and me, on paper ;)</p>
<h2>From hobby repo to boardroom</h2>
<p>Now scale it up, because this is where it stops being a solo-dev quirk.</p>
<p>An ADR log plus one named decider is <strong>the cheapest governance stack that exists</strong>. No tooling. No steering committee. A folder of text files and a name. Organizations design their systems obsessively and leave the decisions about those systems undesigned. I wrote about that in <a href="/blog/decisions-have-an-architecture-too/">Decisions Have an Architecture Too</a>. The ADR is the smallest fix I know for that gap. It does nothing to make decisions faster. It simply makes them <em>exist</em>: a question somebody outside the room can raise, a record somebody not yet hired can inherit, a position the author can defend, and a date saying when all of it was still true.</p>
<p>When I want to judge the health of a team, I ask for their decision log before I read a line of their code. A living log full of honest alternatives tells me more than any maturity assessment. An empty one tells me the architecture lives in somebody&#039;s head. Heads get promoted. Heads get poached.</p>
<p>And there&#039;s a reason for this practice that didn&#039;t exist when the ADR format was invented. Agents read our repositories now. Code tells them <em>what</em> the system does; only decision records tell them <em>why</em>: which constraints were real, which paths were rejected and on what grounds. Decision reasoning is precisely the thing a model cannot infer from source, no matter how good it gets. My eleven small markdown files turn out to be premium context for every future agent, and every future human, working in that repo.</p>
<h2>An audience of one</h2>
<p>So yes: eleven ADRs, zero users, and I&#039;ll write the twelfth tonight without a trace of embarrassment.</p>
<p>Over the years I have arrived at a personal rule. My side projects are where I enforce it hardest, precisely because nobody is watching. <strong>If a decision isn&#039;t worth writing down, it was a mood.</strong></p>
<p>Moods are fine. I&#039;ve shipped plenty of good code on moods. But a mood can&#039;t be reviewed, can&#039;t be inherited, and can&#039;t defend itself six months later.</p>
<p>Open your most important repository and try to find where its five biggest decisions are written down. Not the code that implements them, the reasoning that produced them. If it isn&#039;t in the repo then it is stored in a person. That storage medium gets promoted, poached, and eventually hands in its notice with two weeks&#039; warning and a leaving card.</p>
<p>Mine live in a folder. It took ten minutes each.</p>
]]></content:encoded>
      <category>Architecture Decision Records</category>
      <category>Software Architecture</category>
      <category>Side Projects</category>
      <category>Engineering Practices</category>
      <category>Documentation</category>
    </item>
    <item>
      <title>Questions Are an Architect&#039;s Sharpest Tool</title>
      <link>https://bfrackowiak.pl/blog/questions-are-an-architects-sharpest-tool/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/questions-are-an-architects-sharpest-tool/</guid>
      <pubDate>Mon, 26 Jan 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The artifact of mine that pays off most is not a diagram and not a decision record. It&#039;s a list of questions.</description>
      <content:encoded><![CDATA[<p>The best architecture review I can remember produced no decisions, no action items, and one question. Someone looked at a beautifully complete design for an aggregation service and asked: &quot;Who owns this number after it&#039;s published?&quot; Silence. Two weeks later the design was different, and better, in ways no amount of diagram polishing would have achieved.</p>
<p>I&#039;ve been collecting that silence ever since.</p>
<p>Early in my career I believed seniority meant having answers: fast, confident, preferably with a diagram. A dozen years in, I measure my weeks differently: by the quality of the questions I managed to ask before the answers got expensive. That one I&#039;d defend in any review.</p>
<h2>The question list is a deliverable</h2>
<p>Every solution design I ship travels with a companion artifact: an open-questions file. Beyond that, I maintain separate documents with titles like &quot;architecture questions to product&quot;. They are versioned, statused (open, answered, overtaken by events), and reviewed with the same seriousness as any diagram. Colleagues occasionally find this odd. Aren&#039;t architects paid for answers?</p>
<p>Here&#039;s the asymmetry that convinced me otherwise. <strong>Answers age badly.</strong> An answer encodes the constraints of the day it was written: the team&#039;s size, the vendor&#039;s pricing, the traffic profile, what the organization believed that quarter. Constraints rot silently, but the answer keeps getting repeated, confidently, like a cached value nobody thought to invalidate. Some of the most dangerous sentences in engineering start with &quot;we already decided that.&quot;</p>
<p>Good questions do the opposite: <strong>they compound.</strong> &quot;Who owns this after go-live?&quot; produces fresh truth every single time you re-ask it. &quot;What happens when these two sources disagree?&quot; outlives reorgs, migrations, and three generations of diagrams. An answer is a snapshot. A good question is a probe you can re-run.</p>
<p>That&#039;s why the question list deserves version control. It is the only artifact I produce whose value reliably <em>grows</em> with time.</p>
<h2>Two kinds of questions</h2>
<p>Now the craft, because not everything with a question mark is a tool.</p>
<p>The first kind <strong>exposes hidden assumptions</strong>. &quot;What must be true for this design to work?&quot; &quot;Who pays when this fails at 2 a.m.?&quot; &quot;Which of these arrows is a wish and which is a contract?&quot; These questions are cheap before the design is approved and brutal after go-live. Their entire value is in the timing. They tend to be short, slightly uncomfortable, and impossible to answer with a framework name.</p>
<p>The second kind <strong>performs seniority</strong>. The gotcha delivered for the audience. The technology flex (&quot;have you considered Kafka?&quot;) asked not to learn but to be seen knowing Kafka exists. The fifteen-minute &quot;question&quot; that is actually a speech with rising intonation. You know the one. This kind produces no design changes, only defensive meetings.</p>
<p>My test is simple: would I still ask this question in a one-on-one, with no witnesses? If yes, it&#039;s a probe. If it only makes sense with spectators, it&#039;s theater. I&#039;ve written before about how much of corporate life already is (<a href="/blog/data-driven-theater/">Data-Driven Theater</a>).</p>
<p>One more piece of craft: a question without an owner and a check-back date is not a tool, it&#039;s a sigh. &quot;Someone should clarify this&quot; has never once, in my experience, resulted in someone clarifying anything.</p>
<h2>Open questions as an honesty mechanism</h2>
<p>The deepest value of the question list is what it does to trust.</p>
<p>Every solution design of mine ends with an &quot;Open Questions&quot; section: the things I don&#039;t know, stated before someone else discovers them for me. It felt exposing the first few times. It turned out to be disarming. A review of a &quot;complete&quot; design is a hunt: reviewers win by finding holes. A review of a design with three declared holes is a search party: the reviewers join you, because you&#039;ve already conceded the interesting part (that holes exist) and named where you think they are.</p>
<p>Declared uncertainty, I keep relearning, builds more credibility than performed certainty ever did.</p>
<p>Years ago, in <a href="/blog/technical-leader-identity-disorder/">Technical Leader — Identity Disorder</a>, I leaned on Socrates while untangling my own role. I&#039;ve since stopped treating <a href="https://en.wikipedia.org/wiki/I_know_that_I_know_nothing">&quot;I know that I know nothing&quot;</a> as philosophical decoration. It&#039;s a working method, and the open-questions file is its production implementation. Confidence is not the absence of open questions. <strong>Confidence is knowing exactly which questions are open.</strong></p>
<p>Which is why an <em>empty</em> Open Questions section in a big design is, for me, a red flag with a siren attached. If you can&#039;t name three things you don&#039;t know about your own architecture, that&#039;s not completeness. That&#039;s the first open question.</p>
<h2>Try it</h2>
<p>A small experiment for your next design document. Add an &quot;Open Questions&quot; section with at least three honest entries, each with an owner and a date to check back. Mine look like this:</p>
<pre><code>[open]      Who owns this number after go-live?        → &lt;name&gt;, check back &lt;date&gt;
[answered]  What happens when the two sources disagree? → see the decision record
[overtaken] &lt;the question a reorg answered for you&gt;     → close it, keep the scar
</code></pre>
<p>Then watch what the review becomes.</p>
<p>&quot;Who owns this number after it&#039;s published?&quot; is still on my list. It has never come back with the same answer twice, which is the only test I have found for whether a question is worth keeping, and it is a better test than whether the question made anybody in the room look clever at the time. Collect the ones that sting. Sharpen yours.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Solution Design</category>
      <category>Critical Thinking</category>
      <category>Engineering Leadership</category>
      <category>Communication</category>
    </item>
    <item>
      <title>The Anti-Corruption Layer Is for People Too</title>
      <link>https://bfrackowiak.pl/blog/the-anti-corruption-layer-is-for-people-too/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-anti-corruption-layer-is-for-people-too/</guid>
      <pubDate>Mon, 19 Jan 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>My favorite pattern from Domain-Driven Design protects codebases from foreign models. I keep prescribing it to departments.</description>
      <content:encoded><![CDATA[<p>Ask an architect for their favorite pattern and you&#039;ll usually hear something impressive: event sourcing, CQRS, cells. Mine is humbler: <strong>the anti-corruption layer</strong>. A boring translation shim from the <a href="https://en.wikipedia.org/wiki/Domain-driven_design">Domain-Driven Design</a> toolbox. I love it because it&#039;s the only pattern I know that works equally well on an integration diagram and on an org chart.</p>
<h2>What the layer actually protects</h2>
<p>A quick refresher for the non-DDD crowd. An <a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer">anti-corruption layer</a> sits between your system and an upstream system whose model you do not control: a legacy platform, a vendor API, another team&#039;s service. Its job is translation: their concepts arrive, get mapped into <em>your</em> language at the border, and only then touch your domain.</p>
<p>The name is the important part. Evans didn&#039;t call it &quot;adapter&quot; or &quot;facade&quot;. He called it <em>anti-corruption</em>, because that is what it prevents. And here is what most teams get wrong: <strong>the layer protects your model&#039;s integrity, not your API.</strong> The danger was never that a remote call fails; you have retries for that. The danger is that their <code>status = 7</code>, their nullable-everything contract, their subtly different definition of &quot;customer&quot; starts living inside your entities. You import one convenient enum today, and six months later your code speaks two languages, neither of them fluently. Every feature after that pays the tax.</p>
<p>Is the extra hop always worth it? Honestly, no. Translation costs: another mapping class, more tests, and a guaranteed &quot;why this indirection?&quot; comment in code review. If the upstream model is stable, close to yours, and read-only, map it directly and move on. The layer earns its keep in three cases, and they are worth naming separately because teams usually notice the third one long after it has already cost them a quarter of rework. The upstream model keeps changing. Its language contradicts yours. Or you catch yourself sprinkling <code>if (theirType == LEGACY_B)</code> across your domain logic. That <code>if</code> <em>is</em> the corruption, already inside. Not a warning of it. The thing.</p>
<h2>Semantic corruption, human edition</h2>
<p>Now watch the same mechanism run on people.</p>
<p>Teams import each other&#039;s vocabularies exactly the way codebases import foreign enums: silently and wholesale. The channel isn&#039;t an API; it&#039;s a shared spreadsheet, a copied ticket, a slide lifted from another department&#039;s deck. A word walks across the boundary (&quot;account&quot;, &quot;assessment&quot;, &quot;active&quot;) carrying someone else&#039;s model inside it, and nobody inspects the luggage.</p>
<p>I&#039;ve written before that most &quot;data-driven&quot; fights are really collisions between semantic contracts nobody wrote down (<a href="/blog/data-driven-theater/">Data-Driven Theater</a>). This is how those contracts get broken in the first place. A term optimized for sales, with sales&#039; compromises baked in, becomes an engineering domain term. A status that conflates two different situations gets copied into three more teams&#039; backlogs. The breakage replicates through the organization precisely like corrupted records through an unvalidated pipeline.</p>
<p>Nobody decides any of this. That is the point. <strong>Corruption rarely announces itself; it arrives one borrowed word at a time.</strong></p>
<h2>Translate or unify, but never &quot;just align&quot;</h2>
<p>So what do you do at a boundary, human or technical? You have exactly two honest options.</p>
<p>Adopt a genuinely shared model when the concept is core to both sides and you are prepared to co-own its evolution. This is the ubiquitous-language dream, and it is expensive: shared definitions need governance, a change process, and someone empowered to say no. Reserve it for the handful of concepts that deserve it. This is model work (relations, constraints, evolution), the level I argued architects should stay at in <a href="/blog/mathematicians-vs-lawyers/">Mathematicians vs. Lawyers</a>.</p>
<p>Translate at the boundary when the other side&#039;s model is legitimately <em>theirs</em>: owned by them, stable, optimized for their world. Keep a glossary. Better yet, keep a translator, a person who consciously maintains the mapping and can say out loud that &quot;their &#039;client&#039; is our &#039;buyer&#039;, except during trials&quot;. A named, human anti-corruption layer. It feels like bureaucracy. It costs one page and saves quarters.</p>
<p>The trap is the third option, the one everyone reaches for first: <strong>&quot;let&#039;s just align the teams.&quot;</strong> It sounds cheap. It is the most expensive item on the menu. Alignment means permanent joint ownership of a language, so every future change to the concept now requires both parties, forever, including the parties who will inherit it after the next reorg and were never asked. Most teams that &quot;aligned&quot; in a two-hour workshop actually just adopted whichever vocabulary was spoken loudest, imported its bugs along with it, and called the result consensus.</p>
<p>Choose deliberately: unify what is core, translate what is foreign, and never leave a boundary unmanaged just because the two teams happen to like each other. Friendship is not a data contract.</p>
<h2>The audit</h2>
<p>A small exercise for your next quiet Friday. Pick the three most central words on your team&#039;s diagrams. For each one, ask: where did this word come from? Does it mean the same thing one department over? And if it doesn&#039;t, who is doing the translation?</p>
<p>If the answer to the last question is &quot;nobody&quot;, congratulations. You have just located the exact spot where your model is importing someone else&#039;s bugs, free of charge, on a daily schedule.</p>
<p>I stopped asking teams to align. Now I ask who translates. That is a smaller question and a much easier one to answer honestly, because the answer has a name in it and alignment never does. The person who says &quot;that&#039;s our word, not theirs&quot; is doing architecture, whatever their title says. Every domain word has an accent. The useful part is knowing whose.</p>
]]></content:encoded>
      <category>Domain-Driven Design</category>
      <category>Software Architecture</category>
      <category>Team Communication</category>
      <category>Organizational Culture</category>
      <category>Software Engineering</category>
    </item>
    <item>
      <title>The Migration That Never Starts</title>
      <link>https://bfrackowiak.pl/blog/the-migration-that-never-starts/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-migration-that-never-starts/</guid>
      <pubDate>Mon, 12 Jan 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Every organization has one. Yours does too. It has a folder, a deck, and a birthday.</description>
      <content:encoded><![CDATA[<p>Somewhere in your company&#039;s document storage there is a folder. It might be called &quot;Migration Options,&quot; or &quot;Target Architecture,&quot; or &quot;Platform Modernization Analysis.&quot; It contains several well-written documents, each comparing three to five options, each ending with &quot;recommendation to be validated.&quot; The oldest file is years old. The newest was added last quarter.</p>
<p>I have contributed to such folders. I have <em>created</em> such folders. So take what follows as private observations, not science, but observations from both sides of the crime scene ;)</p>
<p>Here is the conclusion those folders eventually forced on me: <strong>organizations don&#039;t fail migrations. They fail to start them.</strong> The industry loves horror stories about big migrations that went wrong. Honestly, I&#039;ve seen far more damage done by migrations that never left PowerPoint.</p>
<h2>The options-collection ritual</h2>
<p>The mechanism looks thoroughly respectable. A migration is clearly needed: the old platform hurts, everyone agrees, nobody objects. So we do the responsible thing: we analyze. We produce a comparison matrix. Options, strengths, weaknesses, costs, risks.</p>
<p>Then the quarter ends, priorities wobble, and the document goes to the folder. Next quarter someone reasonably notes that &quot;the market has moved&quot; (the market has always moved) and commissions a refresh. A new document is added. <strong>Nothing is ever subtracted.</strong></p>
<p>This is analysis as a socially acceptable form of postponement. Nobody ever got fired for producing a comparison matrix. Producing one <em>feels</em> like progress, reads like diligence in a status report, and carries none of the personal risk of an actual decision. The ritual has all the ceremonial properties of work (effort, artifacts, meetings) with none of the consequences.</p>
<p>And every refresh resets the clock. You can postpone anything indefinitely if you re-analyze it at the right frequency.</p>
<h2>Decision debt</h2>
<p>We track technical debt in backlogs. We track risks in registers. The compounding cost of <em>not deciding</em> is tracked exactly nowhere, and it&#039;s often the largest item on the invisible balance sheet.</p>
<p>Because while the decision waits, the old platform doesn&#039;t wait with it. Every sprint adds new integrations to the thing you intend to leave. Every new feature is another consumer of the legacy contract, another strand in the net that will have to be cut later. The migration doesn&#039;t get cheaper while you analyze it. The price of &quot;not yet&quot; is invoiced monthly and itemized never.</p>
<p>There&#039;s a human line item too, and it compounds faster than the technical one. The third time a migration is announced and doesn&#039;t start, teams stop believing announcements. Engineers who prepared for the move file the experience under &quot;management noise.&quot; So the zombie migration does not only burn analysis budget. It devalues every future promise leadership makes, including the real ones, including the ones with money behind them, and including the one somebody will need people to believe two years from now. Including the one that finally gets funded two years from now, and arrives to a room that has learned not to move until it sees a merged pull request. That currency does not come back at the next all-hands.</p>
<h2>One-way doors, two-way doors</h2>
<p>Amazon popularized a genuinely useful question: is this decision a one-way door or a two-way door? Irreversible decisions deserve slow, heavy scrutiny. Reversible decisions deserve speed; the cost of a wrong reversible decision is just the walk back through the door.</p>
<p>Now look honestly at a migration. Routing one endpoint through the new service: reversible. Dual-writing to both data stores: reversible by design. Moving one read path, one team, one low-risk workload: two-way doors, all of them. The overwhelming majority of migration <em>steps</em> can be undone with a config change and a mildly embarrassing standup update.</p>
<p>Yet the organization treats every step as a bet-the-company moment. Why? Because the migration was framed as one giant decision. &quot;We are moving to X.&quot; <em>That</em> framing is a one-way door, socially if not technically. Nobody wants to personally own the giant door, so everybody keeps studying the hinges.</p>
<p>The trick is almost insultingly simple: stop deciding the migration. Decide the next step. <strong>Decompose until every step is a two-way door, then walk through the first one.</strong></p>
<blockquote><p>&quot;A complex system that works is invariably found to have evolved from a simple system that worked.&quot; ― John Gall, <a href="https://en.wikipedia.org/wiki/Systemantics">Systemantics</a></p></blockquote>
<h2>What actually unblocks</h2>
<p>Three things, in my experience, and none of them is another analysis:</p>
<ol>
<li><strong>A timebox on the analysis.</strong> Options expire like milk. Decide by a date, not by certainty, because certainty is not coming. It never was. An 80%-informed decision this quarter beats a 95%-informed decision that will never be made.</li>
<li><strong>A named decider.</strong> Not a committee, not &quot;alignment,&quot; one accountable person. Consensus is where migrations go to die; a name is where they wake up. I&#039;ve written more about decision rights in <a href="/blog/decisions-have-an-architecture-too/">Decisions Have an Architecture Too</a>.</li>
<li><strong>A first step too small to refuse.</strong> One endpoint, one consumer, two weeks, fully reversible. Impact is beside the point. Its job is to convert the migration from a document into a fact, because facts attract budget, volunteers, and momentum in a way folders never do.</li>
</ol>
<p>A failed migration at least produces knowledge: you learn where it breaks, what it costs, who steps up. A migration that never starts produces only documents, plus a slow, expensive lesson in how little your organization&#039;s announcements are worth.</p>
<p>So, one honest question: which folder in your document storage is quietly approaching its fifth birthday?</p>
<p>Don&#039;t refresh it this quarter. Pick the smallest reversible step it describes and open door number one.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Decision Making</category>
      <category>Legacy Systems</category>
      <category>Engineering Leadership</category>
      <category>Organizational Culture</category>
    </item>
    <item>
      <title>Diagrams Are Conversations, Not Documentation</title>
      <link>https://bfrackowiak.pl/blog/diagrams-are-conversations-not-documentation/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/diagrams-are-conversations-not-documentation/</guid>
      <pubDate>Mon, 05 Jan 2026 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>The most useful diagram of my career was ugly, partially wrong, and deleted within a week.</description>
      <content:encoded><![CDATA[<p>Early in my architecture career I spent three evenings polishing a system diagram. Aligned boxes. Consistent arrow styles. A legend. Color semantics. I presented it to the room, people nodded, someone said &quot;great overview,&quot; and the meeting moved on. That diagram changed exactly nothing, and I was very proud of it.</p>
<p>A few years later I stood at a whiteboard with a marker I had to shake to make work, drew four crooked boxes, and before I finished the fifth a senior developer interrupted: &quot;That&#039;s not how it flows. The events go through the queue first.&quot; Twenty minutes of arguing later, we had discovered that two teams held two different mental models of the same integration, and had been building against both.</p>
<p>The crooked boxes did more for the architecture than the three-evening masterpiece ever did. It took me embarrassingly long to understand why, so let me save you the time: <strong>a diagram&#039;s value is consumed in the meeting where it is drawn.</strong> What survives the meeting is a receipt.</p>
<h2>The half-life problem</h2>
<p>Polished enterprise-architecture diagrams are dead on arrival. Not because they&#039;re wrong on day one, but because the system starts drifting away from them on day two, and no contract of maintenance was ever signed. Everyone wants diagrams to exist; nobody has &quot;update the diagrams&quot; in their sprint. Nobody ever will.</p>
<p>Run this audit in your own organization: ask a team to show you their &quot;current state&quot; diagram, then watch the eyes. The honest answer is always some flavor of &quot;current as of the workshop we ran in spring.&quot; We don&#039;t version diagrams. We hold funerals for them, quietly, by never opening the file again.</p>
<p>Meanwhile the messy whiteboard photo from that one heated session keeps getting reposted in chat threads for months. Not because the sketch is good (it objectively isn&#039;t). Because everyone who was in the room remembers the argument it hosted. The picture is a bookmark for a shared understanding. People who weren&#039;t there see boxes; people who were see the conversation. That gap is the whole point.</p>
<h2>One diagram, one question</h2>
<p>The everything-diagram is the most common failure I see: one canvas trying to answer every question about the system, and therefore answering none. Fifty boxes, four zoom levels, a legend that needs its own legend.</p>
<p>My working rule is brutally simple: <strong>one diagram, one question.</strong> Scope every drawing to the single decision it must support. &quot;Should checkout call inventory directly or go through events?&quot; needs five boxes. It does not need your cluster topology, and it absolutely does not need the boxes that are only there because leaving them out felt impolite.</p>
<p>Anything that doesn&#039;t influence the decision is decoration, and decoration invites feedback about decoration. You wanted a call on the integration pattern; you got forty minutes on whether the auth service should also be pictured.</p>
<p>One small trick changes everything: title the diagram with the question, not the system. &quot;Where should risk aggregation live?&quot; starts a very different meeting than &quot;Platform Overview v7.&quot;</p>
<h2>Draw live, don&#039;t present</h2>
<p>The pattern I trust most costs nothing: draw the diagram <em>in</em> the meeting, badly, in front of everyone.</p>
<p>A finished diagram triggers politeness; people approve documents. An unfinished diagram triggers honesty; people cannot stop themselves from correcting a sketch. And the moment someone says &quot;that arrow is backwards&quot; is the exact moment your architecture is being validated, for free, by the people who know the ground truth. There is no cheaper review process on the market.</p>
<p>I&#039;ll admit why I resisted this for years: presenting polished work is a way of performing competence. Drawing crooked boxes in public feels junior. It isn&#039;t. It takes considerably more seniority to be visibly unfinished in a room than to be privately perfect ;)</p>
<p>The practical version: come with the question written at the top, draw the first three boxes yourself, then hand over the marker, literally or rhetorically. &quot;What am I missing?&quot; outperforms &quot;any questions?&quot; every single time.</p>
<h2>The AI twist</h2>
<p>And now the part that changes the economics of all of the above. AI agents will happily generate a <a href="https://c4model.com/">C4 diagram</a>, a sequence diagram, or a dependency graph from a paragraph of text: on demand, in seconds, regenerated whenever the code changes. The mechanical act of drawing, the thing that used to cost me three evenings, now costs approximately nothing.</p>
<p>Which exposes what was true all along: the scarce skill was never the drawing. <strong>The scarce skill is choosing what to show</strong>: which question, at which altitude, for which audience. An agent can render fifty boxes flawlessly; it cannot know that this particular room, on this particular Tuesday, needs exactly five of them and one uncomfortable arrow.</p>
<p>Diagram-as-artifact is deflating fast. Diagram-as-conversation is appreciating. Plan your skills portfolio accordingly.</p>
<p>So, a small experiment for your next design review: don&#039;t bring the finished diagram. Bring the question, an empty canvas, and the willingness to be corrected in public.</p>
<p>A marker you have to shake to make work is optional. In my case it helped.</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Diagrams</category>
      <category>Communication</category>
      <category>Technical Leadership</category>
      <category>Artificial Intelligence</category>
    </item>
    <item>
      <title>Nobody Owns the Sum</title>
      <link>https://bfrackowiak.pl/blog/nobody-owns-the-sum/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/nobody-owns-the-sum/</guid>
      <pubDate>Mon, 22 Dec 2025 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Every squad owns its piece. The customer buys the whole. Guess which one has no owner.</description>
      <content:encoded><![CDATA[<p>Some time ago I sat in a meeting where somebody asked a simple question: <strong>&quot;Who owns the aggregated risk score?&quot;</strong></p>
<p>Silence. Then everyone answered at once, each about their own piece. One squad owned the data ingestion. Another owned one of the sub-scores feeding the aggregate. A third owned the screen where the final number is displayed to the customer. The number itself (the thing customers actually look at, the thing sales sells and support gets called about) was owned by nobody.</p>
<p>It took three meetings to establish that. Not because anyone was hiding. Everyone genuinely believed someone else had it.</p>
<p>What follows is a pattern I&#039;ve seen too many times to ignore.</p>
<h2>The org chart produces parts</h2>
<p>We assign ownership by component, because components map neatly onto teams. A squad gets a service, a database, a screen. Clean boundaries, clear accountability, everyone can draw their box. So far, so good.</p>
<p>But the most valuable outcomes an organization produces are not components. They are <strong>sums</strong>: the aggregated score, the end-to-end response time, the coherence of the user experience, the total cloud bill. A sum, by definition, crosses boxes. And whatever crosses boxes on an org chart falls between them.</p>
<p><a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway&#039;s law</a> is usually quoted about system design: your architecture will mirror your communication structure. There&#039;s an ownership corollary that gets much less airtime: <strong>an org chart can produce parts, but it cannot produce wholes.</strong> Wholes need an owner that the chart does not naturally generate. Someone has to create that role on purpose.</p>
<p>Nobody plans an orphan. An orphan is simply what&#039;s left over after everyone finishes claiming what&#039;s theirs.</p>
<h2>How to spot one</h2>
<p>Orphaned aggregates don&#039;t announce themselves. They surface as symptoms, and every symptom looks like something else:</p>
<ul>
<li>The ping-pong ticket. A cross-squad bug bounces between teams, each proving, correctly, that their part behaves as specified. Every part worked; the sum was wrong. The ticket ages until a frustrated customer escalates it past all of you.</li>
<li>The decision that waits months. Changing a whole requires a yes from everyone who owns a part. A yes from everyone is, in practice, a yes from no one. So the improvement idea circles the organization like luggage nobody claims.</li>
<li>The invisible roadmap item. Aggregate improvements appear on no backlog, because no backlog hosts them. Each squad&#039;s roadmap is full, full of parts.</li>
<li>The human failsafe. The only owner is a veteran who remembers how the whole thing fits together. That&#039;s memory rather than ownership, and it resigns when they do.</li>
</ul>
<p>Recognize two of these and you have an orphan.</p>
<h2>The architect&#039;s move</h2>
<p>Where I&#039;ve landed on what a solution architect should do about it is less heroic than you might hope.</p>
<p>Name the orphan out loud. This sounds trivial and isn&#039;t. As long as the aggregate has no name, its non-ownership is invisible; there is literally nothing to point at. The moment you name it (&quot;the overall score&quot;, &quot;the end-to-end latency budget&quot;, &quot;the network aggregate&quot;) the question <em>who owns this?</em> stops being unaskable and becomes merely awkward. Awkward is progress.</p>
<p>Then force an explicit decision. Not make it. Force it. The architect&#039;s job is to put the organization in a position where <em>not deciding</em> is the visible, embarrassing option. Getting from &quot;we should discuss this&quot; to a named owner is a craft of its own, and I wrote about that machinery separately in <a href="/blog/decisions-have-an-architecture-too/">Decisions Have an Architecture Too</a>.</p>
<p>Sometimes the honest answer to that decision is a role nobody has yet. Occasionally an existing squad&#039;s charter genuinely grows to cover the sum. More often you end up creating something that didn&#039;t exist before: a capability owner, a small platform team, a named service with a named human. Read that as the org finally matching what it sells.</p>
<p>And one warning from experience. The default answer in the room will be: <em>&quot;the architect can own it.&quot;</em> Refuse politely. Architecture must not become the attic where the organization stores what it doesn&#039;t want to decide about.</p>
<p>Components get owners; sums get hopes. The whole game is converting one hope at a time into an owner.</p>
<h2>Try it on your own system</h2>
<p>Here&#039;s a 15-minute exercise that costs nothing. Walk your value stream end to end, from the first customer click to the last invoice, and count the things that <em>everyone depends on and no one owns</em>. The aggregate score. The overall latency. The consistency of terminology across screens. The total cost of the pipeline.</p>
<p>My prediction: you&#039;ll find at least three. Every organization I&#039;ve looked at has them, because org charts are made of boxes and value is made of sums.</p>
<p>I do that walk now before I draw a single box on a new engagement, which is a habit I picked up the expensive way, by accepting a couple of these orphans myself and finding out that the flattery wears off in about a week while the babysitting does not. The orphans are always there. What changed is that I stopped being surprised by them, and started naming them in the first week instead of the third meeting ;)</p>
]]></content:encoded>
      <category>Software Architecture</category>
      <category>Organizational Design</category>
      <category>Conways Law</category>
      <category>Engineering Leadership</category>
      <category>Ownership</category>
    </item>
    <item>
      <title>Decisions Have an Architecture Too</title>
      <link>https://bfrackowiak.pl/blog/decisions-have-an-architecture-too/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/decisions-have-an-architecture-too/</guid>
      <pubDate>Mon, 15 Dec 2025 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>We design our systems obsessively, then let the decisions about them happen by loudest voice.</description>
      <content:encoded><![CDATA[<p>I once counted the workshops we had held about the same topic: who owns a shared access-control integration. Three. Three workshops, the same diagrams, largely the same people, and at the end of each one the warm feeling of a productive discussion and no decision whatsoever. The fourth workshop was already in the calendar.</p>
<p>The fourth workshop never happened. We assigned letters instead. More on the letters in a moment.</p>
<p>As architects we obsess over system design: boundaries, contracts, failure modes, who calls whom. Meanwhile the <em>decisions about those systems</em>, often worth more than the systems themselves, happen with no design at all. Whoever speaks loudest, or last, or closest to the deadline, wins.</p>
<h2>The meeting that cannot decide</h2>
<p>Here&#039;s the anatomy of a stalled architecture decision, and I bet you&#039;ve lived it.</p>
<p>A question appears: who owns X, do we build Y, do we retire Z. A meeting is called. Smart people attend and say smart things. Somewhere near the end, someone summarizes &quot;so we&#039;re aligned&quot;, and everyone nods, each person having heard a slightly different decision. Two weeks later the topic is back, re-litigated from scratch by whoever wasn&#039;t in the room.</p>
<p>The root cause is almost never missing information. It&#039;s that <strong>nobody knows who actually decides.</strong> A decision made by a meeting belongs to no one, so it evaporates the moment the meeting ends. Un-deciding it costs nothing, because deciding it cost nothing.</p>
<p>We would never build a system where any service can silently overwrite another&#039;s data. Yet we run decision processes where any sufficiently senior passer-by can overwrite last month&#039;s conclusion.</p>
<h2>RAPID, in plain words</h2>
<p>The letters that saved us from workshop number four come from <a href="https://www.bain.com/insights/rapid-tool-to-clarify-decision-accountability/">Bain &amp; Company</a>: <strong>RAPID</strong> (their trademark, their consultants will be pleased I said so). Strip away the consulting varnish and it&#039;s simply an <strong>API contract for decision-making</strong>:</p>
<ul>
<li>R: Recommend. The person who does the analysis and brings a concrete proposal. Not a vibe, a proposal.</li>
<li>A: Agree. The few whose formal sign-off is required (security, compliance). A veto with named grounds, not a mood.</li>
<li>P: Perform. Whoever will do the work. They get a voice <em>before</em> the decision, not a surprise after it.</li>
<li>I: Input. People consulted because they know things. Consulted, not obeyed.</li>
<li>D: Decide. One name. Not a committee, not a forum, not &quot;the leadership&quot;. One name.</li>
</ul>
<p>Five letters, one rule: every letter is a person, and D appears exactly once.</p>
<p>Writing the letters down is what changed the behavior; the framework did nothing on its own. Our ownership dispute that survived three workshops was resolved in days once the letters existed. The R stopped selling to the whole room and wrote one decent document. The I&#039;s gave input without needing to &quot;win&quot;. And the D, suddenly aware the decision was <em>theirs</em> with their name on it, asked sharper questions in one meeting than the previous three had produced in total.</p>
<p>Nothing about the problem changed. Only the architecture of the decision did.</p>
<h2>The anti-patterns</h2>
<p>Once you have the vocabulary, you start seeing the broken decision architectures everywhere.</p>
<p>Everyone has a veto. The A role inflates until half the org can block and nobody can approve, which is a system where every component can halt the pipeline and no component can ship it. The result is decision starvation with extra meetings.</p>
<p>Consensus theater is the harder one to name, because from the inside it looks like respect. The meeting is formally seeking a decision while what it wants is unanimous comfort, so it waits not for the best argument but for the last objection to get tired. I&#039;ve written about the staged version of data-driven meetings in <a href="/blog/data-driven-theater/">Data-Driven Theater</a>. Consensus theater is its twin: same stage, different prop.</p>
<p>Then the alignment ritual. &quot;Alignment meeting&quot; is frequently the phrase an organization uses when nobody is willing to say <em>&quot;I decide.&quot;</em> Alignment is a wonderful outcome and a terrible process goal: you align <em>around</em> a decision, not <em>instead of</em> one.</p>
<p>None of these people are acting in bad faith. That&#039;s the uncomfortable part. Each anti-pattern is locally rational: vetoes feel like diligence, consensus feels like respect, alignment feels like collaboration. The system produced by these good intentions cannot decide anything.</p>
<blockquote><p>&quot;No matter how it looks at first, it&#039;s always a people problem.&quot; ― Gerald Weinberg, The Secrets of Consulting</p></blockquote>
<h2>ADR records what, RAPID records who</h2>
<p>Architects already have a beloved decision artifact: the <a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions">ADR</a>, the Architecture Decision Record. I write them, I love them, I make agents read them. But an ADR documents <em>what</em> was decided and <em>why</em>. It says nothing about <em>who</em> had the right to decide it, and whether they actually did.</p>
<p>The two are complements, not competitors. RAPID (or any explicit decision contract; the brand doesn&#039;t matter) defines who; the ADR preserves what. Pair them and decisions become durable: made once, by a named person, with named input, written down.</p>
<p>Skip the first half and you get my favorite one-liner from this whole experience: <strong>an ADR without a decider is a wish with a timestamp.</strong></p>
<h2>One question instead of one more meeting</h2>
<p>So here&#039;s my practical suggestion. Next time an architecture decision stalls, bouncing between forums and gathering workshops like a hobby, don&#039;t schedule another meeting. Ask one question, in writing, where everyone can see it:</p>
<p><strong>&quot;Who is the D on this?&quot;</strong></p>
<p>If the answer comes quickly, you&#039;ll have a decision within days. If the answer is silence… congratulations, you&#039;ve found the actual blocker, and it was never the technology.</p>
<p>Start with the oldest one. Every organization is carrying a question that has been circling for a year, gathering forums. It is circling because no one has ever been named to end it. That also makes it the cheapest thing on your list to unstick.</p>
]]></content:encoded>
      <category>Decision Making</category>
      <category>Software Architecture</category>
      <category>Governance</category>
      <category>Corporate Culture</category>
      <category>Engineering Leadership</category>
    </item>
    <item>
      <title>Data-Driven Theater</title>
      <link>https://bfrackowiak.pl/blog/data-driven-theater/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/data-driven-theater/</guid>
      <pubDate>Mon, 01 Dec 2025 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Everyone wants decisions backed by data. Almost nobody asks what the data went through to get there.</description>
      <content:encoded><![CDATA[<p>Before we start, a fair warning. What follows is a light reflection: a private opinion built entirely on private observations. There is zero scientific literature behind it. No studies, no citations, no methodology section. Just years of sitting in meeting rooms and paying attention. If that doesn&#039;t sound rigorous enough for you, good catch. That&#039;s exactly the kind of honesty about data sources I wish I saw more often.</p>
<p>I have lost count of how many meetings I&#039;ve attended that were officially <em>data-driven</em>. There was a deck. There were charts. At some point somebody said &quot;the data clearly shows.&quot; And yet, walking out, I couldn&#039;t shake the feeling that I had just watched a performance, not a decision.</p>
<blockquote><p>&quot;There are three kinds of lies: lies, damned lies, and statistics.&quot;</p></blockquote>
<p>Mark Twain popularized that line and attributed it to Benjamin Disraeli. Historians still can&#039;t confirm Disraeli ever said it. I love that detail: the most famous quote about misleading statistics ships with a broken attribution. Even the quote about data lying is a matter of interpretation.</p>
<h2>Same words, different worlds</h2>
<p>Here is my core observation: <strong>making decisions based on data makes sense only when the organization speaks the same language, built on the same concepts.</strong></p>
<p>Take something as innocent as &quot;active user.&quot; For the product team it&#039;s anyone who logged in this month. For sales it&#039;s a paying account. For support it&#039;s someone who opened a ticket and is still breathing. Now put all three in one room, each armed with a chart about &quot;our active users,&quot; and enjoy the show. Everybody is right. Everybody is talking about a different universe.</p>
<p>As a solution architect, I spend a large part of my life on contracts between systems. We version APIs, we validate schemas, we break builds when a field changes its meaning. And then we walk into a meeting where two departments compare numbers built on definitions that were never aligned. These are <strong>semantic contracts between people that nobody ever wrote down</strong>. Being data-driven is empty if those contracts were never seriously considered. The dashboard is just the visible tip of an agreement that doesn&#039;t exist.</p>
<h2>The preprocessing trap</h2>
<blockquote><p>&quot;If you torture the data long enough, it will confess to anything.&quot;</p></blockquote>
<p>That one is usually pinned on economist Ronald Coase, also in a paraphrased form, because apparently we can&#039;t even quote people about data accurately.</p>
<p>Here&#039;s the uncomfortable part: data is surprisingly easy to manipulate, and you rarely need to lie in the data itself. The comfortable place to cheat is earlier, in the preprocessing phase. Which rows were dropped as &quot;invalid&quot;? Which period was chosen as &quot;representative&quot;? Which outliers were &quot;obviously&quot; errors? Every one of those choices is invisible in the final chart. Nobody signs them.</p>
<p>That&#039;s why I believe <strong>explaining how a dataset was cleaned and prepared is often exactly as important as the conclusions drawn from it</strong>. The conclusions are the last five percent of the journey. Everything that shaped the answer happened before.</p>
<p>And there are two symmetric ways to fail here:</p>
<ul>
<li><strong>Conclusions drawn from raw data</strong> give you a picture distorted by outliers. One huge client, one migration script, one QA account with ten thousand test orders, and your &quot;average&quot; is science fiction.</li>
<li><strong>Conclusions drawn from over-cleaned data</strong> give you optimization toward a thesis. Scrub away every inconvenient observation as an &quot;anomaly,&quot; and the data will happily confirm whatever you believed before you opened the file.</li>
</ul>
<p>Between those two extremes lives an honest analysis. You&#039;ll recognize it by one feature: it comes with a changelog.</p>
<h2>It&#039;s rarely the conclusions</h2>
<p>In organizations (I&#039;m deliberately not saying <em>corporations</em>, because this has nothing to do with company size) the problem I keep seeing is almost never the inference step. People are genuinely good at drawing conclusions. Give a smart group a dataset and they will reason about it just fine.</p>
<p>The problem sits earlier and deeper. <strong>It is how the data was collected, and the missing shared language to describe it.</strong> We argue passionately about what the numbers mean. All the while we disagree, without knowing it, about what the numbers <em>are</em>. Calling that process data-driven doesn&#039;t fix it. It just gives the disagreement better slides.</p>
<h2>Unless…</h2>
<p>Of course, there is one scenario where none of this matters.</p>
<p>Maybe your goal is not a decision at all. Maybe it is walking into the meeting as <em>the prepared one</em>, radiating competence from behind a wall of charts. Then yes, data helps enormously. A confident slide with big numbers is a wonderful prop.</p>
<p>But it works under exactly one condition: your audience must not be competent enough to ask about definitions and preprocessing. And trust me, that happens more often than we would like to admit ;) If you are wondering how those audiences end up in those rooms, <a href="https://en.wikipedia.org/wiki/Peter_principle">The Peter Principle</a> has been explaining it since 1969.</p>
<h2>A small experiment</h2>
<p>So here&#039;s my suggestion for your next data-driven meeting. Don&#039;t challenge the conclusions. That&#039;s playing the game on the presenter&#039;s terms. Ask two questions instead:</p>
<ol>
<li><em>&quot;How exactly do we define this metric, and does everyone here use the same definition?&quot;</em></li>
<li><em>&quot;What was removed or transformed during cleaning, and why?&quot;</em></li>
</ol>
<p>Then watch the room.</p>
<p>If the answers come easily, congratulations: you&#039;re in one of those rare places where data-driven means something. If what you get is a pause, a nervous smile, and &quot;let&#039;s take it offline&quot;, you have learned more in ten seconds than the whole deck was going to teach you, and you learned it in front of the same people who were a minute away from nodding along. You now know exactly which layer is broken. It happens to be the cheap one to fix.</p>
<p>Definitions are cheaper than dashboards. Write down what &quot;active user&quot; means here, get two departments to put their names on it, and half the charts in the building start meaning the same thing without anyone rebuilding a single query. That&#039;s an afternoon. I have never seen an organization finish it in one sitting, and I have never seen one regret starting.</p>
]]></content:encoded>
      <category>Data-Driven Decision Making</category>
      <category>Data Quality</category>
      <category>Organizational Culture</category>
      <category>Engineering Leadership</category>
      <category>Software Architecture</category>
    </item>
    <item>
      <title>Stop Calling It Legacy Code</title>
      <link>https://bfrackowiak.pl/blog/stop-calling-it-legacy-code/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/stop-calling-it-legacy-code/</guid>
      <pubDate>Mon, 19 Feb 2024 09:00:00 +0100</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>With AI in the picture, it&#039;s not legacy anymore. It&#039;s a Knowledge Container.</description>
      <content:encoded><![CDATA[<p>We&#039;ve all been there. You join a new team, clone the main repository, open your IDE, and immediately let out a heavy sigh. It&#039;s the dreaded <em>legacy codebase</em>. For years, &quot;legacy&quot; has been the ultimate dirty word in software engineering. It was the monster under the bed, the excuse for missed deadlines, and the justification for multi-million-dollar rewrite initiatives.</p>
<p>But I have a thesis that might make a few software architects uncomfortable: <strong>in the era of AI, legacy code no longer exists.</strong> The entire concept of &quot;legacy&quot; is expiring. What we are left with is something far more valuable, and it&#039;s time we change how we look at it.</p>
<h2>Why Did We Call It &quot;Legacy&quot; Anyway?</h2>
<p>Let&#039;s be honest with ourselves for a second. What actually makes code <strong>legacy</strong>?</p>
<p>Historically, code became legacy the exact moment developers simply didn&#039;t want to maintain it anymore. It was an incentive problem. The promise of working with brand-new, shiny technology means being more efficient, learning new skills, and therefore remaining highly competitive on the job market. Not everyone wants to put &quot;maintained a 10-year-old monolith&quot; at the top of their CV.</p>
<p>To justify moving away from it, we tended to argue that legacy code is inherently more costly to evolve and that adding new features is too slow.</p>
<p>But here is the reality check: <strong>code is incredibly cheap now.</strong> With AI, generating code is no longer the bottleneck. Legacy code isn&#039;t the primary factor slowing down system development; unnecessary <em>complexity</em> is what actually kills productivity.</p>
<p>Oh, and let&#039;s not forget the most important part: many times, that ugly, outdated, legacy code is exactly what is paying our monthly salaries.</p>
<h2>The Tech Stack Doesn&#039;t Matter Anymore</h2>
<p>The fact that the technology used in an older codebase isn&#039;t state of the art means absolutely nothing to a Large Language Model.</p>
<p>As long as the code passes your functional and non-functional acceptance criteria (like performance and scalability), the age of the syntax is irrelevant. What matters right now isn&#039;t the framework. It&#039;s the data stored <em>inside</em> the code.</p>
<h2>From Codebase to &quot;Knowledge Container&quot;</h2>
<p>This brings us to the core of the shift. If it&#039;s not legacy code anymore, what is it? It is a <strong>Knowledge Container</strong>.</p>
<p>Think about it. That repository is the single most accurate, battle-tested database your company owns regarding:</p>
<ul>
<li>Deeply embedded business processes.</li>
<li>Highly specific edge-case acceptance criteria.</li>
<li>Real-world workflows that no one currently working in the office actually remembers.</li>
</ul>
<p>In the past, code went out of date the moment a developer hit <em>commit</em>. Today, that commit just becomes highly structured training data, a rich knowledge base for AI agents to read, understand, and act upon.</p>
<h2>The Rise of the &quot;Dev Operators&quot;</h2>
<p>Because of this, we don&#039;t need massive, groundbreaking &quot;greenfield&quot; initiatives just to keep systems in sync with modern standards.</p>
<p>If the tech stack no longer matters, the role of the human changes. We are moving away from being traditional engineers who manually type out logic, to becoming <strong>&quot;Operators.&quot;</strong> An Operator&#039;s job isn&#039;t to write the code; their job is to leverage the Knowledge Containers, express the business intent to an AI agent, and accurately judge the final result.</p>
<p>The concept of legacy code simply won&#039;t work anymore in the long term. It&#039;s a relic of an era where humans had to read every line.</p>
<p>Now, I just wonder: if we stop tracking Technical Debt, how long will it take for us to invent a metric to measure a system&#039;s <strong>&quot;Knowledge Density&quot;</strong>?</p>
]]></content:encoded>
      <category>Legacy Systems</category>
      <category>Artificial Intelligence</category>
      <category>Software Architecture</category>
      <category>Knowledge Management</category>
      <category>Software Development</category>
    </item>
    <item>
      <title>The Automerger</title>
      <link>https://bfrackowiak.pl/blog/the-automerger/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/the-automerger/</guid>
      <pubDate>Thu, 09 Jul 2020 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>How we taught our repositories to merge themselves, and what two years of running it taught us.</description>
      <content:encoded><![CDATA[<p>If your team uses a branching model with more than two long-living branches, you know the ritual. A bug gets fixed on the release branch. Somebody has to remember to bring that fix back to develop, or the bug will happily reappear in the next release. Multiply that by several release branches, several teams, and several fixes per week, and you have a full-time job nobody applied for.</p>
<p>At the company I worked for, we lived exactly that. GitFlow-style branching, multiple active branches at once (master, develop, releases in flight), and changes that had to propagate across all of them. Manual coordination was error-prone and slow, and the errors were the expensive kind: a regression returning in production because one merge was forgotten.</p>
<p>So we automated it. We call it the <strong>Automerger</strong>.</p>
<h2>What It Does</h2>
<p>The Automerger is a script built with Cake, the C# build automation tool. Its job is deliberately boring:</p>
<ol>
<li>Detect which branches need to be synchronized.</li>
<li>Create a temporary branch and perform the merge there.</li>
<li>Validate the result by building the code and running the tests.</li>
<li>If everything is green, push the merge.</li>
<li>If there is a conflict or a failed build, stop and notify the developers responsible.</li>
</ol>
<p>The key design decision: <strong>the tool only involves humans when a human is actually needed.</strong> A clean merge that builds and passes tests does not deserve anyone&#039;s attention. A conflict does, and then the notification goes to the right team, not to everyone (alert fatigue is real).</p>
<p>Although the tool is written in C#, there is nothing .NET-specific about the workflow. It merges JavaScript and Java repositories just as happily; the validation step simply runs whatever build and test commands the repository defines. It plugs into our CI/CD platform (Azure DevOps in our case) and notifications go wherever the team lives, Slack included.</p>
<h2>Convention over Configuration</h2>
<p>The part I like most: the Automerger needs almost no configuration. It discovers branches and orders them by naming convention. If your branches follow a sensible pattern (and with GitFlow they already do), the tool can infer the merge direction on its own: fixes flow from the oldest release branch, through newer ones, into develop.</p>
<p>One honest requirement comes with that: the workflow relies on <em>real merges</em>, not cherry-picks. Cherry-picked commits look different to git, and the tool cannot reason about them. Convention over configuration works only if you keep the convention.</p>
<h2>Two Years Later</h2>
<p>After two years of running the Automerger every 3–4 hours during working days, the numbers settled around <strong>90% of merges completing fully autonomously</strong>: no conflicts, builds green, nobody interrupted. Nine out of ten of those &quot;remember to merge back&quot; tasks simply stopped existing.</p>
<p>The remaining 10% is exactly where humans belong: genuine conflicts, where two branches disagree about the same lines and somebody has to decide.</p>
<p>The lesson generalizes beyond git. Find the task that is repetitive, mechanical, and only occasionally requires judgment. Automate the mechanical majority, and route the judgment cases to people with full context attached. Developer time is too expensive to spend on merges that a script can prove safe.</p>
<p>What is the &quot;merge back to develop&quot; of your team, the recurring chore everyone does by hand because nobody sat down for a week to kill it?</p>
]]></content:encoded>
      <category>Developer Tools</category>
      <category>Automation</category>
      <category>Engineering Practices</category>
      <category>DevOps</category>
      <category>Software Engineering</category>
    </item>
    <item>
      <title>Technical Leader: Identity Disorder</title>
      <link>https://bfrackowiak.pl/blog/technical-leader-identity-disorder/</link>
      <guid isPermaLink="true">https://bfrackowiak.pl/blog/technical-leader-identity-disorder/</guid>
      <pubDate>Tue, 26 May 2020 09:00:00 +0200</pubDate>
      <dc:creator>Bartosz Frąckowiak</dc:creator>
      <description>Splitting one corporate title into its two words, and what each of them demands.</description>
      <content:encoded><![CDATA[<p>Hi, my name is Bartosz Frąckowiak, for over three years now I&#039;m the so-called Technical Leader and I would like to tell you my story :)</p>
<p>A technical leader can be just a simple position in the corporate hierarchy. It can be a safe position reserved for a person technically based with some leadership skills. Opposite to team leaders who generally should have strong leadership skills and a technical base, not necessarily be a guru in the programming stack. The question you should ask yourself is: what do you expect from your career?</p>
<p>One of the ingredients is your attitude. I have never wanted to make this title just a corporate position to me. The footer in corporate mail means nothing to me. After three years of being a technical leader, I&#039;ve learnt a lot about what it&#039;s like to be one of them (in this particular environment). I have ups and downs. One day I would never change this position to a different one, the next day I was almost ready to quit and move on. I would like to break down this position into smaller pieces for you. Let&#039;s start with splitting just the name &quot;technical leader&quot;. It has two words in it. The first part is strictly &quot;technical&quot;, the second is &quot;leader&quot;. I will try to address these two parts inside this article and explain what it means to me. What I do to be the best possible technical leader.</p>
<h2>&quot;Technical&quot;</h2>
<p>The technical part is all about widely known engineering craftsmanship. The whole category is extremely wide, so let&#039;s split it even more.</p>
<p><strong>Self-development.</strong> The first one is self-development in the context of engineering knowledge. So simple and yet so complex at the same moment. The technical leader needs to join the technical mastery journey no matter what he or she knows already. A technical leader is responsible for analyzing new approaches, new shiny bits and bobs that come to our engineering market. Follow trends, join conferences, and so on. As the market is going fast in various directions, it is a tough nut to crack.</p>
<p>Usually, inexperienced developers are highly motivated. Discovering the engineering world just makes them even happier to go further and further. Somewhere on the road, the developer finds out that it is almost impossible to be a specialist in every branch of engineering.</p>
<p>After some time you find it spiritually releasing to decide not to try to be a specialist in everything, because it is almost impossible. Just find your thing or two that drives you, makes you sleepless through the night, makes you search for the next piece of information about a particular topic. On other topics be up-to-date, more or less. Do you like data science? It&#039;s fine not to know the specification differences between OpenAPI 3.0 and 2.0. Just simply know it&#039;s there; you will deep dive into details when needed.</p>
<blockquote><p>&quot;Let him who would move the world first move himself.&quot; ― Socrates</p></blockquote>
<p><strong>Using knowledge.</strong> The second chapter is using engineering knowledge in the day-to-day workspace. Making the right decisions and leading the team to the chosen common goal, which should describe where the IT department wants to be in the long run. It&#039;s the technical leader&#039;s responsibility to make sure the goal itself correlates to the high-level shape of the software architecture. Architecture as simple as possible while meeting all the needs of the business. Easy to develop and easy to maintain in the long run.</p>
<p>Finding this architecture is a never-ending story. The company evolves and software should follow the development as well.</p>
<blockquote><p>&quot;Clean code is not written by following a set of rules. You don&#039;t become a software craftsman by learning a list of heuristics. Professionalism and craftsmanship come from values that drive disciplines.&quot; ― Robert C. Martin</p></blockquote>
<p><strong>Being a medium.</strong> The third category is being the technical medium and connector between the architect, other teams, and the team itself. At the moment we have eight teams with seven developers each. This is not a space where lack of proper communication can be accepted. Discussing with other technical leaders and the architect takes place twice per week.</p>
<p>Being a connector between the technical leaders&#039; community and the team is an important task which needs to be done every day. Making sure the team knows where we are heading. It&#039;s crucial to the whole IT environment to know what decision has been made and why the technical leaders&#039; community took this approach. In the long run, communication lets the team understand the goal where we are heading and, therefore, be more independent in making decisions and in the end more effective.</p>
<blockquote><p>&quot;The single biggest problem in communication is the illusion that it has taken place.&quot; ― George Bernard Shaw</p></blockquote>
<h2>&quot;Leader&quot;</h2>
<p>Each technical leader has a &quot;leader&quot; inside the position title. Leader means leadership for the team. How to be the best one for the team? Here I will present tangible ideas and practices that are introduced inside my team. I am sure that thanks to those, my team has improved a lot during these three years. Are those rules a golden bullet suited for every team in every corporation? For sure not… Simply looking from a statistical point of view, they will not fit every environment. I hope I can inspire you a bit and encourage discussion.</p>
<p><strong>Dev knowledge sharing.</strong> The first idea is about sharing knowledge in a safe space. I have booked one hour per week, usually at the end of the week when everyone is thinking about the weekend already. The idea is to create a safe space to share any topic you want.</p>
<p>Only one rule applies: you have to talk about engineering. There is no constraint that the topic has to be work-related or has to suit the project the team is working on. Simply, you have something to share? Come and talk about it. What&#039;s more, there are no rules about the presentation itself. If a developer wants to have a high-end presentation, it is appreciated; if a developer presents an idea from a scratch of paper, that&#039;s fine as well.</p>
<p>The idea found interest quickly and became a permanent part of the team&#039;s life. We had meetings where all of us were reviewing pull requests with some new approach, as well as meetings about machine learning algorithms. From TDD to ideas on how to optimize our team efficiency. From coding conventions to project managing strategies. Anything developers are interested in is suited for the local dev knowledge sharing.</p>
<p>After three years of hosting these meetings, I found out that the secret here is feeling safe inside the team. Each presenter received feedback during the meeting knowing that feedback is only for his good and self-improvement. There are no mean jokes, nobody criticizes that the topic isn&#039;t important in any context. It is simply time for a developer to shine and be proud of his knowledge.</p>
<p>These meetings evolved from local dev knowledge sharing to company-wide meetings. We keep both. Local team meetings stay the same, the safe place they were designed to be. There is also a place to share knowledge with the bigger crowd: the whole IT department. Sure, it requires a little more preparation than just a piece of paper and an idea. A nice presentation is one of the unwritten requirements. The audience is multinational, so it also requires globalization of your presentation. Globally hosted knowledge sharing meetings are a much bigger deal than the local ones. Both meetings are held hand in hand. In the end, the outcome is improved team knowledge with no additional cost.</p>
<blockquote><p>&quot;If you have knowledge, let others light their candles in it.&quot; ― Margaret Fuller</p></blockquote>
<p><strong>Meetups.</strong> Starting from the local dev knowledge sharing idea, we moved one step further. We hosted a meetup. The first one was about the approach to one of the projects we were involved in. We had space and time to learn (learn the hard way) TDD inside DDD.</p>
<p>We decided to share our journey with the local community. The event took place at our office, in a workshop formula, and it was specially designed to guide participants into the same mistakes we made. We showed how we handle challenges and what we had learned.</p>
<p><strong>Technical excellence meetings.</strong> On the other side, inside the team, we have team members, NOT resources. Each team member is a human being with a different background, different needs and different goals.</p>
<p>As a technical leader, there is enough space to show myself useful. The idea is to independently develop each team member&#039;s skills based on his or her needs. The concept name is &quot;technical excellence&quot;. There are two kinds of meetings during the month. The first meeting is quick, up to half an hour: a planning and negotiation session whose goal is to find the area that will be developed later. Ideas usually come from the developer; he or she knows best what they&#039;re interested in (well, sometimes it is not the case, as some people need a small hint). My role is to ensure the idea is valid at the basic level and to find common ground between developer needs and company goals.</p>
<p>With a plan in place, there is a proper longer meeting, usually around two hours once or twice per month. This relatively small time belongs completely to the developer. We can do whatever he or she wants as long as it brings educational value. During these three years we had meetings about blockchain, testing, many topics related to DDD, or specific Azure features. During the meeting we can research the topic itself, work on problems related to it, or even prepare a presentation that will be shown later at the IT department knowledge sharing meeting.</p>
<p>The ones that touched me the most are about self-improvement. The majority of my team members found themselves in a place where they want to take a &quot;step forward&quot;, the step that will distinguish them from every other backend developer. Those meetings are the most heart-touching, the hardest ones, but also the most inspiring and rewarding ones. I would recommend it to each technical leader: try to talk not only about facts and technology. Find a way to inspire team members, even a little bit.</p>
<p>The success of those meetings is based on properly planned time for the developer, way ahead. Each of us has some topic that he or she wants to handle when time allows. The truth is that there will be no free time. What&#039;s more, you will have less and less &quot;free time&quot; as your career evolves. Those meetings address the problem: the time frame cannot be moved, every participant needs to be prepared. The meeting must take place.</p>
<p>Every year there is a time of appraisals, the time when your performance gets reviewed. What is important to me, there are feedback forms that let others put opinions about me as a technical leader. In the past, I found a lot of mentions of technical excellence in the feedback forms, and most of them were very positive. For me, it is simply a win and the way to go.</p>
<p>Along with those meetings, I found two things that make me wonder a bit. Since each meeting was between me and another team member, I had to be prepared for every topic, which put pressure on my calendar. The idea I want to test is to follow the same meeting pattern, but with team members running the meetings for each other. I would just watch at the very beginning and help until the point where I&#039;m no longer needed. (At this moment I am part of a team of very experienced developers; the idea might not work with a less experienced team.)</p>
<p>The second matter that bothers me a bit is purely egoistic. Each team member gets a few hours per month to develop himself, but what about me? :) The answer is simple: I do technical excellence with myself (sounds like a split personality). I simply book some time during the month and learn what I&#039;m interested in the most. At the moment the hype is on deep learning.</p>
<blockquote><p>&quot;Be a yardstick of quality. Some people aren&#039;t used to an environment where excellence is expected.&quot; ― Steve Jobs</p></blockquote>
<p><strong>Pair programming.</strong> Again, simple as that and powerful as that. Pair programming in any form that suits the developer and the situation. Starting from just sitting together and being a rubber duck for the partner, to hardcore pure pair programming with the whole team inside a dedicated room. Having not just one additional pair of eyes but three members watching. A live coding session with a large screen presentation and discussion about specific problems.</p>
<p>Which method should we choose? The rule of thumb is to pick &quot;small&quot; pair programming (sitting together, switching keyboards from time to time) for smaller problems like bugs or planned tasks, and the &quot;whole team is watching&quot; meeting for solutions that will affect everyone&#039;s future work. Usually meetings about core decisions made at the beginning of a project, or changes in the direction the team was heading.</p>
<p>A lesson learned here for technical leaders: be prepared to monitor the involvement of others during &quot;everyone is watching&quot; meetings. The tool is so simple and so powerful; in terms of team building and knowledge sharing it is one of the best in my opinion. Be careful not to fall into the trap of being &quot;The Devil&#039;s Advocate&quot;, which causes frustration. Usually stepping back and taking a huge breath is the cure. After all, being pragmatic and empathic is crucial here.</p>
<p><strong>Open source initiative.</strong> Teams at our company are backend based. Usually we have a 5:2 ratio of backend to frontend developers inside a team. Team members produce source code daily. Most of the code is usually not visible to the end user and, therefore, it is harder to appreciate the craftsmanship of individuals.</p>
<p>Here, the idea was to share our concepts with the external world. Share it with the IT community in the form of YouTube videos, source code on GitHub, and articles on medium.com, exactly like this one. Each IT member can express himself or herself to the community about the topic he or she has mastered during nine-to-five work. Every piece of content is signed with the developer&#039;s name, no anonymous content. The sharing idea brings clear benefits to both sides. Developers build a brand for their names, and they build the company&#039;s brand as one with a strong engineering culture.</p>
<blockquote><p>&quot;Open source is a development methodology; free software is a social movement.&quot; ― Richard Stallman</p></blockquote>
<h2>Power of Small Changes</h2>
<p>All the tools and methods described here are working for my team. What I&#039;ve found during a few years of being a technical leader is that team evolution is faster than you think. Each member works hard to become a better version of himself or herself, not only during work hours but also after the nine-to-five period. To keep everyone interested there is a constant need for small improvements, small changes that keep the technical leader&#039;s toolset fresh and joyful for everyone.</p>
<p>If you find yourself in the same corporate position as mine, please share your experiences with the set of tools that you have developed over time.</p>
<blockquote><p>&quot;Great things are not done by impulse, but by a series of small things brought together.&quot; ― George Eliot</p></blockquote>
]]></content:encoded>
      <category>Technical Leadership</category>
      <category>Engineering Leadership</category>
      <category>Team Building</category>
      <category>Knowledge Sharing</category>
      <category>Corporate Culture</category>
    </item>
  </channel>
</rss>