I gave my agent the deployment keys

  • ai
  • agents
  • infrastructure
  • ops

I started with a boring list.

Email. Static sites. Snapshots. A bootstrap path for the next VPS. A way to stop editing production files by hand. The kind of infrastructure work that looks like five small chores until you actually start doing it.

I gave the list to Hermes from Telegram and let it work through the system.

The surprising part was not that the agent could run commands. It can run commands. That stopped being interesting a while ago.

The surprising part was how quickly the work turned into control-plane design.

What changed

The system had accumulated a few different ways to deploy things.

Some static sites lived directly on the VPS. Some services had their own deploy scripts. Caddy was partly source-controlled and partly live state. Backups existed, but some of them were local to the same machine. Secrets were mostly in the right place, except for the places where they weren’t.

None of this was unusual. It was just normal small-server entropy.

But agents are not very good with entropy. They can follow a path if the path is obvious. They get weird when there are five plausible paths and only one of them is safe.

So the work slowly became less about moving services around and more about reducing the number of possible moves.

Static sites moved to Cloudflare Pages. Email moved to Migadu. Caddy became generated output instead of a file to hand-edit. Backups and snapshots moved to systemd timers. Offsite backup sync got its own timer and checksum verification. Deploys moved behind one GitHub Actions workflow with smoke tests after it runs.

There are still a lot of pieces. The difference is that there is now one normal path.

Edit the repo. Validate. Commit. Push. Let CI deploy. Smoke test the public endpoints.

The usual loop, but explicit enough that an agent can operate it.

The root problem

The most useful small change was probably the least exciting one: root-owned files got a promotion helper.

Hermes runs as an unprivileged user, which is good. But infrastructure files live in places like /etc/caddy and /etc/systemd/system, which are root-owned, which is also good. The bad version of this setup is giving the agent broader write access because it keeps running into permission errors.

I did not want that.

So now the agent writes to a staging path, validates the file, and promotes it through a narrow sudo helper that only allows specific destinations. Caddy configs get validated before install. systemd units get checked before daemon reload. Python scripts get compiled before they move into place.

It is a boring piece of plumbing, but it changes the shape of the interaction.

The agent does not need root access to be convenient. It needs a small number of safe escalation paths.

The iii thing

In the middle of this I looked at iii.

The runtime was not the part I wanted to adopt immediately. My VPS does not expose /dev/kvm, and I am not eager to put another runtime in the hot path just because the ergonomics are nice.

But the model stuck.

Workers. Functions. Triggers. A catalog of what exists and what can be invoked.

That maps pretty cleanly onto what I kept wanting for my own infrastructure. Not a platform exactly. More like a small inventory that says: here are the services, here are the deployable surfaces, here are the safe operations, here is how to check whether they worked.

So I added a capability catalog to the infra repo.

It is not very magical. It is markdown generated from manifests. But it gives the agent a shape to look at before it acts.

That seems to matter more than I expected.

What actually changed

Before this, Hermes could deploy things because I knew the system and could steer it.

After this, Hermes can deploy things because more of the system explains itself.

That is a different kind of capability.

A model with shell access can always improvise. Sometimes that works. Sometimes it creates a second deploy path, or edits the generated file instead of the source file, or writes a secret into the wrong place because the right place is only obvious if you remember three old conversations.

The work here was mostly removing opportunities for improvisation.

Not by making the agent weaker. By making the environment less ambiguous.

The part I keep coming back to

I used to think the agent boundary was mostly about permissions. What can it read? What can it write? What needs approval?

I still think that matters.

But the more I run agents against real infrastructure, the more I think the deeper boundary is legibility. Can the agent tell what the system thinks is true? Can it find the normal path? Can it verify the result without asking me to trust it?

If the answer is no, autonomy just means faster guessing.

If the answer is yes, the whole thing feels less like handing over the keys and more like adding another operator to a system that already knows how it wants to be operated.

I’m not sure this is the final shape.

But I am pretty sure agents do better when the safe path is also the obvious one.