↓ Skip to main content

A Tale of Three Months with AI: Guardrails, and How We Are More Alike Than We Wish to Admit

·23 mins

As I prom­ised in A fresh leaf, I want to share my ex­per­i­ences with us­ing LLMs for agen­tic de­vel­op­ment in the last three months. Sum­mar­ised: I star­ted as an ini­tial scep­tic, be­came quite en­thu­si­astic, and am now mod­er­ately en­thu­si­astic and guarded for the fu­ture. Al­low me to take you through my jour­ney.

Com­ing from a back­ground of rig­or­ous en­gin­eer­ing with up-front think­ing about pro­gram struc­tures, al­gorithms, and types, it seemed in­com­pre­hens­ible to me how a jumped-up neural net could gen­er­ate work­ing code, let alone sens­ible sys­tems. How to write un­der­stand­able soft­ware has been hotly de­bated for dec­ades, con­tinu­ously shift­ing as new lan­guages, type sys­tems, and eco­sys­tems in­tro­duce dif­fer­ent trade-offs.

Early in my ca­reer, com­ing from aca­demia, I was fix­ated on find­ing the “just-right” solu­tion: el­eg­ant ab­strac­tions, strict cor­rect­ness, and formal sound­ness. As prob­lems grew lar­ger and messier, and time be­came scarce, hold­ing out for per­fec­tion be­came an obstacle. I found far more sat­is­fac­tion in achiev­ing some­thing func­tional, with el­eg­ance as a wel­come second. Hav­ing both over­lap is fant­astic; achiev­ing the ac­tual goal re­mains both a pri­or­ity and a ne­ces­sity.

This shift in per­spect­ive ex­plains why LLMs, and agen­tic de­vel­op­ment, even­tu­ally proved so at­tract­ive to me. Ex­pect­ing a model to gen­er­ate a “just-right” ar­chi­tec­ture, code, or text only causes frus­tra­tion. Con­versely, it acts as an en­gine to reach con­crete mile­stones in areas I was al­ways curi­ous about without hav­ing the time to do things “prop­erly”, or simply elim­in­at­ing the toil of writ­ing boil­er­plate when I can de­scribe my in­tent with less mech­an­ical pre­ci­sion.

If your goal is simply to achieve some­thing con­crete, they are re­mark­ably ef­fect­ive. That spans from some­thing as simple as in­teg­rat­ing an un­fa­mil­iar lib­rary to break­ing ground in an un­fa­mil­iar prob­lem do­main where you lack the tools and vocab­u­lary.

The greenfield honeymoon
#

My cata­lyst was a device sit­ting in my liv­ing room. After re­ports emerged that its suc­cessor was ag­gress­ively hos­tile to user pri­vacy, I wanted to verify whether my own hard­ware be­haved the same way. Since I had no back­ground in firm­ware in­spec­tion or packet ana­lysis on em­bed­ded devices, I was ini­tially dis­cour­aged. Luck­ily, I no­ticed right then that I had a free trial month of Co­dex ly­ing around, so I de­cided to see how far these so-called “amaz­ing fron­tier” mod­els could carry me.

I star­ted out by talk­ing in ChatGPT and ask­ing ques­tions on how to solve my prob­lem.

This quickly be­came us­ing Co­dex to work on my laptop, let­ting it down­load im­ages, and run­ning local scripts. The way of work was very in­ter­act­ive, prompt-based, and re­l­at­ively un­struc­tured and un­guided.

I was amazed by how well the model worked when told to achieve a very spe­cific goal and even more so when giv­ing the con­text and con­straints in a single prompt: as long as the prompt wasn’t too big.

When I poin­ted out er­rors, the mod­els com­pli­men­ted me lav­ishly on the dis­cov­ery and apo­lo­gised pro­fusely for the de­fect.1 Yet that con­tri­tion yiel­ded no dur­able change in their sub­sequent be­ha­viour. It was just text, learned from reams of doc­u­ments.

Asserting control
#

As soon as the code­base and pro­gram to de­tect any ne­far­i­ous be­ha­viour was grow­ing, I no­ticed the per­form­ance of the mod­els de­grade.2 Re­sponses lost their ini­tial speed, pre­ci­sion, and cor­rect­ness. The model began over­look­ing in­ter­de­pend­ent up­dates that re­quired lock-step co­ordin­a­tion, shed­ding con­text es­tab­lished in earlier turns. In other words, it re­quired more hand-holding, for which I had neither time, nor pa­tience, nor ap­pet­ite.

