Architecture
Qwack uses a host model — one collaborator runs the AI agent, everyone else participates through a relay.
The host model
Section titled “The host model”There are three components in a Qwack session:
Host — One collaborator runs the OpenCode agent on their machine. They have full filesystem access and execute all code locally. API keys and credentials never leave the host’s machine. Conversation content (prompts, responses, tool output) is relayed so collaborators can follow along.
Collaborators — Everyone else runs Qwack in relay mode. Their TUI looks identical to the host’s — same streaming output, tool calls, and file diffs — but prompts are routed through the Qwack server to the host’s agent instead of running locally.
Qwack Server — A lightweight WebSocket relay that forwards messages between participants. It never runs an AI agent, executes code, accesses filesystems, or stores API keys.
Host vs. non-host
Section titled “Host vs. non-host”| Aspect | Host TUI | Non-Host TUI (relay mode) |
|---|---|---|
| Agent execution | Local OpenCode agent runs | No local agent |
| Prompt handling | Sent to local agent | Intercepted → sent to server |
| Agent output | Rendered from local SSE events | Rendered from server WebSocket relay |
| Tool execution | Local filesystem access | None — view only |
| Sidebar | Presence + todos | Presence + todos (same) |
| Code editing | Agent edits files directly | Must git pull / sync separately |
The non-host TUI still starts OpenCode (for the file browser, settings, etc.) but the prompt input is intercepted before reaching the agent.
Prompt flow: host → agent
Section titled “Prompt flow: host → agent”1. Host types prompt in Qwack TUI2. Prompt goes to local OpenCode agent (normal flow)3. Plugin captures prompt text, mirrors to server for broadcast4. All collaborators see the prompt in their TUI5. Agent processes prompt, output relayed to allPrompt flow: non-host → agent
Section titled “Prompt flow: non-host → agent”1. Non-host types prompt in Qwack TUI2. TUI intercepts — does NOT send to local agent3. TUI sends to server: { type: "prompt:request", content: "...", authorName: "sarah" }4. Server relays to host's plugin: { type: "prompt:execute", content: "...", requestedBy: "sarah" }5. Host's plugin injects prompt into local OpenCode agent6. Agent processes prompt in its EXISTING conversation context7. Output flows back: agent → plugin → server → all TUIs + web clientsWhat the server sees vs. what stays on host
Section titled “What the server sees vs. what stays on host”| Data | Where it stays | What the server sees |
|---|---|---|
| API keys | Host’s machine only | Never |
| Source code files | Host’s machine only | Never |
| Environment variables | Host’s machine only | Never |
| Tool execution (shell, file I/O) | Host’s machine only | Never |
.env, credentials, secrets | Host’s machine only | Never |
| Prompt text | Host + server (relay) | Yes (for relay, TLS-protected) |
| Agent output text | Host + server (relay) | Yes (for relay, TLS-protected) |
| Collaborator names + presence | Server | Yes (routing metadata) |
| Session metadata | Server | Yes |
Host transfer
Section titled “Host transfer”The host role can be transferred deliberately or automatically:
Deliberate transfer via /qwack host <user>:
1. Current host runs: /qwack host bob2. Server broadcasts session:host_change to all clients3. Old host: switches to relay mode (conversation preserved)4. New host: creates local OpenCode session, receives context snapshot5. New host's agent gets full history injected into system promptAuto-failover on host disconnect:
1. Host disconnects (crash, network, close terminal)2. Server broadcasts presence:leave, starts 5-second grace period3. Non-host prompts are buffered (up to 20 messages)4. If host reconnects within grace period: buffered prompts delivered5. If not: server auto-promotes next connected collaborator6. New host receives event history + snapshotOffline sync
Section titled “Offline sync”When the host disconnects from the Qwack session but continues working locally:
- The plugin queues events (prompts, output, messages) to
~/.config/qwack/offline-queue.json - The TUI shows
⏳ N queuedwhile offline - On reconnect, events are flushed to the server with
replayed: trueflag - Collaborators see historical events merge into the timeline by timestamp
Queue limit is 10MB — oldest events are dropped if exceeded.
Tech stack
Section titled “Tech stack”| Layer | Technology |
|---|---|
| Runtime | Bun |
| TUI | SolidJS (Ink-like terminal rendering) |
| API | Hono |
| Database | DynamoDB (production) / SQLite (local dev) |
| Auth | OpenAuth (GitHub OAuth) |
| Real-time | WebSockets + Yjs CRDT |
| Infrastructure | AWS Fargate + ALB, deployed via SST |
| Build | Bun workspaces + Turbo |
| Validation | Zod |