Anton, chapter 6: Two boundaries
Four days produced the densest stretch of the project so far.
By the end of them, Anton could discover other Anton instances, run skills in a sandbox, browse unfamiliar websites, manage a permission-aware family vault, and review his own failures.
Those features looked unrelated until a security model emerged beneath them.
Anton had two boundaries.
The agent boundary determines who may ask for an action.
The skill boundary determines what the code performing that action is allowed to do.
One governs authority. The other governs capability.
Neither can replace the other.
A mesh of Antons
I wanted Anton instances to find one another without becoming one shared system.
They should not share a database, secrets, or unrestricted access. They should discover one another and forward specific calls according to the relationship between them.
I called the protocol SCUT: Symmetric Cluster Universal Transport.
Every node had the same shape. Probes handled discovery, heartbeats handled liveness, and an invocation forwarder carried requests between instances.
The important idea was that the instance itself was the identity, while the relationship between two instances defined the available permissions.
A clone serving another household might be allowed to ask my Anton for a media search without seeing the family vault, wine collection, or Plex credentials.
Federation should be relationships, not shared infrastructure.
The skill boundary
The existing skill runner was a Node service with no meaningful runtime isolation.
A skill could inspect every environment variable, execute arbitrary commands, read the filesystem, or connect anywhere on the network.
That was tolerable while everything was personal code on one private server.
It was not tolerable once other instances could forward invocations.
I rewrote the runner on Deno.
Each skill now ran in a Worker with only the permissions it declared: selected environment variables, specific network hosts, and explicit filesystem paths.
Nothing more.
The rewrite was painful because Deno's stricter execution model revealed every assumption Node had quietly permitted: loose imports, implicit package resolution, hidden environment dependencies, broad filesystem access.
Each failure was inconvenient and useful.
The runtime was showing me what each skill actually required.
The result was a capability boundary the code could not cross merely because an agent asked it to.
Secrets belong at the call site
Vaultwarden came out next.
Secrets moved into an encrypted Postgres table: one system to back up, one system that survived cloning, one place to inspect and edit from the dashboard.
But storage was only half the problem.
A secret must reach the skill that needs it without becoming visible to everything else.
The parent process environment could not safely contain all secrets. A Worker with no requested environment access might still exploit a mistake in the surrounding process if the values were already present there.
Secrets therefore travelled to the Worker through postMessage, at the moment of invocation.
The sandbox is only as honest as the boundary you actually defend.
Explore agentic, build deterministic
The browser was the next repeated problem.
Doctolib, the building management portal, and the consulate monitor each had their own Playwright script. The code duplicated intent and relied on selectors that broke whenever a page changed.
I replaced them with one generic browser agent capable of navigating, clicking, typing, evaluating, and taking screenshots.
The LLM could explore a page it had never seen and find a workable path.
That did not mean the LLM should execute the same path forever.
A pattern emerged:
Explore agentic. Build deterministic.
The browser agent is excellent at scouting an unfamiliar interface. Once a workflow becomes stable and repetitive, the result should be encoded directly whenever possible.
The model discovers the path. Software records it.
The request_input mechanism also found its final shape here. Any running tool could pause, ask a person for a code or decision, and resume with the answer.
Human participation stopped being a special case inside Doctolib and became a general runtime primitive.
A vault with visibility
The family vault introduced a different kind of boundary.
Documents could be marked family or personal, with explicit visibleTo overrides. The answer to "who can see this?" lived on the document itself rather than being inferred through a complicated role graph.
Vision extraction and fact generation followed. Expiry dates could become reminders; scanned documents could become searchable facts.
But the cost pattern from the browser repeated.
Running vision over every document would be slow and wasteful. The model should scout difficult files and help establish the extraction path, not become the default parser for everything.
The LLM is the scout, not the worker.
The same vault absorbed a family Notion workspace. A few awkward imports were resolved directly rather than asking the model to keep improvising around a dependency problem.
Sometimes rewriting one import is cheaper than adding intelligence.
Anton reviews Anton
The final change was the one I had been waiting to build.
Every night, Anton reviewed the previous day's execution traces, grouped failures, and created GitHub issues for the clusters it found.
Labels drove a simple state machine:
needs-triage, then ready-to-fix, then fixed-locally, then deployed.
This was only possible because the traces were complete.
Without traces, Anton would have judged its final answers. With traces, it could inspect which tools were called, what they returned, where the reasoning stopped, and what actually failed.
Self-reflection is only useful when the substrate is honest.
A system cannot meaningfully critique a story it invented after the fact.
Two questions
Several smaller changes reinforced the same architecture.
All agents converged on one delegate registry. The dashboard showed only agents the selected user could invoke. Prompt-injection handling gained explicit trust markers and a risk trail. Directives became standing instructions that would need periodic pruning. The consulate monitor began as an observation over six months and narrowed to four only after seeing the data.
Observe first. Tune second.
By the end of the four days, Anton could communicate across an authenticated mesh. Skills ran with minimum permissions. Secrets were decrypted only where they were needed. Websites could be explored by a general browser agent. Documents carried their own visibility. Every night, the system inspected its own failures.
The architecture rested on two questions:
Who is allowed to ask for this?
What is the code allowed to do?
The first question belongs to agents and relationships.
The second belongs to skills and the runtime.
Security became much easier to reason about once I stopped asking one boundary to answer both.