At that point, I also star­ted work­ing on some other ideas that had been ly­ing dormant in my head. I used an agent to cre­ate a Ter­ra­form pro­ject to spin up a small timeseries-db backup in Google Cloud con­strained such that it would stay within the free tier. Cre­at­ing the pro­vi­sion­ing scripts was the work of mere mo­ments. It checked the ex­act siz­ing re­quire­ments for stay­ing within the free tier in­clud­ing some caveats, whipped the Ter­ra­form code to­gether, and ad­ded the Tails­cale in­teg­ra­tion. All from a simple prompt of what I wanted to achieve and the fact that I wanted to be able to re­con­struct the VM from a single com­mand. That was awe­some.

It was here that I first star­ted sus­pect­ing a deeper link between mod­els and hu­mans: it all came down to how bounded the task was. Over the years, I no­ticed that when you ask a per­son an open-ended ques­tion, their an­swer is mostly in­flu­enced by their re­cent his­tory, their emo­tional state, and whatever prob­lem they were just wrest­ling with. Mod­els work al­most identic­ally. Ask them a broad, open-ended ques­tion and you get a gen­eric re­sponse shaped by their pre-trained weights and im­me­di­ate con­text win­dow. Get­ting either a hu­man or a model to think out­side that de­fault box re­quires tight, ex­pli­cit con­straints, and care­ful prod­ding (or prompt­ing).

One of the of­ten touted best-practices I en­countered in the be­gin­ning was to have a three-phase ap­proach. Use one agent to plan the work in­ter­act­ively, pro­du­cing a han­dover doc­u­ment. Then task a second agent with im­ple­ment­ing that han­dover doc­u­ment un­at­ten­ded. Fi­nally pass the con­trol to a re­view agent, which gets the diff and the plan, and re­views it. The ob­vi­ous im­prove­ment is to make the last two com­mu­nic­ate and work in a loop un­til no more find­ings are es­tab­lished.

I first tried auto­mat­ing this di­vi­sion of work (plan­ning, im­ple­ment­a­tion, and re­view) by writ­ing con­ven­tions as prose in AGENTS.md. As code grew and threads grew longer, how­ever, prose in­struc­tions be­came less ef­fect­ive and eas­ily ig­nored by the model. To take this work­flow fur­ther, I wanted to com­bine the spe­cial­ised strengths of dif­fer­ent mod­els: Co­dex as the fast, local work­horse for writ­ing code, and Gem­ini as the thought­ful re­viewer for ar­chi­tec­ture. To let them talk dir­ectly to each other, I built gemini-mcp.

Here I hit an­other quirk of model psy­cho­logy: autonom­ous mod­els pos­sess such su­preme self-certainty that re­view trig­gers re­quire ex­pli­cit pro­gram­matic con­di­tions. A loose prose dir­ect­ive like “Ask Gem­ini for help when un­cer­tain” gen­er­ated pre­cisely zero in­voc­a­tions; the model was never un­cer­tain. Con­versely, a de­term­in­istic cir­cuit breaker (“At­tempt to solve each prob­lem a max­imum of three times; if still fail­ing, con­sult Gem­ini via MCP, im­ple­ment its re­com­mend­a­tions, and re­peat”) re­li­ably promp­ted Co­dex to es­cal­ate for second opin­ions.

This put me with an im­me­di­ate se­cur­ity chal­lenge: be­cause I was us­ing this setup to as­sess the pri­vacy of the TV, I needed a clas­si­fier to pre­vent prompts between mod­els from look­ing like of­fens­ive se­cur­ity probes, which could get my Google ac­count sus­pen­ded.

Scaf­fold­ing the clas­si­fier was quick work. Mak­ing it func­tion re­li­ably, how­ever, in­volved dozens of subtle ar­chi­tec­tural choices that flew past too fast for me to prop­erly eval­u­ate. Steer­ing the model through prose in AGENTS.md simply wasn’t giv­ing me the con­trol I needed. That was the mo­ment I turned to sys­tem­atic, de­term­in­istic tool­ing, start­ing with a simple dir­ect­ive: run make check after every modi­fic­a­tion to verify that the pro­ject was still co­her­ent.

This worked so well that ever since I have been ex­tend­ing what is be­hind this check con­tinu­ously. Re­ly­ing on what I already knew worked well in lar­ger pro­jects with mul­tiple con­trib­ut­ors, I de­cided to use strict tools to check as much as pos­sible. This meant auto­matic lint­ing, format­ting, and up­grades of the dif­fer­ent types of files in the pro­ject: Mark­down, code, YAML files, ADRs, format­ting, mod­ern­isa­tion of Go, and de­pend­ency up­grades. Moreover, I de­cided to take more out of the hands from the agents and not even bother them with format­ting and easy to fix lint­ing er­rors by auto­mat­ic­ally run­ning format­ting, auto­matic im­ports, and adding --fix to golangci-lint already as part of the check step to en­sure all easy fixes were im­ple­men­ted be­fore the agent even saw the res­ult.3

