The Guy in the Basement
Or: why your agent forgets, why it derails, and why a better model won't fix it
The bug that made me want to write this one was an assistant that said hello twice.
A student opens their lesson. The tutor greets them. The student types “ok”. The tutor greets them again, warmly, by name, as though they had just walked in.
My first thought was that the model was being dim. It wasn’t. The instruction to greet the student was sitting in the system prompt, and the system prompt goes out on every single call. The model had no idea it had already said hello, because as far as it was concerned it had never said anything to anyone in its life. It read an instruction telling it to greet the student, and it did exactly that.
The fix was four lines. We made that section of the prompt conditional, so once the greeting has happened it gets replaced with an explicit instruction never to greet again. Nothing about the model changed. We had been telling it to do something and then acting surprised when it did.
It is an embarrassing bug, and it is also a small version of nearly every agent failure I have dealt with in production. Plenty of people are building agents right now and not many of them can tell you why theirs break.
The explanations you get tend to be vibes. The model isn’t smart enough. The prompt needs work. Wait for the next release. Sometimes that’s true. Usually it isn’t. Most agent failures I’ve seen in production came from a misunderstanding of what a language model actually is. Once you have the right mental model, a whole class of bugs stops being mysterious and starts being obvious.
What follows is the mental model I use. It isn’t a beginner’s simplification, it’s what I actually have in my head when I design these systems.
There’s a guy in the basement
Imagine a very well-read person locked in a windowless basement. He has read most of the internet. He is genuinely brilliant at language, at reasoning, at pattern recognition, and he has somehow come out of it still willing to help.
He has no memory, no view of the outside, and no way to act on the world.
The only thing that ever happens is this: a note gets slid under the door. He reads it, writes a response on the back, slides it back out. Then he forgets everything: the note, his answer, that the exchange happened at all. The next note arrives to a man who has never seen a note before in his life.
That’s a language model. The chat interface, the memory, the tools, the agent that seems to pursue goals over hours: all of that is machinery built outside the basement door.
Hold onto that image, because five things fall out of it, and they explain almost everything.
One: he only ever writes the next token
The guy doesn’t compose an answer and then write it down. He writes one token, roughly a word fragment, then looks at everything written so far, including what he just wrote, and picks the next one. Then again. Then again.
That is the whole mechanism. Nothing is executing a plan and nothing is being retrieved from storage. The apparent coherence is the by-product of doing next-token prediction extremely well, thousands of times in a row.
Two practical consequences:
The first is that he can’t unsay things. Once a token is on the page it’s part of the input for every token after it. An early wrong turn doesn’t get corrected, it gets built on. This is why a response that starts badly usually ends badly, and why retrying often beats arguing.
The second is that you can steer the shape of the output much more reliably than its truth. He’s very good at continuing patterns. If your prompt establishes a format, he’ll follow it. That’s real leverage. But the same machinery that makes him follow format also makes him confidently continue a pattern into something false. The plausible next token isn’t always the true one. So hallucination isn’t a separate defect bolted onto the system. It is the same pattern-continuation ability running over a gap in what the model knows.
Two: he is stateless
This is where most people building their first agent go wrong.
The model does not remember your conversation. When you send message ten in a chat, the API doesn’t nudge a running process that’s been listening the whole time. It starts a fresh guy in a fresh basement, and slides him a note containing all ten messages.
If you’ve used the API you’ve seen this, though it’s easy to look straight past what it means:
const response = await client.messages.create({
model: "...",
messages: [
{ role: "user", content: "My name is Irek." },
{ role: "assistant", content: "Nice to meet you, Irek." },
{ role: "user", content: "What's my name?" },
],
});
The model “remembers” your name in exactly the sense that it can read it in the note in front of it. Nothing more. Every turn, the entire history gets re-sent. Every turn, a brand new person reads it cold.
Once you internalise this, a lot of behaviour stops being spooky. The model didn’t forget what you told it twenty minutes ago: that text simply isn’t on the note anymore. It didn’t change its mind about the architecture. The earlier reasoning fell out of the window and it re-derived a different answer from what was left.
“Memory” in every product you’ve used is a feature of the application, not the model. Somebody wrote code to decide what goes back on the note. It feels like being remembered. It is closer to being re-briefed, every ninety seconds, by an extremely polite stranger.
Three: the context window is the note
The note has a size limit. That limit is the context window, and everything the model knows for that one call lives inside it: the system prompt, the conversation history, retrieved documents, tool definitions, tool results, and the response being generated.
Which reframes the job. If the model only knows what’s on the note, then the interesting engineering question is not what should I ask? It’s what should be on the note, and what should not?
And here is the part people get backwards: more context is not better.
It’s tempting to treat a large window as a solved problem and just dump everything in. In practice that degrades results. Relevant instructions get diluted by irrelevant material. Attention spreads thin across a long note, and things in the middle get weighted less than things at the edges. Contradictions creep in: something you said at turn three quietly conflicts with something at turn thirty, and the model has no way to know which one you still mean.
A focused note gets better results than a comprehensive one, reliably enough that I now treat it as the default.
The urge to paste everything in is the same urge that produced the enterprise dashboard with forty-one widgets, on the reasoning that the user might want any of them. It did not work on the user either.
Frontend people will recognise this. Decide what to show, decide the order, decide what to leave out, respect a hard budget. That’s information architecture. Frontend engineers have been doing this for twenty years. We just did it for a screen and a human, and now we’re doing it for a note and a machine. I think this is the single most transferable skill from frontend into AI work, and it’s badly underrated.
Four: reasoning is thinking out loud on the note
If the model only writes the next token, and it can read what it has already written, then something interesting follows: giving it room to write intermediate steps materially improves the answer.
That’s all “reasoning” is. The model writes its working out before the conclusion, and each of those tokens becomes input for the next. It’s not a hidden deliberation layer. It’s the guy scribbling on the note before committing to an answer, and the scribbles genuinely help, because token n now conditions on the reasoning at token n−1 instead of leaping straight to a conclusion.
It’s also why reasoning costs you: those tokens occupy the note. More thinking means less room for everything else. There’s a real trade, and it’s not always worth it.
Five: the agent is the loop outside the basement
Now the leap that matters.
The model cannot do anything. It reads and writes text. It cannot query your database, call your API, or reserve a server.
An agent is the code standing outside the basement door running a loop:
Slide a note under the door, including a list of tools available and what each one does
The guy writes back: either an answer, or “call
search_infrastructurewith these arguments”The code outside, not the model, actually makes that call, in the real world, with real permissions
Write the result onto a new note, along with everything relevant from before
Slide it back under the door
Repeat until done, or until you stop it
That’s it. That’s an agent. There’s no autonomy inside the model; the autonomy lives in the loop. When people say an agent “decided” to do something, what happened is the model emitted a tool call and a piece of ordinary software executed it.
This is enormously clarifying for two reasons.
Security lives outside the basement. The model’s tool call is a request, not an action. Whatever runs that request decides what’s permitted. So authorisation belongs in the layer that executes, enforced against the actual user’s identity, not in the prompt. Prompts are not a security boundary. The guy in the basement is easy to talk into things; the code outside the door is not, and that’s where the check goes.
Tool count is a context problem. Every tool definition sits on the note, eating budget and competing for attention. Hand the model fifty tools with overlapping names and it picks wrong. Not because it’s dumb, but because you handed it an ambiguous menu. You have built the eleven-page laminated diner menu, and you are surprised that everybody keeps ordering the burger.
I’ve done this at scale, and the numbers were fifty-one down to eighteen. The first version had a tool for everything and chose badly between them; the version that shipped had a smaller, deliberately designed surface and reached further than the large one did. Cutting the menu is one of the highest-leverage things you can do to an agent, and it feels like removing features right up until you watch it work. That’s a whole post of its own, and it’s the next one.
So how do you make him reliably smart?
Put the five together and the real problem is exposed.
The guy is brilliant. He is also amnesiac, blind, and working from a single sheet of paper you wrote. Every failure you’re debugging is a failure of what was on the note: the derailed refactor, the agent that forgot the constraint you gave it ten steps ago, the confident wrong answer.
You can’t make the model smarter. The only thing you control is the quality of what it reads.
Which is why, in production, the thing that consistently rescues long-running agent work isn’t a better prompt. It’s structure that lives in the repository and gets fed to the model deliberately:
A spec: what we’re building and, crucially, what we’ve decided not to do. Written down, versioned, reviewed like code. Ambiguity in the spec becomes drift in the output, every time.
A plan: the spec broken into steps, so the agent works on one thing with a clear definition of done, rather than holding an entire feature in its head.
A devlog: a running record of what was done, what broke, what was tried and rejected. This is the memory the model doesn’t have. When the window rolls over, the devlog is what survives.
Decision records: the why. Code tells you what the system does; it almost never tells you which three alternatives were considered and why they lost. That’s exactly the context an agent needs to avoid re-proposing something you already ruled out.
People call this spec-driven development, and the name undersells it. It looks like process, but it’s engineering: you are building a durable, structured context store for a component that has no memory, and then choosing what to load from it on each call.
Worth saying plainly that this is not a thing I invented. It has a name, a growing body of tooling, and people arguing about the right shape of it. GitHub’s Spec Kit is the most visible open-source take; there are others, and there are courses now. I am not going to walk through the canon here, partly because it deserves its own post and partly because what I actually want to show you is narrower and more useful: the specific pieces of this that survived contact with a real deadline in a real codebase. Some of them are standard. Some of them I arrived at because something broke.
What this looks like when you actually do it
I’ve been running this on a production app, an AI tutoring platform where teachers build lessons and students talk to an assistant. Let me show you the parts that earned their place, because most process dies on contact with a deadline and these didn’t.
There’s a startup checklist in a file the agent reads before anything else. Not “here’s the repo, good luck”, but three documents in a fixed order: architecture and common tasks, then current state (what works, what’s broken, what changed recently), then the testing strategy. Every session begins by loading the same note. Consistency in the input turns out to matter more than cleverness in the prompt.
The rules are written as prohibitions rather than aspirations. These four carry the most weight:
No new file without a reuse analysis.
Name the files you examined and why they can’t be extended.
No rewrite where a refactor would do.
No generic advice. Every suggestion citesfile:line.
No ignoring existing architecture. Load the patterns before proposing changes.
Notice these aren’t style preferences. Each one targets a specific, predictable failure mode of a stateless model: it can’t see your codebase, so left alone it will confidently invent a parallel version of something you already have.
The reuse rule in particular was bought and paid for. An audit of my repo at one point found a hundred and thirteen instances of duplicated logic, around eleven hundred and sixty wasted lines: four separate implementations of relative-date formatting, the same modal-close logic written three times, error handling reinvented per feature. Not because anyone was careless. Because every session began with a component that had never seen the codebase, needed a function, couldn’t find it, and wrote a perfectly good one. Thirty times over. It is the most diligent junior engineer you have ever hired, turning up each morning with absolutely no recollection of yesterday, and being marvellous about it.
The rules aren’t there to make the agent polite. They’re there to force it to look before it builds.
There’s a state machine, so that “done” isn’t a matter of opinion: PLAN → BUILD → DIFF → QA → APPROVAL → APPLY → DOCS. The plan gets approved before any code is written. The diff gets generated before anything is applied. Two gates, both human. Yes, this is slower. So is code review, and we appear to have survived that. This is the single highest-value piece of the whole system, because an agent that goes straight from instruction to applied change is an agent whose mistakes you find later, in production, with no memory of how they got there.
Every task carries a budget, which is the part nobody writes about and everybody needs. A ceiling on max build-QA cycles, max tokens, max wall-clock minutes. Without them an agent will happily burn an hour and forty thousand tokens circling a problem it isn’t going to solve. An agent without a ceiling treats “fix this” as a lifetime appointment. The budget isn’t cost control. It’s a tripwire that tells you to stop and re-plan instead of letting the loop grind.
Decision records live separately from all of it. Why X over Y. This is the file that pays back the most and gets written the least, which is also the situation with tests, and we all know how well that argument goes at four in the afternoon. The code will never tell you which three alternatives lost and why.
And the devlogs, which I would give up last. One per working session, dated. What was done, what broke, what was tried and abandoned. Here’s the actual opening of one of mine, verbatim:
# DEVLOG 2026-03-10
## Session Start
- Previous session: 2026-03-04 (Phase 6-7 voice polish)
- Since then: 17 PRs merged (#46-#63) by other sessions — ElevenLabs TTS, Realtime API, voice UX fixes
- Current branch: `main`
- Context: ElevenLabs grant rejected, need pay-per-use TTS solution
That’s four lines, and it’s a working memory transplant. The model that reads it has never seen this project. Four lines later it knows where things stand, what changed while it was away, and, critically, the constraint driving today’s work. That session ended up migrating the whole text-to-speech layer to a different provider, dropping the cost from roughly $330 a month to $40, and the devlog is the only reason the next session understood why the code suddenly looked different.
None of it touches the model. All of it changes what the model gets to read.
The symmetry worth noticing
The reason a new engineer struggles in a large codebase is the same reason an agent does: the code holds the what, and years of why live in people’s heads. Write the why down and you’ve helped both.
I’ve built onboarding material that walks new hires through years of accumulated architectural history, and I’ve built context stores that let an agent work productively in an unfamiliar repo, and they are very nearly the same artefact. That’s not a coincidence, and it’s a thread worth pulling on another time.
Why this belongs to us
If you write frontend, this is not somebody else’s discipline.
The work is deciding what a consumer sees, in what order, within a hard budget, so it can act correctly. We have always done that. The consumer used to be a person with eyes, and the budget was screen space and attention. Now the consumer is increasingly a model, and the budget is a context window.
It is the same problem rendered for a different consumer.
So the interface isn’t disappearing so much as turning inside out. Instead of designing one interface for everyone, we’re going to spend more of our time defining the systems and the data that let an interface be generated for each person. That’s a move up the stack, not a march toward the exit.
But you can’t design for a consumer you don’t understand. So start here: there’s a guy in the basement, he forgets everything, and all he ever sees is the note you wrote him.
He’ll say hello twice if that’s what the note tells him to do. He isn’t being dim. He’s being obedient, to the letter, to a piece of paper you wrote and forgot you wrote.
Write better notes.
Next in this series: what happens when the note has to be assembled from ten thousand documents you can’t fit on it: retrieval, embeddings, and the ways chunking quietly ruins your day.

