<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tri2b — Field Notes</title>
    <link>https://tri2b.cloud/blog/</link>
    <atom:link href="https://tri2b.cloud/rss.xml" rel="self" type="application/rss+xml" />
    <description>Essays on software craft, AI-assisted development, and running a self-managed Kubernetes cluster — field notes from Jacques Barnard's solo studio, Tri2b.</description>
    <language>en</language>
    <lastBuildDate>Tue, 08 Sep 2026 11:46:24 GMT</lastBuildDate>
    <item>
      <title>The agent that keeps the agents on task</title>
      <link>https://tri2b.cloud/blog/the-agent-that-keeps-agents-on-task/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/the-agent-that-keeps-agents-on-task/</guid>
      <pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate>
      <description>When one session both does the work and keeps the task record honest, the record falls behind. I have been trying a split: a manager agent owns task hygiene, worker agents own execution, and a durable queue sits between them. This is a field note on that experiment, including where it strains.</description>
      <category>AI</category>
      <category>Practice</category>
      <content:encoded><![CDATA[<p>The failure is quiet. A worker agent is halfway through a change and the task record still says the work is queued. A second agent, or a person, reads the record, sees an open item, and starts the same thing. A third item was finished on Tuesday, but its status was never updated: the session that did the work ran out of context before it got to the bookkeeping. Nobody lied. The record simply fell behind the work, and once it is behind it stops being something anyone trusts.</p>
<p>This note is about one response to that drift: splitting the roles apart. A manager agent owns task hygiene, meaning the job of keeping the shared record of the work true, and never writes the code. Worker agents own execution and never own the plan. A durable queue sits between them, and it is the only thing they share. What follows is how I run that, what it changed, and where it still strains. It is an observed practice from my own projects, not a method I am prescribing.</p>
<p>I stopped treating the drift as carelessness some time ago. It is structural. When one session plans a change, executes it, reports progress, and keeps the record accurate, the record is the part that loses. Execution has a compiler and a test suite pulling on it. The record has nothing pulling on it except discipline, and discipline is the first thing a busy context window drops.</p>
<h2>Why the record falls behind.</h2>
<p>A session doing real work accumulates context fast: files read, test output, half-formed plans, the reason an approach was abandoned. The task record wants a different kind of attention, in short structured updates at the right moments. This item is claimed. This one is blocked on a credential. This one is done, and here is the evidence.</p>
<p>Each of those is an interruption, and each costs a little of the attention the work needs. So updates cluster at the beginning and the end, if they arrive at all.</p>
<p>The middle goes dark, and the middle is exactly where a second agent needs to know that something is already in flight. When a session ends, what it knew about the state of the work ends with it, unless someone wrote it down somewhere durable. A transcript is not durable. It scrolls away, it belongs to one tool, and nobody reads it later.</p>
<p>There is a subtler cost. When one session decides both what done means and whether it is done, the definition bends toward whatever was achieved. A definition of done written before the work starts, and checked by someone other than the writer, is a small guard against that.</p>
<h2>The shared record.</h2>
<p>The source of truth is <a href="https://projects.tri2b.cloud">Projects</a>, which I wrote about in <a href="https://tri2b.cloud/blog/any-agent-one-record/">an earlier note</a>. A Project holds Tasks, Tasks hold SubTasks, and a SubTask is the unit of work an agent can claim. Each SubTask carries a definition of done, an ordered list of steps, dependencies on other SubTasks, a status, and a running log of notes.</p>
<p>I reach that record through pctl, a command-line client. Agents reach the same record over MCP, the Model Context Protocol, so a coding agent calls structured tools rather than shelling out and parsing text. The dashboard is a rendered view of the record, not a second copy of it, which is why there is nothing to keep in sync between them.</p>
<h2>The manager agent.</h2>
<p>A dedicated session whose only job is task hygiene, and which can run on a different machine from every worker. It takes intent from me in prose and turns it into SubTasks, each with a definition of done. It sets dependencies, so an item that cannot start yet sits as pending rather than tempting a worker into it.</p>
<p>Then it watches. For progress that has stopped arriving. For items that say queued but have a branch full of commits behind them. For completions with no evidence attached. When a worker reports a blocker, the manager records it where a person will see it. When work finishes, it reconciles the result back into the plan so the next item can be shaped.</p>
<h2>The worker agents.</h2>
<p>A worker session asks the queue for work, and the server offers it the next SubTask meeting three conditions: every dependency is done, the worker's role is authorised to see it, and no other worker currently holds it.</p>
<p>The worker then executes, usually with a small worker team underneath it: one sub-agent traces the affected surfaces, one makes the change, one writes the check that proves it. If the definition of done is met, the worker reports completion with the evidence. If it is not, the worker reports what it found and why it stopped.</p>
<h2>Two loops, one queue.</h2>
<p>The manager loop is: read intent, decompose, define done, queue, watch, reconcile. The worker loop is: request, claim, execute, verify, report. The two meet only at the queue.</p>
<p>Neither loop needs the other's context. The manager agent never needs to know how a change was made. The worker agent never needs to know why this item is ahead of that one. The queue is the interface, and the SubTask record is the message that crosses it.</p>
<p>Running the manager as a separate session is not architectural purity, it is about pressure. A session doing the work is under constant pressure to spend its context on the work. A session that only manages the record is under no such pressure, so the record gets the attention it needs. Running it on a separate machine takes the same argument one step further, and that is the part I have tested least: the hope is that a worker machine which runs out of disk, or gets reclaimed, cannot take the manager down with it.</p>
<h2>What the lease does, and what it does not.</h2>
<p>When the server offers a SubTask, the worker takes an exclusive lease on it, and while that lease is held the server will not offer the same SubTask to anyone else. The lease is short, about a minute, and every progress report renews it.</p>
<p>If the reports stop, the lease expires and the SubTask returns to the queue. This is the part worth being precise about. Expiry changes what the record says and makes the item claimable again. It does not reach into the first worker and stop it. A process that is still running is still running.</p>
<p>What the server does instead is reject the stale report: the next time the lapsed worker checks in, it is told its lease is gone, which is the signal to stop rather than to push on. That limits the damage without undoing it, and a completion that arrives after a lapse still lands, recorded as having arrived late.</p>
<p>So the lease is coordination metadata, not an execution guarantee. Where duplicate effects would be harmful, the work itself has to be idempotent, or fenced by something outside the lease. Two agents rarely run the same SubTask. Rarely is not never, and the design should not be read as promising otherwise.</p>
<h2>A concrete run.</h2>
<p>This site is itself a Project in the system. One item started as a sentence from me: an agent asking for a business contact should get exactly one unambiguous address.</p>
<p>Why that is not trivial takes one paragraph of background. Every page here is published twice: once as HTML for people, and once as a markdown twin at the same URL for machines that ask for it. A machine reading this site does not read the page you are reading. It reads the twin.</p>
<p>The manager turned my sentence into a SubTask with four steps: decide whether one or two mailboxes stay public, update every source that renders an address, label each mailbox's purpose if both remain, and add a readiness check that catches drift. Its definition of done named the surfaces that had to agree: the visible HTML, the markdown twins, the structured data, the in-page agent tools, and the Agent Skill files.</p>
<p>A worker claimed it, and its team found the defect quickly. Every public-facing surface named the business address. The CV's markdown twin named the recruiting address. So a person browsing the site got the right mailbox, and a machine reading the CV got the wrong one, and would have handed a business enquiry to recruiting. The fix was a single contact module with an explicit purpose per address, and every surface reading from it.</p>
<p>Then the part I actually care about. The worker's completion note listed what it had verified in the build output and what it had not: the live check could only pass once a release deployed. The manager did not mark the item done. It recorded the note, kept the item open with the remaining condition stated, and shaped a dependent item for the capability document that needed the same addresses.</p>
<p>Ten days later a different worker session, on a different machine, read the record cold and knew within a minute that three items were code-complete and waiting on a deploy, and that the useful work was elsewhere. Nobody had to find a transcript.</p>
<h2>What improved, and how I know.</h2>
<p>Two things I can point at directly. The record is now accurate often enough that I read it instead of asking, which was not true six months ago. And ownership is unambiguous: a claimed item has exactly one holder, a released item has none, and I no longer find items sitting assigned to a session that ended days ago.</p>
<p>One thing I can describe but have not measured. Two workers now pull from the same queue without colliding, so more runs in parallel than before. I have not instrumented throughput, and I am not going to quote a number I did not collect.</p>
<p>One thing that surprised me. Completion notes became more useful: they say what was verified, how, and what remains, and the note quoted above is typical rather than exceptional. My explanation is that a worker writing for a manager that will check the note against the definition of done writes differently from one writing into a transcript nobody will read. That is a mechanism I find plausible, not a result I measured.</p>
<h2>What still fails.</h2>
<p>Decomposition is a judgement, and the manager gets it wrong. Sometimes it splits an item so fine that workers spend more time reporting than building.</p>
<p>Sometimes it queues an item whose real dependency is a product decision rather than another SubTask, and a worker burns a session discovering that. Three items on this site sat blocked for weeks on exactly that: an API catalogue for an endpoint that had never been deployed. The worker that found it wrote a precise note and stopped, which is the right behaviour. A better manager would have caught it before queueing.</p>
<p>Permission boundaries bite in ways that are easy to forget. The dispatcher only hands work to agent principals, meaning identities registered as agents rather than as people. A session authenticated as me cannot ask for work at all: it has to take the operator path instead, acting on the record directly through pctl. That is deliberate and correct, and a worker that does not know which path it is on still wastes its first few minutes finding out.</p>
<p>And the manager is overhead, a whole session whose output is a tidy record. On a project with one worker and a dozen items it costs more than it saves. The failure that worries me most is quieter than that: a manager agent that becomes very good at the record and forgets the record is not the point.</p>
<h2>Where the human stays.</h2>
<p>None of this removes me. It changes what I do. I set direction, which is the sentence of intent the manager decomposes. I resolve ambiguity when a worker's note ends in a question rather than a result. I accept the completions that matter, particularly anything that ships to a live surface, and I decide when a blocker is worth clearing and when an item should be cancelled instead. The deploy at the end of that contact-address run was mine to trigger, and it should have been.</p>
<p>What I no longer do is reconstruct the state of the work from chat. That was most of my coordination time, and it is the part the machines are plainly better at.</p>
<h2>Trying it, and knowing whether it worked.</h2>
<p>If you want to test this, keep it small. One project. One session whose only job is the record: give it the intent, let it write the SubTasks, and insist on a definition of done for each before anything is queued. One or two worker sessions that claim from the queue and report back with evidence rather than summaries. Run it for a week.</p>
<p>Then judge it on signals you can actually observe. How long does it take a session that was not there to pick up the work cold, minutes or a re-read of the whole history? How many items carry a status the branch behind them contradicts? How often did two agents touch the same work? What share of completions carry evidence you could check yourself? And what did the manager session cost you, in tokens and in your own attention?</p>
<blockquote>The split earns its coordination cost at the point where a session that was not there can pick the work up from the record alone. Below that bar you are paying for bookkeeping and buying a tidier version of the same confusion.</blockquote>
<p>There is one thing a tidy record cannot tell you, and it is the important one: whether the work was worth doing. A beautifully reconciled queue of the wrong items is still the wrong items. That judgement stays mine, and no amount of hygiene moves it.</p>
<p>What I have observed is narrower than a recommendation. When several agents work in parallel, the record of that work needs attention that is not being consumed by the work itself, and that attention can come from a model. So far the trade has been worth it on the projects where drift was costing more than a session. If you are running something similar, or you have found that the split does not hold for your work, I would like to hear how it differs.</p>]]></content:encoded>
    </item>
    <item>
      <title>Any agent, any session, one record everyone works from</title>
      <link>https://tri2b.cloud/blog/any-agent-one-record/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/any-agent-one-record/</guid>
      <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
      <description>Most tools bolt an AI layer onto software built for people. Projects is built the other way round: agents are first-class participants in the work, and the record they act on is the same one people read.</description>
      <category>AI</category>
      <category>Go</category>
      <category>Practice</category>
      <content:encoded><![CDATA[<p>AI agents are individually useful long before they are easy to coordinate. Give several of them a codebase and the failure mode is familiar: two agents start the same change, a dependency is buried in a markdown file, a blocker disappears into a transcript, and the person responsible has to reconstruct the truth from chat. The problem is not that the agents cannot write code. It is that they do not yet share a dependable account of the work.</p>
<p>I built <a href="https://projects.tri2b.cloud">Projects</a> for that gap. It gives people and models the same durable record of what is ready, who owns it, what is blocked, and what done means. A Project contains Tasks; Tasks contain SubTasks; and a SubTask is the small, explicit unit of work an agent can execute. The result is not an AI layer bolted onto a tracker. It is a work system in which the AI is a participant.</p>
<h2>A work loop, not a chat transcript.</h2>
<p>Start with a SubTask: the change to make, the file or branch it concerns, its definition of done, and any work that must finish first. An authorized agent asks the Dispatcher for work. The server checks the dependencies, access, durable assignees, and current leases, then offers the next eligible SubTask. The agent does not choose a convenient item or race another agent for it.</p>
<p>Once it accepts the work, the agent holds an exclusive lease and streams progress back to the same record. If it hits an external problem, it reports a blocker with a reason and releases the lease. The person directing the work sees that state in the terminal or local dashboard, can read the history, resolve the blocker, and intervene safely when necessary. Progress has somewhere better to live than a message that will scroll away.</p>
<p>The record lives on the server rather than inside a session, and that is what makes it durable in the ways that matter. It survives the end of a session, so work I interrupt at the end of one day is still waiting for me the next. It follows me between devices, because nothing important is held in local context on one machine. And it outlives any particular provider: the state does not belong to whichever model or vendor happened to be executing, so changing the agent I use does not mean reconstructing where things stood.</p>
<h2>One contract, many surfaces.</h2>
<p>By contract I mean the shared definition of what the system can do and the rules it enforces. In Projects, that contract is a typed protobuf and gRPC API. It defines operations such as creating a SubTask, requesting work, reporting progress, resolving a blocker, and finding out who the caller is. The server is the authority behind it: it owns the data, decides what is eligible, and enforces access and state transitions once.</p>
<p>That distinction matters when a tool serves a person and a model. It is tempting to make the agent integration its own product, with its own rules and its own view of the world. I have made that mistake. It creates two places for behaviour to drift and two places for a security decision to be wrong. Instead, I keep the core contract stable and build deliberately thin adapters around it.</p>
<h2>One core, three ways in.</h2>
<p>For people, pctl is the command-line interface: an interactive terminal view when I am exploring work, structured JSON when I am scripting it, and a local dashboard for totals, blockers, and live progress. For agents, projects-mcp is the primary interface, and it is deliberately not tied to one vendor or one product: any coding agent that speaks MCP can connect to it. Inside that agent, the model calls structured tools such as list_projects, request_work, and report_progress instead of shelling out and parsing text. For ordinary integrations, projects-rest exposes the same capability over HTTP and JSON.</p>
<p>Those adapters are intentional code, not magic generation. Adding a capability still means designing the operation and giving each surface a useful translation. What does not get copied is the behaviour: the caller identity is forwarded to the core, the core applies the same rules, and no adapter gets to decide on its own what a caller may see or change. Three interfaces can evolve without becoming three competing systems.</p>
<h2>Agents with boundaries.</h2>
<p>An agent is not a privileged service account with a view of everything. Agent Types are principals in the same access model as people, and an Agent Type describes a role in the work rather than a particular tool or model behind it. They have to be added to a Project, their membership can be scoped and revoked, and they can only receive work they are allowed to see. The lease makes execution exclusive; the access model makes it bounded. That is the difference between letting an AI loose in a system and giving it a real, accountable role in one.</p>
<p>The same discipline makes the human side better. Projects keeps durable assignees separate from the transient agent lease, records versions of work items, and lets an operator inspect history, compare changes, or revert an earlier state. The person is not reduced to approving prompts. They retain a clear view of what the system is doing and a deliberate way to change course.</p>
<h2>The stack serves the model.</h2>
<p>The implementation is Go, protobuf and gRPC over PostgreSQL with row-level access controls. Buf keeps the generated contract code consistent, while the production stack uses Auth0 for identity and Fly.io with managed Postgres. I keep a k3s environment for development. These choices are not the product, but they support the same idea: explicit interfaces, portable clients, and one place where the rules live.</p>
<h2>Working with me and the machine.</h2>
<p>This is the kind of AI-enabled software practice I want to build. Use models to move faster, but give their work a shared record, a bounded scope, a visible definition of done, and a way for people to understand and redirect it. The useful future is not humans outside the loop while agents improvise inside it. It is people and agents working from the same facts, through interfaces designed for each of them.</p>
<p><a href="https://projects.tri2b.cloud">Projects</a> is available now. Connect it to whichever coding agent you already use through MCP, use pctl where a terminal is the right surface, and let the work itself stay coherent as the number of agents grows.</p>]]></content:encoded>
    </item>
    <item>
      <title>An ode to interfaces</title>
      <link>https://tri2b.cloud/blog/an-ode-to-interfaces/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/an-ode-to-interfaces/</guid>
      <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
      <description>Interfaces are the bridge that makes machines usable by people, and it is changing under our feet. As more of the traffic runs between machines instead, the bridge is not disappearing so much as shifting: who it is for, what it is made of, and what it is there to do.</description>
      <category>Practice</category>
      <category>AI</category>
      <category>UX</category>
      <content:encoded><![CDATA[<p>Much of the industry I have worked in is centred on graphical interfaces, where enormous effort goes into making machines easier for humans to use. Yet the screen is only one kind of interface. An interface is not defined by its appearance but by its purpose: it is the boundary across which information, intent, and control are exchanged. Some interfaces are visual, others textual, mechanical, electrical, or entirely invisible to human beings.</p>
<p>For much of computing history, the interfaces that received the most attention were those designed for people. Websites, applications, dashboards, and operating systems all focused on making machines understandable and usable. Increasingly, however, interfaces are being designed for machines themselves. APIs, protocols, event streams, and autonomous agents now spend more time communicating with one another than waiting for human input. As that balance shifts, I find myself wondering whether our understanding of interfaces needs to shift with it. What is an interface really? What has it always been for? And what is it becoming?</p>
<h2>Where interfaces came from.</h2>
<p>The interfaces we picture first are the human-facing kind, and they exist because machines were capable long before they were usable. The early ones could compute plenty; the trouble was that reaching that capability meant punch cards and priesthood. So we built friendlier seams to close the gap. Terminals replaced cards. The desktop metaphor replaced the command line for most people. Touch replaced the cursor for a generation that never met one. Each step was the same move repeated: take capability that already existed and put it within reach of more people. The interface was never the point. Access was.</p>
<h2>What the interface was always for.</h2>
<p>Strip it down and an interface does one thing: it translates. It takes raw capability and makes it legible to whatever is on the other side. For the human-facing kind, that meant making capability learnable and safe for a person to drive: encoding what you are allowed to do, what you are not, and what just happened when you did. That translation layer was often the whole product. Two companies could have the same underlying capability, and the one that made it usable won, not because its engine was better, but because a person could actually get at it. Usability was a moat, and for decades it was a deep one.</p>
<h2>When the human stops being in the loop.</h2>
<p>Here is what unsettles me. A growing share of work no longer has a human at the surface at all. Agents call APIs. Systems integrate directly. One model hands structured output to another and neither of them needs a screen, a button, or a tooltip. The seam is still there (the API is every bit an interface), but it is no longer one a person looks at. For that traffic, the human-facing surface is not value. It is overhead: a translation into a language nobody in the conversation speaks. If the thing consuming your capability is a model, it is fair to ask what, exactly, the screen is for.</p>
<p>The examples are everywhere once you look for them. Stripe won by being an API first and a dashboard second; the money moves through code that no human watches in the moment it clears. A Kubernetes cluster reconciles a state you declared in a file: the control loop does the work and clicks nothing. A GitHub Actions pipeline builds, tests, and ships on a push, with nobody sitting at a console approving each step. And an agent stringing together a dozen tool calls is, at every hop, one program handing structured data to another. In all of it, the polished screen we would once have laboured over is either absent or sitting unwatched off to the side.</p>
<p>I do not think that question has a comfortable answer, and I distrust anyone who says it does. A real portion of what we used to lovingly design and polish is becoming a back end with no front. Pretending otherwise is the kind of attachment that gets you overtaken.</p>
<h2>From input to insight.</h2>
<p>I do not think the answer is a single prompt box swallowing everything, though that is the fashionable guess. What I see instead is a change in what the human-facing surface is for. As agents take over the doing, the controls thin out: fewer buttons to press, fewer fields to fill, fewer of the small manual actions we used to design whole screens around. But the surface does not leave with them; it changes job. It stops being the place where you put work in and becomes the place where you watch it happen. It is the difference between a form where you set the replica count by hand and a dashboard where you watch a rollout move: pods going green, error rates holding flat, traffic shifting across, and you stepping in only if something looks wrong. Less a control panel you operate, more a window you read.</p>
<h2>The objections, and what survives them.</h2>
<p>Before I commit to letting it go, the objections deserve a fair hearing, because there are good ones. You cannot sell what nobody can see: software is still bought from a demo, and a back end with no front is a hard thing to put in front of a buyer. People learn a system through its surface; strip the screens away and onboarding becomes an act of faith. Regulated work needs somewhere to stand: an auditor, a compliance officer, a court all want a place to look and a record of who did what and why. And, as we have already said, even machines need an interface to each other: an API contract, a tool definition, an OpenAPI spec is still something a person designed, still a surface that can be clear or confusing. The interface does not vanish when the human leaves the loop. It changes audience.</p>
<p>Most of these hold, and I think they sharpen the point rather than blunt it. The sales demo, the onboarding tour, the audit log: none of them is an interface for doing the work. They are interfaces for buying it, learning it, answering for it. That is exactly the migration I am describing: away from the surface where a human drives the capability, toward the surface where a human understands and vouches for it. The agent-to-agent contract is the same lesson in another key. Design simply moves to where the consumer is. If the consumer is a model, you shape the schema with the care you once gave the screen: clear names, sane defaults, errors that explain themselves. The work does not stop being interface work. It stops being screen work.</p>
<h2>What is left, and why it matters more.</h2>
<p>So I land somewhere less tidy than I started. The interface as a thick translation layer wrapped around every capability is fading, and I think the honest move is to let it go. But there is a second interface underneath it, and that one is growing. Even when the machine does the work, a human still has to understand it, trust it, correct it, and answer for it. Automate everything beneath the surface and you raise the value of the surface that remains, because it is now the only way left to see into a system doing more than ever on its own. The screen stops being a control panel for the capability and becomes a control panel for the autonomy: the seam where a person can still see what happened, trust it or refuse to, and reach in when it is wrong. That job is not shrinking; it is growing, precisely because the cost of not noticing has gone up. It was never about the pixels. It was about keeping a person in a position to understand and decide. The more capable the machine becomes, the less that position is optional.</p>]]></content:encoded>
    </item>
    <item>
      <title>Running with the machine</title>
      <link>https://tri2b.cloud/blog/running-with-the-machine/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/running-with-the-machine/</guid>
      <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
      <description>Technology only ever advances toward cheaper, faster, better. If that is also your value proposition, it is coming for you. The only safe position is the one you are willing to give up.</description>
      <category>Practice</category>
      <category>AI</category>
      <category>Leadership</category>
      <content:encoded><![CDATA[<p>Technology moves in one direction. It gets cheaper, it gets faster, it gets better. That is the only thing it does, and it does it relentlessly, on a timescale that does not care about your plans. Every year the thing that was hard last year is a little more solved, a little more commoditised, a little more available to someone who could not do it before. There is no version of the future where this stops being true. It is the closest thing to a law that our field has.</p>
<p>I want to follow that single observation all the way to where it leads, because where it leads is uncomfortable, and I think most of us avoid it on purpose.</p>
<h2>If your offer is cheaper-faster-better, you are on the wrong axis.</h2>
<p>Say plainly what you sell. Strip away the language and most of us are offering some version of the same promise: I do this task cheaper, or faster, or at higher quality than the alternative. That is a good promise. It is also exactly the axis technology advances along. You and the machine are running in the same race, in the same direction, toward the same finish line. The difference is that it does not get tired and it does not stop, and every year there are more of it.</p>
<p>When you compete on cheaper-faster-better against a thing whose entire nature is to get cheaper, faster, and better, the outcome is not in doubt. It is a question of when, not whether. The day it does your task better than you is not a risk to be managed. It is a date you have not been told yet. I find it clearer to plan as though that date is real, because it is.</p>
<h2>Attachment is the thing that kills you.</h2>
<p>Here is the move that turns an inevitability into a catastrophe: attachment. You pick a task, you get good at it, and then you decide that task is who you are. You dig in. You defend the territory. You tell yourself that what you do is too nuanced, too human, too skilled to be overtaken, right up until the morning it is overtaken anyway, and then there is no ground left to stand on because you spent years refusing to move.</p>
<p>I have watched this happen to skills I was once proud of. Hand-tuning queries the optimiser now handles. Writing the boilerplate a generator now emits. The wiring between systems that a framework now assumes. None of these were trivial when I learned them. All of them were, at some point, a thing I could charge for. The ones I clung to became dead weight. The ones I let go of on time became a stepping stone to the next thing.</p>
<p>The fatal error is never the obsolescence. The obsolescence was always coming. The fatal error is standing still while it arrives.</p>
<h2>Stop racing it. Run with it.</h2>
<p>There is another option, and it is the only one that holds up. You stop trying to out-run the machine and you learn to run alongside it instead. You specialise in a task, you do it well, and you stay completely unsentimental about it. When the machine catches up and does that task better than you, you do not dig in and fight to hold the lead. You cede that stretch of ground and push on to the trail ahead, the part it has not reached yet. Running is forward motion; the moment you plant your feet, you are no longer running, you are being left behind.</p>
<p>This is what I mean when I say I build with the machine and not around it. Around it is the dug-in position: brace against the pace, protect the task, get overtaken anyway. With it means treating every advance as the next stretch of trail rather than a threat to your perimeter. The things the tooling absorbed this year are the things I no longer have to spend myself on, which means I can spend myself on the things it cannot do yet. Next year the pace will quicken again, and I plan to keep stride with it.</p>
<p>It helps to treat every position you occupy as a stride in a longer run, never a place to stand still. The query work, the boilerplate, the integration glue, even the architecture and judgement I lean on now: all of it is a place my feet are for the moment, not a place I own. As long as I keep moving with the technology, the position stays valuable. The instant I anchor and stop, the clock starts, and the overtaking begins.</p>
<h2>There is no permanent safe ground.</h2>
<p>This is the part I would rather not say, so I will say it directly. There is no final position. There is no skill so deep that you can learn it once and coast. The relevance is real, but it is always temporary, and the only way to keep it is to keep moving. Adaptation is not an event you survive and then put behind you. It is the permanent condition of doing this work at all.</p>
<p>That sounds exhausting written down. In practice it is the opposite of exhausting, because it removes the worst part: the dread of being caught out. You are not waiting to be made obsolete and hoping it holds off. You are already moving, already in stride, which means the ground shifting under you is not a catastrophe. It is just the next stretch of road. You are either running with the technology or being left behind by it. I would rather choose the first while the choice is still mine to make.</p>]]></content:encoded>
    </item>
    <item>
      <title>Sculpting, not building</title>
      <link>https://tri2b.cloud/blog/sculpting-not-building/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/sculpting-not-building/</guid>
      <pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate>
      <description>For most of my career I stacked software up from foundations. Lately I've been chipping it out of the block instead.</description>
      <category>Practice</category>
      <category>Craft</category>
      <content:encoded><![CDATA[<p>For most of my career, writing software felt like construction. You laid foundations (schemas, models, core abstractions) and built upward from there. Each floor had to be solid before you added weight to it. You wrote the tests, hardened the edges, and only moved up once the ground beneath you was stable. It was a good discipline. It forced you to think carefully about what each layer needed to be before you committed to it.</p>
<p>The trade-off was that you often discovered the wrong thing was solid. You had poured concrete for a building that needed to be somewhere else.</p>
<h2>The shift.</h2>
<p>The way I work now feels more like sculpting. You start with the whole mass: a rough, oversized shape that contains the final form somewhere inside it. The shape is wrong everywhere, but it is all there. Then you work inward. Broad strokes first: the main planes, the overall proportions. Then finer and finer passes until the detail emerges and the thing becomes itself.</p>
<p>In software terms: I now reach for the entire surface of a feature before any of it is correct. Routes, controllers, components, migrations: all of it, rough. The shape is there. Then I start chipping. This field should be nullable. This component wants to be split. This query will not survive real data. Each pass brings it closer to what it needed to be, and the whole time I can see where it is going.</p>
<h2>Why they feel different.</h2>
<p>The builder's instinct is to not move forward until the current thing is right. The sculptor's instinct is to not get attached to any part until the whole is visible. The builder's risk is over-engineering foundations for the wrong building. The sculptor's risk is mistaking an early rough shape for the finished piece: shipping the chip-marks as character.</p>
<p>I have made both mistakes. I have spent days perfecting an abstraction for a feature that pivoted the following week. I have also convinced myself a rough sculpt was done because it broadly worked and I was tired of looking at it.</p>
<h2>What changed.</h2>
<p>Part of it is experience: I can read the shape of a system faster than I used to, which makes the rough first pass less rough. When the broad strokes are closer to right, each subsequent pass costs less.</p>
<p>Part of it is the tools. AI-assisted development dramatically accelerates the first pass. You can have the whole rough structure in place in hours rather than days, which makes the sculpting approach viable for scopes that previously demanded the builder's careful sequencing just to stay manageable.</p>
<p>Neither approach is always right. Some problems (a payment integration, a security boundary, anything where a wrong foundation costs you everything) still demand the builder's rigour. But for most product work, I have found that seeing the whole thing wrong is a faster path to seeing it right than building only what you are certain of.</p>]]></content:encoded>
    </item>
    <item>
      <title>Why I run a Kubernetes cluster for side projects</title>
      <link>https://tri2b.cloud/blog/why-k8s-side-projects/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/why-k8s-side-projects/</guid>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <description>It's overkill, on purpose. Here's what running my own cluster has taught me about production-grade thinking.</description>
      <category>Infra</category>
      <category>k8s</category>
      <category>Practice</category>
      <content:encoded><![CDATA[<p>For years I told myself a VPS and a docker-compose file were enough. They were, right up until they weren't. The day a stray cron job on a shared VPS consumed all the available memory and took three running services down with it, I started reaching for something with guardrails.</p>
<p>A Kubernetes cluster at home is, on paper, absurd overhead for hobby code. The setup takes a weekend. The maintenance takes an evening here and there. If you measure it purely against the apps it serves, the cost-to-value ratio looks ridiculous. But that is not the right way to measure it.</p>
<h2>The real benefit isn't uptime.</h2>
<p>It is that every shortcut I would normally take (I'll add resource limits later, I'll wire up health probes when the app is stable, I'll set up proper log shipping eventually) simply vanishes. There is no just SSH in and tail the log. There is the log aggregator, or there is nothing. There are the manifests, the rollout strategy, the liveness probe that bounces the pod at 3am when it deadlocks, or there is the crash I find the next morning.</p>
<p>Running production-grade infrastructure for toy apps forces production-grade habits. When the day job hands me a similar shape of problem (an app with real users, real consequences), the muscle is already there. I have made the mistakes on code nobody depends on.</p>
<h2>What it actually costs.</h2>
<p>Time, mostly. The initial setup is non-trivial: a lightweight distribution on a couple of machines, a container registry, a wildcard cert, an ingress controller, a basic monitoring stack. That is a weekend if you have done it before and two weekends if you haven't. The ongoing cost is lower: an hour a month, usually less.</p>
<p>Money, very little. The cluster runs on hardware I already owned. Having a cluster also means I can spin up a new service in under ten minutes (a database, a deployment, an ingress rule) and tear it down without paying for idle compute.</p>
<h2>The right call for the right person.</h2>
<p>It is not the right call for everyone. If your time is scarce and your appetite for infrastructure is low, a managed platform with a sensible free tier will serve you better. But if you want the practice, and you want it to feel real, there is no substitute for being the person who has to fix it when it breaks.</p>]]></content:encoded>
    </item>
    <item>
      <title>The Laravel + Inertia sweet spot</title>
      <link>https://tri2b.cloud/blog/laravel-inertia-sweet-spot/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/laravel-inertia-sweet-spot/</guid>
      <pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate>
      <description>Why this stack keeps showing up in everything I build, and where it stops being the answer.</description>
      <category>Laravel</category>
      <category>Inertia</category>
      <category>Practice</category>
      <content:encoded><![CDATA[<p>Most products I work on are not SaaS at consumer scale. They are internal tools, line-of-business apps: the workhorses that keep a company running but rarely make it into a case study. Maintenance dashboards. Job card systems. CRMs built around a workflow that no off-the-shelf tool quite fits. For that shape of problem, Laravel + Inertia is the most productive stack I have found.</p>
<p>The appeal of Laravel is well-documented enough that I will not relitigate it. It is opinionated in exactly the right places (routing, auth, queues, mail, notifications) and flexible where flexibility matters. You can move fast without accumulating the kind of debt that makes moving fast unsustainable.</p>
<h2>The trick is Inertia.</h2>
<p>Inertia erases the API layer for apps that do not need one. You write a controller, return a page component with some props, and your frontend is a React or Vue component that receives those props. State lives where it belongs. There is no auth token dance, no duplicated validation logic, no API spec to maintain for an endpoint only your own frontend will ever call.</p>
<p>The mental model stays simple: a request comes in, a controller handles it, a component renders the result. Every developer who has worked with a server-rendered framework immediately understands it. Onboarding a new engineer is measured in hours, not days.</p>
<h2>Where it stops working.</h2>
<p>The seams show in a few situations. Real offline support (a field app that works without connectivity) needs something different. Deeply interactive flows that don't map cleanly to pages fight the page-based mental model. And if you are building a public API regardless, the calculus changes: a thin API plus a proper SPA costs about the same in complexity.</p>
<p>Knowing when to leave a stack is as important as knowing when to use it. Reading the shape of the problem before you start is cheap. Refactoring out of a stack mid-project is expensive.</p>
<h2>For the right problem, it's hard to beat.</h2>
<p>But for the 80% of problems that look like forms, lists, dashboards, and workflows (the unglamorous, load-bearing software that most engineering teams actually spend most of their time on), this is the stack I reach for, every time.</p>]]></content:encoded>
    </item>
    <item>
      <title>On feedback as fuel</title>
      <link>https://tri2b.cloud/blog/feedback-as-fuel/</link>
      <guid isPermaLink="true">https://tri2b.cloud/blog/feedback-as-fuel/</guid>
      <pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate>
      <description>A short note on what changed when I stopped treating code review as evaluation.</description>
      <category>Practice</category>
      <category>Leadership</category>
      <content:encoded><![CDATA[<p>The fastest period of growth in my career started the year I stopped reading code review comments as a verdict. A line left on a PR is not a judgement of my worth as an engineer. It is a signal: about my code, about the reviewer's mental model, about the gap between the thing I wrote and the thing the system needs.</p>
<p>That sounds obvious when written down. It did not feel obvious when someone left twelve comments on a PR I thought was clean.</p>
<h2>The reframe.</h2>
<p>Treating each comment as data instead of as a grade changes the shape of the whole conversation. When I stopped defending, I started listening. When I started listening, I started understanding, not always agreeing, sometimes I push back and I am right to, but understanding what the comment is actually pointing at.</p>
<p>The second-order effect was that my own review comments got better. When I was not worried about how mine would land, I could focus on what I was actually trying to communicate. I got clearer. I got kinder. The reviews I leave now are the ones I would have wanted to receive.</p>
<h2>The compounding.</h2>
<p>Constructive feedback is the cheapest mentorship available. You do not need a formal arrangement or a structured programme. You need a team that gives a damn and enough self-possession to let the signal in. A single well-aimed review comment (the kind that points at a design flaw you would have carried forward for years) is worth more than months of self-study. You cannot find the blind spot you cannot see.</p>
<h2>What I try to do now.</h2>
<p>I try to leave one comment on every PR that is not about correctness but about craft: a question, an alternative, a pattern I have found useful. Not a prescription. An invitation. And when I receive a comment I do not immediately agree with, I sit with it before responding. Usually it opens something up. The ratio has shifted, and the work is better for it.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
