# Protocol

> The raw messages a program sends and receives on the workspace API: the hello line, calls, answers, events and error codes, for any language.

Source: https://hivemind.griiken.com/guide/api-protocol/

The messages behind the SDKs, for writing a client in any language. This page reflects workspace API version `0.30`.

<AgentPrompt description="Paste this into your coding agent to write a client for the workspace API in a language with no SDK." page="api-protocol">{`
Using the messages on this page, write a client for the Hivemind workspace API in the language I name.

1. Ask me for the language and the workspace folder. Make a token with the smallest role it needs: \`hive grant create <name> -w <folder> --role view --expires 7d --json\`. Keep the token out of this chat and out of git: read it from the environment variable \`HIVE_DRIVE_TOKEN\`, with \`HIVE_DRIVE_SOCK\` and \`HIVE_WORKSPACE\`.
2. Connect to \`HIVE_DRIVE_SOCK\` (a Unix socket on Linux and macOS, a named pipe on Windows). Within 10 seconds send one line: \`{"hello": {"token": "<token>", "workspace": "<folder>"}}\`.
3. Send calls as one JSON line each, \`{"id": n, "method": "...", "params": [...]}\`. Match answers by \`id\`; they can come out of order. A line without an \`id\` is an event, or a JSON list of events.
4. Handle every error code in the table on this page.
5. Call \`agent.list\` for the folder, print the result, then print events for 30 seconds. Show me the output.
`}</AgentPrompt>

## Find the socket [#find-the-socket]

`hive grant create` prints the socket's path (`socket` with `--json`). By default it is:

| System | Socket |
|---|---|
| Linux | `~/.config/hivemind/drive.sock`, or `$XDG_CONFIG_HOME/hivemind/drive.sock` |
| macOS | `~/Library/Application Support/hivemind/drive.sock` |
| Windows | A named pipe. Use the path `hive grant create` prints. |

The app or `hive host` must be running, with the workspace open.

## Messages [#messages]

Every message is one line of JSON, both ways.

### Hello [#hello]

Send this within 10 seconds of connecting:

```json
{"hello": {"token": "hgr_…", "workspace": "/home/you/work/api"}}
```

`workspace` is the workspace's folder. Leave it out when the token reaches only one workspace.

| You get back | Means |
|---|---|
| `{"hello": {"api": "0.30", "as": {"client": "dashboard", "role": "view", "workspace": "/home/you/work/api"}}}` | You are in. `role` is what you may do. |
| `{"error": {"code": "FORBIDDEN", "message": "…"}}` | Wrong, revoked or expired token, or a workspace it does not reach. The connection closes. |
| `{"error": {"code": "BAD_REQUEST", "message": "…"}}` | No hello, no workspace named, or the workspace is not open. The connection closes. |

### Calls and answers [#calls-and-answers]

A call has an `id` you choose, a `method` and a list of `params`. Its answer has the same `id`, and either a `result` or an `error`:

```json
{"id": 1, "method": "agent.list", "params": ["/home/you/work/api"]}
{"id": 1, "result": {"seq": 0, "agents": []}}
```

```json
{"id": 2, "method": "agent.start", "params": ["/home/you/work/api", {"program": "claude", "prompt": "Run the tests"}]}
{"id": 2, "error": {"code": "FORBIDDEN", "message": "agent.start is not open to your role on this workspace"}}
```

Where a method takes a folder (`repo`), give the workspace's folder. Answers can come out of order, so match them by `id`.

### Events [#events]

A line without an `id` is an event, `{"event": "…", "params": [...]}`, about the workspace's tiles. Several events from one moment may arrive as a JSON list on one line.

- `agent.changed`: an agent appeared, changed or went.
- `agent.event`: an agent finished replying (`turnEnded`), started waiting on you (`waiting`), was answered (`answered`), came, went, or reported.
- `status.changed`: an agent's status changed.
- `terminal.data`: output of a terminal you opened.
- `tile.lines`: the last line of terminals you watch (`tile.watchLines`) changed, at most every 250 ms.
- `file.changed`: files changed in the workspace.

## Error codes [#error-codes]

