# QQQ
**Quick Query Quorum** is a minimal, in-memory multi-user voice 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 display name, language, and group purpose.
## 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, CHAT_MODEL, STT_MODEL, TTS_MODEL, and TTS_VOICE.
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 |
| `QQQ_PUBLIC_URL` | unset | Optional externally visible URL |
| `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` | required | Speech-to-text model |
| `TTS_MODEL` | required | Text-to-speech model |
| `TTS_VOICE` | required | Text-to-speech voice |
| `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 |
| `CLAIM_MODE` | `replace` | Floor claim behavior: `replace` or `queue` |
| `DEFAULT_LANGUAGE` | `en` | Onboarding and STT fallback language |
The selected provider and base URL must support chat completions, speech
generation, and transcription.
## API Model
Public routes are `GET /`, `GET /health`, and
`GET /api/sessions/:id/exists`. Session creation and joining use
`POST /api/sessions` and `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 run sequentially, and only the current floor holder may record.
## 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 three-participant
Brainstorm conversation, prompt-cache accounting, forced tool calls, and a real
TTS-to-STT round trip. Cache verification asserts a cold write followed by
cache reads and lower warm-request costs. Total cost is not expected to remain
constant because new input and output tokens are still billable. 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).