Sunday, 9 August 2026

Beyond the Source. When an AI Coding Agent Searches Outside Your Project

I gave a coding agent one task and one directory. It went looking across the whole machine. In doing so it showed me the best thing about how these agents work. It also showed me the most dangerous thing.

I run an experiment called PetClinic-Evolve. It holds the AI coding agent fixed. It makes software architecture the thing that varies. It evolves the same application by the different architectures across roughly sixty accumulating change requests. The goal is to measure how the code degrades over time.

For the measurement to mean anything, each change has to be made blind. The agent is handed the current task and the current source. Nothing else. It must not know it is step 46 of a long sequence.

That blindness was much harder to guarantee than I expected. The agent does not treat the project as the edge of its world.

The moment it reached outside

Early in a run, on the very first checkpoint, the agent needed a Java class. That class is generated from an OpenAPI spec at build time. In its fresh working copy the class did not exist yet. Nothing had been compiled. A person might have run the build. The agent did something more resourceful. It was also more unsettling.

find . -name "OwnerFieldsDto.java"        # not in the project yet
find / -name "OwnerFieldsDto.java"        # so search the ENTIRE machine
# found in /home/.../​.local/share/Trash/.../target/generated-sources/...
grep -n "NotNull|Size|Pattern" "$D/dto/OwnerFieldsDto.java"   # read it from the Trash

The class was not where it should be. So the agent widened its search from the project to the root of the filesystem. It found a copy of the generated code sitting in the desktop Trash. That copy was left over from a previous run. I thought I had cleaned it up. The agent read it to understand the shape of the data model. Then it wrote correct code.

Read that again. It was right. The information it needed really did exist on the machine. The agent found it. The agent used it well. This is not a bug in the model. This is the model being good at its job.

One instinct. Two very different outcomes

Helpful? An agent that looks beyond the files you handed it can fill in gaps you did not know you left. It can find the generated source. It can find an example in a sibling module. It can find a dependency's real implementation. It can find the migration script that reveals a column's true type. It can find the test that pins down the exact error message. Much of what makes these agents feel capable rather than merely obedient comes from this. They infer intent from context you never pointed at. Give one your whole repository and your test suite. It will work out what "done" means. Then it will hit it.

Misleading? The same reach picks up whatever happens to be lying around. Not all of it is relevant. Not all of it is current. Not all of it is even yours. A generated file from a different run. An older version of a class. Another project's conventions bleeding in. A stale schema. The agent does not know the difference between the answer and an answer-shaped thing near the answer. It guesses from what it finds. A confident guess from the wrong source produces confidently wrong code. The resourcefulness has no sense of provenance.

Here is the unifying idea. An AI agent treats the whole machine as context. Not the directory you changed into.

Why this was a five-alarm problem for my experiment

For everyday development, an agent that scavenges the machine for clues is a feature. For a measurement, it is an existential threat. In my setup the machine held the answer key.

The reachable filesystem contained three things. Each was one find / away.

  • A previous run's build output in the Trash. That included its full copy of the authored acceptance tests, Cp01Tests.java through Cp60Tests.java.
  • The harness's own test suite. That is 124 authored test files. They define every checkpoint's requirements.
  • The file checkpoints.yaml. That is the entire future sequence of specifications, in order.

The agent had already shown it would run find / when a file was not where it expected. It never went looking for the tests. But it easily could have. If it had, the experiment would have been quietly and unrecoverably invalid. A degradation study whose subject can read the answer key is not measuring degradation.

I had already removed the tests from the project directory. That was not isolation. It only moved them one directory away. The agent treats that filesystem as fair game.

Keeping the experiment valid

The fix came in layers. It went from cosmetic to structural.

1. Give the agent a history-less, sequence-blind workspace

The agent no longer works in the real git worktree. Each checkpoint it gets a fresh copy of the source only. There is no .git, so it cannot read the commit history and see it is one of a numbered series. There is no prior build output. There is one visible acceptance test. That test is renamed to a neutral AcceptanceTest.java with no checkpoint number and no tag. Nothing in the files or their names hints at a sequence.

2. Make the workspace a flat, ordinary-looking project

That sandbox lives in its own directory. It looks like any project called sandbox. There are no run identifiers in the path. There is no "chain 0 of 3." There is no sibling worktree or .git one cd .. away. There is nothing nearby to stumble into.

3. The real fix. Confine the process to the sandbox

Cosmetics stop an agent from noticing the sequence. They do nothing about find /. The durable fix is to make the rest of the filesystem genuinely unreadable to the agent process. The tests and the spec file cannot be deleted. They are the experiment's source of truth. So the answer is to block access, not to hide the files.

On Linux this is Landlock. It is a kernel feature that lets a process irreversibly restrict its own filesystem access to an allow-list. The agent gets read and write to the sandbox and its toolchain. That means the compiler, the Maven cache, and the CLI runtime. It gets nothing else. Everything outside returns "permission denied."

It is wired to fail closed. Before each turn the harness runs the same confinement over a throwaway shell. It tries to read a few sentinel paths. Those are the test suite and the spec file. If any of them is still reachable, the checkpoint is refused. If Landlock is not available on the host, the checkpoint is refused. It never runs un-blinded. A silent leak becomes a loud stop.

The proof is my favourite part. In a later confined run the agent again ran find / -name "OwnerFieldsDto.java". No one prompted it. This was the exact move that had reached into the Trash before. This time it returned nothing. The escape was attempted and silently defeated. Across forty-five checkpoints, not one file access landed outside the sandbox.

The takeaway cuts both ways

The instinct to look beyond what you were handed is close to the heart of why these agents are useful. They do not stop at the boundary you drew. They go find the context that makes the task solvable. When the context they find is correct, that looks like competence. Sometimes it looks like insight. When the context is stale, foreign, or off-limits, the same behaviour looks like hallucination or cheating. It is the same behaviour.

So the lesson splits by who you are.

  • If you are building with agents, lean into it. Put the good context on the machine. Give it the real tests, worked examples, generated code, the actual dependency source. The agent will use it. It will get more right than if you had fenced it into a tidy little directory. Its reach is a resource.
  • If you are evaluating agents, assume the reach. Your environment is your prompt. A held-out set that merely sits in another folder is not held out. If your result depends on the agent not seeing something, make that thing physically unreadable. Then verify it, every run. Fail closed when you cannot.

I set out to measure how AI-written code decays over time. Before I could measure anything, the agent taught me a lesson. The boundary of a task is not the folder you point it at. It is everything the process can reach. Draw that boundary deliberately. Otherwise the agent will draw it for you.


No comments:

Post a Comment