You cannot authenticate a process that shares your uid
We wanted checkpoints that only the harness could request, not the agent. Under a shared uid and a shared process tree that check cannot be built — so we changed the property instead of trying.
agentfs takes automatic snapshots while an agent works, so that a run which
goes badly can be walked back. The obvious way to place them well is to let
the harness say when: Claude Code fires a hook at the end of each turn, which
is a natural boundary.
Which raises the natural next question. How do we know a checkpoint request came from the harness and not from the agent?
You don’t, and you can’t
They share a uid. They share a process tree. Whatever secret the harness could
present — a token in a file, an environment variable, a nonce on a socket, a
value in its own memory — the agent can read, because the kernel has no basis
on which to distinguish them. Same uid means same access to /proc, same
access to the filesystem, same everything. Any proof of identity is replayable
by precisely the party you are trying to exclude.
This is the same dead end as trying to make a game client trustworthy, and it fails for the same reason: the thing being authenticated has full control over the machinery doing the authenticating.
We spent a while looking for a clever way around it. There isn’t one. Not a hard one — an impossible one.
Change the property instead
The move is to stop trying to build the check and pick a property that does not need it:
- The trigger lives outside the sandbox and runs unconditionally. A watcher on the host samples for change, fires on quiet, and has a ceiling that fires regardless.
- The in-sandbox channel is a hint that can only add a checkpoint, never suppress one.
- Forging a hint is therefore a non-event. You get an extra snapshot.
The property we provide is “the agent cannot prevent a checkpoint” — deliberately not “only the harness can request one.” The second is unachievable. The first is what we actually needed, and it survives an agent that is actively hostile and knows exactly how the mechanism works.
This only works because snapshots are additive. Nothing destructive can
ever go behind a forgeable signal. restore is not on that channel and must
not be put there — an agent that can trigger a restore can destroy work, and
no amount of care in the hint parser changes that.
The second-order problem
Checkpoints self-prune to a budget, which quietly reintroduces the attack from the other side. If eviction were naive, an agent could spam hints until every genuine idle checkpoint had been crowded out of the budget — defeating the mechanism through exactly the channel we left forgeable on purpose.
So eviction order is timed → hint → idle: agent-influenced entries go
first. The forgeable channel can add noise; it cannot displace signal.
Two smaller consequences of the same reasoning. Hint labels are untrusted input that ends up in a commit message, so they are stripped of control characters and truncated. And the label never claims a hint came from the harness, because that is not a thing we can know — it says a hint arrived, which is all that is true.
The general shape
When two parties are indistinguishable to the kernel, stop looking for a credential and start looking for an asymmetry that already exists. Ours was position: the watcher is outside the boundary the agent is confined by, and that is a difference no forged message can erase.