I ran an experiment to find out just how useful context can be in the world of apps. I built an app and shipped it on two platforms: same idea, same developer, and a similar agent on each. On paper they were identical, but in practice they turned out very different, and the only difference between them was that one started with its context written down and the other started without it.

One version started with its context written down from day one: a spec and a file telling the agent what the project was like (architecture, conventions, what to respect). The other started with little more than an installation README, and stayed that way for months.

The repository told the story by itself. The version with context had a clean, coherent architecture with no loose ends. The other had decisions made on the fly, half-finished error handling, several abandoned TODOs in the code, and an architecture document that showed up late, once the mess was already made. The one variable that set them apart was context.

Picking up the message from how important it is to specify things so the model doesn't wander off: an agent will always fill information gaps with the first thing that works, so context matters even more, because it decides whether those gaps get filled with information from your project or with inventions that happen to fit.

To keep this from getting abstract, we'll use the same example that runs through the whole series: a shared-expenses app. You know the type: the ones for splitting the bill on a trip or at a dinner with friends. Its MVP is deliberately modest: create a group per event, add an expense saying who paid, split it in a configurable way, see a balance screen showing who owes whom, and settle up.

What's out of v1 has also been decided: no real payments inside the app, no multi-currency, no offline mode. We'll hang everything relevant about context on this app, because good ideas only really sink in when they run into a concrete feature.

What is an app's “context,” really?

An app's context is the contract the agent reads before touching the code: the documents that tell it how this particular app works. It's not an installation README or decorative documentation. It's what turns “build me a screen” into “build me a screen that fits in here.”

This is worth spelling out because it's easy to miss. It's tempting to think context means “telling the agent what the app does,” but it isn't that, or at least not only that. Context is telling it which decisions have already been made, so it doesn't make them again. In the shared-expenses app, the fact that a split can be equal, by percentage, or by fixed amounts is a product decision the agent has to respect, not reinvent every time it touches that area.

In our projects, this contract lives in three layers for each app:

Layer What it answers Example
What it is What does the app do, and by what rules? Groups per event, configurable split, balance, multi-currency out of the MVP
How it's built What architecture, and why? Clean + UI/domain/data layers, local DB for data, backend only for sync
What not to touch What must be respected, no exceptions? Calling the backend from the UI is forbidden

Turned into a file, the shared-expenses app's contract looks something like this. It's the CONTEXT.md we leave at the root of the repo so the agent reads it before anything else:

# CONTEXT — Shared-expenses app

## What it is
Split an event's expenses (trip, dinner) among several people.
MVP:
- Create a group per event.
- Add an expense saying who paid.
- Configurable split: equal shares / percentages / fixed amounts.
- Balance screen: who owes whom.
- Settle up.
Out of v1 (DO NOT implement):
- Real payments inside the app.
- Multi-currency.
- Offline mode.

## How it's built
Clean Architecture. Layers: ui / domain / data.
- ui    → screens and state, no business logic.
- domain→ use cases and rules (the split lives here).
- data  → repositories and data sources.

## Persistence
Local database → source of truth for the data.
Backend → only for syncing between devices.
FORBIDDEN to call the backend from the UI. The UI talks to domain;
domain talks to data; data decides local vs. backend.

## Conventions
Screen state: loading / error / data.
Errors: never a TODO; always real handling.

None of this is exotic. Half the file describes things the app doesn't do. That “out of v1” section carries as much weight as the MVP, because it's what stops the agent, the moment it smells an amount field, from building you the currency conversion nobody asked for. What matters isn't what it says, but that it's written down before the agent starts making decisions on its own.

Without context, the agent doesn't stop. It improvises

An AI agent with no context doesn't stop to ask. It improvises a plausible architecture and keeps going. Since it never says “I don't know,” it fills every decision we didn't give it with whatever it finds reasonable, and what it finds reasonable almost never matches what we've already built.

In the version without context, that played out predictably: errors patched “just to get by” with a TODO instead of real handling, no rule about when to use the local database and when the backend, and patterns that changed from one screen to the next. The agent wasn't wrong by its own logic. The problem is that it didn't have ours.

It's easier to see with a feature from the expenses app. Say we ask it to add percentage-based splitting to a group. Without context, a request like this:

Add the ability to split an expense by percentages among the group members

The agent, helpful to the point of being overbearing, does everything at once: it puts the split logic inside the screen widget, calls the backend directly to “save the percentages,” invents a new model instead of touching the expense that already exists and, while it's at it, leaves a “//TODO: validate that they add up to 100%” just in case.

It works in the demo, but the result was three core principles broken in a single feature: business logic embedded in the interface, a backend connection that violated the contract, and redundant domain code.

It didn't do it in bad faith. We asked for a split… without telling it where the split lives in our app. And where there's no rule, there's improvisation.

Faced with a decision… Without context With context
Architecture Infers a plausible one, different on each screen Applies the one you defined
Error handling A TODO “just to get by” The real pattern you already use
Data: local or backend Decides on the fly Follows your explicit rule
Code that already exists Reinvents or duplicates it Reuses it
Result Snowball A piece that fits

The agent doesn't improvise because it's bad. It improvises because we didn't give it the information it needed, so it had to.

With CONTEXT.md in front of it, you don't even need to phrase the same request differently. The same task, now read through the contract, sounds like this:

