Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

JSON output shapes

Every read/status command takes a global --json flag and prints one pretty JSON value to stdout (status lines stay on stderr — see the api output discipline). This page is the machine-consumer’s reference: the send-outcome vocabulary you classify by, the session-digest schema, the shell relay’s MAC-stamped frames, and the catalog of --json shapes.

A JSON value on stdout, a status tag on stderr, the truth in the exit code. A program reads whichever it needs and never has to scrape a human line.

Send outcomes

The closed set of spt send outcome lines — SENT, SENT(WAN), QUEUED, QUEUED(idle-only), DEFERRED, NO_PERCH, and the WAN failure tags — is documented with its exact conditions in Messaging → Send outcomes. The one rule a caller must encode: classify by exit code (0 = every delivered/spooled outcome, non-zero = every failure), and treat QUEUED as success — the message is durably spooled and drains when the target next comes online; never retry on it.

Session digest — endpoint digest --json

spt endpoint digest <id> --json prints the endpoint’s activity digest as a projection of its session logs (not a PTY scrape). The top-level object:

{ "turns": [ /* Turn, oldest → newest */ ] }

A --after <seq> cursor that predated the retained window adds one top-level field, "after_predates_window": true, so a consumer knows it missed rows.

Turn — one user-opened turn:

FieldTypeNotes
inputstring | nullThe input that opened the turn; null for a preamble turn.
entriesarray of entryAgent/tool/boundary/context entries in stream order.
input_seqnumberOmitted when absent.
partialboolOmitted when false; true on the open trailing turn.

Entry — an externally-tagged variant (the tag key names the kind):

VariantFields
Agenttext (string); seq, ts optional
ToolSprinttools (array of {name, arg}); seq, ts optional — consecutive tool uses collapse into one sprint
Boundarykindclear | compact | boot; ts optional
Contextkindpsyche_download | echo_mirror | owl_message; body (string); ts optional

ts is an RFC3339-UTC ordering key. --follow --json streams per-update deltas instead of the snapshot shape above.

This is the digest read shape. It is distinct from the digest record an adapter pushes via spt api digest-entry / a [digest] extractor — that ingest contract (role/text/tool/ts) is documented in the manifest digest-record reference.

The shell relay — MAC-stamped frames

Two poll surfaces authenticate differently:

  • spt api poll <id> — the agent hook-channel drain. Reads the caller’s own perch spool; the manifest [inject] set must include the hook method. Each row prints as one whole <EVENT …> envelope on stdout.
  • spt api poll <shell-id> --link <token> — the shell relay drain. The link token is the credential: it resolves the (owner, shell) pair and is refused (exit 1, AUTH_REFUSED) if no instance holds it. Rows are emitted raw, one per line — deliberately not <EVENT>-wrapped (the shell child parses its own vocabulary).

Shell-relay frames are MAC-stamped. The on-wire form is:

<mac> <frame>

— a 64-hex-char HMAC-SHA256 over the frame bytes, one ASCII space, then the raw frame. The key is SHA-256(link_token); a frame with no valid MAC is dropped, never processed. Agent-perch surfaces (spt ready, api listen, api poll <id>) never emit stamped frames — a consumer of agent traffic only ever sees <EVENT> / <EVENT-PART> lines.

--json catalog

Commands that emit --json, and the top-level shape each prints. Fields marked optional are omitted when empty.

CommandTop-level shape
endpoint list · whoami{ self, subnets[], local[] }self: {id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}; subnets[]: {name, endpoints[]} where each endpoint is {id, node, node_label, status, resources, endpoint_type?, project?}; local[]: {id, state, address, ready, alive, unbound, project?}
endpoint digest{ turns[] } — see above
endpoint description show{ id, description }
endpoint role{ id, role }
api endpoint-info [<id>]{ id, endpoint_type, adapter, local_node:{label,key}, attached_node:{label,key}|null, controlled, project, cwd, subnets[] } (always JSON)
daemon status{ running, pid, net_up, pump_heartbeat_ms, managed_by, managed_active, subnets[], local_endpoints[], broker_image?, broker_stale?, stall_evict_count?, stall_evict_last_ms? }
subnet status [--nodes]{ daemon_running, subnets[] } — each {name, node_count, endpoint_count, nodes[]}
subnet show-code{ subnet, code, otpauth_uri? }
notif list{ notifs[] } — each {notif_id, subnet, kind, state, from_id, head}
access list{ entries[] } — each {endpoint, nodes[], locked}
grant list{ grants[] } — each {capability, agent, node, qualifier}
adapter list{ adapters[] } — each {name, kind, mode, version, source_dir, active}
adapter version <option>{ adapter, version }
shell list{ owner, shells[], instantiable[] } — each shell {id, alias, adapter, status}

All shapes are additive-forever: new keys may appear, existing keys keep their meaning. Parse tolerantly (ignore unknown fields) and a newer daemon never breaks an older consumer.