# The bill for free software

> AI agents made writing code nearly free, and I'm all in. But building with them, and building products that run on AI, takes more expertise, not less. Free software is real for an app with one or two users. An app with thousands needs a different approach — and it has a price.

By Aleksandr Kondaurov · Oct 27, 2026 · 11 min read
Tags: llm, agents
URL: https://kondaurov.dev/blog/the-bill-for-free-software/

On October 25 a coding agent gave my word game an iPad layout, a landscape mode and a web admin console. One day, three commits. The day itself was easy: a few rounds of looking at the result and telling the agent what to improve. What made it easy came earlier — the harness and the architecture I had set up so the agent knows how code in this project is supposed to be written.

The next day I found one line in my AI assistant's instructions: the current date, down to the hour. Because of that line, once an hour, the AI provider forgot the whole conversation it had been keeping ready and read it again from scratch, at full price. It was the seventh different reason in two and a half months that the same mechanism had quietly broken.

Those two days are the whole post. There's a sentence I keep hearing — software is free now, anyone can have an app made just for them — and the first day looks like proof. The second day, and the setup behind the first, say what it leaves out.

<!-- excerpt -->

## I came around

I want to be clear about where I stand, because this isn't a skeptic's post. In April I wrote that [LLMs don't write production code for me](/blog/best-use-of-llms/). Today 849 of the 947 commits in my main repository carry an AI co-author line.

I went through the stages. Large language models are a revolution, and agents built on them are an amazing tool for routine work, done on their own. A frontend, a backend for that frontend, a settings screen, the glue between two services, a build pipeline: that part really did become close to free. If all you need is an app for yourself, describe it and you'll have it by evening.

So the myth isn't made up. It just stops one step too early.

## Cheaper to write, not to own

Writing software got cheap. Owning it didn't. Owning means keeping it working, trusting it with real data, and changing it without breaking it, and that comes with a bill:

- **Tokens.** Every model call in your product, every day, for as long as it runs. How well your prompts are laid out can change that line severalfold.
- **Subscriptions.** The coding agents themselves, often more than one, because keeping up means trying what the others do.
- **The expert.** The human who knows why the AI bill jumped, which model fits which job, when the agent is going in circles and what to measure. This is the largest item, and it doesn't show up on any invoice.
- **Upkeep.** The rules, guides and tests that make the agent useful age every month, along with the models and tools underneath.

<svg viewBox="0 0 400 330" style="width:100%;max-width:480px;display:block;margin:1.5rem auto;font-family:var(--font-sans);font-size:15px" role="img" aria-label="Writing software with the agent: a day, nearly free. Owning it comes with a bill: tokens for every model call, subscriptions to the agents, the expert who knows where to look — the largest item, on no invoice — and upkeep of the rules and tests, which age every month">
  <defs>
    <marker id="bill-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0 0 L10 5 L0 10 z" fill="var(--accent)"/>
    </marker>
  </defs>
  <rect x="4" y="4" width="392" height="66" rx="12" fill="var(--bg-raised)" stroke="var(--border-strong)"/>
  <text x="24" y="44" fill="var(--text)" font-size="19" font-weight="700">write it</text>
  <text x="376" y="35" text-anchor="end" fill="var(--text-secondary)">a day with the agent</text>
  <text x="376" y="56" text-anchor="end" fill="var(--text-muted)">nearly free</text>
  <path d="M200 70 V96" stroke="var(--accent)" stroke-width="2" marker-end="url(#bill-arrow)"/>
  <rect x="4" y="102" width="392" height="224" rx="12" fill="var(--accent-subtle)" stroke="var(--accent)"/>
  <text x="24" y="136" fill="var(--accent)" font-size="19" font-weight="700">own it — the bill</text>
  <line x1="24" y1="150" x2="376" y2="150" stroke="var(--accent)" stroke-dasharray="3 5"/>
  <text x="24" y="182" fill="var(--text)">tokens</text>
  <text x="376" y="182" text-anchor="end" fill="var(--text-muted)">every model call</text>
  <text x="24" y="220" fill="var(--text)">subscriptions</text>
  <text x="376" y="220" text-anchor="end" fill="var(--text-muted)">the agents themselves</text>
  <text x="24" y="258" fill="var(--text)" font-weight="700">the expert</text>
  <text x="376" y="258" text-anchor="end" fill="var(--text-muted)">largest, on no invoice</text>
  <text x="24" y="296" fill="var(--text)">upkeep</text>
  <text x="376" y="296" text-anchor="end" fill="var(--text-muted)">rules and tests age</text>
</svg>

Software is free the way a puppy is free. Getting one costs nothing. Looking after it is the price.

The expert is the biggest item because the hard part comes in two kinds. One is building software that uses AI inside: an assistant, a resume writer, anything where a model makes decisions while real people use it. The other is building software with agents. The date line from the first paragraph belongs to the first kind, so I'll start there.

## One mechanism, seven ways to break

When your product sends a model the same long beginning again and again — instructions, tool descriptions, the conversation so far — the provider can keep that beginning ready and charge about a tenth of the price to read it again. This is the prompt cache. It's the single biggest lever on what an AI product costs, and it has one rule: change one character early on, and everything after it is forgotten and paid for again at full price. Writing into the cache even costs a quarter more than sending the same text without it.

<svg viewBox="0 0 400 360" style="width:100%;max-width:480px;display:block;margin:1.5rem auto;font-family:var(--font-sans);font-size:15px" role="img" aria-label="Two requests with the same long beginning. Within the hour, the instructions, tools and conversation so far are read from the cache at about a tenth of the price, and only the new message is paid in full. An hour later the time line at the very top has changed, so everything after it is read again at full price and written into the cache again, a quarter more">
  <text x="4" y="20" fill="var(--text-secondary)" font-weight="600">the same hour</text>
  <rect x="4" y="30" width="268" height="100" rx="10" fill="var(--accent-subtle)" stroke="var(--accent)"/>
  <text x="138" y="60" text-anchor="middle" fill="var(--text)" font-weight="600">instructions, tools,</text>
  <text x="138" y="80" text-anchor="middle" fill="var(--text)" font-weight="600">the conversation so far</text>
  <text x="138" y="112" text-anchor="middle" fill="var(--accent)">from the cache: ~1/10 price</text>
  <rect x="280" y="30" width="116" height="100" rx="10" fill="var(--bg-raised)" stroke="var(--border-strong)"/>
  <text x="338" y="70" text-anchor="middle" fill="var(--text)" font-weight="600">new</text>
  <text x="338" y="90" text-anchor="middle" fill="var(--text)" font-weight="600">message</text>
  <text x="338" y="112" text-anchor="middle" fill="var(--text-muted)">full price</text>
  <text x="4" y="172" fill="var(--text-secondary)" font-weight="600">an hour later</text>
  <rect x="4" y="182" width="84" height="100" rx="10" fill="var(--bg-raised)" stroke="var(--accent)" stroke-width="2.5"/>
  <text x="46" y="222" text-anchor="middle" fill="var(--accent)" font-weight="700">time:</text>
  <text x="46" y="242" text-anchor="middle" fill="var(--accent)" font-weight="700">15:00</text>
  <text x="46" y="264" text-anchor="middle" fill="var(--text-muted)" font-size="13">changed</text>
  <rect x="96" y="182" width="176" height="100" rx="10" fill="var(--bg-raised)" stroke="var(--border-strong)" stroke-dasharray="5 4"/>
  <text x="184" y="216" text-anchor="middle" fill="var(--text)" font-weight="600">everything after it</text>
  <text x="184" y="242" text-anchor="middle" fill="var(--text-muted)">full price again,</text>
  <text x="184" y="262" text-anchor="middle" fill="var(--text-muted)">rewritten: +25%</text>
  <rect x="280" y="182" width="116" height="100" rx="10" fill="var(--bg-raised)" stroke="var(--border-strong)"/>
  <text x="338" y="222" text-anchor="middle" fill="var(--text)" font-weight="600">new</text>
  <text x="338" y="242" text-anchor="middle" fill="var(--text)" font-weight="600">message</text>
  <text x="338" y="264" text-anchor="middle" fill="var(--text-muted)">full price</text>
  <text x="200" y="316" text-anchor="middle" fill="var(--text-secondary)">change one character early on,</text>
  <text x="200" y="338" text-anchor="middle" fill="var(--text-secondary)">and the rest is paid for again</text>
</svg>

It sounds simple. Here is how it went for me, in order.

1. **August: a number at the top.** The assistant's instructions showed the user's remaining budget. It changed every turn, so every turn re-wrote the whole conversation into the cache. Writes dwarfed reads.
2. **August: a sliding window of pictures.** The model saw the last few images of a chat, and every new image shifted the window by one. That rewrote an older message, and the cache was gone from there on. On the invoice, the cache-write line was four times the cache-read line, and a write costs twelve and a half times a read.
3. **October 1: the wrong first line.** My resume writer works in rounds, and every prompt started with "Iteration N". So the 9–16 thousand tokens of the person's work history, the same in every round, were never read from the cache.
4. **The same day: the wrong explanation.** I moved the history into a message of its own, as the obvious reading of the docs suggested. The cache still read 1,354 of about 7,600 tokens, before and after. The real rule was that OpenAI only saves an entry where a request ends or where you explicitly mark one.
5. **October 21: a missing mark on the other provider.** Anthropic saves only at marked points too, but by different rules. The assistant's conversations had no mark at the end of the history, and three turns in a row read 0 of about 17,700 tokens from the cache. With the mark, the second and third read 17,600.
6. **October 26: how hard the model thinks.** The app picked the model's effort anew for every message, and a changed effort breaks the cache. Consecutive picture turns started cold 40% of the time when the effort changed, against 23% when it didn't.
7. **October 26: the hour.** The date line from the first paragraph.

Every one of these was invisible in the product. Answers were fine, nothing crashed. The only sign was in the numbers, so I built a check that holds it now: a test where a second person's conversation reads 19,391 of 19,576 tokens from the cache the first person left. That's the kind of [harness an AI-native app needs](/blog/harness-evals-labs-judge/), and building it took more engineering than the features it protects.

The two providers didn't even agree on how long the cache lives: five minutes on one, thirty on the other. On the same kind of work, that was the difference between 70% and 35% of turns starting cold. You have to know both, and both change.

That's building on AI. Building with it has its own version of the same problem: the machinery is invisible until you learn where to look.

## The colleague who forgets everything

An agent is a strong new hire who knows nothing about your project and forgets everything between conversations. Everything you'd tell a person in their first month has to be written down, and every session starts by reading it again.

In my repository that writing is now a project of its own: 13 files of house rules, 9 skills (step-by-step guides for recurring jobs), 37 READMEs that work as maps, and 75 records of design decisions with the reasons behind them. None of it is code, and all of it ages. Commits that change the agent's instructions went 1, 1, 11, 18, 26, 53 from May to October. In October, one commit in six was about the agent's environment, not the product.

<svg viewBox="0 0 400 300" style="width:100%;max-width:480px;display:block;margin:1.5rem auto;font-family:var(--font-sans);font-size:15px" role="img" aria-label="Commits that change the agent's instructions, per month from May to October: 1, 1, 11, 18, 26, 53">
  <text x="4" y="20" fill="var(--text-secondary)" font-weight="600">commits that change the agent's</text>
  <text x="4" y="42" fill="var(--text-secondary)" font-weight="600">instructions, by month</text>
  <line x1="4" y1="262" x2="396" y2="262" stroke="var(--border-strong)"/>
  <rect x="22" y="259" width="44" height="3" rx="6" fill="var(--accent)"/>
  <text x="44" y="251" text-anchor="middle" fill="var(--text)" font-weight="700">1</text>
  <text x="44" y="286" text-anchor="middle" fill="var(--text-muted)">May</text>
  <rect x="84" y="259" width="44" height="3" rx="6" fill="var(--accent)"/>
  <text x="106" y="251" text-anchor="middle" fill="var(--text)" font-weight="700">1</text>
  <text x="106" y="286" text-anchor="middle" fill="var(--text-muted)">Jun</text>
  <rect x="146" y="227" width="44" height="35" rx="6" fill="var(--accent)"/>
  <text x="168" y="219" text-anchor="middle" fill="var(--text)" font-weight="700">11</text>
  <text x="168" y="286" text-anchor="middle" fill="var(--text-muted)">Jul</text>
  <rect x="208" y="204" width="44" height="58" rx="6" fill="var(--accent)"/>
  <text x="230" y="196" text-anchor="middle" fill="var(--text)" font-weight="700">18</text>
  <text x="230" y="286" text-anchor="middle" fill="var(--text-muted)">Aug</text>
  <rect x="270" y="179" width="44" height="83" rx="6" fill="var(--accent)"/>
  <text x="292" y="171" text-anchor="middle" fill="var(--text)" font-weight="700">26</text>
  <text x="292" y="286" text-anchor="middle" fill="var(--text-muted)">Sep</text>
  <rect x="332" y="92" width="44" height="170" rx="6" fill="var(--accent)"/>
  <text x="354" y="84" text-anchor="middle" fill="var(--text)" font-weight="700">53</text>
  <text x="354" y="286" text-anchor="middle" fill="var(--text-muted)">Oct</text>
</svg>

Then there are the mechanics of the agent's attention. It sees only what fits into its context, a working memory with a hard limit. When a long session fills it, the tool compresses the conversation into a summary, and sometimes the one detail that mattered doesn't make it in. When the agent hands a search to a helper, the helper starts fresh and doesn't see what the main agent saw. Getting good work out of an agent means knowing where it can't see, and [noticing when it keeps patching instead of thinking](/blog/dont-follow-the-agent/).

That would be manageable if it held still. It doesn't.

## A target that moves

Since mid-May my commits name five different models as co-authors, from two model families, some of them in two context sizes. Each one changed how much the agent can hold, how well it reasons and what it costs. A habit that worked in July wasn't the best way by September.

And that's one vendor. Claude Code, Codex, OpenCode and the rest work in roughly the same way, and they're all moving the same direction, but they're built by different companies. They read their instructions from different files, compress long sessions differently, extend themselves in different ways. In July I folded the separate file one tool reads into the READMEs every tool reads, because keeping two copies of the same knowledge was already a cost.

That's a lot to carry. Whether it's worth carrying depends on how many people use what you build.

## Free for one, not for thousands

Here is the part the myth gets right: none of those seven cache problems matters for an app with one or two users. A broken cache costs them cents. A slightly worse answer, nobody notices. An app made for yourself in an evening is genuinely free, and that's wonderful.

Now multiply. Every one of those mistakes grows with the number of people, the length of their conversations and the number of pictures in them. A line that costs cents for one person is a real budget for thousands, and an answer that's slightly off for one person becomes a pattern of complaints. At that scale you need a different approach: measure every call, read real conversations, test changes against real cases before shipping them, and know which provider rule you're about to trip over.

None of that comes from typing a request into an agent. It comes from someone who knows to look.

## Where this leaves me

I'm not going back. Agents took the routine off my plate, and I build more than I ever have. The revolution is real, and calling it hype would be silly.

But I think it's just as silly to believe these systems will close every gap on their own — solve the engineering, fix the business, and the money will start flowing. They're complex systems with many moving parts, built by different companies that keep moving, and they work as well as the person running them understands them.

The typing became free. The knowing didn't, and that's where the price went.