I in­tro­duced this rig­or­ous track­ing in my first big vibe-coded pro­ject, semantic-editor. The whole point of the pro­ject was to let the agent worry about in­tent while del­eg­at­ing the nitty-gritty edit­ing work to de­term­in­istic AST tools. I ap­plied that same mind­set to the re­pos­it­ory it­self, us­ing strict checks for the mark­down, the ADRs, and the schemas. This ap­proach worked pretty well, and the pro­ject was pro­gress­ing smoothly.

Around that time, an­other per­sonal pro­ject came into my head. I had al­ways felt sad that in this age of search en­gines and lately om­ni­scient (and of­ten wrong) LLMs, my kids didn’t have the ex­per­i­ence to just browse an en­cyc­lo­pe­dia, guided by in­terest. For me the con­crete ex­ample of this was Mi­crosoft En­carta ‘97. It gave me end­less brows­ing, learn­ing of all kinds of things, and im­port­ant to my par­ents (and now me), re­l­at­ively safe be­cause it was cur­ated con­tent.

As you may have guessed, I de­cided to vibe code the an­swer to this prob­lem as well and make a viewer of En­carta data. Be­cause in-repository de­cision track­ing had op­er­ated sat­is­fact­or­ily, I re­lied on that es­tab­lished har­ness to guide de­vel­op­ment.

How­ever, be­cause this was my first GUI app, I sensed it needed an­other layer of con­straints and de­cision track­ing which I dubbed “GUI-invariants” as a spe­cial­ised form of ADR for GUI de­cisions such as lay­out on a more gen­eric level in­stead of spe­cific de­cisions. Get­ting the agent to con­sider the in­vari­ants proved re­l­at­ively simple by just adding it to the agent in­struc­tions. Let­ting it in­ter­pret them in a way was harder, be­cause in­vari­ants are in­her­ently gen­eric. So they needed more con­crete pos­it­ive and neg­at­ive ex­amples and be more pre­script­ive.

For some reason I had a quirk that I wanted all text to be “prop­erly” type-set such as in TeX. And why not? We are cus­tom cod­ing this pro­ject and I have tokens to burn, so let’s have at it. The agent was very help­ful in this bounded con­text. A simple as­sign­ment, a start­ing ex­ample al­gorithm, the spe­cific con­straint of the SwiftUI in­fra­struc­ture, and an or­acle to de­term­ine whether it did it right. This al­lowed me to set a simple goal and I could walk away from it.

These three main pro­jects and sev­eral side pro­jects, all shared one im­port­ant prop­erty: they were green­field pro­jects where I in­di­vidu­ally was the sole owner and de­cision maker.

In urban plan­ning terms, green­field land is ef­fort­less to tra­verse and de­velop, pre­cisely be­cause it is vir­gin ground: un­touched, open, and un­en­cumbered by his­tory. Brown­field sites, by con­trast, are con­tam­in­ated swaths of land burdened by bur­ied found­a­tions, un­doc­u­mented pipelines, and wrapped in a ven­eer of toxic leg­acy waste. Left un­at­ten­ded, soft­ware tends to fol­low an identical tra­ject­ory. When pro­jects ex­pand without dis­cip­line, the vir­gin meadow rap­idly de­teri­or­ates into a muddy, haz­ard­ous swamp that en­gin­eers dread to touch.

Yet that de­cay is avoid­able. Long-lived sys­tems like the Linux ker­nel sur­vive dec­ades of churn not through bur­eau­cratic up­front plan­ning, but through un­com­prom­ising main­tainer gate­keep­ing: strict sub­sys­tem bound­ar­ies, re­lent­less re­view against es­tab­lished pat­terns, and clear com­mit ra­tionale.4 We should learn the les­son and ap­ply it to agen­tic pro­gram­ming. Be­cause gen­eral pur­pose mod­els lack this in­her­ent in­sti­tu­tional memory and in­trinsic mo­tiv­a­tion, we must co­dify that gate­keep­ing dir­ectly into the en­vir­on­ment: track­ing ar­chi­tec­tural de­cisions, en­for­cing in­vari­ant suites, and main­tain­ing de­term­in­istic mech­an­ical guard­rails.

Handled with that dis­cip­line, a code­base avoids be­com­ing a toxic brown­field. In­stead, it be­comes a cul­tiv­ated glass­house: a struc­tured, highly pro­duct­ive en­vir­on­ment where auto­mated checks sup­ply con­tinu­ous nour­ish­ment, and re­cor­ded con­text al­lows de­vel­op­ment to con­tinue to flour­ish at speed.

From the green pasture into the mud
#