| Code | Means |
|---|---|
| `FORBIDDEN` | The role does not allow it. Nothing ran. |
| `BAD_REQUEST` | Wrong params, or a path outside the workspace. Nothing ran. |
| `UNKNOWN_METHOD` | No method by that name. |
| `NOT_FOUND` | No such tile, frame or workspace. |
| `ASLEEP` | The agent is asleep: wake it first. |
| `NO_AGENT` | The tile runs no agent (a tool, a browser, a shell), and the method asks an agent's reply. |
| `UNSUPPORTED` | The agent cannot do that: it does not tell Hivemind when it finishes replying, or keeps no sessions. |
| `TIMEOUT` | What it waited on did not come in time. |
| `UNAVAILABLE` | It needs one of your windows open. |
| `RATE_LIMITED` | Too many started at once. Ask again later. |
| `DEPTH_EXCEEDED` | Agents started by agents, too many levels deep. Start it from higher up. |
| `OCCLUDED` | Something is drawn over the tile `tile.look` would picture; the message names it. Nothing was pictured. |
| `FAILED` | It ran and failed. `message` says why. |

## A whole example [#a-whole-example]

This connects, lists the agents and tries to start one. With a `view` token the start is refused. Both run on Linux and macOS, over a Unix socket.

<CodeGroup>
```python Python
import json, os, socket

SOCK = os.environ["HIVE_DRIVE_SOCK"]
TOKEN = os.environ["HIVE_DRIVE_TOKEN"]
FOLDER = os.environ["HIVE_WORKSPACE"]

s = socket.socket(socket.AF_UNIX)
s.connect(SOCK)
f = s.makefile("rw")

def send(msg):
    f.write(json.dumps(msg) + "\n")
    f.flush()

def call(id, method, *params):
    send({"id": id, "method": method, "params": list(params)})
    while True:
        msg = json.loads(f.readline())
        if isinstance(msg, dict) and msg.get("id") == id:  # not an event
            return msg

send({"hello": {"token": TOKEN, "workspace": FOLDER}})
print(f.readline())                   # {"hello": {"api": "0.30", "as": {…}}}
print(call(1, "agent.list", FOLDER))  # {'id': 1, 'result': {'seq': 0, …}}
print(call(2, "agent.start", FOLDER, {"program": "claude", "prompt": "Run the tests"}))
```

```js Node
import net from "node:net";
import readline from "node:readline";

const { HIVE_DRIVE_SOCK, HIVE_DRIVE_TOKEN, HIVE_WORKSPACE } = process.env;
const socket = net.connect(HIVE_DRIVE_SOCK);
const waiting = new Map();
let next = 1;

const call = (method, ...params) => new Promise((resolve) => {
  const id = next++;
  waiting.set(id, resolve);
  socket.write(JSON.stringify({ id, method, params }) + "\n");
});

readline.createInterface({ input: socket }).on("line", (line) => {
  const msg = JSON.parse(line);
  if (waiting.has(msg.id)) { waiting.get(msg.id)(msg); waiting.delete(msg.id); }
  else console.log(line);                // the hello, then events
});

socket.write(JSON.stringify({ hello: { token: HIVE_DRIVE_TOKEN, workspace: HIVE_WORKSPACE } }) + "\n");
console.log(await call("agent.list", HIVE_WORKSPACE));
console.log(await call("agent.start", HIVE_WORKSPACE, { program: "claude", prompt: "Run the tests" }));
socket.end();
```
</CodeGroup>

## Start an agent and wait for its reply [#start-an-agent-and-wait-for-its-reply]

With an `agents` token:

```json
{"id": 3, "method": "agent.start", "params": ["/home/you/work/api", {"program": "claude", "prompt": "Run the tests"}]}
{"id": 3, "result": {"tile": "…"}}
{"id": 4, "method": "agent.read", "params": ["<tile>", 600000]}
{"id": 4, "result": {"text": "…", "finalStatus": "turn", "truncated": false}}
```

`agent.read` waits up to the milliseconds you give for the agent to finish replying. `finalStatus` is `turn`, `timeout`, `working`, `idle`, `closed` or `waiting`. A tile that runs no agent is refused `NO_AGENT`, and an agent asleep `ASLEEP`.

The full protocol is [`spec/workspace-api.md`](https://github.com/dip497/hivemind/blob/main/spec/workspace-api.md) in the repository.

<LastValidated />
