A persistent desktop where autonomous agents work: tools, sandbox, privilege, durable state and kernel observation. Plus a loop that knows to stop — including when it delivered without fulfilling the request.
Swapping models changes little. What decides whether an agent works is what it can touch and what it does until it stops. This repository is those two layers, built and measured.
The model is the smallest of the three. The loop decides what it does until it stops; the harness decides what it can touch.
the two layers
One test tells them apart, and it is short
If you can fix it by changing a prompt or a stop condition, it is the loop. If you have to change what the agent can do, it is harness. Almost every agent diagnosis starts by getting this question wrong.
Harness
what it can touch
10 tools: shell, 6 browser ones, take-over, delegation to another coding agent
Per-user sandbox, with sudo on a closed list
Encrypted vault — the model uses the credential without reading it
Durable state on a volume that outlives the machine
Shape of the error: a tool failure becomes text in the history, it does not kill the task
Observation: OpenTelemetry around the loop and eBPF in the kernel
Changes by recompiling and reinstalling. It is what the agent is able to do.
Loop
what it does until it stops
Up to 60 iterations per invocation, 180 turns counting resumes
Loop detector: 3 identical failures in a row
Cost ceiling in dollars, per task
Wall-clock ceiling
Completion check before accepting that it finished
Seven stop reasons, each with what to ask the person for
Changes with an environment variable. It is what the agent does with what it has.
loop engineering
Stopping is not fulfilling
The loop finished a task when the model stopped calling tools. That measures that it stopped, not that it did what was asked — and the gap is where the silent defect lives: the task reads “done”, nobody looks, and the request never happened.
Verification demands evidence quoted from the history: a verifier that could approve without pointing at anything would approve out of inertia.
The seventh stop reason points the opposite way from the others
Five reasons describe what the site demands — password, 2FA, CAPTCHA, payment, human presence. The sixth, guardrail, contains what went too far: loop, cost, turns, time. The seventh, unverified, marks what stopped too early. On screen the question differs in each case: in one you decide whether to continue, in the other you find out what is missing.
harness engineering
Three extension points, and picking the wrong one costs rework
The same test from the previous section decides where a new capability goes. The colour of each row says which layer.
skillA repeated procedure, in markdown. It goes into the prompt.loop
connectorA manifest that grants reach to an API — with a credential the model uses and never reads.harness
runnerAnother coding agent, for work that is about writing software.harness
The common mistake is a skill that teaches calling the API with curl: the credential moves to the command line, within reach of exactly who it should contain.
privilege
The declared adversary is the model itself
The machine publishes nothing: only port 22 in the firewall, with screen, browser and API on loopback. The security question is not who gets in from outside — it is what the model reaches through the shell tool, which is the product’s function, not a flaw.
User
Who it is
Reaches
agentd
the service
the encrypted vault, /etc/agentd
agent
the model’s tools
/workspace, sudo by closed list
root
the operator, via a key that exists only on the laptop
everything
observation
Three layers, because one does not answer the whole question
The first knows the intent and is blind to what the system did. The second knows the fact and is blind to the intent. Only together do they answer “the model tried X and the machine did Y”.
agentdOpenTelemetry spans around the loop, following the GenAI conventions. No prompt or command content leaves the machine — a hash goes in place of the arguments.loop
agent-probeA purpose-built eBPF collector, in Go. Every execve with its full path, every outbound connection flagged when the destination is a private network.harness
PSICPU, memory and disk pressure read from the kernel. This is not eBPF, and saying so is part of the design: the kernel already computes it, and recomputing would cost more and measure worse.harness
The kernel layer is the only one the model cannot switch off from inside — and even it falls to anyone with sudo. That is why the proof that it is alive lives off the machine: absence of data is the signal.
firewall22/tcp · everything else on 127.0.0.1invariant
agent-probeeBPF · execve + connectstarts at boot
tela 5stopped without fulfilling the requestneeds you
containment
Every loop ceiling adjusts without recompiling
A ceiling that only changes by recompiling is a ceiling switched off. And there is one more reason: the containment test has to force it, because asking the model to fail produces a test that passes and fails at random — measured, with the same prompt across two runs.
Variable
Default
What it stops
AGENTD_MAX_TURNS
180
Accumulated turns, carried across resumes
AGENTD_MAX_TOOL_FAILURES
3
Identical failures in a row — this is the loop detector
AGENTD_MAX_COST_USD
3.00
Inference per task, in dollars
AGENTD_MAX_VERIFY_ATTEMPTS
2
Times the gap goes back before calling a person
AGENTD_MAX_CONCURRENT_TASKS
—
Concurrent tasks across the whole machine
getting started
From zero to an observed machine
task checkChecks binaries, token, key and latency — before spending anything
AGENT_OS=nixos task upDurable volume and droplet, declarative system, end-to-end validation
task deploy && task vaultInstalls the agent over root SSH and provisions the encrypted vault
task obs:upBrings up the observation backend on the laptop, with Nix
task probe:deployCompiles the BPF objects and installs the kernel collector
task openOpens the live screen through a tunnel — nothing is exposed