While agents seem to shine on green­field pro­jects that they man­age them­selves, which is why vi­be­cod­ing works so well, they start fall­ing flat when pro­jects be­come lar­ger or they need to be used in es­tab­lished pro­jects. This is be­cause the struc­ture agents use to loc­ate where to make changes mir­rors the lat­ent pat­terns in­grained dur­ing train­ing.5 What a co­in­cid­ence, this is also the same for hu­man en­gin­eers.

My ex­per­i­ence cre­at­ing the ini­tial draft of the dynamic-fault-injection fil­ter for an open-source “Built on En­voy” pro­ject ex­posed this bound­ary. I had the found­a­tional dis­tri­bu­tion for­mula and prior ex­per­i­ence run­ning it in a JVM-based side­car; dir­ect­ing an agent to scaf­fold the ini­tial Go plu­gin was straight­for­ward. The fric­tion began when in­teg­rat­ing it up­stream. Un­like self-owned pro­jects where you can in­vent your own short­cuts, I needed to fit cleanly into the project’s es­tab­lished dis­cip­line: ad­her­ing to its battle-tested test pat­terns, doc­u­ment­a­tion con­ven­tions, and re­pos­it­ory lay­outs.

Any­one can staple a quick fix onto a code­base, and just that quickly an­other wart is ad­ded. The ac­tual chal­lenge is fig­ur­ing out where the change truly be­longs, and do­ing the painstak­ing re­work so it looks like it was de­signed that way from the start. Oc­ca­sional con­trib­ut­ors face this con­stantly: de­cid­ing when to live with an ex­ist­ing pat­tern, and when to roll up your sleeves and re­write sur­round­ing code to make it fit prop­erly.

Nav­ig­at­ing that fric­tion suc­ceeded primar­ily be­cause I re­ceived gen­er­ous, pre­cise guid­ance from the lead main­tainer. That hu­man feed­back sup­plied the un­spoken re­pos­it­ory con­text and ar­chi­tec­tural in­tent that neither I nor the model could ex­tract from the code alone.

I could then feed those main­tainer in­sights dir­ectly back into prompts, giv­ing the agent the con­crete bound­ar­ies it needed to shape the im­ple­ment­a­tion cor­rectly.

How­ever, sit­ting down quietly with a cup of tea and look­ing over the code, I real­ised the code or­gan­isa­tion was still a bit off. That in­tu­ition was only avail­able as lat­ent skill in my head based on code scattered across the re­pos­it­ory, and ad­mit­tedly not ap­plied con­sist­ently enough for an un­trained eye like mine on the pro­ject. This is where con­crete ADRs and con­trib­ut­ing guides earn their keep: they turn what is locked in maintainers’ heads into ex­pli­cit rules agents can ac­tu­ally read.

When tack­ling big­ger pro­jects, and more-insufficient us­age lim­its, I star­ted look­ing into other mod­els and no­ticed that the model fam­il­ies tend to have sim­ilar be­ha­viour.

To me these are the qual­i­fic­a­tions of the model fam­il­ies, that I have per­sonal ex­per­i­ence with. Even though they are a math­em­at­ical prob­ab­il­istic bag of para­met­ers, they seem to have emer­gent per­son­al­it­ies:

Hy­per­act­ive Ju­nior En­gin­eer Qwen: The Qwen fam­ily (both 3.6 dense, MoE, and 3.8) gen­er­ates code at blis­ter­ing speed, rarely paus­ing to ask ques­tions. It de­mands highly gran­u­lar guid­ance. The 2-bit Sa­luki quant­isa­tion dis­plays this im­puls­ive­ness even more acutely.

Ex­pens­ive easily-distracted CS Pro­fessor Gem­ini: Ex­tremely strong at re­du­cing a prob­lem de­scrip­tion to its core and find­ing the aca­demic con­sensus on the solu­tion fit. It is par­tic­u­larly strong in Go and machine-learning top­ics, and great at ex­plain­ing why mod­els be­have in cer­tain ways. Es­pe­cially at the ar­chi­tec­ture level.

How­ever, it is ex­pens­ive for routine cod­ing, not that fast or good, and gets stuck in re­pet­it­ive read loops, end­lessly in­spect­ing files without mak­ing changes:

Gemini stuck in a read-inspection loop

Gem­ini 4.0 Ar­gon is even worse for every­day work: a re­view of a small piece of text burns through 50% of my weekly Pro al­low­ance. No mat­ter how good it is, it is un­us­able in this way.

De­pend­able, rabbit-holing work­horse GPT: This is the fam­ily that is ac­tu­ally in­ter­est­ing, es­pe­cially given the pri­cing strategies. In gen­eral, I found GPT to rabbit-hole into a par­tic­u­lar solu­tion dir­ec­tion faster and re­fuse to get out of it, where Gem­ini could step back. Sol 6.1 (and prior with 5.6 Terra) is the per­fect re­viewer and Luna (both 5.6 and 6) is the per­fect work­horse. Luna is de­cently smart, but es­pe­cially be­cause it is vir­tu­ally free. Both Sol and Luna have the tend­ency to double down on a chosen path and are very hard to get to think out­side the box or take a step back.

