> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stagewhisper.io/llms.txt
> Use this file to discover all available pages before exploring further.

# BYO protocol

> The reasoning protocol that StageWhisper AI assistants implement.

StageWhisper speaks a protocol that any AI assistant can implement to receive reasoning jobs and return results. This page describes the contract.

## Architecture

```
StageWhisper Desktop (E2EE) ↔ StageWhisper Backend (blind relay) ↔ AI Assistant Plugin
```

The desktop encrypts analysis prompts. The backend routes encrypted envelopes to the paired assistant. The assistant decrypts, reasons, encrypts the result, and posts it back. The backend never sees plaintext content in BYO mode.

## Reasoning channel

The reasoning channel is request/response:

1. A `ReasoningJob` is created by the backend and published to the `assistant_channel` pubsub topic for the integration.
2. The assistant plugin subscribes to an SSE stream and receives the job envelope.
3. The plugin executes the reasoning (via local LLM, remote API, or subagent).
4. The plugin posts the result back to the backend's reasoning job completion endpoint.

### Envelope shape

```json theme={null}
{
  "event_type": "reasoning_job",
  "job_id": "uuid",
  "purpose": "live_analysis | session_summary | capability_probe",
  "deadline_at": "ISO 8601",
  "schema_version": 1,
  "envelope_version": 1,
  "response_schema": { "...": "JSON Schema" },
  "payload": { "...": "request payload" },
  "provider_kind": "openclaw | hermes"
}
```

### BYO encryption

When the job is BYO-encrypted, the `payload` contains a `BYOEnvelope` with `ciphertext` and `nonce` fields. The plugin decrypts using the XChaCha20-Poly1305 key derived from the X25519 key exchange performed during pairing.

## SSE stream

The plugin connects to `GET /api/v1/openclaw/integrations/{id}/stream` with the relay token as Bearer auth. Events are delivered as Server-Sent Events.

## Heartbeat

The plugin posts heartbeat updates to `/api/v1/openclaw/integrations/{id}/heartbeat` every 30 seconds, carrying host status and capability information.

## Capability probe

When the backend sends a reasoning job with `purpose: "capability_probe"`, the assistant self-describes its available skills and returns a capability profile. This is displayed in the desktop assistant settings.

## Chat channel

The chat with a paired assistant is one continuous conversation, keyed by `session_id` (the relay session). It is append-only: either side may add a message at any time, like a normal messaging app. There is no per-message turn to open and close, and no one-reply-per-message rule. The same conversation spans the live call and the post-call review, and the post-call summary is just another message in it.

In the direct-relay topology there is no backend. The desktop runs a loopback HTTP listener and advertises a callback URL (tunneled when the assistant runs on a remote host). The assistant posts every message it wants to add to the conversation to this listener.

### Endpoint

`POST {callback_url}/tasks/{task_id}` with the callback token as Bearer auth. The path `task_id` must be a UUID, but it is only a correlation handle, not a lock. What identifies the conversation is `session_id` in the body. Echo `session_id` back exactly as received: the desktop routes on it, and a session prefixed with `insights:` renders your reply in the Insights tab instead of the chat.

### Body

```json theme={null}
{
  "session_id": "string",
  "message_id": "string",
  "user_message_id": "string",
  "status": "message | typing | errored",
  "reply_text": "string",
  "label": "string",
  "error_code": "string",
  "error_message": "string"
}
```

`session_id` and `status` are required. For `status: "message"`, send a stable, unique `message_id`; the desktop dedupes by it, so retries are safe. `user_message_id` is optional and links a message to the user message it answers, used only to scope the typing indicator.

### Statuses

| status    | purpose                                      | fields read                   | effect                                                            |
| --------- | -------------------------------------------- | ----------------------------- | ----------------------------------------------------------------- |
| `message` | add an assistant message to the conversation | `message_id`, `reply_text`    | appends a chat message; repeatable; never closes the conversation |
| `typing`  | the assistant is working                     | `label`                       | shows a working indicator and resets the inactivity timer         |
| `errored` | the turn failed                              | `error_code`, `error_message` | shows an error in the conversation                                |

The desktop still accepts the older one-shot statuses (`completed`, `silent`, `tool_call`) for backward compatibility, but new assistants should use `message` and `typing`.

### How a conversation flows

Stream the agent's work as it happens: emit `typing` with a short `label` ("Searching the CRM") while working, and one `message` per thing the agent says. An agent that produces several outputs (an answer, a follow-up, then a result after a long tool run) sends several `message` callbacks in order, each with its own `message_id`. Nothing finalizes the conversation, so there is no timeout that kills a slow or multi-step turn.

User messages arrive as plain text and are appended to the same conversation; feed them straight to the agent's persistent per-session memory. This includes an approval reply like "/approve" or "continue": they are ordinary text the agent interprets itself. Do not special-case them, and do not assume one user message yields exactly one reply. Because the agent keeps its own session memory, an approval reaches the paused agent and it resumes, streaming further `message` callbacks.

Messages for a `session_id` the desktop no longer knows are dropped with `{"ok": true, "ignored": true, "reason": "session_ended"}`.

### Insights

Live insights for the Insights tab come from the transcript, not the chat. When the desktop forwards a transcript turn for analysis, it sets `session_id` to `insights:<call-session>`. Reply to it exactly as you would any other turn (a normal `message` with `reply_text`); because you echo `session_id` back unchanged, the desktop sees the `insights:` prefix and renders your reply in the Insights tab instead of the conversation. Keep it to one short sentence, and reply `none` (or stay silent) when nothing is worth surfacing. The coaching framing for these turns (the situation, the active playbook, and the one-sentence contract) is set by the desktop and sent once as a `system_prelude` on the `insights:` session when the call starts, so treat that prelude as the standing context for the session and do not prepend a coaching instruction of your own. Insights never appear in the chat, and transcript analysis never pollutes it.

## Providers

StageWhisper's provider registry maps `provider_kind` values (e.g., `openclaw`, `hermes`) to descriptors with install commands, pairing instructions, and protocol version requirements. Each provider implements the same envelope contract.

Current providers: [OpenClaw](/agents/openclaw), Hermes (coming soon).
