# WebSocket Protocol

Real-time bidirectional event bus. Single connection per client.

### Connection

```
ws://{host}{BASE_PATH}/ws?token={access_token}
```

**Lifecycle:**
- Server sends `ping` every 54 seconds
- Client must respond with `pong` within 60 seconds or disconnection
- Max message size: 4 KB
- Write deadline: 10 seconds

### Event Envelope

All messages are JSON:

```json
{
  "type": "event.type",
  "payload": { ... }
}
```

### Room Subscription

Clients subscribe to rooms for scoped event delivery:

```json
{ "type": "subscribe", "payload": { "room": "channel:uuid" } }
{ "type": "unsubscribe", "payload": { "room": "channel:uuid" } }
```

### Event Routing Table

| Prefix | Direction | Description |
|--------|-----------|-------------|
| `channel.created` | → client | New channel created |
| `channel.updated` | → client | Channel metadata changed |
| `channel.deleted` | → client | Channel removed |
| `message.created` | → client | New message in subscribed channel |
| `message.updated` | → client | Message content changed |
| `message.deleted` | → client | Message removed |
| `cursor.updated` | → client | Branch cursor moved |
| `typing.start` | ↔ both | Participant started typing (payload includes `participant_id`, `participant_type`) |
| `typing.stop` | ↔ both | Typing ended |
| `participant.joined` | → client | Participant added to channel |
| `participant.left` | → client | Participant removed from channel |
| `participant.updated` | → client | Participant role changed |
| `presence.update` | → client | Participant online/idle/offline status change |
| `model.changed` | → client | Channel model roster changed |
| `persona.updated` | → client | Persona metadata changed |
| `kb.updated` | → client | Knowledge base changed |
| `kb.document.status` | → client | Document processing status update |
| `note.created` | → client | Note created |
| `note.updated` | → client | Note content changed |
| `note.deleted` | → client | Note removed |
| `notification.new` | → client | New notification |
| `notification.read` | → client | Notification marked read |
| `memory.extracted` | → client | New memory extracted |
| `memory.status` | → client | Memory approved/rejected |
| `role.fallback` | → client | Role fallback triggered |
| `user.mentioned` | → client | User was @mentioned in a channel |
| `workflow.assigned` | → client | Workflow stage assigned to team/user |
| `workflow.claimed` | → client | Workflow assignment claimed |
| `workflow.advanced` | → client | Workflow stage advanced |
| `workflow.completed` | → client | Workflow instance completed |
| `workspace.updated` | → client | Workspace state changed |
| `project.updated` | → client | Project metadata changed |
| `extension.updated` | → client | Extension config changed |
| `settings.updated` | → client | Global settings changed |
| `user.updated` | → client | User profile changed |
| `file.created` | → client | File uploaded or generated (§17b.4) |
| `file.deleted` | → client | File removed |

Direction: `→ client` = server-to-client only, `← client` = client-to-server
only, `↔ both` = bidirectional.

---

## 19. Appendix: Enums

### Channel Types

`direct` (single-user), `group` (multi-user/multi-model), `workflow` (staged)

### Participant Types

`user`, `persona`, `session`

### Participant Roles

`owner`, `member`, `observer`

### Presence Statuses

`online`, `idle`, `offline`

### Message Roles

`user`, `assistant`, `system`, `tool`

### Scopes

`personal`, `team`, `global`

### Visibility (model catalog)

`enabled`, `disabled`, `team`

### User Roles

`admin`, `user`

### Team Member Roles

`admin`, `member`

### Provider Types

`openai`, `anthropic`, `openrouter`, `venice` (extensible via registry)

### Model Types

`chat`, `embedding`, `image`

### Memory Scopes

`user`, `persona`

### Memory Statuses

`pending`, `approved`, `rejected`

### Workspace Owner Types

`user`, `project`

### Workspace Statuses

`active`, `archived`

### Index Statuses

`pending`, `indexing`, `ready`, `error`

### KB Document Statuses

`pending`, `chunking`, `embedding`, `ready`, `error`

### Provider Health Statuses

`healthy`, `degraded`, `down`

### Routing Policy Types

`provider_prefer`, `team_route`, `cost_limit`, `model_alias`

### Extension Tiers

`browser` (implemented), `starlark` (future), `sidecar` (future)

### Grant Types

`team_only`, `global`, `groups`

### Resource Grant Scopes

`persona`, `kb`

### Notification Types

`role.fallback`, `kb.ready`, `kb.error`, `grant.changed`,
`memory.extracted`, `user.mentioned`, `workflow.assigned`,
`workflow.claimed`, `task.completed`, `task.failed`,
`task.budget_exceeded`

See [enums.md](enums.md#notification-types) for full descriptions.

### Git Auth Types

`https_pat`, `https_basic`, `ssh_key`

### Policies

| Key | Default | Description |
|-----|---------|-------------|
| `allow_registration` | `"true"` | Allow new user self-registration |
| `allow_user_byok` | `"true"` | Allow users to add personal API keys |
| `allow_user_personas` | `"true"` | Allow users to create personal Personas |
| `allow_team_providers` | `"true"` | Allow team admins to configure team providers |
| `kb_direct_access` | `"true"` | Show KB picker in channel (false = strict enterprise mode) |
| `require_email` | `"false"` | Require email on registration |
| `default_system_prompt` | `""` | Injected into all conversations (admin override) |

---

## Page Routes (Non-API)

Server-rendered Go template surfaces. Not REST endpoints — these
return HTML pages.

| Route | Surface | Description |
|-------|---------|-------------|
| `/login` | Login | Standalone login/register page |
| `/` | Chat | Main chat interface |
| `/chat/:chatID` | Chat | Chat with specific channel loaded |
| `/editor` | Editor | Workspace file editor |
| `/editor/:wsId` | Editor | Editor with specific workspace |
| `/notes` | Notes | Notes interface |
| `/notes/:noteId` | Notes | Notes with specific note loaded |
| `/admin` | Admin | Platform administration |
| `/admin/:section` | Admin | Admin with specific section |
| `/settings` | Settings | User settings |
| `/settings/:section` | Settings | Settings with specific section |

All page routes (except `/login`) require authentication via
`AuthOrRedirect` middleware (reads `sb_token` cookie). Admin routes
additionally require `RequireAdminPage()`.

**Dynamic surface routing** for admin-installed extension surfaces is
not yet implemented. The current five surfaces are hardcoded in Go
route registration. Future: extension manifests declare surface routes,
the page engine dynamically registers them with appropriate data
loaders. See EXTENSIONS.md for the design direction.
