squad
Cloud agents that keep working while you sleep. Give one a standing job — watch a repo, track a rival, fix a check that broke — and it wakes on its own to do it, then writes back to tell you how it went.
> every monday at 8, diff the other three's pricing and changelogs and mail me what moved
‹wake› monday: diff the three, mail what moved
‹→ email› Two moved. One put a seat price on the plan that was flat before, at $29; the other took the free tier down to a trial and said so nowhere but the pricing page.
Three problems decide the whole design
An unattended agent reads what strangers write
A GitHub webhook is authentic and still relays an issue body typed by anyone. So every event carries a trust level, and everything that is not from the operator arrives fenced, as data — in one place, so a new channel adapter cannot forget to do it.
Only an operator gives instructions.
A credential can be spent anywhere
So the agent never holds one. Egress goes through a proxy that matches the request against operator-approved grants and attaches the secret afterwards. An agent that talks itself into exfiltrating its own API key has nothing to send.
The agent never sees a key.
An agent can edit its own definition
Its repository holds a manifest, but a manifest is a request. Grants live in the control plane's config file, which the agent cannot write, and an agent may ask for a capability it does not have.
Nothing it says can grant it anything.
The only road out is the proxy
The sandbox network is internal, which really is unrouted — a container on it cannot reach the host or the internet by any address. So the proxy is not a convenience the agent could route around, and reaching the model provider is a grant like any other, written onto the request on its way out.
A request that was denied, or came back 401 or 429, is said the moment it happens, because it is the reason the agent is about to misbehave. The ones that worked are counted rather than printed.
18:12:53 maxi bash pnpm -r test18:12:53 maxi bash ✗ after 12.4s: FAIL test/turn.test.ts > carries the failure detail18:12:53 maxi read packages/control-plane/src/turn.ts18:12:53 scout egress ✗ denied GET api.github.com/repos — no_matching_host18:12:53 maxi answer El test esperaba el mensaje viejo.18:12:53 maxi spent 1m38s · 91.2k tokens · $0.02 · api.deepseek.com ×12
squad logs — what every agent runs, answers and spends.
One control surface, and it is a terminal
A running plane listens on a unix socket in its state directory. There is no password because there is nothing to authenticate: the socket is 0600, and reaching it already means holding a file the operator owns. That is also why it is the only way in that carries operator trust.
squad on its own opens the console, because someone typing the command with nothing after it is asking to see the thing, not to be told a fact about it.
It is the one at the top of this page. The column on the left is every agent the plane has, up, stopped or mid-turn — thinking gets a mark of its own because with several agents on screen it is the one thing you cannot find out by asking again in a second. What each has spent today is on its row, yellow at four fifths of its ceiling and red at it. Under the last agent is the row that makes one. tab swaps the panel for the log feed, / opens the commands, and ! is the door into the box the agent lives in — the same directory, the same environment, the same proxy.
| squad chat demo | talk to one in the scrollback, turn after turn |
| squad ls | what each agent is and whether it is up |
| squad wake "check the open issues" | take one turn, and wait for the answer |
| squad logs | follow what every agent runs, answers and spends |
| squad rm demo --purge | the sandbox, and with --purge the repository inside it |
A line starting with / is a command about the agent rather than something said to it, answered without waking anything. It configures the plane from inside it: nothing here is a file you edit and redeploy.
| /limit | what it has spent today, and the ceiling for it |
| /model | what it thinks with, and what else there is |
| /mcp | the MCP servers it has, and the shelf to add from |
| /serve | a port inside it, on the machine you are sitting at |
| /telegram | the bot it answers on, and how to pair one |
| the address it is reached at, and how to connect a mailbox |
Three ways in, and only two of them may instruct
| webhook | Signed, on the one port that is published. The secret proves which system sent the request, never that a person meant what is inside it. |
| telegram | A bot per agent, connected with /telegram and paired by a link. Anyone else who writes to it is heard as a participant. |
One mailbox for the whole plane, connected once with /email. Every agent is reached at its own tag — agents+scout@… — and answers in the thread. |
A webhook's secret says which system sent a request. The other two say who: Telegram authenticates the account behind every message, and a mail is judged by the Authentication-Results header your own provider wrote at delivery, when it checked DKIM and DMARC. So those two can be paired to a person — with a phrase that is spent the moment it is used — and what that person writes is an instruction.
Neither costs a domain, a certificate or an open port: both reach out rather than being reached. Mail from anyone unpaired is left unread rather than fenced, because an address strangers already have is one where every message read would spend a turn.
Nothing has to write to it at all. wake_me asks for another turn and leaves the agent a note to be told then — a file the plane reads and removes, not a path out of the sandbox, so it is checked rather than trusted: one appointment at a time, between a second and a month, and never carrying operator trust however it asks.
A repository it owns
On its first boot an agent gets a repository in its own volume, at /home/agent/.self:
| agent.yaml | name, model, and the capabilities it asks an operator for |
| soul.md | who it is; appended to the system prompt on every turn |
| skills/ | SKILL.md folders, loaded by pi |
| memory/ | what it chose to remember, partitioned by users, projects and reference |
| tools/ | scripts it wrote for itself |
It is scaffolded once, git-initialised, and then left alone: turns run inside it, so what the agent learns and what it can do are files it edits and commits itself. The control plane never writes there again, because the second write would be the control plane overwriting the agent's own work.
Nothing in that repository grants anything. agent.yaml lists capability requests, and an operator answers them in the config file the agent cannot reach.
Seven packages, no build step
It is a runtime, not a harness. The thinking is done by pi — squad gives it a machine to live on, a way to be woken, and a boundary to work inside.
| events | Trust levels, fencing, the durable event bus, and the renderer that turns a batch of events into one turn |
| proxy | The egress broker: a CONNECT-terminating MITM proxy with a local CA, grant matching and credential injection |
| sandbox | The Docker driver: container per agent, named volume for its repository, exec streams demultiplexed from the daemon's framing |
| scheduler | Cron and one-shot wakeups, persisted, with Vixie cron semantics and DST-correct wall-clock matching |
| channels | Where events come from and replies go: a signed webhook, a Telegram bot per agent, and one mailbox for the whole plane |
| agent-repo | The agent's own git repository: manifest, soul, skills, memory, tools |
| control-plane | Wires it together, takes turns by running pi in the sandbox, and reads a YAML config |
| client | The squad you install: picks where the plane lives, puts one there, and dials it — over a socket here or ssh vps squad relay |
The honest list
- A long-lived pi session. Each wakeup runs
pi --printagainst a per-agent session directory, so context carries across turns but the process does not. The plumbing for a persistent session is written and tested, and unused, because the published pi has no server entry point to run yet. - Slack, Discord and the rest. Webhooks, Telegram and mail are there, and a reply is routed by the channel of the event that caused it, so an agent answering a GitHub hook cannot be steered into replying elsewhere by anything in the payload. The others are adapters nobody has written.
- Anything multi-tenant. One config file, one operator, one machine.
- Isolation stronger than a container. One Docker container per agent, not a microVM — because if self-hosting needed microVMs nobody would run it. It is not a boundary to put hostile code inside.