Aiming the Agent
Two weeks, a stack I’d never shipped, and an AI doing most of the typing. What I learned was less about the tech than about how to point it.
A CTO handed me a confidential deck and a month: take a working centralized product and make it decentralized. Records that replicate across independent operators, governance of who’s allowed in, decided on-chain. Smart contracts, a peer-to-peer database, content-addressed storage, libp2p — almost none of it tech I’d shipped before.
I built it mostly by pairing with an AI agent, and that’s the part worth writing down. Not because the agent was magic — because it wasn’t. Everyone has a tool now that writes code faster than they can read it. Far fewer people have figured out that raw speed is a liability until you’ve aimed it. An agent left to its own devices is a fast, confident collaborator sprinting in a slightly wrong direction, and it will have written four files before you notice. Everything I did over those two weeks was, in one form or another, a way to aim that speed.
There were two kinds of aiming. One was friction in how I talked to it. The other was structure it couldn’t talk its way around. They reinforced each other, and the project worked because both were present.
Friction
The default failure of a coding agent is that it answers a question by building something. Ask “how should we handle X” and you get an implementation of X — past the point where you could cheaply change your mind. So the first and most important thing I did was refuse to let research, planning, and building happen in the same breath. They were three separate conversations. I’d research a question until I had real tradeoffs in front of me, decide, and only then ask for code. The plan was a document I reviewed before a single line existed. This sounds bureaucratic; it’s the opposite. It’s what let me move fast, because I was never unwinding code to revisit a decision the code had quietly made for me.
Within that, the division of labor was simple: the agent was extraordinary at breadth, and I owned the verdict. It could survey five governance libraries with their licenses and effort costs faster than I could open the first tab. But the call that actually mattered — I didn’t want a copyleft license — wasn’t anywhere in that survey. It lived in my head, a business constraint the agent had no way to know. The pattern held everywhere. Let it map the territory; don’t let it choose the destination.
And when it recommended, I made it recommend honestly — because the most corrosive thing an agent does is manufacture confidence. Early on it framed two storage designs as a real choice — “this one stores the data here, that one stores it there” — when both stored it in exactly the same place. It also called one option “the industry standard,” then reversed itself a day later. Each of those cost me trust, and lost trust is expensive in a way that’s easy to underestimate: once you’ve caught the agent inventing a distinction or faking a standard, you have to re-verify everything it tells you, and now its speed is worth nothing. So the rule became: if a distinction isn’t real, collapse it; if you don’t know, say you don’t know; never dress a guess as a convention.
The same instinct shows up as guessing instead of reading. An agent that doesn’t know what the code or the plan says will cheerfully invent an answer and build on it — at one point it conjured a whole polling mechanism and re-proposed an approach I’d already rejected, both from assuming rather than looking. I told it, bluntly, that every time it assumes it makes a mistake, and that became a rule with teeth: read the plan and the code first, say what you found, then act, and if you’re not sure, ask. The cost of an assumption isn’t the wrong step, it’s the three steps built on top of it before anyone notices.
The companion rule is think, then speak once — no visible “wait, actually, no, let me redo that” mid-stream. An agent that narrates its own second-guessing in real time is exhausting to read and impossible to trust; I’d rather it think silently and hand me one answer it stands behind. That alone made the whole collaboration legible.
There’s a scope half to this too. Asked for a small, specific change, an agent loves to do more — refactor the neighbourhood, raise a downstream concern, hand you an option nothing actually needs. I wanted the opposite: do exactly the thing asked, at exactly its size, and surface the bigger stuff as a flag rather than acting on it or silently dropping it. But “small” never meant “cheap.” When a real choice came up, the default was the durable, industry-standard answer, not the demo hack that happens to pass today’s test — and if the proper fix was genuinely out of scope, it got written down as an explicit follow-up instead of quietly skipped. A shortcut you’ve named is a decision; a shortcut you’ve hidden is a debt.
The single highest-leverage habit, though, was the one I’m most tempted to keep for myself. When I kept asking about the same thing, the agent learned to treat that as evidence that its explanation was broken — not that I was slow. This is exactly backwards from how these tools behave by default. Press an agent twice on the same point and it will restate its previous answer, more slowly, with more words, as if volume were the problem. What I needed was for it to assume the defect was in what it had said and rebuild the explanation from a different angle.
The storage confusion above is the case in point. I kept asking partly because the answer was wrong, but mostly because it didn’t sit right — it sounded bizarre, and I wanted to actually understand it rather than nod along. That’s the real signal, and it’s broader than error: a repeated question can mean “you’re wrong,” or “that doesn’t make sense to me,” or just “I want to know more,” and all three are telling you the same thing — the explanation hasn’t landed yet. It only dissolved when the explanation got torn down and rebuilt — “the database has no storage of its own; its log entries are objects in the content-addressed store, so ‘in the log’ and ‘in storage’ name the same place.” I never got there by hearing the original answer again. I got there because the agent finally treated my third question as its problem instead of mine.
None of this would have survived contact with a fresh session if I’d had to re-teach it every morning. That’s the quiet load-bearing piece: I wrote the agreement down. Read before you act. Be terse. Flag a decision, don’t make it silently. Never touch a secret file. Every correction worth making twice became standing instruction the agent carried into the next conversation, and the project-wide ones lived in a committed file so they bound anyone who worked in the repo, not just me on a disciplined day. A lesson you don’t persist is a lesson you pay for again every session — and over two weeks of many sessions, that bill is the whole budget.
Structure
Friction shapes a conversation. It doesn’t survive one. For that you need artifacts — things written down where neither of us could drift away from them — and the discipline above is mostly what kept those artifacts honest.
The backbone was a register of open questions and a log of decisions, run as a one-way conveyor. Every uncertainty went into one file — exactly one, because the failure mode is scattering half-answers across a dozen documents until no one knows what’s actually settled. Each question carried the options I’d weighed and who owned the answer, since some calls were genuinely the CTO’s and not mine. And crucially, “defer, with a reason” was a real answer, not an evasion. Contract upgradeability: not important yet, redeploy if we have to, revisit if this graduates. Parking a question deliberately is the opposite of forgetting it; it’s what freed my attention for the few questions that actually blocked progress.
When one resolved, it graduated to the decisions log in a fixed ritual — dated, numbered, folded back into the design. That log was append-only: to reverse a decision you added a new entry that superseded the old one, never edited it away. The most valuable thing in the whole repo turned out to be those reversals. I chose a governance library, reversed it the next day over its license, and the superseded entry — left intact — explained both what I’d thought and why I’d changed my mind. Three weeks later that was worth more than any amount of clean final-state documentation.
Two structural ideas did most of the real work. The first: before committing to any plan, I spiked the one assumption that could kill the project. Could two independent operators actually replicate this data peer-to-peer over our stack at all? I time-boxed a spike at exactly that question and committed the findings. It earned its cost on day one by killing an assumption the deck and I had both been carrying — that the storage layer would run as its own container, the way a database does. It can’t; the library embeds that node in-process. Discovering that in a throwaway spike instead of in week three is the entire reason spikes exist.
The second idea was naming what was expensive. Most decisions are cheap — a config value, an adapter, a redeploy — and a few are one-way doors. I wrote down the short list of things that would actually hurt to get wrong: authorization living in roles rather than a hard-coded owner, a record format that was versioned and self-verifying, the fast index treated as a rebuildable cache and never the source of truth, records signed the moment they’re written. Everything else I declared swappable on purpose and hid behind an interface. That one separation is what let me be relaxed about ninety percent of the build and genuinely careful about the ten percent that mattered.
Underneath that ran a constraint I applied to nearly every decision: this was a proof of concept, but it was a proof of concept of a real system, and I refused to build anything now that would have to be thrown away later. There’s a difference between a shortcut and a dead end. Replicating every record to every operator is a shortcut — fine at two operators, and the path to a smarter scheme later is just a config dial, not a rewrite. Hard-coding a single owner where governance belongs is a dead end — you’d have to rip it out. So for each MVP choice I’d ask the same quiet question: does the cheap version I’m shipping now foreclose the real version I’ll want later? If the answer was no, I took the cheap version without guilt. There was exactly one genuine one-way door in the whole design, and because I’d named it as such, I made that single call deliberately instead of stumbling through it. Everything else was allowed to be quick precisely because I’d checked it could grow up.
Then the work itself was deliberately small. Thirteen tasks, roughly half a day each, each on its own branch and pull request. Small isn’t a virtue for its own sake here — it’s what kept the documents from drifting. Updating the decisions log inside a one-day PR is nothing; inside a two-week PR it silently rots, and rot is the failure mode for every plan I’ve ever watched die. When a change was too big to keep honest — flipping the entire write path to a new model — I split it so the system worked at every commit: first write to both places, then cut over to one and sync the rest. At no point was there a broken half-migration sitting on a branch waiting to be someone’s bad afternoon.
Proof
The last thing the structure bought me was the ability to trust that it worked, which with an agent is not a given. Tests weren’t a phase at the end; they were how each of those expensive invariants got held down, and they ran in CI from early. Contract suites drove the full governance lifecycle — propose, vote, queue, execute — and caught the ugly regression where re-admitting a removed operator silently duplicated them. A layer of unit tests over the peer-to-peer service pinned the signing, the record format, the key handling, the cache sync, so I could refactor the swappable parts without flinching.
But the test that mattered most was the one that crossed the boundary the product actually claims to cross: two independent operators, stood up separately, with an automated assertion that a record created on one verifies on the other. That’s a different and harder thing than a record round-tripping inside a single stack, and it’s where the real bugs lived — port collisions, hostnames that resolve in one stack and not across two, a write-access default that let the creator write but blocked the joiner. I proved it twice: two stacks on one machine as the cheap loop, then real separate clusters for the demo. None of the cross-boundary bugs were visible until I ran it across an actual boundary.
I also had the agent review its own work cold, right before deploying, with one instruction: find what’s wrong, unsafe, or not actually as swappable as the plan claims. An agent in build mode is trying to make the thing work and will unconsciously read its own code charitably; an agent told to break it reads differently. It found a real hole the plan had never anticipated — replicated records were being accepted as long as they carried any internally-consistent signature, with nothing checking that the signer was an approved operator. The signing existed; its output just wasn’t wired to the authorization decision. That’s precisely the class of gap that survives building and only falls to an adversarial second pass.
What it comes down to
The tech was unfamiliar and most of it I’ll never touch again. The method I’ll use on the next thing, and it has almost nothing to do with decentralization. Separate the decisions that are expensive from the ones that are cheap, and spend your attention accordingly. Write both kinds down somewhere they can’t drift from the code. And run the agent with enough friction that its speed points where you want it — because the speed was never the hard part. Aiming it was.