hello
The server’s half of the handshake: the protocol range it speaks, and who this connection is.
Direction: Chips → Harness
Sent by: Chips only; refused from a client
Requires a run: no
Schema: hello.json
Fields
Section titled “Fields”Every event also carries the envelope.
| Field | Required | Type | Description |
|---|---|---|---|
v |
yes | 1 |
Protocol major version. |
type |
yes | "hello" |
|
id |
yes | string |
The event’s own id. Stable across a retry of the same event. |
ts |
yes | string |
When it happened, by Chips’ clock. Stamped on the way in; a value the sender writes is replaced. |
workspace_id |
yes | string |
A workspace identifier: ws_ followed by a ULID. |
space_id |
no | string |
The space, when the event belongs to one. |
run_id |
no | string |
The run, when the event belongs to one. |
seq |
no | number |
Assigned by the server on append, monotonic per workspace. Absent on the way in. |
idempotency_key |
no | string |
Required on every client-originated write. Sending the same key twice is a no-op. |
actor |
yes | object | The participant an event is attributed to. |
actor.kind |
yes | "person" | "harness" | "agent" | "system" |
Whether a person, a harness, one of a harness’s agents or Chips itself acted. |
actor.id |
yes | string |
The participant id, or system for Chips. |
actor.harness_id |
no | string |
For an agent: the harness it speaks through, whose budget and runs are the agent’s (ADR 0087). Filled in by Chips. |
protocol |
yes | object | The negotiated range. A client picks a version inside it or is refused. |
protocol.min |
yes | number |
Oldest major version the server accepts. |
protocol.max |
yes | number |
Newest major version the server speaks. |
server |
yes | object | Who the client is talking to. |
server.name |
yes | string |
Usually chipsd. |
server.version |
yes | string |
The server’s own version. |
harnesses |
yes | object[] | The harness the token belongs to, and only that one. One connection speaks for all of the harness’s agents (ADR 0087), which agents lists. A harness running inside another is a lane, not a duplicate. |
harnesses[].harness_id |
yes | string |
A harness identifier: hrn_ followed by a ULID. |
harnesses[].display_name |
yes | string |
The harness’s name in the workspace. |
harnesses[].owner_id |
yes | string |
The person accountable for this harness. |
harnesses[].capabilities |
no | string[] |
What the harness can do. |
harnesses[].ephemeral |
no | boolean |
True for a transient child minted for one lane, which inherits its lead’s settings. |
harness |
no | object | The harness this connection is: harnesses[0], said once. Read this rather than the list. |
harness.harness_id |
yes | string |
A harness identifier: hrn_ followed by a ULID. |
harness.display_name |
yes | string |
The harness’s name in the workspace. |
harness.owner_id |
yes | string |
The person accountable for this harness. |
harness.capabilities |
no | string[] |
What the harness can do. |
harness.ephemeral |
no | boolean |
True for a transient child minted for one lane, which inherits its lead’s settings. |
agents |
no | object[] | The harness’s agents, each with its rooms (ADR 0087). Everything the connection sends names one of them as actor; a harness’s own id is read as its one agent in the room. Sent only to a harness that declares agents. |
agents[].agent_id |
yes | string |
A agent identifier: agt_ followed by a ULID. |
agents[].key |
yes | string |
The harness’s own name for it. |
agents[].name |
yes | string |
What it is called, and so its @handle. |
agents[].lead |
yes | boolean |
Whether it is the harness’s Main agent. |
agents[].state |
yes | "active" | "archived" |
Archived when its thing was put away. |
agents[].spaces |
yes | string[] |
The rooms it is in. One at most, once one room per agent is the rule. |
agents[].room |
no | string | null |
The open room it is in (ADR 0087: one at a time), or null while it is free. What it says while free is held. |
agents[].held |
no | number |
How many of its words Chips is holding, said while it was in no room. |
autonomy |
yes | 0 | 1 | 2 | 3 |
The level in force for this connection. |
policy |
no | object | The rules that fill in the details. |
policy.autonomy |
yes | 0 | 1 | 2 | 3 |
0 observe, 1 ask first, 2 ask when it matters, 3 autonomous and report back. Presets that expand to rules. |
policy.rules |
yes | object[] | Evaluated in order; the workspace cap always wins. |
policy.rules[].match |
yes | string |
A glob over action kinds, for example payment.*. |
policy.rules[].when |
no | object | Extra conditions on the action’s flags, and for a command, how its parts start. |
policy.rules[].when.external |
no | boolean |
|
policy.rules[].when.irreversible |
no | boolean |
|
policy.rules[].when.money_usd_over |
no | number |
|
policy.rules[].when.risk |
no | enum[] | One of external, external_communication, irreversible, destructive, financial, credential, privacy, legal. |
policy.rules[].when.starts_with |
no | string[] |
For an action that carries a command (ADR 0239): the words a part of it starts with. A command goes ahead on such rules only when every part starts with one that allows it. |
policy.rules[].decision |
yes | "allow" | "ask" | "deny" |
What to do when the rule matches. |
policy.rules[].approvers |
no | string[] |
Who may answer, when the decision is ask. |
policy.rules[].reason |
no | string |
Shown to the harness, and recorded in the audit log. |
policy.rules[].off |
no | boolean |
Turned off by a person (ADR 0195): kept in the list to turn back on, and skipped by the policy engine until then. |
policy.rules[].made_by |
no | string |
Who made it by answering an ask with Always (ADR 0195). Filled in by Chips. |
policy.rules[].made_at |
no | string |
When a person’s answer made it (ADR 0195): Always, or This session. Filled in by Chips. A rule a person’s answer made never allows an action that matters. |
budget |
no | object | The ceiling, and what has been spent. |
budget.limit_usd |
yes | number |
The ceiling for this harness. |
budget.spent_usd |
yes | number |
Spent so far, estimated. |
spaces |
no | string[] |
The spaces this credential is a member of as it connects. A client that kept a cache drops what is not here: a membership it lost while away is the one event a replay can never bring it, because it is no longer in the space that event belongs to. |
workspace |
no | { name: string; } |
The workspace as it is called now. A device keeps the name it was told when it paired and takes this one when they differ, so a workspace renamed since is called by its new name without pairing again. Absent when the workspace has no name. |
workspace.name |
yes | string |
Example
Section titled “Example”{ "v": 1, "type": "hello", "id": "evt_01K4ZQPX000000000000000073", "ts": "2026-09-02T14:22:15.004Z", "workspace_id": "ws_01K4ZQPX000000000000000001", "space_id": "spc_01K4ZQPX000000000000000002", "actor": { "kind": "system", "id": "system" }, "protocol": { "min": 1, "max": 1 }, "server": { "name": "chipsd", "version": "0.0.0" }, "harnesses": [ { "harness_id": "hrn_01K4ZQPX000000000000000004", "display_name": "Scout", "owner_id": "usr_01K4ZQPX000000000000000003", "capabilities": ["drive.read", "crm.read", "email.send"], "ephemeral": false } ], "autonomy": 2, "spaces": ["spc_01K4ZQPX000000000000000002"], "policy": { "autonomy": 2, "rules": [ { "match": "email.send", "when": { "external": true }, "decision": "ask", "approvers": ["owner"], "reason": "External mail always asks." }, { "match": "payment.*", "when": { "money_usd_over": 500 }, "decision": "ask", "approvers": ["role:finance"] }, { "match": "drive.read", "decision": "allow" } ] }, "budget": { "limit_usd": 25, "spent_usd": 1.47 }}