A Tale of Three Months with AI: Guardrails, and How We Are More Alike Than We
A Tale of Three Months with AI: Guardrails, and How We Are More Alike Than We Wish to Admit
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
Close menu Writing
Projects
Tags
About
A Tale of Three Months with AI: Guardrails, and How We Are More Alike Than We Wish to Admit
20 October 2026·23 mins
Ai Programming
Table of ContentsThe greenfield honeymoonAsserting control
From the green pasture into the mud
The Engineering harnessInstitutional Knowledge
What is next: leveraging the speedup gains from agentic development and corporate fit
ConclusionPostscript
Table of ContentsThe greenfield honeymoonAsserting control
From the green pasture into the mud
The Engineering harnessInstitutional Knowledge
What is next: leveraging the speedup gains from agentic development and corporate fit
ConclusionPostscript
As I promised in A fresh leaf, I want to share my experiences with using LLMs for agentic development in the last three months. Summarised: I started as an initial sceptic, became quite enthusiastic, and am now moderately enthusiastic and guarded for the future. Allow me to take you through my journey.
Coming from a background of rigorous engineering with up-front thinking about program structures, algorithms, and types, it seemed incomprehensible to me how a jumped-up neural net could generate working code, let alone sensible systems. How to write understandable software has been hotly debated for decades, continuously shifting as new languages, type systems, and ecosystems introduce different trade-offs.
Early in my career, coming from academia, I was fixated on finding the “just-right” solution: elegant abstractions, strict correctness, and formal soundness. As problems grew larger and messier, and time became scarce, holding out for perfection became an obstacle. I found far more satisfaction in achieving something functional, with elegance as a welcome second. Having both overlap is fantastic; achieving the actual goal remains both a priority and a necessity.
This shift in perspective explains why LLMs, and agentic development, eventually proved so attractive to me. Expecting a model to generate a “just-right” architecture, code, or text only causes frustration. Conversely, it acts as an engine to reach concrete milestones in areas I was always curious about without having the time to do things “properly”, or simply eliminating the toil of writing boilerplate when I can describe my intent with less mechanical precision.
If your goal is simply to achieve something concrete, they are remarkably effective. That spans from something as simple as integrating an unfamiliar library to breaking ground in an unfamiliar problem domain where you lack the tools and vocabulary.
My catalyst was a device sitting in my living room. After reports emerged that its successor was aggressively hostile to user privacy, I wanted to verify whether my own hardware behaved the same way. Since I had no background in firmware inspection or packet analysis on embedded devices, I was initially discouraged. Luckily, I noticed right then that I had a free trial month of Codex lying around, so I decided to see how far these so-called “amazing frontier” models could carry me.
I started out by talking in ChatGPT and asking questions on how to solve my problem.
This quickly became using Codex to work on my laptop, letting it download images, and running local scripts. The way of work was very interactive, prompt-based, and relatively unstructured and unguided.
I was amazed by how well the model worked when told to achieve a very specific goal and even more so when giving the context and constraints in a single prompt: as long as the prompt wasn’t too big.
When I pointed out errors, the models complimented me lavishly on the discovery and apologised profusely for the defect.1 Yet that contrition yielded no durable change in their subsequent behaviour. It was just text, learned from reams of documents.
As soon as the codebase and program to detect any nefarious behaviour was growing, I noticed the performance of the models degrade.2 Responses lost their initial speed, precision, and correctness. The model began overlooking interdependent updates that required lock-step coordination, shedding context established in earlier turns. In other words, it required more hand-holding, for which I had neither time, nor patience, nor appetite.
At that point, I also started working on some other ideas that had been lying dormant in my head. I used an agent to create a Terraform project to spin up a small timeseries-db backup in Google Cloud constrained such that it would stay within the free tier. Creating the provisioning scripts was the work of mere moments. It checked the exact sizing requirements for staying within the free tier including some caveats, whipped the Terraform code together, and added the Tailscale integration. All from a simple prompt of what I wanted to achieve and the fact that I wanted to be able to reconstruct the VM from a single command. That was awesome.
It was here that I first started suspecting a deeper link between models and humans: it all came down to how bounded the task was. Over the years, I noticed that when you ask a person an open-ended question, their answer is mostly influenced by their recent history, their emotional state, and whatever problem they were just wrestling with. Models work almost identically. Ask them a broad, open-ended question and you get a generic response shaped by their pre-trained weights and immediate context window. Getting either a human or a model to think outside that default box requires tight, explicit constraints, and careful prodding (or prompting).
One of the often touted best-practices I encountered in the beginning was to have a three-phase approach. Use one agent to plan the work interactively, producing a handover document. Then task a second agent with implementing that handover document unattended. Finally pass the control to a review agent, which gets the diff and the plan, and reviews it. The obvious improvement is to make the last two communicate and work in a loop until no more findings are established.
I first tried automating this division of work (planning, implementation, and review) by writing conventions as prose in AGENTS.md. As code grew and threads grew longer, however, prose instructions became less effective and easily ignored by the model. To take this workflow further, I wanted to combine the specialised strengths of different models: Codex as the fast, local workhorse for writing code, and Gemini as the thoughtful reviewer for architecture. To let them talk directly to each other, I built gemini-mcp.
Here I hit another quirk of model psychology: autonomous models possess such supreme self-certainty that review triggers require explicit programmatic conditions. A loose prose directive like “Ask Gemini for help when uncertain” generated precisely zero invocations; the model was never uncertain. Conversely, a deterministic circuit breaker (“Attempt to solve each problem a maximum of three times; if still failing, consult Gemini via MCP, implement its recommendations, and repeat”) reliably prompted Codex to escalate for second opinions.
This put me with an immediate security challenge: because I was using this setup to assess the privacy of the TV, I needed a classifier to prevent prompts between models from looking like offensive security probes, which could get my Google account suspended.
Scaffolding the classifier was quick work. Making it function reliably, however, involved dozens of subtle architectural choices that flew past too fast for me to properly evaluate. Steering the model through prose in AGENTS.md simply wasn’t giving me the control I needed. That was the moment I turned to systematic, deterministic tooling, starting with a simple directive: run make check after every modification to verify that the project was still coherent.
This worked so well that ever since I have been extending what is behind this check continuously. Relying on what I already knew worked well in larger projects with multiple contributors, I decided to use strict tools to check as much as possible. This meant automatic linting, formatting, and upgrades of the different types of files in the project: Markdown, code, YAML files, ADRs, formatting, modernisation of Go, and dependency upgrades. Moreover, I decided to take more out of the hands from the agents and not even bother them with formatting and easy to fix linting errors by automatically running formatting, automatic imports, and adding --fix to golangci-lint already as part of the check step to ensure all easy fixes were implemented before the agent even saw the result.3
I introduced this rigorous tracking in my first big vibe-coded project, semantic-editor. The whole point of the project was to let the agent worry about intent while delegating the nitty-gritty editing work to deterministic AST tools. I applied that same mindset to the repository itself, using strict checks for the markdown, the ADRs, and the schemas. This approach worked pretty well, and the project was progressing smoothly.
Around that time, another personal project came into my head. I had always felt sad that in this age of search engines and lately omniscient (and often wrong) LLMs, my kids didn’t have the experience to just browse an encyclopedia, guided by interest. For me the concrete example of this was Microsoft Encarta ‘97. It gave me endless browsing, learning of all kinds of things, and important to my parents (and now me), relatively safe because it was curated content.
As you may have guessed, I decided to vibe code the answer to this problem as well and make a viewer of Encarta data. Because in-repository decision tracking had operated satisfactorily, I relied on that established harness to guide development.
However, because this was my first GUI app, I sensed it needed another layer of constraints and decision tracking which I dubbed “GUI-invariants” as a specialised form of ADR for GUI decisions such as layout on a more generic level instead of specific decisions. Getting the agent to consider the invariants proved relatively simple by just adding it to the agent instructions. Letting it interpret them in a way was harder, because invariants are inherently generic. So they needed more concrete positive and negative examples and be more prescriptive.
For some reason I had a quirk that I wanted all text to be “properly” type-set such as in TeX. And why not? We are custom coding this project and I have tokens to burn, so let’s have at it. The agent was very helpful in this bounded context. A simple assignment, a starting example algorithm, the specific constraint of the SwiftUI infrastructure, and an oracle to determine whether it did it right. This allowed me to set a simple goal and I could walk away from it.
These three main projects and several side projects, all shared one important property: they were greenfield projects where I individually was the sole owner and decision maker.
In urban planning terms, greenfield land is effortless to traverse and develop, precisely because it is virgin ground: untouched, open, and unencumbered by history. Brownfield sites, by contrast, are contaminated swaths of land burdened by buried foundations, undocumented pipe
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。