Across all these model fam­il­ies, one shared quirk emerged: an al­most in­furi­at­ing de­fault bias to­ward Py­thon for writ­ing quick scripts. In­struct­ing an agent ex­pli­citly not to use Py­thon in­vari­ably yiel­ded TypeScript. This is clearly the grav­it­a­tional pull of pub­lic train­ing data, where Py­thon and TypeScript ex­amples dwarf everything else.6 It con­stantly left me long­ing for some­thing like goshell: a single stan­dalone Go file de­clar­ing its de­pend­en­cies in­line, ex­ecuted dir­ectly without scaf­fold­ing an en­tire mod­ule.

AI com­pan­ies love to shout about break­throughs at the ab­so­lute peak of their fron­tier mod­els. Yet what ac­tu­ally changed my day-to-day ex­per­i­ence was not a dra­matic leap in the raw in­tel­li­gence of the top tier. The real shift was the surge in qual­ity among the af­ford­able mod­els: the ones ac­tu­ally in reach for every­day work. A model like Sol 6.1 brings reas­on­ing that used to be­long ex­clus­ively to flag­ships, but at a price where you can af­ford to keep it run­ning con­tinu­ously in tight feed­back loops. It is not that the ab­so­lute Pareto fron­tier has moved, but that the af­ford­able Pareto fron­tier has moved. In that sense, prac­tical agen­tic de­vel­op­ment has be­come far more of an eco­nom­ics game than a sci­ence game.7 Slash­ing the price of a mon­ster flag­ship like As­tra or Ar­gon would be an easy way to win the “smartest model” con­test, but sub­sid­ising those gar­gan­tuan in­fer­ence costs would burn through cash at a stag­ger­ing rate. The pro­vider that wins the prac­tical de­veloper eco­sys­tem won’t ne­ces­sar­ily be the one with the cleverest pa­per, but the one that de­liv­ers re­li­able reas­on­ing at a price where hun­dreds of daily it­er­a­tions don’t break the bank.

Choos­ing the right model for the task at hand is an im­port­ant part of be­ing able to work in large code-bases. How­ever, more is needed.

The Engineering harness
#

Look­ing back at my ca­reer on what made suc­cess­ful teams achieve their suc­cesses be­sides their so­cial skills and trust among them­selves, was the fact that they spend time on great tool­ing. Even the best of minds have a limit to how much they can keep in their RAM, how fast they can write new code, and how fast they can di­li­gently re­view code.

They use tools to pre­vent dis­cus­sions about code style by ap­ply­ing lin­ters and auto format­ting rules, any­where from in a check com­mand, a git hook, or in the PR re­views. This avoids noise.

They have an in­teg­ral test-suite, that lets them move fast ahead with ideas, while mis­takes are still caught. This in­teg­ral test suite goes fur­ther than mere unit tests and in­cludes tools that have been around in vari­ous vari­ations.

It auto­mat­ic­ally tests for:

  • Per­form­ance
    • Mi­crobench­mark­ing
    • Pro­fil­ing
  • Se­cur­ity
    • Static ana­lysis
    • Dy­namic pentest­ing tools
    • Fuzzers
  • Qual­ity
    • Lint­ing
    • Cov­er­age and cov­er­age trends
    • Prop­erty tests and muta­tion test­ing

No­tice how far away this is from reams of prose. Hu­mans with a lim­ited span of at­ten­tion and lim­ited brain ca­pa­city can­not map their every ac­tion against a book­work of ar­chi­tec­ture and pro­ced­ural rules, it would be too time con­sum­ing. They use heur­ist­ics to drive their “on­line” work, and “off­line” guard rails to pre­vent their over­sights from hit­ting cus­tomer im­pact. These same meas­ures ap­ply with agen­tic cod­ing.

Be­cause agents are LLMs with ex­tra rules, as long as you have com­pute power, and suf­fi­cient sup­ply of well thought out jobs / is­sues, the agents can pro­duce code faster and without bot­tle­necks. This in­creases the lever­age, arm, or mul­ti­plier of how much agents be­ne­fit from hav­ing these guard­rails in place.

Institutional Knowledge
#

Guard­rails in the form of rig­or­ous auto­mated test suites, as in­tro­duced above, carry us only so far. They can­not re­mem­ber the WHY: why do we have a cer­tain test, why do we im­ple­ment a fea­ture, why did we choose a par­tic­u­lar lib­rary.

