|
# Snek RPC Protocol API Specification
|
|
|
|
retoor <retoor@molodetz.nl>
|
|
|
|
Version 1.0 — February 2026
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Protocol Overview](#1-protocol-overview)
|
|
2. [Connection](#2-connection)
|
|
3. [Request/Response Format](#3-requestresponse-format)
|
|
4. [Authentication](#4-authentication)
|
|
5. [Channel Operations](#5-channel-operations)
|
|
6. [Messaging](#6-messaging)
|
|
7. [Events](#7-events)
|
|
8. [Visual Feedback](#8-visual-feedback)
|
|
9. [Message Routing](#9-message-routing)
|
|
10. [Streaming](#10-streaming)
|
|
11. [Image Handling](#11-image-handling)
|
|
12. [Tool System](#12-tool-system)
|
|
13. [Error Handling](#13-error-handling)
|
|
14. [Constants Reference](#14-constants-reference)
|
|
|
|
---
|
|
|
|
## 1. Protocol Overview
|
|
|
|
The Snek RPC protocol is a JSON-based remote procedure call protocol transported over WebSocket. Clients send method invocations as JSON objects and receive corresponding responses matched by a unique call identifier. The server also pushes unsolicited events (messages, typing indicators, joins/leaves) over the same connection.
|
|
|
|
Key characteristics:
|
|
|
|
- **Transport**: WebSocket (RFC 6455)
|
|
- **Encoding**: JSON over WebSocket text frames
|
|
- **Concurrency model**: Multiplexed — multiple outstanding calls share one connection, matched by `callId`
|
|
- **Direction**: Bidirectional — client sends requests, server sends responses and pushes events
|
|
|
|
---
|
|
|
|
## 2. Connection
|
|
|
|
### Endpoint
|
|
|
|
```
|
|
wss://snek.molodetz.nl/rpc.ws
|
|
```
|
|
|
|
Plaintext variant (development only):
|
|
|
|
```
|
|
ws://snek.molodetz.nl/rpc.ws
|
|
```
|
|
|
|
### WebSocket Parameters
|
|
|
|
| Parameter | Value | Description |
|
|
|-----------|-------|-------------|
|
|
| Heartbeat interval | 30 seconds | WebSocket ping/pong keepalive (`WS_HEARTBEAT`) |
|
|
| Request timeout | 190 seconds | Maximum wait for an RPC response (`DEFAULT_REQUEST_TIMEOUT`) |
|
|
| Receive retry delay | 1.0 seconds | Delay before retrying after a receive error (`WS_RECEIVE_RETRY_DELAY`) |
|
|
|
|
### Connection Lifecycle
|
|
|
|
```
|
|
1. Open WebSocket to wss://snek.molodetz.nl/rpc.ws
|
|
2. Call login(username, password)
|
|
3. Call get_user(null) to retrieve authenticated user info
|
|
4. Call get_channels() to enumerate available channels
|
|
5. Enter receive loop — process events and messages
|
|
6. On disconnect — reconnect with exponential backoff
|
|
```
|
|
|
|
### Reconnection Strategy
|
|
|
|
| Parameter | Value |
|
|
|-----------|-------|
|
|
| Max retries | 3 (`RECONNECT_MAX_RETRIES`) |
|
|
| Initial delay | 1.0 seconds (`RECONNECT_INITIAL_DELAY`) |
|
|
| Backoff factor | 2.0x (`RECONNECT_BACKOFF_FACTOR`) |
|
|
|
|
Delays follow: 1s → 2s → 4s. After exhausting retries the client should re-establish the full connection from step 1.
|
|
|
|
---
|
|
|
|
## 3. Request/Response Format
|
|
|
|
### Client Request
|
|
|
|
Every RPC call from client to server uses the following JSON structure:
|
|
|
|
```json
|
|
{
|
|
"method": "<method_name>",
|
|
"args": [<positional_arg_1>, <positional_arg_2>, ...],
|
|
"kwargs": {<keyword_args>},
|
|
"callId": "<unique_identifier>"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `method` | string | yes | RPC method name |
|
|
| `args` | array | yes | Positional arguments (may be empty `[]`) |
|
|
| `kwargs` | object | yes | Keyword arguments (may be empty `{}`) |
|
|
| `callId` | string | yes | Client-generated unique ID to correlate response. Recommended: 16-character hex string (e.g., `os.urandom(8).hex()`) or UUID v4. |
|
|
|
|
### Server Response
|
|
|
|
The server responds with a JSON object containing the same `callId`:
|
|
|
|
```json
|
|
{
|
|
"callId": "<matching_call_id>",
|
|
"data": <result_value>
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `callId` | string | Matches the request `callId` |
|
|
| `data` | any | Return value — object, array, string, or null depending on the method |
|
|
|
|
Additional top-level fields may be present depending on context (e.g., `event`, `message`, `username`).
|
|
|
|
### Fire-and-Forget Requests
|
|
|
|
Some calls do not require a response. The client sends the request normally but does not wait for a matching `callId` response. This is a client-side optimization — the server may still send a response.
|
|
|
|
---
|
|
|
|
## 4. Authentication
|
|
|
|
### login
|
|
|
|
Authenticate with the Snek platform.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"method": "login",
|
|
"args": ["<username>", "<password>"],
|
|
"kwargs": {},
|
|
"callId": "<id>"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"callId": "<id>",
|
|
"data": { ... }
|
|
}
|
|
```
|
|
|
|
Must be the first call after establishing the WebSocket connection.
|
|
|
|
### get_user
|
|
|
|
Retrieve user information.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"method": "get_user",
|
|
"args": [null],
|
|
"kwargs": {},
|
|
"callId": "<id>"
|
|
}
|
|
```
|
|
|
|
Pass `null` as the sole argument to retrieve the authenticated user.
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"callId": "<id>",
|
|
"data": {
|
|
"username": "botname",
|
|
"nick": "BotNick"
|
|
}
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `username` | string | Login username (unique identifier) |
|
|
| `nick` | string | Display name |
|
|
|
|
---
|
|
|
|
## 5. Channel Operations
|
|
|
|
### get_channels
|
|
|
|
Retrieve all channels visible to the authenticated user.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"method": "get_channels",
|
|
"args": [],
|
|
"kwargs": {},
|
|
"callId": "<id>"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"callId": "<id>",
|
|
"data": [
|
|
{
|
|
"uid": "channel_unique_id",
|
|
"name": "Channel Name",
|
|
"tag": "dm"
|
|
},
|
|
...
|
|
]
|
|
}
|
|
```
|
|
|
|
### Channel Object
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `uid` | string | Unique channel identifier. Matches pattern `^[a-zA-Z0-9_-]+$` |
|
|
| `name` | string | Human-readable channel name |
|
|
| `tag` | string | Channel type. `"dm"` indicates a direct message channel |
|
|
|
|
### Channel Types
|
|
|
|
| Tag | Behavior |
|
|
|-----|----------|
|
|
| `"dm"` | Direct message — bot always responds to messages |
|
|
| other / none | Public channel — bot responds only if explicitly joined or triggered |
|
|
|
|
---
|
|
|
|
## 6. Messaging
|
|
|
|
### send_message
|
|
|
|
Send a message to a channel.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"method": "send_message",
|
|
"args": ["<channel_uid>", "<message_text>", <is_final>],
|
|
"kwargs": {},
|
|
"callId": "<id>"
|
|
}
|
|
```
|
|
|
|
| Argument | Type | Required | Description |
|
|
|----------|------|----------|-------------|
|
|
| `channel_uid` | string | yes | Target channel UID |
|
|
| `message_text` | string | yes | Message content (Markdown supported) |
|
|
| `is_final` | boolean | yes | `true` for complete messages, `false` for streaming partial updates |
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"callId": "<id>",
|
|
"data": { ... }
|
|
}
|
|
```
|
|
|
|
### Drive URL Rewriting
|
|
|
|
Relative drive paths are rewritten before processing:
|
|
|
|
```
|
|
(/drive.bin → (https://snek.molodetz.nl/drive.bin
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Events
|
|
|
|
The server pushes unsolicited events over the WebSocket connection. Events do not have a `callId` matching any pending request.
|
|
|
|
### Event Structure
|
|
|
|
```json
|
|
{
|
|
"event": "<event_type>",
|
|
"data": { ... }
|
|
}
|
|
```
|
|
|
|
### Message Event
|
|
|
|
Messages appear as a special case — they carry top-level fields instead of being nested under `data`:
|
|
|
|
```json
|
|
{
|
|
"event": "message",
|
|
"message": "Hello world",
|
|
"username": "sender_username",
|
|
"user_nick": "SenderNick",
|
|
"channel_uid": "target_channel_uid",
|
|
"is_final": true
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `event` | string | `"message"` |
|
|
| `message` | string | Message content |
|
|
| `username` | string | Sender's username |
|
|
| `user_nick` | string | Sender's display name |
|
|
| `channel_uid` | string | Channel where the message was sent |
|
|
| `is_final` | boolean | `true` for complete messages, `false` for streaming updates |
|
|
|
|
Clients should ignore messages where `is_final` is `false` unless implementing a streaming display.
|
|
|
|
### set_typing Event
|
|
|
|
Server-pushed event indicating a user is typing or providing visual feedback.
|
|
|
|
```json
|
|
{
|
|
"event": "set_typing",
|
|
"data": { ... }
|
|
}
|
|
```
|
|
|
|
### join / leave Events
|
|
|
|
```json
|
|
{
|
|
"event": "join",
|
|
"data": {
|
|
"channel_uid": "<channel_uid>"
|
|
}
|
|
}
|
|
```
|
|
|
|
```json
|
|
{
|
|
"event": "leave",
|
|
"data": {
|
|
"channel_uid": "<channel_uid>"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Event Handling Pattern
|
|
|
|
Events are dispatched to handler methods named `on_<event_type>`. The `data` object is unpacked as keyword arguments:
|
|
|
|
```
|
|
Event: {"event": "join", "data": {"channel_uid": "abc123"}}
|
|
Handler: on_join(channel_uid="abc123")
|
|
```
|
|
|
|
Unknown events are silently ignored.
|
|
|
|
---
|
|
|
|
## 8. Visual Feedback
|
|
|
|
### set_typing
|
|
|
|
Set a colored typing indicator for the bot in a channel. Used for visual "thinking" feedback.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"method": "set_typing",
|
|
"args": ["<channel_uid>", "<html_color_code>"],
|
|
"kwargs": {},
|
|
"callId": "<id>"
|
|
}
|
|
```
|
|
|
|
| Argument | Type | Description |
|
|
|----------|------|-------------|
|
|
| `channel_uid` | string | Target channel |
|
|
| `html_color_code` | string | HTML hex color code (e.g., `"#FF0000"`) |
|
|
|
|
### Color Generation
|
|
|
|
Random bright colors are generated using HSL with these ranges:
|
|
|
|
| Component | Min | Max |
|
|
|-----------|-----|-----|
|
|
| Hue | 0.0 | 1.0 |
|
|
| Saturation | 0.7 | 1.0 |
|
|
| Lightness | 0.5 | 0.7 |
|
|
|
|
The color must match the pattern `^#[0-9A-Fa-f]{6}$`.
|
|
|
|
---
|
|
|
|
## 9. Message Routing
|
|
|
|
Incoming messages are classified and routed in the following priority order:
|
|
|
|
| Priority | Condition | Handler |
|
|
|----------|-----------|---------|
|
|
| 1 | `username == self.username` | `on_own_message(channel_uid, message)` |
|
|
| 2 | Message starts with `"ping"` | `on_ping(username, user_nick, channel_uid, message_after_ping)` |
|
|
| 3 | Message contains `@nick join` or `@username join` | `on_join(channel_uid)` |
|
|
| 4 | Message contains `@nick leave` or `@username leave` | `on_leave(channel_uid)` |
|
|
| 5 | Message contains `@nick` or `@username` | `on_mention(username, user_nick, channel_uid, message)` |
|
|
| 6 | Default | `on_message(username, user_nick, channel_uid, message)` |
|
|
|
|
### Channel Permission Rules
|
|
|
|
For `on_message` processing, the bot applies these rules:
|
|
|
|
1. **DM channels** (`tag == "dm"`): Always respond.
|
|
2. **Public channels**: Respond only if:
|
|
- The bot has been explicitly joined to the channel, OR
|
|
- The message matches a configured trigger pattern, OR
|
|
- The bot's username appears in the message text.
|
|
|
|
### Ping/Pong
|
|
|
|
Messages starting with `"ping"` trigger an automatic `"pong"` response:
|
|
|
|
```
|
|
Incoming: "ping hello"
|
|
Response: "pong hello"
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Streaming
|
|
|
|
The protocol supports incremental message delivery using the `is_final` field on `send_message`.
|
|
|
|
### Sending Streaming Messages
|
|
|
|
```
|
|
1. send_message(channel_uid, "Partial cont...", false) ← partial update
|
|
2. send_message(channel_uid, "Partial content h...", false) ← partial update
|
|
3. send_message(channel_uid, "Partial content here.", true) ← final message
|
|
```
|
|
|
|
Each partial message replaces the previous one in the UI. The final message (`is_final=true`) marks the message as complete.
|
|
|
|
### Receiving Streaming Messages
|
|
|
|
When receiving events, messages with `is_final=false` represent in-progress content from another user or bot. Standard bot implementations skip non-final messages and only process the final version:
|
|
|
|
```
|
|
if not data.is_final:
|
|
continue
|
|
```
|
|
|
|
### Streaming Update Interval
|
|
|
|
The minimum interval between streaming updates is `0.0` seconds (`STREAMING_UPDATE_INTERVAL`), meaning updates are sent as fast as they are generated.
|
|
|
|
---
|
|
|
|
## 11. Image Handling
|
|
|
|
### URL Extraction
|
|
|
|
Image URLs are extracted from message text using the pattern:
|
|
|
|
```
|
|
https?://\S+\.(?:png|jpg|jpeg|gif|bmp|webp|svg)(?:\?\S*)?
|
|
```
|
|
|
|
Matched URLs are stripped of trailing `.`, `'`, and `"` characters.
|
|
|
|
### Image Formats
|
|
|
|
Two encoding formats are supported:
|
|
|
|
#### OpenAI Format (default)
|
|
|
|
Images are sent as separate content blocks in the OpenAI multi-modal message format:
|
|
|
|
```json
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "message text with URL removed"
|
|
},
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {
|
|
"url": "data:image/png;base64,<base64_encoded_data>"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### DeepSeek Format
|
|
|
|
Images are inlined into the text content as tagged references:
|
|
|
|
```json
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "message with [image: data:image/png;base64,<base64_data>] inline"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### MIME Type Detection
|
|
|
|
MIME types are resolved in order:
|
|
|
|
1. File extension via `mimetypes.guess_type()`
|
|
2. Magic bytes detection:
|
|
|
|
| Bytes | MIME Type |
|
|
|-------|-----------|
|
|
| `\x89PNG\r\n\x1a\n` | `image/png` |
|
|
| `\xff\xd8` | `image/jpeg` |
|
|
| `GIF87a` or `GIF89a` | `image/gif` |
|
|
| `RIFF....WEBP` | `image/webp` |
|
|
|
|
3. Fallback: `image/png`
|
|
|
|
---
|
|
|
|
## 12. Tool System
|
|
|
|
The tool system follows the OpenAI function calling specification. Tools are serialized as function definitions, sent alongside the chat completion request, and executed when the LLM returns `tool_calls`.
|
|
|
|
### Tool Definition Schema
|
|
|
|
Each tool is serialized as:
|
|
|
|
```json
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "<method_name>",
|
|
"description": "<docstring>",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"<param_name>": {
|
|
"type": "<json_type>",
|
|
"default": "<default_value>"
|
|
}
|
|
},
|
|
"required": ["<param_without_default>"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Type Mapping
|
|
|
|
| Python Type | JSON Schema Type |
|
|
|-------------|-----------------|
|
|
| `str` | `"string"` |
|
|
| `int` | `"integer"` |
|
|
| `bool` | `"boolean"` |
|
|
| `list` | `"array"` |
|
|
| `dict` | `"object"` |
|
|
| `None` | `"null"` |
|
|
| (default) | `"string"` |
|
|
|
|
Parameters with default values are optional; parameters without defaults are listed in `required`.
|
|
|
|
### Tool Call Flow
|
|
|
|
```
|
|
1. Client sends chat completion request with tools array
|
|
2. LLM returns response with tool_calls array
|
|
3. Client executes each tool call
|
|
4. Client sends tool results back as role:"tool" messages
|
|
5. Client sends another chat completion request with updated context
|
|
6. Repeat until LLM returns a text response without tool_calls
|
|
```
|
|
|
|
### Tool Call Response (from LLM)
|
|
|
|
```json
|
|
{
|
|
"role": "assistant",
|
|
"content": "",
|
|
"tool_calls": [
|
|
{
|
|
"id": "call_abc123",
|
|
"type": "function",
|
|
"function": {
|
|
"name": "database_find",
|
|
"arguments": "{\"table\": \"users\", \"query\": {\"active\": true}}"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Tool Result Message
|
|
|
|
```json
|
|
{
|
|
"role": "tool",
|
|
"tool_call_id": "call_abc123",
|
|
"content": "<stringified_result>"
|
|
}
|
|
```
|
|
|
|
### Tool Call Limits
|
|
|
|
| Parameter | Value | Description |
|
|
|-----------|-------|-------------|
|
|
| Max depth | 25 | Maximum consecutive tool call rounds (`MAX_TOOL_CALL_DEPTH`) |
|
|
| Max repeated calls | 5 | Maximum identical tool invocations (`MAX_REPEATED_TOOL_CALLS`) |
|
|
| Max consecutive errors | 3 | Abort tool loop after 3 consecutive errors |
|
|
|
|
### Method Exclusions
|
|
|
|
Methods are excluded from tool serialization if:
|
|
|
|
- The name starts with `_`
|
|
- The name is `serialize` or `handle`
|
|
- The attribute is not callable
|
|
|
|
---
|
|
|
|
## 13. Error Handling
|
|
|
|
### Exception Hierarchy
|
|
|
|
```
|
|
BotError
|
|
├── ConnectionError Connection/WebSocket failures
|
|
├── AuthenticationError Login failures
|
|
├── RPCError RPC call failures, timeouts
|
|
├── ToolError Tool execution failures
|
|
├── DatabaseError Database operation failures
|
|
├── ValidationError Input validation failures
|
|
└── CircuitBreakerOpenError Circuit breaker tripped
|
|
```
|
|
|
|
### RPC Error Conditions
|
|
|
|
| Condition | Behavior |
|
|
|-----------|----------|
|
|
| WebSocket closed/closing | `RPCError` raised |
|
|
| WebSocket error frame | `RPCError` raised |
|
|
| Response timeout (190s) | `RPCError` raised |
|
|
| Invalid JSON in response | Logged as warning, skipped |
|
|
| Connection closed during call | `RPCError` raised |
|
|
|
|
### Reconnection on Send Failure
|
|
|
|
When `send_message` fails, the client retries with exponential backoff:
|
|
|
|
```
|
|
Attempt 1: send → fail → wait 1s
|
|
Attempt 2: reconnect + send → fail → wait 2s
|
|
Attempt 3: reconnect + send → fail → raise ConnectionError
|
|
```
|
|
|
|
Each reconnection attempt performs a full login sequence on a new WebSocket.
|
|
|
|
### Circuit Breaker
|
|
|
|
Tool execution is protected by a circuit breaker pattern:
|
|
|
|
| Parameter | Value |
|
|
|-----------|-------|
|
|
| Failure threshold | 5 consecutive failures |
|
|
| Recovery timeout | 60 seconds |
|
|
| Half-open max calls | 3 |
|
|
|
|
States: `CLOSED` → (threshold exceeded) → `OPEN` → (timeout elapsed) → `HALF_OPEN` → (success) → `CLOSED`
|
|
|
|
When the circuit breaker is open, tool calls fail immediately with `CircuitBreakerOpenError`.
|
|
|
|
---
|
|
|
|
## 14. Constants Reference
|
|
|
|
### Connection
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `DEFAULT_WS_URL` | `wss://snek.molodetz.nl/rpc.ws` | Default WebSocket endpoint |
|
|
| `DEFAULT_OPENAI_URL` | `https://api.openai.com` | Default LLM API base URL |
|
|
| `DEFAULT_SEARCH_API_BASE` | `https://search.molodetz.nl` | Default search API endpoint |
|
|
| `WS_HEARTBEAT` | 30.0s | WebSocket heartbeat interval |
|
|
| `DEFAULT_REQUEST_TIMEOUT` | 190s | RPC call timeout |
|
|
| `HTTP_CONNECT_TIMEOUT` | 90.0s | HTTP TCP connect timeout |
|
|
|
|
### Reconnection
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `RECONNECT_MAX_RETRIES` | 3 | Maximum reconnection attempts |
|
|
| `RECONNECT_INITIAL_DELAY` | 1.0s | First retry delay |
|
|
| `RECONNECT_BACKOFF_FACTOR` | 2.0 | Delay multiplier per retry |
|
|
| `WS_RECEIVE_RETRY_DELAY` | 1.0s | Delay after receive error |
|
|
|
|
### Concurrency
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `MAX_CONCURRENT_OPERATIONS` | 100 | WebSocket semaphore limit |
|
|
| `MAX_CONCURRENT_REQUESTS` | 10 | HTTP connection pool limit |
|
|
| `THREAD_POOL_WORKERS` | 8 | Thread pool for blocking operations |
|
|
| `PROCESS_POOL_WORKERS` | 4 | Process pool for CPU-intensive tasks |
|
|
| `EXECUTOR_TIMEOUT` | 30.0s | Thread/process pool call timeout |
|
|
|
|
### Message Handling
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `MAX_MESSAGE_HISTORY` | 50 | Recent messages retained in memory |
|
|
| `DEFAULT_OPENAI_LIMIT` | 200 | Max messages sent to LLM per session |
|
|
| `CONTEXT_WINDOW_TOKEN_LIMIT` | 120000 | Token budget for LLM context |
|
|
| `STREAMING_UPDATE_INTERVAL` | 0.0s | Min interval between streaming updates |
|
|
|
|
### Tool Limits
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `MAX_TOOL_CALL_DEPTH` | 25 | Max consecutive tool call rounds |
|
|
| `MAX_REPEATED_TOOL_CALLS` | 5 | Max identical tool invocations |
|
|
| `MAX_RETRY_ATTEMPTS` | 3 | General retry limit |
|
|
|
|
### Timing
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `TASK_CHECK_INTERVAL` | 0.5s | Service loop tick interval |
|
|
| `THINKING_TASK_INTERVAL` | 1.0s | Typing indicator refresh interval |
|
|
| `SERVICE_CLEANUP_INTERVAL` | 3600s | Periodic cleanup cycle |
|
|
| `SHUTDOWN_TIMEOUT` | 5.0s | Max wait for graceful shutdown |
|
|
| `MIN_COUNTER_UPDATE_INTERVAL` | 240s | Debounce for counter updates |
|
|
|
|
### Agentic
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `AGENTIC_MAX_RESEARCH_QUERIES` | 8 | Max search queries per research task |
|
|
| `AGENTIC_MAX_PLAN_STEPS` | 10 | Max steps in a generated plan |
|
|
| `AGENTIC_SCRATCHPAD_SIZE` | 50 | Scratchpad buffer size |
|
|
|
|
### Visual
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `COLOR_HUE_MIN` | 0.0 | HSL hue range start |
|
|
| `COLOR_HUE_MAX` | 1.0 | HSL hue range end |
|
|
| `COLOR_SATURATION_MIN` | 0.7 | HSL saturation range start |
|
|
| `COLOR_SATURATION_MAX` | 1.0 | HSL saturation range end |
|
|
| `COLOR_LIGHTNESS_MIN` | 0.5 | HSL lightness range start |
|
|
| `COLOR_LIGHTNESS_MAX` | 0.7 | HSL lightness range end |
|
|
|
|
### Media
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `MEDIA_HUNT_DEDUP_MAX_SIZE` | 10000 | Dedup cache max entries |
|
|
| `MEDIA_HUNT_DEDUP_TTL` | 86400s | Dedup cache TTL (24 hours) |
|
|
| `MEDIA_HUNT_HEAD_TIMEOUT` | 5.0s | HEAD request timeout |
|
|
| `MEDIA_HUNT_FETCH_TIMEOUT` | 30.0s | Full fetch timeout |
|
|
|
|
### Bot Name Sanitization
|
|
|
|
The following bot names are sanitized in outgoing messages to prevent cross-triggering:
|
|
|
|
```
|
|
snek, grok, snik, lisa, gemma, joanne, ira, thomas
|
|
```
|
|
|
|
Sanitization inserts a hyphen after the first character (e.g., `snek` → `s-nek`).
|
|
|
|
---
|
|
|
|
## Appendix A: Complete Connection Example
|
|
|
|
```
|
|
CLIENT → WebSocket CONNECT wss://snek.molodetz.nl/rpc.ws
|
|
SERVER ← 101 Switching Protocols
|
|
|
|
CLIENT → {"method":"login","args":["mybot","mypassword"],"kwargs":{},"callId":"a1b2c3d4e5f6g7h8"}
|
|
SERVER ← {"callId":"a1b2c3d4e5f6g7h8","data":{...}}
|
|
|
|
CLIENT → {"method":"get_user","args":[null],"kwargs":{},"callId":"b2c3d4e5f6g7h8a1"}
|
|
SERVER ← {"callId":"b2c3d4e5f6g7h8a1","data":{"username":"mybot","nick":"MyBot"}}
|
|
|
|
CLIENT → {"method":"get_channels","args":[],"kwargs":{},"callId":"c3d4e5f6g7h8a1b2"}
|
|
SERVER ← {"callId":"c3d4e5f6g7h8a1b2","data":[{"uid":"ch1","name":"general","tag":"public"},{"uid":"ch2","name":"DM","tag":"dm"}]}
|
|
|
|
--- receive loop ---
|
|
|
|
SERVER ← {"event":"message","message":"hello @MyBot","username":"alice","user_nick":"Alice","channel_uid":"ch1","is_final":true}
|
|
|
|
CLIENT → {"method":"send_message","args":["ch1","hey, what's up?",true],"kwargs":{},"callId":"d4e5f6g7h8a1b2c3"}
|
|
SERVER ← {"callId":"d4e5f6g7h8a1b2c3","data":{...}}
|
|
```
|
|
|
|
## Appendix B: Streaming Example
|
|
|
|
```
|
|
CLIENT → {"method":"set_typing","args":["ch1","#FF6B35"],"kwargs":{},"callId":"e5f6g7h8a1b2c3d4"}
|
|
|
|
CLIENT → {"method":"send_message","args":["ch1","Working on",false],"kwargs":{},"callId":"f6g7h8a1b2c3d4e5"}
|
|
CLIENT → {"method":"send_message","args":["ch1","Working on it...",false],"kwargs":{},"callId":"g7h8a1b2c3d4e5f6"}
|
|
CLIENT → {"method":"send_message","args":["ch1","Working on it... done!",true],"kwargs":{},"callId":"h8a1b2c3d4e5f6g7"}
|
|
```
|
|
|
|
## Appendix C: Bot State Machine
|
|
|
|
```
|
|
INITIALIZING → INITIALIZED → CONNECTING → CONNECTED → RUNNING → SHUTTING_DOWN → SHUTDOWN
|
|
↓
|
|
ERROR
|
|
```
|
|
|
|
| State | Description |
|
|
|-------|-------------|
|
|
| `INITIALIZING` | HTTP sessions, signal handlers, and background tasks being set up |
|
|
| `INITIALIZED` | Setup complete, ready to connect |
|
|
| `CONNECTING` | WebSocket connection in progress |
|
|
| `CONNECTED` | Authenticated and channels enumerated |
|
|
| `RUNNING` | Processing messages in the receive loop |
|
|
| `SHUTTING_DOWN` | Graceful shutdown initiated, cancelling background tasks |
|
|
| `SHUTDOWN` | All resources released |
|
|
| `ERROR` | Initialization or fatal runtime failure |
|