The program is not its text
AI was supposed to augment our intelligence. Instead, agents now write software faster than anyone can read it. The question is whether people can still understand it, change it, and stay in charge of where it goes.
Human agency, here, is a symbiosis, J. C. R. Licklider’s word from 1960: agents generate code; humans understand it, change it, maintain it. AI makes the first half cheap. This essay asks which tools keep the second half alive.
Better agent performance and greater human agency are two different outcomes. I propose two separate tests. The first asks how well the agents build and extend a program. The second asks whether people can take over and change it.
“Writing code becomes cheap” hides four activities, not one. Producing a candidate. Checking that it does what was asked. Fitting it into what already exists. Keeping it useful when dependencies, requirements and people change.
An agent that writes a missing library removes the first cost. It creates the other three.
Take a small service that needs an HTTP client. The calls for a demo come in a minute. Then the real questions arrive: what happens when a connection stalls, when a response is malformed, when a retry repeats an operation that must happen once? Those questions do not get cheaper because the first draft did.
The cost that matters is the whole cost of a change: expressing it, checking it, integrating it, diagnosing it, coming back to it a year later.
When one cost falls, the bottleneck moves.
A language’s features say little about how hard the next change will be. Assumptions spread across modules, generated code, foreign interfaces, mutable state and runtime behaviour.
For an agent, and for the person checking the agent, what matters is what the environment hands over.
When a computation fails, can you see the stack and the values? Can you tell an application error from an integration problem? Can you reproduce the failure? Can you tell which definition actually ran, not merely one with the same name in some file?
That last question carries the most weight. A name points to whatever currently carries it. A person, like an agent, needs the exact object, and its lineage: where it came from, what it replaced, what changed.
These are questions about tools. Stack traces need context. Interoperability needs explicit contracts. Interactive development needs to know which version of a computation produced an observation.
In the first future, agents open the door to small ecosystems. You no longer wait for a general library; you ask for the narrow binding you need. A team builds tools around its own conventions. Knowing how to understand and reshape an environment becomes worth more than the size of its catalogue.
That future has a condition. What the agent writes must stay cheap to check and to maintain. Otherwise every saved dependency becomes a local debt.
In the second future, agents strengthen the big ecosystems. Where a good library exists, the agent only connects documented parts. Examples, diagnostics and tests carry it to a reliable result.
Both can happen in one project. My bet: the first future wins wherever checking a narrow, agent-written binding costs less than learning and trusting the general library it replaces, and that only holds when the environment ties each definition to its tests and history.
A source file is one representation of a program. The environment around it holds more: which tests exercise a definition, which documentation describes it, what changed between versions, which dependencies belong to a given run.
Most of these relationships are rebuilt every time, from names, directory layouts, comments and history. Humans do it. Agents do it too, at great cost, again and again.
What happens when the environment already knows them, and says so?
bb.py is my attempt
at an answer, for Python. It normalizes a function’s syntax tree and
derives an identity from it. Two functions that normalize to the same
tree, under the same encoding, get the same identity, whatever their
names. Dependencies point at identities, not at file paths. Tests and
history attach to the function itself. Functions are written one at a
time through bb edit, each with its checks and their
documentation.
The identity covers exactly that: one normalized tree, under one encoding. It does not prove that two different trees mean the same thing. It does not capture the runtime or the Python version. A link between versions records history; calling it “bug fix” does not prove a bug was fixed. And a test attached to a function does not prove the test checks enough.
Git, Nix and IPFS already address content, but by file, package or blob. Unison identifies definitions by content too. bb.py works at the level of one Python function, in a supported subset of the language, as a prototype.
The experiment compares three conditions, all run with Claude Code and Opus 5.5:
| Condition | Setup | Purpose |
|---|---|---|
| A, files | One agent writing a single app.py with beyondlive |
The reference |
| B, bb.py | One agent writing the site as bb.py functions, with beyondlive | bb.py against files, with one writer |
| C, bb.py with architects | An architect splitting the site among parallel workers on one bb.py pool | Architects against one writer, both on bb.py |
Three questions follow. With a single writer, does bb.py beat files? On bb.py, do architects beat one writer? And how does the whole system with architects compare with plain files, in quality and in cost?
B and C write in bb.py’s supported subset of Python. That is a constraint, and its consequences get reported. The benchmark’s sites are interactive, so browser code is part of the work; the boundary between the Python subset and the browser, the assets and the deployment gets fixed before the runs. A against B does not isolate hashing alone: it tests bb.py with all its tools and conventions. And with no architects working on files, the experiment cannot tell whether architects help on their own or only with bb.py.
Building from scratch. WebGen-Bench supplies website requests and tests the resulting applications: a web-navigation agent executes each test case against the running site.
Living with your own code. SlopCodeBench has agents extend their own code across 196 checkpoints and measures how its structure erodes. Here the agent pays for its past decisions. This is where lineage should matter most.
The benchmark’s own evaluation stays fixed across conditions; only generation changes. Every run records models, budgets, tools, documentation, chances to verify and human interventions, and counts the cost of every agent. Traces explain failures and repair work. The protocol is fixed before the final runs, and the harness is developed on separate tasks.
An agent can pass every test and still leave its maintainer unable to change the program.
The last experiment gives people a change to make in Python applications that agents wrote, once in ordinary files and once through bb.py. It measures whether they finish, how long it takes, what breaks, how much help they need, and whether they can explain what they did.
More reliable has one meaning, fixed before the runs: the proportion of participants who complete the requested change without introducing a regression. How much help they needed is a separate report. If completion is equal but people need less help with bb.py, the two measures tell different stories.
This is the test that decides. I would recommend the store only if participants complete the task without regressions more often with bb.py. If they don’t, the store isn’t worth it.
When candidate code costs almost nothing, the growing pile of computations still has to be understood, by people and by agents. Which relationships stay implicit? Which become queryable? How does a failure, a test result or an explanation attach to the exact object it concerns?
None of this means abandoning text. Text is a superb medium for expression, inspection, exchange and revision. Rebuilding a textual view from a structured store is a sensible way to work.
I suspect more data does not teach models where a program’s seams are. But the argument does not depend on it: whatever models learn inside, we can judge the interfaces they work through.
Agents may write the code. People must still be able to read it, change it and own it.
Text is a representation of the program. It is not the program.
Python already inspects a great deal. Tracebacks carry the frames,
the debugger shows the values, and the inspect module hands
over a function’s source and signature.
What Python lacks is identity per function, independent of names and files. bb.py adds that, for a supported subset, as a prototype.
bb.scm, its Scheme sibling, and Kernel come next, for reasons that need their own essay.
This essay proposes personal research. It reports one pilot, run with two models, in the annexes, and guarantees nothing about bb.py’s implementation.
This pilot ran before the protocol of section 5 was fixed. It tests one thing only: whether agents build a site through bb.py about as well as through a file, at what cost, and with what structure.
What ran. Twenty WebGen-Bench training tasks, none of them used while reworking bb.py. Each task is the instructions for one site. All three conditions ran Claude Code with Opus 5.5, with the same instructions and the same login account:
app.py with
beyondlive;bb edit, with beyondlive;Each site was built once, in its own store, sharing nothing with the others except the prompts and beyondlive. So nothing written for one site could be reused in another, and this pilot says nothing about reuse or propagation. Each site was graded five times by a Playwright copy of the benchmark’s browser tester, with Mistral as the grading model.
Results. Scores are tests passed over tests, per site, averaged over the five gradings. Other figures are medians per site.
| A, files | B, bb.py | C, bb.py with architects | |
|---|---|---|---|
| Score | 78.2% | 75.9% | 72.3% |
| Against A: wins, losses, ties | 6, 13, 1 | 5, 14, 1 | |
| Against A: mean difference, 95% interval | −2.3 [−5.9, +1.2] | −5.9 [−11.8, 0.0] | |
| Agents | 1 | 1 | 7 |
| Output tokens | 53k | 37k | 112k |
| Cache reads | 1.3M | 2.1M | 7.9M |
| Tool calls | 20 | 30 | 120 |
| Working time | 7.5 min | 6.1 min | 14.6 min |
| Functions | 57 | 45 | 57 |
| Lines per function, mean and max | 22, 142 | 27, 133 | 34, 223 |
| Cloned lines | 34 | 25 | 47 |
| Duplicated constants | 65 | 55 | 95 |
C against B: 8 wins, 9 losses, 3 ties, −3.6 points [−10.8, +3.6].
Scores. B did about as well as A: the interval runs from six points below to one above. C did worse than A, at the edge of significance, and cost far more: seven agents, twice the output, six times the cache reads and twice the working time. The grader is noisy: on average, the best and worst of a test’s five gradings differ by 0.3 on a scale of 0 to 1, which is why each test was graded five times.
Structure. B writes fewer, longer functions than A, but less cloned code and fewer duplicated constants. C duplicates more than both.
Tools. Agents barely used bb.py’s map of the code. B
ran bb tree once per site and bb search twice.
Before writing a new function, B looked nothing up in the median site:
neither the tree nor any other context. C’s workers looked up context
before 42% of their new functions, and consulted the tree before 10%.
The difference fits a simple reading: an agent that wrote every function
in this session finds the map in its own transcript, while C’s workers
did not write each other’s functions and have to look. Even so, C
duplicates the most, so looking up context was not enough to prevent
rewriting. The tree should matter most when that memory is gone: in a
new session, for another agent, or for a person.
Expectations, written before the run.
bb tree before writing new functions:
failed, because the transcript already serves as the map;Rule breaks. None in A. Two sites in B. In C, a median of two per site, mostly functions saved with shell loops.
What it shows. One agent writing through bb.py builds about as well as one writing a file, a little faster, with fewer output tokens but more re-reading. Splitting the work among an architect and workers costs much more and did worse. And the tools that should give agents a map of the code went almost unused.
What it does not show. That bb.py is better; anything about reuse or propagation; anything about people. The human test decides, and it has not run.
The same 20 tasks, the same three conditions and the same instructions, run with Mistral Vibe, Mistral’s coding agent for the terminal, and Mistral Large 4 as its model. The grader is the same Playwright copy of the benchmark’s tester, now with Mistral Large 4 as its grading model, and three gradings per test instead of five. Because the harness, the model and the grader all changed, scores in this annex can be compared with each other, not with the first annex.
| A, files | B, bb.py | C, bb.py with architects | |
|---|---|---|---|
| Score | 75.6% | 72.6% | 70.7% |
| Against A: wins, losses, ties | 7, 12, 1 | 6, 12, 2 | |
| Against A: mean difference, 95% interval | −2.9 [−11.5, +5.7] | −4.9 [−11.0, +1.3] | |
| Agents | 1 | 1 | 7 |
| Output tokens | 107k | 98k | 254k |
| Cache reads | 3.4M | 3.5M | 10.2M |
| Tool calls | 56 | 53 | 276 |
| Working time | 17.3 min | 13.9 min | 16.6 min |
| Functions | 41 | 45 | 49 |
| Lines per function, mean and max | 22, 104 | 18, 105 | 20, 112 |
| Complexity per function, mean | 4.8 | 3.6 | 3.8 |
| Total lines | 1092 | 801 | 904 |
| Cloned lines | 9 | 26 | 10 |
| Duplicated constants | 39 | 37 | 54 |
C against B: 6 wins, 9 losses, 5 ties, −1.9 points [−11.1, +7.2].
Scores. The order is the same as with Opus: A, then B, then C, and no difference is significant. B’s interval is wide because of one site, train_001104, where B passed 1 test in 6 against A’s 4.5. Without it, B against A is −0.1 points [−6.9, +6.6]. The three gradings of a test differ by 0.17 to 0.25 on a scale of 0 to 1, less than in the first annex, but with three gradings instead of five and a different grading model, the two spreads cannot be compared.
Cost. Mistral wrote about twice the output tokens Opus did, in every condition. B again cost no more than A and was the fastest. C used seven agents, 2.4 times A’s output and five times its tool calls, but its working time was close to A’s.
Structure. Here bb.py pushed the other way from the first annex: B and C wrote shorter, simpler functions than A, and less code overall. But B cloned more code than A, 26 lines against 9.
Tools. Mistral’s workers in C used bb.py’s tools
more than Opus’s did: ten bb tree calls, eight
bb caller and twenty bb search per site.
Before writing a new function, B consulted the tree 6% of the time, C
almost never.
Rule breaks and interventions. No rule breaks in any condition. One intervention: a writer in A exited and was relaunched.
What it adds. The first annex’s result holds with another model and another grader: one agent writing through bb.py builds about as well as one writing a file, at no extra cost. Architects and workers still did not help. What changes with the model is the shape of the code: with Mistral, bb.py gave smaller, simpler functions, and with Opus it gave fewer, longer ones. So the structure bb.py produces depends on the agent using it as much as on bb.py itself.