In smal­ler, one-person green­field pro­jects, re­cord­ing the de­cisions in simple ADRs was good enough to keep the agent bounded and give me a mech­an­ism for manu­ally point­ing out to ad­here to the ADRs. In sprawl­ing brown­fields and cul­tiv­ated glass­houses alike, de­cisions come from many dif­fer­ent dir­ec­tions; the in­sti­tu­tional ex­per­i­ence of the en­gin­eer­ing corps is het­ero­gen­eous and in con­tinu­ous flux.

This is what or­gan­isa­tional the­or­ists and soft­ware ar­chi­tects also refer to as “In­sti­tu­tional memory”. 8

In hu­mans this know­ledge is gradu­ally in­grained into the en­gin­eer through ex­per­i­ence. If you are lucky they stay on the pro­jects long enough to provide con­tinu­ity.

Agents have no such con­text at all. Ac­tu­ally, en­gin­eers are also fal­lible. They can for­get. (Con­text com­pac­tion and fresh ses­sions) They can have a lapse of fo­cus or be­ing dis­turbed. (The “lost-in-the-middle” ef­fect9.) They can move on to a new pro­ject. (Start­ing brand new chats.)

With agents, that de­par­ture hap­pens con­stantly: every time you open a brand-new chat thread, every un­re­cor­ded in­sight simply evap­or­ates into /dev/null (un­less you count the pro­vider re­train­ing on your prompts ;-) ).

Thus we need a sys­tem that ac­tu­ally be­ne­fits both en­gin­eers and agents and so I ex­ten­ded the har­ness in two ways:

First: I in­tro­duced Re­search Ques­tions, cap­tur­ing the in­tent of any change or fea­ture. Ba­sic­ally it rep­res­ents the in­box to the pro­ject. Be­fore an im­ple­ment­a­tion starts, stake­hold­ers and en­gin­eers dis­cuss goals, trade-offs, and ex­plore al­tern­at­ives. This leads to what to build and more im­port­antly what not to build. This is con­text that very rarely sur­vives into is­sues, pull-requests, and/or com­mit mes­sages and is there­fore lost. By re­cord­ing these de­lib­er­a­tions in the re­search ques­tions, it re­tains this in­form­a­tion while at the same time also al­lows ADRs to stay small, con­crete, and fo­cussed, to stay kind to the lim­ited con­text win­dows of Agents.

Secondly: Even with this mech­an­ism in place, I no­ticed that there was still drift so I ad­ded a rule to check nightly or weekly to do a full re­con­cili­ation of changes against es­tab­lished ADRs and of code against Re­search Ques­tions to see whether new data had come in or we ac­tu­ally already made de­cisions.

This now closes the loop in the har­ness:

  • Agents drive change
  • Lin­ters, com­pilers, and tests pro­tect the qual­ity of the im­ple­ment­a­tion and doc­u­ment­a­tion.
  • Re­con­cili­ation loops at vari­ous levels and scopes (code change, daily, weekly, monthly)

This high­lights a fun­da­mental par­al­lel: the core en­gin­eer­ing dis­cip­line re­mains identical for hu­man en­gin­eers and autonom­ous agents; only the ex­e­cu­tion ve­lo­city dif­fers. Im­prove­ments in­tro­duced for en­gin­eers be­ne­fit agents even more dra­mat­ic­ally. We must heed what soft­ware en­gin­eer­ing has demon­strated over the past half-century.

This en­tire har­ness stems from a core con­vic­tion: let the neural net fo­cus on high-level reas­on­ing and in­tent, while let­ting de­term­in­istic CPU tools handle the mech­an­ical veri­fic­a­tion.

Com­pilers (es­pe­cially with strong type check­ers), lin­ters, and test suites ex­ecute in mil­li­seconds with ab­so­lute cer­tainty. Burn­ing ex­pens­ive, prob­ab­il­istic tokens on veri­fic­a­tion that a local bin­ary handles in mi­cro­seconds is waste­ful in the ex­treme.

This same con­vic­tion also drives my pref­er­ence for dense lan­guages such as Haskell. Strict types, pur­ity as a first prin­ciple, and a dense syn­tax help to keep func­tions small, isol­ated, and fo­cused. It al­lows an en­gin­eer to hold a func­tion in their work­ing memory, reason about its be­ha­viour, and en­sure its cor­rect­ness. From the out­side it is enough to know that the types match. Af­ter­wards, type sig­na­tures alone dic­tate how to com­pose func­tions, rarely re­quir­ing sub­sequent re­pairs due to un­fore­seen de­fects.

What is next: leveraging the speedup gains from agentic development and corporate fit
#