Add percentage-based splitting to an expense.
- The split rule goes in domain (use case), never in the UI.
- Reuse the existing expense model; don't create a new one.
- Persist to the local DB; the backend only syncs (don't call it from here).
- Screen state: loading / error / data. No TODOs: validate that they add up to 100%.

The difference isn't that we write more. It's that those four lines were already written once, in the contract, and the agent carries them into every task. The piece fits instead of landing wherever it lands. And the best part: the next feature (an expense filter by person, say) inherits exactly the same rules without us repeating them.

The trap of after-the-fact context

Documenting the architecture after writing the code isn't building context. It's performing an autopsy. In the version that went wrong, that document arrived late, with most of the code already written. It didn't steer a single one of those decisions. It just described, in neat handwriting, the mess that already existed.

And that's the nuance most people skip: a contract you sign after the work is done isn't a contract, it's minutes of a meeting. Context only does its job (stopping the agent from improvising) if it exists before the code it's supposed to guide.

I say this from experience. In the version that went wrong, we ended up with an architecture document with diagrams, well-named layers, the works. And it was useless, because it described code that had already decided otherwise.

Documenting after the fact is an autopsy: you learn what killed the project, but the project is already on the table. The balance feature, for instance, calculated who owes whom in three different places in that version, and no later document was going to unify that for me.

With context from the start: clear architecture with no loose ends. Without context for months: months of improvising, and even when context arrives late, to-dos, ad hoc.

How to set up context before you start

Context gets built at the beginning, before the code, and across the three layers we've already seen: what it is, how it's built, and what not to touch. It doesn't have to be long. It has to exist before the first line of code. In our best-structured apps, each one starts with its own documentation folder (spec, decisions, design), validated before anything gets programmed.

In practice, for the shared-expenses app, that meant a short session with the agent discussing the contract, not writing code. Something as simple as this before the first commit:

repo/
|–– CONTEXT.md       <-- what it is, how it's built, what not to touch
|–– docs/
|   |–– spec.md      <-- the detailed MVP: groups, expenses, split, balance
|   |__ decisions.md <-- why local DB as the source of truth
|__ (still not a single line of code)

It looks like very little, but with half an hour of definition we're saving ourselves weeks of fighting an agent that pulls in a direction you don't want. When we later asked for the balance screen, we didn't have to explain again that the “who owes whom” calculation lives in domain, because it already knew.

To make the contract work:

  • Write it before the first commit, not halfway through.
  • Keep it short: decisions and rules, not an encyclopedia.
  • Update it when a decision changes, not after it has already broken something.

Context is a living contract, not an archive document.

And a warning, because the pendulum easily swings to the other extreme: context isn't a dump of everything you know. If you stuff in an encyclopedia, the agent gets lost just as it would with nothing. Context is the contract: the decisions and rules that truly bind it, not every implementation detail. If it doesn't constrain a decision, it doesn't belong.

Context isn't just for new apps. It's for a single feature too

Most of the time we're not building an app from scratch. We're adding a feature to one that already exists. And there, context matters just as much, or even more. The agent needs to know where the piece fits, what to reuse, and what to leave alone, or it will reinvent what we've already built.

A down-to-earth example from the expenses app: ask it to “add a filter for expenses by person on the group screen.” Without feature-level context, the agent might build us a brand-new data layer, duplicate the expenses repository that already exists, or solve the filter with a different pattern than the rest of the app. It works, but we end up with two ways of doing the same thing. It's the snowball again, now at feature scale.

With feature-level context, the request changes in nature:

Add a filter for expenses by person on the group screen.
- Filter using the existing ExpenseRepository; don't create another source.
- Follow the pattern of the expense list screen.
- Filtering is domain logic: solve it in the use case, not in the widget.
- Don't touch the expense model.

The agent stops inventing and simply puts the piece where it belongs. It's the same leap as with percentage-based splitting, but at an even smaller scale, which makes it easier to forget to provide context, and that's exactly when it bites.

A feature's context:

  • Where it fits: the screen or module, and the pattern to follow.
  • What to reuse: the repositories, components or utilities that already exist.
  • What not to touch: whatever falls outside this feature's scope.

It's the same contract, one size smaller.

It's the same “define and scope” idea from the first article in the series, applied to context: every task, however small, has its own slice of context. Providing it before you start is what keeps the agent, while helping you with one feature, from messing up the other twenty.

Pain or boost: it changes how we talk to the agent

Setting up context changes, above all, how you communicate with the agent. Without it, in every session we repeat the same rules, correct the same drifts, and check every output in case it has made something up. With context, we point at the task and the agent stays on the rails we laid down once.

With the expenses app, you notice it in that we stopped writing three-paragraph messages explaining the architecture every time. We went from “build this, but remember the logic goes in domain, and don't call the backend, and reuse the repository, and please no TODOs…” to simply “add the filter by person,” and the contract did the rest of the work in every prompt.

That's the difference between AI being a pain or an accelerator. It isn't set by the model you use, but by whether we gave it the contract before asking it to work. At its core, it's the difference between directing and praying.

In this series, context grows out of the definition phase from the first article and sets up the next ones: design, stack, spec and development. The immediate next step is giving the agent reusable capabilities (skills), which lean on precisely this contract to know how and where to act. But the order never changes: first the contract, then the first line of code.

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.