Skip to content
SkyKeephelp

Connecting a remote agent

An agent running on ANOTHER machine can query this vault the same way a browser does: over the network, through the same TLS front door, presenting a bearer token. This page is the operator's path from nothing to a working connection. The tools the agent may then call are documented on the agent tools page; this page covers only how it gets a token in the first place.

  1. What reaches the vault, and from where

    The same front door everything else uses: the tls-proxy container is the deployment's only published HTTP entry point (ADR-0010), and it proxies every path — including the agent routes below — to the webserver, which itself publishes no port at all. Nothing about connecting an agent widens what is reachable from outside the deployment; the agent reaches exactly the same https://<your-host>:<tls-port> address a browser does. Once connected, an agent talks JSON-RPC to POST /mcp — scoped tools covering what a person can do to documents (searching, reading, thinking, uploading, editing, comparing versions, and the review queue) and nothing the administrator's console does. They are described in full on the agent tools page, which the paragraph at the top of this page links.

  2. Step 1 — register the agent client (administrator, audited)

    Signed in as an administrator, POST /api/admin/agents with the principal_id of the HUMAN who will own this agent — an agent can never be ownerless, by rule enforced again in the database itself. The response carries client_id, principal_id and client_secret. client_secret is shown EXACTLY ONCE: nothing stores it in the clear, and if it is lost the only recovery is registering a new client. The registration itself is audited under the admin console's own trail, naming the administrator who acted and the owner named.

    POST /api/admin/agents # {"owner_principal_id": "<the owner's principal id>"}
  3. Step 2 — grant the agent's own compartments

    A freshly registered agent can reach nothing: it has zero compartment grants, which resolves to the empty set on both sides — the query gate's ordinary fail-closed default, not a special case for agents. Grant it read, or write (which carries read with it), on whatever compartments the person running it should be able to reach through it, the same way you would grant a person — POST /api/admin/grants with the agent's principal_id. The engine additionally refuses any grant to an agent that is not read or write: an agent can never hold edit or admin authority, whatever the request asks for. Grant narrowly: these compartments are also the ceiling on every LATER use of this agent, including the ones where it acts for somebody else. An agent acting for a person reaches the overlap between that person and itself, so what you withhold here is withheld from every one of those uses too.

  4. Step 3 — the agent exchanges its client credentials for a token

    From wherever the agent runs, POST /api/agent/login with client_id and client_secret in the body — never in a URL, same rule as every credential in this vault. A correct pair returns an ordinary bearer token good for FIVE MINUTES (the same ceiling every non-human session in this vault carries, and it is a ceiling: nothing an operator configures can raise it). There is no refresh call — an agent that needs to keep working past five minutes asks again with the same client credentials, which is also where a revoked or disabled client stops working immediately rather than at its next long-lived renewal.

    POST /api/agent/login # {"client_id": "...", "client_secret": "..."}
    POST /api/admin/agents/revoke # {"client_id": "..."} — administrator only, audited: the client's credential and its live tokens die now; the agent's identity and audit history stay
  5. Step 4 — use the token exactly like a human session

    Present it as Authorization: Bearer <token> on any portal query route. NOT on POST /mcp: that transport takes the delegation tokens of the step below and refuses this opaque session, because a delegation names the person an agent acts for and an opaque session names only the agent, and the credential that reaches the document surface must be the one the audit trail can speak about. An agent that wants POST /mcp while acting purely as itself mints a chain one link long at POST /api/agent/self and reaches exactly what this token reaches. From this point the agent IS an ordinary principal to the vault: the same three-identity verification, the same compartment predicate resolved from its own grants, the same audited outcome on every call — nothing about being an agent widens what it may reach or skips what gets recorded. A token that is missing, malformed, expired, or belongs to a disabled client is refused, and the refusal itself is audited. That is the token the step above issues, and it carries the agent's own authority and nothing else. The step below adds tokens that carry a PERSON'S authority as well; those are narrower than this one rather than wider, and POST /mcp is the only surface that takes them.

  6. Step 5 — an agent acting for a person, and a device approved from another screen

    Three further ways an agent can come by a token, and all three end in the same place: a bearer token presented to POST /mcp in the ordinary Authorization header. Every credential in all three travels in the POST body, never in a URL, and every refusal is the same detail-free 403 — uniform on purpose, because a refusal that says WHICH part was wrong tells a caller which of its guesses had the right shape. The first flow is the agent acting as ITSELF without going through the portal: POST /api/agent/self with its own client credentials returns a token carrying a signed chain one link long, which is to say a token that states who acted and on whose behalf, and gives the agent as both answers. The second is the agent acting FOR a person: POST /api/agent/exchange with its own credentials and the sign-in token of the person it is acting for. What comes back reaches the INTERSECTION of what that person reaches and what the agent itself reaches — never wider than either of them. That is the whole safety argument for delegation: a person cannot widen an agent by lending it their sign-in, and an agent cannot widen a person by being generously granted. The third is for an agent running somewhere with no browser at all. POST /api/agent/device/begin returns two codes: a device code the agent keeps to itself, and a short user code it shows the person. The person, signed in on their own screen, approves that short code with POST /api/agent/device/approve — their own session in the Authorization header, the code in the body — and the agent then collects its token with POST /api/agent/device/poll. An approved grant is collected ONCE: a second poll on the same device code is refused, so a short code read over somebody's shoulder is worth nothing the moment the agent has spent it. Every token from all three flows lives at most FIVE MINUTES — the same ceiling every non-human session in this vault carries — and a delegated token never outlives the authority of the person it acts for. An ADMINISTRATOR or a clientadmin can NEVER be the person a token acts for: the vault refuses when the token is minted and refuses again every time it is used. Those accounts hold no compartments and they do hold console authority, so acting for one would never be borrowing reach — it would only ever be borrowing the console. Revoking an agent client kills its delegations at once rather than at some next renewal, because the chain is re-checked against the vault on EVERY use: a revoked client, a disabled person anywhere in the chain, or a compartment grant taken back stops the token inside those five minutes rather than after them. Every grant, every refusal and every use is audited, and the audit row for a served request carries the whole chain — who acted, for whom, under which client — so the record answers the question a delegated request actually raises. One boundary is worth stating plainly, and it runs both ways: POST /mcp is the ONLY surface that accepts a delegated token, and a delegated token is the ONLY agent credential POST /mcp accepts. The portal's own routes still want an ordinary session, so an agent that wants those signs in as itself at POST /api/agent/login, exactly as the step above describes; an agent that wants the tools mints a chain here, even when the only name in that chain is its own.

    POST /api/agent/self # {"client_id": "...", "client_secret": "..."} — the agent as itself, a chain of one link
    POST /api/agent/exchange # {"client_id": "...", "client_secret": "...", "subject_token": "..."} — acting for that person, and reaching the overlap of the two
    POST /api/agent/device/begin # {"client_id": "...", "client_secret": "..."} — returns the device code the agent keeps and the short user code it shows the person
    POST /api/agent/device/approve # {"user_code": "..."} — the PERSON calls this from their own signed-in session, their session in the Authorization header
    POST /api/agent/device/poll # {"device_code": "...", "client_secret": "..."} — once, and once only: a collected grant is spent

Pointing Hermes Agent at this vault

Hermes Agent reads its MCP servers from ~/.hermes/config.yaml under mcp_servers. A server entry with a url speaks MCP over streamable HTTP — the transport this vault already offers — and its headers are sent verbatim, which is where the bearer token goes. Nothing extra has to be installed on the vault, and nothing has to run beside the agent: there is no wrapper process and no second endpoint.

mcp_servers:
  skykeep:
    url: "https://<your-host>:<tls-port>/mcp"
    headers:
      Authorization: "Bearer ${env:SKYKEEP_TOKEN}"
    ssl_verify: "<path-to>/skykeep-ca.pem"
    timeout: 300

Four things in that file matter, and each of them is a rule rather than a preference:

What the agent can then do is the agent tools page: reading, searching and thinking over documents, uploading and editing them, comparing versions, and working the review queue — all inside whatever compartments the token reaches, and nothing outside them. The administrator's console, anybody's account and the audit trail are not tools and are not reachable this way, whatever the agent asks for.

If the agent reports that every call is refused, the token has almost certainly expired; if it reports that it cannot connect at all, the certificate authority path is the first thing to check. Both are on troubleshooting beside the other connection faults.