With de­term­in­istic guard­rails gov­ern­ing both code qual­ity and in­sti­tu­tional memory, a curi­ous dy­namic emerges. The bot­tle­neck shifts up­wards from code gen­er­a­tion to co­ordin­at­ing con­cur­rent, sens­ible streams of work.

Ad­di­tion­ally, with the ap­proach of us­ing ADR and Re­search Ques­tions as out­lined in this art­icle, the in­sti­tu­tional memory re­mains siloed in a single re­pos­it­ory, whereas ar­chi­tec­tural prin­ciples, designs, and or­gan­isa­tional con­ven­tions span the en­tire or­gan­isa­tion and there­fore mul­tiple pro­jects. The cur­rent ap­proach still lacks a clean mech­an­ism to un­lock that higher-level in­sti­tu­tional memory to the agent without flood­ing its con­text win­dow with (po­ten­tially) ir­rel­ev­ant his­tory.

These two chal­lenges are where an act­ive body of re­search is cur­rently dir­ec­ted, and what I will fo­cus on next.10

Conclusion
#

I like to end with some­thing that has been cyc­ling in my head all along and my AI quoted back to me when re­view­ing this con­clu­sion:

soft­ware en­gin­eer­ing has never been about syn­tax; it has al­ways been about man­aging fal­lible, context-bounded in­tel­li­gence, whether made of car­bon or sil­icon.

(Gem­ini, when re­peat­ing back what I told it above.)

Soft­ware Pro­gram­ming has tra­di­tion­ally centred on syn­tax, al­gorithms, and code gen­er­a­tion. In that nar­row do­main, mod­ern mod­els ex­cel: they pro­duce func­tional routines, wire up lib­rar­ies, and spit out syn­tax with as­ton­ish­ing ve­lo­city. On that level, pro­gram­ming as we know it has be­come very niche.

Con­versely, Soft­ware En­gin­eer­ing has al­ways be­gun where raw pro­gram­ming ends.11 It en­com­passes es­tab­lish­ing ar­chi­tec­tural bound­ar­ies, align­ing tech­nical trade-offs with busi­ness pri­or­it­ies, and man­aging fal­lible, context-bounded in­tel­li­gence across time.

Al­though agents are tire­less pro­gram­mers, they pos­sess zero busi­ness con­text. They lack the know­ledge whether a re­pos­it­ory is a throwaway pro­to­type that needs to ship by Fri­day or a core pay­ment en­gine where a subtle edge case cre­ates legal li­ab­il­ity. En­gin­eer­ing tran­scends syn­tax be­cause it de­mands com­mer­cial judge­ment: know­ing when to ac­cept tech­nical debt, how to bal­ance el­eg­ance against com­mer­cial dead­lines, and which prob­lems ac­tu­ally mat­ter to the busi­ness. In­sti­tu­tional memory (cap­tured in our ADRs and Re­search Ques­tions) is simply the dur­able re­cord of those busi­ness trade-offs. The agent can gen­er­ate the syn­tax, but the en­gin­eer must sup­ply the stra­tegic con­text: the ar­chi­tec­tural bound­ar­ies, the busi­ness pri­or­it­ies, and the long-term in­tent that code alone can never ex­press.

Ul­ti­mately, in the con­text of soft­ware en­gin­eer­ing, hu­man en­gin­eers and autonom­ous agents are far more alike than we might care to ad­mit. Both are fal­lible, context-bounded in­tel­li­gences prone to lapses in at­ten­tion, re­cency bias, and un­stated as­sump­tions. The hard-won dis­cip­lines of dec­ades of soft­ware en­gin­eer­ing (clear bound­ar­ies, ex­pli­cit con­tracts, auto­mated in­vari­ant test­ing, and dur­able re­cords of in­tent) were al­ways meant to safe­guard us from our own lim­it­a­tions. It turns out nicely that these ex­act same dis­cip­lines are also what al­low AI to suc­ceed. Hope­fully this marks the time where we are able to bring these prac­tices in daily use.

What we need to fig­ure out next is how to lever­age the im­mense speed gains agen­tic pro­gram­ming gives us while stay­ing sane as en­gin­eers ourselves.

Postscript
#

