Run your agents anywhere, anytime with the network.
Cloud Agents for the work that cannot live on your laptop, plus the naming and directory that lets networks find each other.
Built on Baton, the open-source agent runtime: baton.wiki
Cloud Agents for the work that cannot live on your laptop, plus the naming and directory that lets networks find each other.
Built on Baton, the open-source agent runtime: baton.wiki
Hosted naming and a signed endpoint record, so two networks find each other without either one running DNS.
Cloud Agents we run and keep reachable. You pick how one is served — Standard or Premium — and pay for what actually runs.
The public directory: workspace templates and agent skills, published and installed by name.
Everything BatonCloud does is reachable from the CLI. There is no operation here that needs a browser, and the panel never calls this service directly — it asks your local CLI, and the CLI talks to us.
The endpoint is configuration with a default, not a constant. That is what makes BatonCloud a provider rather than the provider.
export BATON_CLOUD_URL=https://dev.batoncloud.org baton cloud status --output json
Status is three values, never two: connected, not_registered, unreachable. Unreachable is not the same as unregistered — the first says we do not know, the second would send you to register something you may already have.
Two credentials are involved and they prove different things. A browser sign-in proves who may claim a name; a signature from your control plane proves which network is being registered. Neither substitutes for the other.
baton cloud register # identity only, no hosted name baton cloud register my-team # identity + claim a hosted name
The name is optional on purpose. Registering an identity and claiming a name are separable acts — a network that already has nike-agents.dev resolves through DNS and never needs a hosted name, but still registers so that later signatures are checkable.
A hosted network name is a label, not a hostname. It becomes the first half of an address local-part:
hosted network nike@batoncloud.org
agent nike.coder@batoncloud.org network folds into the local-part
own domain network agents.nike.com the network IS the domain
agent coder@agents.nike.comSo the same word network lives in two different places depending on which tier you are in. In the second tier we are not involved in your naming at all — you prove control of the domain with a TXT record and BatonCloud never learns the name. Three rules follow from the address being something a person has to say out loud and write down:
a.b.c@batoncloud.org ambiguous with no rule to choose a reading.+. It already means sub-addressing in an email address.Two keypairs. Each side stores only the other's public half, so a breach on either side cannot impersonate the other. There is no shared secret anywhere in this model and no route that accepts one.
Your network BatonCloud SK_A / PK_A SK_C / PK_C A → Cloud SK_A signs ─────────> verify PK_A "I am Network A" Cloud → A verify PK_C <───────── SK_C signs "I am the Cloud A authorised"
Create a binding in the dashboard, copy the passkey it mints, and install it locally:
baton integrations add --passkey-file ./passkey.pem \ --provider baton-cloud --scopes skill.install
For reads that act as the network rather than as your account, exchange a signed attestation for a short-lived token:
POST /api/auth/network/token
{ "network_id": "net_…", "payload": "<base64>", "signature": "<base64>" }
payload, newline-separated:
cloud-token
<network_id>
<audience> e.g. https://dev.batoncloud.org
<RFC3339 UTC timestamp>
<uuid nonce>
→ { access_token, token_type: "Bearer", expires_in: 900, scope: "network" }This token cannot claim a name or touch billing. That limit is mechanical rather than a matter of good behaviour: the token carries no account identity at all, so the routes that need one cannot load it. A network key proves what a network is — never what its owner is entitled to claim.
Every failure has the same three fields, and the third is written for a person.
{ "code": "CLOCK_SKEW",
"message": "the timestamp is 612s away from this service's clock",
"remediation": "Check this machine's clock — this is not a key problem." }CLOCK_SKEW is deliberately its own code and never shares one with a bad signature. When a clock has drifted every request fails, and an operator told "invalid signature" will spend the outage investigating a key that was never broken.
The full route list, request shapes and status codes are in the OpenAPI document.
Agent applications sit on top. Baton is the agent and network layer under them. Below that is a row of choices — where your agents actually run — and under it the harness and the model. That row is where we are, and it is a row precisely so that anyone in it can be swapped for anyone else.
Read the arrow between the runtime tier and Baton carefully: it points up. A provider binds into Baton across the Provider Contract — Baton does not run on BatonCloud, and BatonCloud is not a tier of Baton. We are one cell of four in that row, every cell is the same node model, and pointing the CLI at a different one changes nothing above it.
The same three as above, with what is in each one. Everything here is reachable from the CLI — there is no operation that needs a browser.
Naming and endpoint publication, so two networks can find each other without either of them running DNS. Once they have, we are out of the path.
Agents we provision, patch and keep reachable, doing work that outlives the session. This is the part you pay for — operations, not a capability the open edition lacks.
The public directory: workspace templates and agent skills, published and installed by name, readable before you federate with anyone.
Five of them. The rest — keys, limits, what happens if we go away — are on the FAQ page.