# QQQ
**Quick Query Quorum** is a minimal, in-memory multi-user chat with an
OpenAI-compatible AI facilitator. It provides one self-contained Rust binary
and a browser interface with no database or frontend build step.
Sessions, credentials, history, and generated audio exist only in process
memory and disappear on restart. Participant tokens remain in browser
`sessionStorage`; they are sent as bearer credentials and never appear in URLs
or session views.
The first interaction is a private AI conversation, not a group session. QQQ
creates a joinable session only after the user explicitly requests a group
conversation and provides a concrete group purpose. Display names and language are optional.
## Install
Install the latest release from crates.io:
```bash
cargo install qqq
```
Create the configuration and start QQQ:
```bash
cp .env.example .env
# Set OPENAI_API_KEY and CHAT_MODEL for text mode.
qqq
```
Open <http://127.0.0.1:8787> and press the orb.
To run directly from a checkout, use `cargo run --release`. Exported variables
override values loaded from `.env`.
## Docker
Release images are published for `linux/amd64` and `linux/arm64`:
```bash
docker run --rm -p 8787:8787 \
--env-file .env \
-e QQQ_BIND=0.0.0.0:8787 \
ghcr.io/maxylev/qqq:latest
```
Pin a version tag such as `0.1.2` in production. QQQ must bind to `0.0.0.0`
inside a container for the published port to be reachable.
## Configuration
| `QQQ_BIND` | `127.0.0.1:8787` | Listener address |
| `OPENAI_BASE_URL` | `https://openrouter.ai/api/v1` | OpenAI-compatible API base URL |
| `OPENAI_API_KEY` | required | Server-only provider credential |
| `CHAT_MODEL` | required | Chat-completion model |
| `STT_MODEL` | unset | Speech-to-text model; set with both TTS variables to enable voice mode |
| `TTS_MODEL` | unset | Text-to-speech model; set with STT model and TTS voice to enable voice mode |
| `TTS_VOICE` | unset | Text-to-speech voice; set with both model variables to enable voice mode |
| `TTS_FORMAT` | `mp3` | Speech response format |
| `OPENAI_HTTP_REFERER` | unset | Optional provider attribution header |
| `OPENAI_APP_TITLE` | unset | Optional provider attribution header |
| `MAX_SESSIONS` | `100` | Maximum active in-memory sessions |
| `MAX_PARTICIPANTS_PER_SESSION` | `8` | Maximum participants per session |
| `SESSION_TTL_SECONDS` | `14400` | Idle session lifetime |
| `MAX_HISTORY_MESSAGES` | `512` | Append-only history limit |
| `MAX_AUDIO_BYTES` | `10000000` | Multipart audio upload limit |
| `INPUT_BATCH_MS` | `350` | Model input batching interval |
| `DELIVERY_TIMEOUT_SECONDS` | `45` | Delivery progress timeout |
| `DEFAULT_LANGUAGE` | `en` | Onboarding and STT fallback language |
| `PRIVATE_CONVERSATION_PROMPT` | built-in private-assistant prompt | Private onboarding system prompt; must preserve explicit group intent and concrete-purpose requirements |
| `GROUP_SESSION_PROTOCOL_PROMPT` | built-in delivery protocol | Appended to the model-generated, use-case-specific group prompt after promotion |
The selected provider and base URL must support chat completions. Voice mode also requires speech generation and transcription.
## API Model
Public routes are `GET /` and `GET /health`. `POST /api/sessions` initially
creates a private provisional conversation; it becomes joinable and
URL-addressable only after explicit promotion through bootstrap. Joining uses
`POST /api/sessions/:id/join`. All other session routes require bearer authentication, including the WebSocket hello at
`/api/sessions/:id/ws`.
History begins with one stable system message and is append-only. User messages
preserve arrival order, and each assistant `send_messages` tool call remains
paired with its tool result. Each session runs one model call at a time.
Deliveries play in FIFO order. Each dispatch independently may update the displayed next-speaker hint when it completes; participant input remains open to every connected participant.
## Development
Run the same checks as CI:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo package --locked
docker build -t qqq:dev .
```
Offline tests exercise in-process Axum routes, authorization, multipart and raw
audio validation, delivery transitions, and WebSocket semantics.
### Live Provider Tests
Ignored live tests use real provider requests and spend credits. Put credentials
and model names in the ignored `.env.test`, then source it without printing its
contents:
```bash
set -a
source .env.test
set +a
cargo test live_ -- --ignored --test-threads=1
```
The paid suite validates private-to-group promotion, a generic three-participant
dispatch scenario, forced tool calls, and a real TTS-to-STT round trip. A manually
dispatched `Live provider E2E` GitHub workflow runs the same checks with secrets
from the protected `openrouter-e2e` environment.
## HTTPS And Limits
Mobile microphone access requires trusted HTTPS. Terminate TLS at a same-origin
reverse proxy; QQQ intentionally does not implement TLS:
```nginx
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
```
QQQ intentionally provides one process with no persistence, accounts,
recovery, audio broadcast, database, or history truncation. A session closes
when it reaches its history limit.
## License
QQQ is available under the [MIT License](LICENSE).