A working method · Six chapters
AI Powered
Agents write code quickly. Most of the engineering lives around them: what they read first, what they agree to before they start, and what checks their work once they say it is done.
Told through one real feature from a project I worked on, start to finish.
01The brief
One sentence, a dozen unknowns
The request arrived as one line: let many organizations share one blog platform without ever seeing each other's data. It reads like a feature. It is closer to a property — something that has to stay true of every query, every link and every email the platform will ever produce.
Hand that sentence straight to an agent and you get a confident diff that answers the half of the question it understood. So the first hour was not code. It was pulling the sentence apart until every branch ended in something a person could decide, and writing those decisions down where the agent would find them.
How the brief branched
- Many organizations, one platform, no shared data: the sentence as it arrived.
- Where does a request learn which workspace it belongs to? From the address it was sent to, never from anything the caller can type.
- What enforces the boundary? Two layers that fail independently — a row policy in the database and a tenant filter in every query.
- Can the platform's own staff see inside a workspace? Only by stepping into it explicitly, never through a wider rule.
- What about everything that leaves the system — links, emails, images? Each one has to be built for the workspace it came from.
- What proves it holds? An isolation test that tries to read across the boundary and fails the build if it can.
02Context
The agent reads before it writes
An agent starts every session knowing nothing about your codebase except what you put in front of it. That makes context the actual interface — and the obvious move, one enormous instructions file, is the wrong one. Everything in it competes for the same attention, and the rule that matters today is buried under the ones that mattered last month.
What worked was an index at the centre and a scoped rule file for each area of the system. The index is short and points outward. A rule loads only when the agent touches the area it governs, so the data-layer rule is in the room exactly when somebody is about to write a query — and nowhere else.
- An index, not an encyclopedia
- The top-level file says where things are and which lines must never be crossed. The reasoning lives one hop away.
- Rules carry the why
- “Always filter by workspace” gets optimised away the first time it looks redundant. “Because the row policy alone is one mistake from a leak” does not.
- Invariants as tripwires
- A short list of things that break silently, each pointing at the rule that argues for it and the test that catches it.
- Signal over noise
- Every line in context costs attention. If a rule has not changed a decision in months, it moves out of the index.
- 0:00
› Scope article lookups to the current workspace.
- 0:01
Read index
- 0:02
The index sends anything touching data access to the data-layer rule.
- 0:03
Read data-layer rule
- 0:04
Every lookup carries the tenant filter, even behind a row policy — the policy alone is one mistake from a leak.
- 0:06
Edit article lookup
- 0:07
findArticle(slug)- where slug = :slug+ where slug = :slug+ and workspace = :current
- 0:09
Run the isolation test
- 0:11
Pass — a read across workspaces returns nothing
- 0:11
Lookup scoped. The test tried to cross the boundary and found nothing there.
What the session shows
- The task arrives: scope article lookups to the current workspace.
- The agent opens the index first, and the index points it at the data-layer rule.
- The data-layer rule loads, and with it the reason the tenant filter is required even behind a row policy.
- Only then does the agent write the change — the lookup now carries the workspace it belongs to.
- It runs the isolation test, which tries to read an article across the boundary and finds nothing.
03Plan
Plan before code
The cheapest place to be wrong is a plan. Before any change of size, the agent writes one — what it will touch, in what order, and what would tell us it worked — and I read it the way I would read a colleague's design note. Most corrections happen here, in a sentence, instead of later in a diff.
Skip it and the agent plunges: it picks the first plausible path and builds on it, and you discover the wrong assumption three files in, when undoing it costs more than the feature did.
With a plan: done at tick 18, fixed in the plan. Plunge: done at tick 24, found much later.
With a plan
- 0Read
Reads the brief and the context file
- 1Assume
Assumes staff see every workspace through a wider rule — and writes that down
- 2Read
Explores the codebase: where a request learns its workspace
- 4Plan
Writes the plan: the layers it will touch, in no stated order
- 6Gate
I read the plan
- 7Fix on paper
I catch the wider rule: staff step into a workspace instead. One sentence.
- 8Fix on paper
I ask for the order: the policy waits for the column it reads
- 9Gate
Approved — no code exists yet
- 10Build
Schema: the column, then the row policy that reads it
- 12Build
Data access: every query carries the tenant filter
- 14Build
Request handling: staff step in explicitly
- 16Build
Host resolution: the address decides the workspace
- 17Green
Isolation test passes
- 18Done
Done, in the order the plan said
Fixed in the plandone at 18 · 2 spent fixing
Every wrong decision was corrected on paper, before a line of code existed.
Plunge
- 0Build
Starts writing immediately
- 1Assume
Widens the rule so staff see every workspace
- 2Build
Schema: the policy first, reading a column a later step adds
- 4Build
Data access — on top of the wider rule
- 6Build
Request handling — on top of the wider rule
- 8Build
Host resolution — on top of the wider rule
- 9Green
Every check green — a fresh database applies all the steps at once
- 10Fail
Review finds the wider rule under three layers
- 11Rework
Rewrite: data access, request handling and host resolution unpicked
- 17Green
Ships. Nothing looks wrong.
- 21Fail
Much later, a database that applies steps one at a time fails — quietly
- 22Rework
Traced back to the order, and fixed by hand
- 24Done
Done
Found much laterdone at 24 · 8 spent fixing
Shipped, then failed much later — the wrong order surfaced long after anyone remembered choosing it.
Plan or plunge
- With a plan: the agent lists the layers — schema, data access, request handling, host resolution — and the order they depend on each other.
- I catch one wrong assumption in review: staff access was going to widen a rule, and it needed to be an explicit step into a workspace instead.
- The plan is corrected in one sentence, before a line of code exists.
- Without one: the agent widens the rule, three layers build on top of it, and the fix is a rewrite.
04Act
Several agents, one reviewer that wasn't there
Once the plan holds, the work fans out. Schema, data layer, request handling, host resolution and DNS form a graph, and anything without an unmet dependency can run at the same time. More agents is not automatically faster: two of them editing the same file is a merge conflict with extra steps, so the graph decides how wide to go.
Then a reviewer that took no part in writing the code reads all of it. Independence is the whole point — the agents that wrote a query are the worst placed to notice it quietly lost its tenant filter. A human gate sits after that, and nothing merges past it.
3 agents: 22 ticks, 4 conflicts.
- Schema (shared), ticks 0 to 2
- Waits on Schema, ticks 2 to 5
- Request handling, ticks 5 to 9
- Host resolution (shared), ticks 9 to 12
- Isolation test, ticks 12 to 14
- DNS, ticks 14 to 16
- Idle — nothing ready, ticks 16 to 17
- Schema (shared), ticks 0 to 2
- Same file as the other half — sequenced after it, not merged, ticks 2 to 5
- Data layer · members, ticks 5 to 8
- Row policy, ticks 8 to 10
- Host resolution (shared), ticks 10 to 11
- Same file as the other half — sequenced after it, not merged, ticks 11 to 14
- Idle — nothing ready, ticks 14 to 17
- Waits on Schema, ticks 0 to 5
- Data layer · articles, ticks 5 to 8
- Same file as Data layer · members — sequenced after it, not merged, ticks 8 to 11
- Links and email, ticks 11 to 14
- Same file as Host resolution — sequenced after it, not merged, ticks 14 to 17
- Restores the tenant filter, ticks 19 to 20
- Reads the whole change; flags a query that lost its tenant filter while resolving the conflict in Data layer · articles, ticks 17 to 19
- Re-reads the fix, ticks 20 to 21
- Approves the merge, ticks 21 to 22
- Work
- Waiting
- Conflict
- Coordination
- Review
- Fix
- Human gate
3 agents: 22 ticks, 4 conflicts.
The reviewer reads the whole change after every branch lands, and the human gate comes last — whatever the agent count.
Finish time by agent count, this run
- 1 agent32 ticks
- 2 agents22 ticks
- 3 agents22 ticks, selected
- 4 agents21 ticks, the fastest
- 5 agents22 ticks
How the work fanned out
- The schema change runs first; everything else depends on it.
- The data layer and request handling run in parallel once the schema lands.
- Two agents touch the same file, and the conflict is resolved by sequencing them rather than merging.
- Host resolution and DNS follow, each depending on the layer before it.
- An independent reviewer reads the full change and flags one query that lost its tenant filter.
- The fix goes in, and a person approves the merge.
05Verify
Break the invariant on purpose
“Done” from an agent is a claim, not a result. The loop only closes when something other than the agent checks the work — a type check, a build, and for this feature above all, the isolation test that tries to read one workspace from inside another.
The useful exercise is to switch the defences off one at a time and watch what happens. Each layer covers for the other, and the test is what turns a quiet failure into a loud one before it ships.
Defences
- Row policy
The database refuses rows from another workspace.
- Tenant filter
Every query names the workspace it belongs to.
- Isolation test
A test tries to read across the boundary, layer by layer.
- Workspace Areads
- Filterrefuses
- Policyrefuses
- Workspace Buntouched
Blocked twice. Isolation test: passes.
Blockedisolation test passes
Blocked twice.
The tenant filter never asks for the other workspace, and the row policy would refuse it anyway. A leak needs both layers to fail, and the isolation test passes.
| Row policy | Tenant filter | Isolation test | Outcome |
|---|---|---|---|
| on | on | on | Blocked twice. (current) |
| on | on | off | Blocked twice. |
| on | off | on | Blocked by the row policy alone. |
| on | off | off | Blocked by the row policy alone. |
| off | on | on | Blocked by the tenant filter alone. |
| off | on | off | Blocked by the tenant filter alone. |
| off | off | on | Caught by the isolation test. |
| off | off | off | Ships silently. |
Three ways it can go
- Row policy and tenant filter both on: a cross-workspace read is blocked twice, and the isolation test passes.
- One layer off: the other still blocks the read — and the isolation test, written to check each layer, flags the missing one.
- Both layers off with no isolation test: the read succeeds, every check is green, and the leak ships silently.
06The loop
It is a loop, not a pipeline
None of these steps happens once. A review turns up something the rules should have said, so the rules change; the next plan starts from better context; the next check is sharper. That loop is the actual product of working this way, and the code is what it leaves behind.