This blog post is it­self an ex­ample of that dy­namic: mak­ing some­thing of higher qual­ity in the same amount of time. I spent about a day writ­ing this post. Us­ing LLMs in a tar­geted way al­lowed me to test dif­fer­ent phras­ing and let the model hunt for in­con­sist­en­cies in my ar­gu­ments. All text was hand-written (or rather, typed), then re­viewed along­side Gem­ini to re­fine lo­gical flow, prune me­an­der­ing thoughts, and source primary ref­er­ences. I hope you en­joyed the jour­ney.


  1. M. Sharma et al., “To­wards Un­der­stand­ing Sy­co­phancy in Lan­guage Mod­els”, arXiv:2310.13548, 2023; E. Perez et al., “Dis­cov­er­ing Lan­guage Model Be­ha­vi­ors with Model-Written Eval­u­ations”, arXiv:2212.09251, 2022. back

  2. C.-P. Hsieh et al., “RULER: What’s the Real Con­text Size of Your Long-Context Lan­guage Mod­els?”, arXiv:2404.06654, 2024. back

  3. C. Sad­owski et al., “Tri­corder: Build­ing a Pro­gram Ana­lysis Eco­sys­tem”, Pro­ceed­ings of the 37th In­ter­na­tional Con­fer­ence on Soft­ware En­gin­eer­ing (ICSE), pp. 598–608, 2015. back

  4. M.M. Leh­man, “Pro­grams, Life Cycles, and Laws of Soft­ware Evol­u­tion”, Pro­ceed­ings of the IEEE, 68(9), pp. 1060–1076, 1980; D.E. Perry and A.L. Wolf, “Found­a­tions for the Study of Soft­ware Ar­chi­tec­ture”, ACM SIGSOFT Soft­ware En­gin­eer­ing Notes, 17(4), pp. 40–52, 1992; M.W. God­frey and Q. Tu, “Evol­u­tion in Open Source Soft­ware: A Case Study of Linux”, IEEE In­ter­na­tional Con­fer­ence on Soft­ware Main­ten­ance (ICSM), pp. 131–142, 2000; G. Kroah-Hartman, “Linux Ker­nel De­vel­op­ment Model”, The Linux Ker­nel Doc­u­ment­a­tion, 2018; D. Spinel­lis and P. Avger­iou, “Evol­u­tion of the Unix Sys­tem Ar­chi­tec­ture: An Ex­plor­at­ory Case Study”, IEEE Trans­ac­tions on Soft­ware En­gin­eer­ing, 47(6), pp. 1134–1163, 2021. back

  5. R. Hu et al., “Schrödinger’s Code Re­pos­it­ory: Eval­u­at­ing LLMs on Dy­namic Code­bases”, arXiv:2406.03444, 2024; R. Brooks, “To­wards a the­ory of the cog­nit­ive pro­cesses in com­puter pro­gram­ming”, In­ter­na­tional Journal of Man-Machine Stud­ies, 18(6), pp. 543–554, 1983. back

  6. D. Ko­cetkov et al., “The Stack: 3 TB of Per­missively Li­censed Source Code”, Trans­ac­tions on Ma­chine Learn­ing Re­search, 2022; F. Cas­sano et al., “MultiPL-E: A Scal­able and Poly­glot Ap­proach to Bench­mark­ing Neural Code Gen­er­a­tion”, IEEE Trans­ac­tions on Soft­ware En­gin­eer­ing, 49(7), pp. 3675–3691, 2023. back

  7. M. Za­haria et al., “The Shift from Mod­els to Com­pound AI Sys­tems”, Com­mu­nic­a­tions of the ACM, 67(6), pp. 24–27, 2024; L. Chen, M. Za­haria, and J. Zou, “FrugalGPT: How to Use Large Lan­guage Mod­els While Re­du­cing Cost and Im­prov­ing Per­form­ance”, arXiv:2305.05176, 2023. back

  8. E.W. Stein and V. Zwass, “Ac­tu­al­iz­ing Or­gan­iz­a­tional Memory with In­form­a­tion Sys­tems”, In­form­a­tion Sys­tems Re­search, 6(2), pp. 85–117, 1995; P. Kruchten, H. Ob­bink, and J. Stafford, “The Past, Present, and Fu­ture for Soft­ware Ar­chi­tec­ture”, IEEE Soft­ware, 23(2), pp. 22–30, 2006. back

  9. Nel­son F. Liu et al., “Lost in the Middle: How Lan­guage Mod­els Use Long Con­texts”, Trans­ac­tions of the As­so­ci­ation for Com­pu­ta­tional Lin­guist­ics, 12, pp. 157–173, 2024. back

  10. T.R. Sumers et al., “Cog­nit­ive Ar­chi­tec­tures for Lan­guage Agents”, Trans­ac­tions on Ma­chine Learn­ing Re­search, 2024; S. Hong et al., “MetaGPT: Meta Pro­gram­ming for A Multi-Agent Col­lab­or­at­ive Frame­work”, ICLR, 2024. back

  11. T. Win­ters, T. Man­shreck, and H. Wright, Soft­ware En­gin­eer­ing at Google: Les­sons Learned from Pro­gram­ming Over Time, O’Reilly Me­dia, 2020; F.P. Brooks Jr., The Myth­ical Man-Month: Es­says on Soft­ware En­gin­eer­ing, Addison-Wesley, 1975. back