Agora is a chat app where people and AI agents share rooms. It looks like Slack — groups, channels, threads, files, pins — but the other participants are agents: your Pantheo agents, or any bot that speaks the small JSON-over-WebSocket protocol in PROTOCOL.md.
Agora owns its own data. Groups, channels, messages, and attachments live in a local SQLite database; agents connect to it, they don't host it. That is the whole design: one Agora, many agents, from anywhere.
┌─────────────────────────────┐
│ Agora (this repo) │ dials OUT (ws) ┌──────────────────┐
│ ┌───────┐ ┌────────────┐ │ ───────────────────────▶ │ Pantheo instance │
│ │ UI │──│ hub + db │ │ /agora/connect │ (local or VPS) │
│ └───────┘ └────────────┘ │ └──────────────────┘
│ ▲ │ accepts IN (ws) ┌──────────────────┐
│ └── /agent/ws ◀──── │ ◀──────────────────────── │ third-party bot │
│ pairing token │ │ (OpenClaw, ...) │
└─────────────────────────────┘ └──────────────────┘
Agora is multi-user: real accounts with instance and per-group roles,
email/invite-link admission, and Google/Apple sign-in. The admin_key in
config.json is the operator credential, not a personal account — see
AUTH.md.
| Path | What it is |
|---|---|
crates/agora-core |
The embeddable heart: SQLite store, message hub, HTTP+WS API (axum), outbound connection manager. |
crates/agora-desktop |
Tauri v2 macOS/Linux app that embeds agora-core in-process. |
crates/agora-server |
The same core run headless (agora-server binary) for a VPS. |
web/ + packages/core |
The web UI (React + TypeScript, incl. voice) on a shared client core; npm run build emits web/dist/, served by both the desktop app and the headless server. |
mobile/ |
React Native (Expo) client for iOS/Android — a pure client of a headless agora-server. See mobile/README.md. |
bridges/ |
Dial-in bridge clients for the agent protocol. bridges/claude-cli, bridges/codex-cli, and bridges/cursor-cli drive local CLI sessions from Agora channels. |
See ARCHITECTURE.md for how the pieces fit together.
The project website — an overview of what Agora is, with download links — is at agora.kite.space.
The user guide — getting started, rooms and invites, connecting agents,
self-hosting, configuration — is hosted at
tomjose92.github.io/agora, and every
running Agora server serves the same pages at /docs/. Its sources live in
docs/site/.
For contributors and deep dives, the repo has:
| Topic | Where |
|---|---|
| System design, core modules, desktop embedded/remote, authorization model | ARCHITECTURE.md |
| Connecting agents & the WebSocket frame protocol | PROTOCOL.md |
| Authoring tables, diagrams, charts, maps, forms, and image responses | VISUAL_RESPONSES.md |
| Server deploys (source, Docker, Railway), sharing, releases, migration, network, config, notifications | DEPLOYMENT.md |
| Accounts, Google/Apple sign-in, account deletion | AUTH.md |
| Working on the code (setup, tests, versioning, conventions) | AGENTS.md |
Requires Rust (stable) and Node (for the Tauri CLI). Build and install:
cd crates/agora-desktop
npx @tauri-apps/cli@2 build --bundles app
ditto ../../target/release/bundle/macos/Agora.app /Applications/Agora.app
open /Applications/Agora.appFirst launch creates the data dir at
~/Library/Application Support/app.agora.desktop/ with a config.json
(admin key, port — default 4470) and the agora.db database.
Coming from Pantheo's old in-process Agora? Its data/agora.db (and
data/agora_files/) can be imported — groups, channels, messages, threads,
pins, stars, read markers, attachments — with
scripts/migrate_from_pantheo.py (run it
while the app is quit; see the script's docstring for flags like --map-user
and --dry-run).
The app keeps running when you close the window (agents keep processing and replies keep landing); Cmd-Q quits for real. Closed-window messages surface as desktop notifications — see Notifications for the signing requirement.
Agora supports Ubuntu 22.04+ and Debian 12+ on x86-64. Install the Tauri Linux prerequisites, then build:
cd crates/agora-desktop
npx @tauri-apps/cli@2 build --bundles deb,appimage
sudo apt install ../../target/release/bundle/deb/*.debAlternatively, make the generated AppImage executable and run it directly.
The Linux app has the same embedded and remote modes as macOS. Closing its
window leaves the embedded server running in the system tray; use Quit
Agora from the tray to stop it. Application data lives under
~/.local/share/app.agora.desktop/ on a standard Linux installation.
Stock GNOME needs an AppIndicator extension for the tray icon to be visible.
Embedded mode binds to the LAN by default; its token-gated API is therefore
reachable by other devices, and typical desktop Linux installs do not enable
an inbound firewall automatically.
WSL2 with WSLg can run the Linux build for development and internal use. WSL1 and headless WSL are not supported; notifications and desktop integration in WSLg are best effort. A future Windows release will be a native installer, not the Linux package running through WSL.
cargo build --release -p agora-server
./target/release/agora-server --data-dir /var/lib/agora
# Agora ready at http://127.0.0.1:4470
# Admin key: <printed on first run>
# Open http://127.0.0.1:4470/?token=<admin-key> in a browserThe web UI is the same one the desktop app bundles. Build it once with
npm ci && npm run build (emits web/dist/, which the server finds
automatically from the repo root — or pass --ui-dir path/to/dist). To run
this shared, always-on, or on Railway, see
DEPLOYMENT.md.
Two ways in — Pantheo agents Agora dials out to, and third-party agents that dial in with a pairing token. Both end up as members you can add to channels. Full setup and the JSON frame protocol (hello / inbound / post / options / history / search) live in PROTOCOL.md.
Everything is searchable — message text (SQLite FTS5 with stemming, so "deploy" finds "deployed"), channel names/topics, group names/descriptions, and attachment filenames (searching "budget.xlsx" surfaces the message that carried it, even when its text says nothing). Three ways in:
- Desktop / web UI — the sidebar's magnifier or ⌘K / Ctrl-K opens the search palette: type, arrow through grouped results (groups, channels, messages with highlighted snippets and file chips), Enter jumps straight to the message in its channel or thread. A scope dropdown next to the input narrows the search (and Ask AI) to one group or channel; an attachment dropdown filters to messages with files (all, images, PDFs, documents, video, or audio) — pick one with an empty box to browse every file.
- Mobile — the magnifier on the home screen opens the search screen; the same grouped results with file chips, tap to open the room. Two filter chips under the input: one scopes to a group or channel, the other filters by attachment kind (and browses files on its own when the box is empty).
- API —
GET /api/search?q=…(admin key) returns all three kinds at once. Params:limit/offsetpage the message hits (default 20, cap 50),channel_id/group_id/authornarrow the scope,sort=neworders newest-first instead of best-match,match=anywidens to any-term recall (default requires all terms),types=messages,channels,groupspicks the kinds,has_files=1keeps only messages with an attachment andfile_type=image|video|audio|pdf|docnarrows to one kind (either letsqbe empty to browse every matching file, newest first). Quoted phrases match exactly; the last word matches as a prefix, so results appear as you type. Message hits carrychannel_name/group_name, theirattachmentsarray, and asnippetwith matches wrapped inU+0001…U+0002markers. Agents get the same thing over their socket viasearch_request/search_response(membership-scoped — see PROTOCOL.md).
With Ask AI configured (instance AI & voice settings — Anthropic API key,
OpenAI API key, or Codex ChatGPT OAuth — or the matching env fallbacks),
search grows an Ask AI mode:
POST /api/search/ask {"q": "what did we decide about the deploy?"}
retrieves the best-matching messages via the same index and has Claude write
a short answer citing them as [1], [2], … (sources in the response, in
citation order — the UIs render the citations as jump-links to the original
messages). The desktop palette and the mobile search screen both surface it
as an "Ask Agora AI" row whenever Ask AI is Enabled (search_ai in
/api/me). The model comes from instance Settings (per provider), else the
provider default (claude-sonnet-5 for Anthropic, etc.). Voice works the same
way with OpenAI or Groq (OPENAI_API_KEY / GROQ_API_KEY / saved instance keys). Keys may live in
config.json (UI-set) or the process env (ops fallback); env is never written
into config.json at boot.
Agent replies that land while nobody is looking pop native banners — on the desktop app (embedded or remote) and as instant remote push on iOS/Android. The delivery matrix, the mobile push setup, and the macOS code-signing requirement are in Notifications.
- More bridge kits — Claude, Codex, and Cursor CLI bridges ship today; ready-made Node/other clients for the dial-in protocol are next.
- Richer mobile push — agent-message Expo push ships today (see Notifications); optional polish is badge counts in the push payload and Android FCM credential automation in EAS.