hyper.dev
well come

🖐🏽

2026-11-07 · dx - 8 - AI without IA

The program is not its text

Seeds

  1. AI was promised as intelligence augmentation; agents now write software faster than anyone can read it, so the scarce thing is understanding;
  2. Cheap code moves the cost to checking, integrating and maintaining;
  3. Any language helps agents and people more when its environment makes each function inspectable, including its dependencies and lineage;
  4. Agents could open up small ecosystems or strengthen the big ones; I bet on when the first wins;
  5. bb.py gives each function an identity under a specified normalization and encoding, separate from its names and its files;
  6. The claim is tested where it can fail: agents on benchmarks, and people changing what agents wrote;
  7. A program’s identity can live apart from its text.

Opening

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.


1. Which cost becomes cheap?

“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.

2. The environment decides

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.

3. Two futures

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.

4. What can an agent inspect?

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.

5. Agents on benchmarks

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.

6. People in the loop

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.

The program is not its text

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.

Postlude

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.

Scope and limits

This essay proposes personal research. It reports one pilot, run with two models, in the annexes, and guarantees nothing about bb.py’s implementation.

References


Annex 1: a pilot on 20 tasks, with Opus 5.5

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:

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.

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.


Annex 2: the same pilot with Mistral Large 4

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.