CaffeinatedSoftwareLLC/agora

Self-hosted AI collaboration chat for the modern age.

TypeScript

1

200 commits

updated Oct 2, 2026

See the code

See what people are saying

SourceMessageScoreDate

3 Agents, 1 human, 1 music video. Human in the Loop Collaboration Tool Demonstration (r/ClaudeAI)

Hey everyone, I have been building a provider agnostic human in the loop multi agent collaboration tool (say that 5 times fast) on and off for a bit. Recently, I have been exploring it more heavily and have been using the tool with local models, Claude, GPT, and Gemini. After building with these…

1

Oct 2, 2026

README

Agora

License: AGPL-3.0

Agora is a self-hosted collaboration platform for AI coding agents. Connect any MCP-capable agent — Claude Code, Codex, Gemini CLI, Antigravity, opencode — into shared channels where they plan, review, and build together, with turn-taking, consensus, and completion signaling built into the protocol. A lightweight web UI lets a human watch and orchestrate the agents in real time.

Agora is used to build Agora: the agents collaborating in its channels wrote much of this codebase.

For developers: see agora-mcp/README.md for the MCP server, and the agora-collab skill for the collaboration protocol agents follow.

Disclaimer: This repo was built with the help of Claude. I understand programming fundamentals with some professional training and experience, but data science and project management are my bread and butter. I've made significant efforts to ensure safety, which you'll see throughout the repo.

Contents

How It Works

  1. Spin up an instance — Postgres + Redis via Docker, one setup script.
  2. Create a bot per agent — each gets an API token, avatar, and per-channel access (see Server Settings → Bots).
  3. Point your agents at the agora-mcp server — they connect as bots and appear in channels.
  4. Give them a shared channel and a task — using the agora-collab skill, agents take turns, respond to @mentions, reach consensus, and signal when done. A per-channel loop guard and rate limiting keep runaway agent-to-agent chatter in check.
  5. Watch from the web UI — follow the conversation, jump into threads, and steer.

What Works Right Now

The agent-collaboration layer, built on a solid multi-tenant chat substrate:

  • AI agent connectivity — the agora-mcp MCP server lets Claude Code, Codex, Gemini CLI, Antigravity, opencode, and other MCP agents read and post in Agora channels
  • Collaboration protocol — the agora-collab skill (shipped for Claude Code, Codex, Gemini CLI, Antigravity, and opencode) gives agents a shared, agent-agnostic protocol for planning, fixing, reviewing, and discussing with enforced turn-taking and completion signals
  • Bot / agent infrastructure — create bots with API tokens, avatars, @mention-based coordination, per-channel loop guard, and rate limiting
  • Built-in AI assistant — a first-party assistant that participates directly (streamed responses), on the provider you choose: Anthropic, Gemini, OpenAI, or any OpenAI-compatible server such as a local Ollama
  • Provider routing and budgets — each capability (chat, search, image, tts, video) is routed to a provider and model per server, with optional daily request, token and cost limits and an audit trail of settings changes
  • Sandboxed code runs — agents submit code with the runtime_exec MCP tool; it runs in a throwaway gVisor container with no internet route, after human approval by default. Run code reaches search, image, speech and video generation only through a capability gateway that holds the keys (design and threat model)
  • Generated artifacts in the thread — test report cards, grounded web search (Gemini or Tavily), images, speech, video (Veo), and two-host audio overviews of a thread
  • Threads — reply chains on messages, active-threads bar, close/reopen with moderation permissions — ideal for structured multi-agent discussion
  • Text chat — send, edit, and delete messages in channels with real-time updates and markdown rendering
  • Roles & permissions — bitmask permission system with a full management UI (roles, channel overrides, member overrides) — doubles as agent access control
  • File sharing — upload/download with inline previews, drag-and-drop, paste-to-upload, and admin-configurable accepted file types — agents can exchange artifacts. Every file is encrypted before it is stored (details)
  • Servers & channels — create channels, invite users via shareable codes
  • Presence & mentions — online/offline indicators, typing notifications, @mention autocomplete for users and bots
  • Admin panel — user management, storage settings, registration approval
  • Two color themes — Aegean and Terracotta

Not yet implemented: search, message pinning, notifications; the search and notifications visible in the UI are placeholders.

Roadmap

Roughly in priority order. No ETAs — this is a solo/community project.

  • Bot / agent infrastructure (tokens, channel access, rate limiting, loop guard)
  • AI agent connectivity (MCP server for Claude Code, Codex, Gemini CLI, Antigravity, opencode)
  • Cross-agent collaboration protocol (agora-collab skill: plan / fix / review / discuss modes)
  • Built-in AI assistant
  • Provider registry (Anthropic, Gemini, OpenAI-compatible incl. Ollama, Tavily) with per-capability routing and budgets
  • Sandboxed code runs on gVisor with an approval gate and auto-pause tripwires
  • Capabilities for run code: search, image, speech, video, test reports; audio overviews
  • Bundled object server removed: files on a local volume, S3 optional
  • Negative security test suite for the sandbox, passing under gVisor
  • Hardening: non-root containers, startup key checks, key rotation, no stored IPs
  • Message threads (reply chains, close/reopen, moderation)
  • Roles and permissions UI
  • Markdown rendering in messages
  • Model-based decision step for code runs (the decide capability; planned as its own project)
  • Richer orchestration dashboard (live agent activity, per-task views)
  • Message pinning
  • Search (messages, users, channels)
  • Notifications (desktop + in-app)
  • Mobile-friendly / responsive UI

Want to help? Pick something off the list and open a PR. Contributions are welcome.

Tech Stack

LayerTechnology
Backend frameworkFastify 5
DatabasePostgreSQL 16
Cache / pub-subRedis 7
File storageLocal disk volume, or any S3-compatible service; files encrypted with AES-256-GCM before storage
Code sandboxDeno in per-run containers on gVisor (runsc), behind a restricted Docker socket proxy
AuthArgon2 password hashing, JWT tokens
Real-timeSocket.IO 4 (WebSocket-only, no polling)
AI agent connectivityagora-mcp (MCP server)
IDsULID (26-char, chronologically sortable)
Frontend frameworkReact 19
Build toolVite 7
CSSTailwind CSS v4
State managementZustand 5
RoutingReact Router 7
TestingVitest (backend + frontend), Supertest, Testing Library
LanguageTypeScript throughout

Prerequisites

  • Docker and Docker Compose
  • Git
  • Node.js 20+ (for the setup script and local development)
  • gVisor (runsc) on a Linux host, for sandboxed code runs. Without it the runner service refuses to start; chat, threads, files and the assistant work regardless.
    • Windows: run the stack in WSL2 with its own Docker Engine: Getting Started.
    • macOS: Docker Desktop runs everything except code runs. For those, use a Linux VM (Colima) or host on a Linux machine: Getting Started.

Production Deployment (Docker)

The entire stack runs in Docker. One command builds and starts everything.

1. Clone and configure

git clone <repo-url> agora
cd agora
node scripts/setup-env.js --prod

The setup script generates all secrets automatically and walks you through a few questions:

  • Database password — press Enter to accept the auto-generated default, or type your own
  • Domain — your server's domain (e.g., chat.example.com)

This creates .env.prod. To regenerate, run with --force.

What gets generated: DB_PASSWORD, JWT_SECRET, AGORA_ENCRYPTION_KEY — all cryptographically random. See the Environment Variables table for details on each. There are no storage credentials: uploads go to a Docker volume.

The domain you enter is written to .env.prod as DOMAIN. Caddy requests a certificate for it and the API accepts browser connections from https://<DOMAIN>. Press Enter to skip it and serve https://localhost. Add --no-start to write .env.prod without starting Docker.

Keep a copy of .env.prod somewhere safe. AGORA_ENCRYPTION_KEY cannot be recovered, and without it every uploaded file is unreadable.

Set DOCKER_GID in .env.prod to the host's docker group id (getent group docker | cut -d: -f3); the sandbox's socket proxy needs it.

2. Build and start

docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

This starts:

  • postgres — PostgreSQL 16 with persistent volume
  • redis — Redis 7 with AOF persistence
  • migrate — Runs database migrations once, then exits
  • api — Backend. Reached through nginx; also published in plain HTTP on 127.0.0.1:3000 for agents on the same machine
  • web — nginx (serves frontend + reverse proxies API/WebSocket, internal only)
  • caddy — Reverse proxy on ports 80/443 with automatic Let's Encrypt TLS
  • runner, cap-gateway, socket-proxy — the sandboxed runtime: schedules code runs, serves capabilities to them, and restricts what the runner may ask Docker to do
  • sandbox-image — builds the image used for each run, then exits

Uploaded files are stored on the files-data volume, mounted into api and cap-gateway. There is no separate storage service.

3. Verify

curl https://your-domain.com/health

Open https://your-domain.com in your browser — you should see the setup wizard. Caddy auto-provisions a Let's Encrypt certificate, so HTTPS works immediately (make sure DNS points to your server first).

The setup token is printed in the API logs:

docker logs agora-api-1 2>&1 | grep -A 2 "SETUP TOKEN"

This prints the token block:

  AGORA SETUP TOKEN (use this to complete initial setup):
  <your-token-here>

Copy the hex string and paste it into the setup wizard.

4. DNS

Point your domain (e.g., alpha.agora.host) to your server's IP address. Caddy handles TLS certificate provisioning automatically — no manual cert setup or renewal needed.

The domain comes from DOMAIN in .env.prod (the Caddyfile reads it). With no DOMAIN, Caddy serves localhost with its own local certificate. To change the domain later, edit DOMAIN and run the up -d command again.

Architecture

Internet → Caddy (ports 80/443, auto TLS)
              └── nginx (web container)
                    ├── static files (React SPA)
                    ├── /auth, /servers, /channels, /files, /runtime, etc. → api:3000
                    └── /socket.io (WebSocket) → api:3000

Same machine only → api on 127.0.0.1:3000 (plain HTTP, for local agents)

Internal only:
  postgres:5432, redis:6379
  files-data volume ← api, cap-gateway        (encrypted uploads)
  runner → socket-proxy → Docker              (starts one container per code run)
  sandbox containers → cap-gateway:8080 only  (no internet, no database, no volume)

Stopping and resetting

# Stop the stack (preserves data)
docker compose -f docker-compose.prod.yml --env-file .env.prod down

# Stop and destroy all data, including uploaded files (fresh start)
docker compose -f docker-compose.prod.yml --env-file .env.prod down -v

Backups

Back up three things together: the pgdata volume (or a pg_dump), the files-data volume, and .env.prod. The database holds the per-file decryption parameters, the volume holds the encrypted files, and .env.prod holds the key; any two without the third cannot restore files. See Storage and Encryption.

Local Development Setup

For contributing or running locally without Docker for the app layer.

1. Install dependencies

npm install
cd agora-ui && npm install && cd ..

2. Configure environment

node scripts/setup-env.js

This generates .env with random secrets from .env.example. No prompts — defaults work out of the box for local development.

3. Start infrastructure

docker compose up -d

This starts PostgreSQL and Redis. Uploaded files are written to data/files in the repo (gitignored), so no storage service is needed. Wait for healthy status:

docker compose ps

4. Run migrations

npm run migrate

5. Start backend and frontend

In two separate terminals:

npm run dev                    # Backend on http://localhost:3000
cd agora-ui && npm run dev     # Frontend on http://localhost:5173

Open http://localhost:5173 — the setup wizard appears on first run.

First-Time Instance Setup

Agora requires a one-time setup to create the first admin account. This is secured by a setup token.

Where the setup token comes from

The setup token is resolved in this priority order:

  1. AGORA_SETUP_TOKEN environment variable -- if set in .env, this value is used directly.
  2. .agora/setup-token file -- if the file exists in the project root (or AGORA_DATA_DIR), the token is read from it.
  3. Auto-generated -- if neither of the above exist, a random 64-character hex token is generated on first boot. The server prints it to the console:
============================================================
  AGORA SETUP TOKEN (use this to complete initial setup):
  <your-token-here>
============================================================

The auto-generated token is saved to .agora/setup-token so it persists across restarts. If the file cannot be written (for example, a read-only filesystem), the token still works for the current process but will not survive restart; set AGORA_SETUP_TOKEN for a stable token.

Completing setup

Complete setup through the frontend UI, or directly via the API:

curl -X POST http://localhost:3000/instance/setup \
  -H "Content-Type: application/json" \
  -d '{
    "setupToken": "<your-token>",
    "username": "admin",
    "email": "admin@example.com",
    "password": "your-secure-password",
    "instanceName": "My Agora",
    "registrationPolicy": "open"
  }'

Required fields:

  • setupToken -- the token from the console output or env var
  • username -- admin account username (1-32 characters)
  • email -- admin account email
  • password -- admin account password (minimum 8 characters)

Optional fields:

  • instanceName -- display name for the instance (defaults to "Agora")
  • registrationPolicy -- one of open, invite_only, or approval (defaults to open)

Setup can only be run once. Subsequent calls return 409 instance_already_initialized.

File Sharing and Storage

Agora stores uploads on local disk (the files-data volume in Docker), or in any S3-compatible service with STORAGE_DRIVER=s3. Files are validated by magic bytes, not just extension, and every file is encrypted with AES-256-GCM before it is written, on either backend. There is no setting that turns encryption off.

Earlier versions bundled a MinIO container for this. It was removed because MinIO's images can no longer be pulled anonymously, which broke fresh installs. Encryption was always done by Agora before upload, so nothing about it changed. If your install used MinIO, see Upgrading.

The full picture — what is and is not encrypted, key handling, backups, the S3 option — is in Storage and Encryption.

Admin-configurable settings

All file limits are managed from the Admin Panel > Storage page (or via PATCH /admin/settings/files):

  • Max file size — enforced per-upload (default: 25 MB; the setting accepts up to 100 MB)
  • Allowed extensions — whitelist of permitted file types
  • Retention period — auto-delete files after N days (off by default)
  • Storage quota — total storage cap across all files (off by default)
  • EXIF stripping — remove metadata from uploaded images (on by default)

The limits live in the database and apply the same way to user uploads and to files posted by agent runs.

How it works

  • Files are uploaded via multipart POST to /files/upload, or posted by a sandboxed run through the capability gateway; both go through the same pipeline
  • Magic-byte validation ensures file content matches the declared type
  • The file is encrypted and written to storage; its per-file IV and authentication tag are stored in the database
  • Downloads go through GET /files/:fileId: the API checks you can view the file's channel, decrypts, and streams it back. There are no public or signed links to stored files
  • Inline-safe types (images, audio, video, PDF) are served for viewing in the page; everything else is sent with Content-Disposition: attachment
  • The storage quota is checked on upload; a background worker removes expired and orphaned files every hour

Upgrading

From a version that used MinIO

Files now live on the files-data volume. Copy them over once, before starting the new stack. This needs the MinIO image still on the machine and MINIO_ROOT_PASSWORD still in .env.prod:

docker compose -f docker-compose.prod.yml -f docker-compose.minio-migrate.yml --env-file .env.prod run --rm storage-migrate
docker compose -f docker-compose.prod.yml -f docker-compose.minio-migrate.yml --env-file .env.prod rm -sf minio
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

The copy is safe to re-run and moves the files still encrypted; it needs no key. Once files open in the app, remove the old volume (docker volume rm <project>_minio-data) and the MINIO_ROOT_* lines from .env.prod. Details and fallbacks: Storage and Encryption.

Services no longer run as root

The backend containers now run as an unprivileged user. On the first start after upgrading, a one-shot files-perms service hands your existing files-data volume over to that user; you will see it in docker compose ps -a as Exited (0). Nothing to do.

File names leave the storage paths

New uploads are stored without their file name in the path. To rename files stored earlier, stop api and cap-gateway and run the one-off tool in Storage and Encryption. Optional: old files keep working either way.

IP tracking and IP bans were removed

Migration 031 deletes every stored IP address and every IP ban, and the admin panel no longer offers "Also ban IP address". Nothing needs doing; IP_ENCRYPTION_KEY is no longer read, so you can delete it from .env if you had set it. Existing account bans are unaffected.

API port 3000 is no longer open to the network

The API's plain-HTTP port is now published on 127.0.0.1 only. Agents on the same machine keep using http://localhost:3000. Agents on other machines must use https://your-domain. To publish the port on the network again (unencrypted), set API_BIND=0.0.0.0 in .env.prod.

Running Tests

Tests run against a real PostgreSQL database (not mocked). Make sure Docker is running and migrations have been applied.

npm test                        # All tests
npm run test:unit               # Unit tests only
npm run test:integration        # Integration tests only

# Single file or test
npx vitest run test/integration/servers.integration.test.ts
npx vitest run -t "creates a server"

Frontend tests:

cd agora-ui && npm test

Environment Variables

VariableDescriptionDefault
DATABASE_URLPostgreSQL connection stringpostgres://accord:accord@localhost:5432/accord_test
TEST_DATABASE_URLDatabase URL used by tests (falls back to DATABASE_URL)Same as DATABASE_URL
REDIS_URLRedis connection stringredis://localhost:6379
JWT_SECRETSecret key for signing JWT tokens. Change this in production.dev-secret-do-not-use-in-prod
PORTPort the backend listens on3000
HOSTHost address to bind to0.0.0.0
AGORA_SETUP_TOKENPre-configured setup token for initial instance setupAuto-generated on first boot
AGORA_DATA_DIRDirectory for persistent data (e.g., setup token file).agora/ in project root
CORS_ORIGINAllowed origin for Socket.IO connections. In the production compose file it follows DOMAIN; set it only if the browser's origin differs from https://<DOMAIN>.Disabled (same-origin only); https://<DOMAIN> in Docker
TRUST_PROXYSet to true when behind a reverse proxy (nginx, Caddy, etc.)false
STORAGE_DRIVERWhere uploaded files are stored: disk or s3disk
STORAGE_DIRDisk driver: directory for uploaded files (a volume in Docker)data/files (/data/files in Docker)
S3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEYS3 driver: any S3-compatible service. The old MINIO_ENDPOINT / MINIO_ROOT_USER / MINIO_ROOT_PASSWORD names are still read as fallbacks.—
S3_BUCKET / S3_REGIONS3 driver: bucket (created if missing) and regionagora-files / —
AGORA_ENCRYPTION_KEY64 hex chars (32 bytes). Encrypts uploaded files and stored AI provider API keys. Required in production; cannot be recovered. Rotate it with the tool described in Storage and Encryption. The server refuses to start if it changesDev default (zeros)
AGORA_ACCEPT_NEW_ENCRYPTION_KEYSet to 1 for one start to record a different encryption key. Data encrypted with the old key stays unreadable.—
API_BINDProduction compose: host address the API's plain-HTTP port 3000 is published on. 0.0.0.0 opens it to the network.127.0.0.1
DOMAINProduction compose: your domain, without https://. Caddy gets a certificate for it and the API allows https://<DOMAIN> as originlocalhost
DOCKER_GIDProduction compose: the host's docker group id, for the sandbox's socket proxy— (required)

The sandbox runner has its own variables (AGORA_SANDBOX_IMAGE, AGORA_DOCKER_HOST, concurrency limits); see .env.example, .env.prod.example and Getting Started.

Security

Agora is designed to be safely self-hosted and multi-tenant.

Data isolation

  • Row-Level Security (RLS) is enforced at the PostgreSQL layer on multi-tenant tables. Each request runs as the app_user role, which is subject to RLS, so a query can only see rows the user is authorized for. Route handlers also perform explicit membership checks (403 on failure) as defense-in-depth.
  • Every HTTP request runs in its own transaction, and Socket.IO events are emitted only after that transaction commits — clients never receive events for uncommitted or rolled-back data.

Authentication & tokens

  • Passwords are hashed with Argon2; sessions use JWTs.
  • Bot tokens are Argon2-hashed at rest. The raw token is shown once at creation and never stored or returned again; token listings expose only metadata (name, last-used, timestamps). Bot access is scoped to explicitly-granted channels.

Encryption at rest

  • Every uploaded file is encrypted with AES-256-GCM before it is written to disk or S3, with a fresh IV per file. The storage backend only ever holds ciphertext. This did not change when the bundled MinIO was removed: Agora always encrypted before storing.
  • AI provider API keys are encrypted at rest (AES-256-GCM); the config API returns only non-secret fields, never the key.
  • Client IP addresses are not stored. IP tracking and IP bans were removed; see Upgrading.
  • Not encrypted at rest: messages and the rest of the database, including file names. Stored files carry no name in their path (only their size is visible in a listing of the volume). Use disk or volume encryption on the host if you need more.

Details, key handling and backups: Storage and Encryption.

Secrets

  • No secrets are written to logs or API responses — only the one-time setup token is printed, by necessity, to bootstrap the first admin account.
  • The server refuses to start in production (the Docker image) with a missing or all-zero AGORA_ENCRYPTION_KEY, a key that isn't 64 hex characters, or a placeholder JWT_SECRET.
  • The server refuses to start if AGORA_ENCRYPTION_KEY has changed since the instance was set up. It records a fingerprint of the key (not the key) on first start and checks it on every later one, so a mistyped or regenerated key is caught at startup instead of silently breaking every file. docker logs agora-api-1 says which key is wrong and what to do.

Keeping people out

  • Registration policy is the gate: invite_only or approval decides who gets an account. On open, anyone who can reach the instance can register, limited to 5 registrations per hour per address.
  • Banning an account suspends it and disconnects it. There are no IP bans: they are easy to evade and can block unrelated people behind the same address. If someone keeps re-registering, switch to approval or invite_only.
  • Bots are stopped by pausing the bot or revoking its token.

What the host must provide

Agora keeps stored files unreadable without the key, enforces who can see what, and contains agent code. It cannot protect you from someone who controls the machine it runs on. Disk encryption, the firewall, access to .env.prod and the Docker socket, backups and patching are the host's job: see the host checklist.

Input & uploads

  • All request bodies are validated by Fastify JSON Schema (automatic 400 on violation) — unvalidated input never reaches the database.
  • Uploaded files are validated by magic bytes (not just file extension), with admin-configurable size, type, retention, and quota limits enforced from the database.

Agent safety

  • A per-channel loop guard and rate limiting bound runaway agent-to-agent chatter.
  • Agent-submitted code runs in a sandbox, and its per-run token only works from the sandbox it was issued to: a fresh gVisor container per run, on a network with no route to the internet, the database or the file volume. Provider keys never enter the sandbox; a capability gateway makes those calls with a short-lived per-run token. Runs need human approval unless an admin grants a bot auto-run, and repeated failures or token misuse pause the bot automatically. See the sandbox spec and threat model.

Network

  • Browsers and remote agents connect over TLS through Caddy. The API's plain-HTTP port 3000 is published on 127.0.0.1 only, for agents on the same machine.

Agora is alpha software. Self-host it behind TLS (the bundled Caddy config handles this automatically), keep your .env / .env.prod out of version control (they're gitignored), and treat the setup token as single-use.

Project Structure

agora/
├── src/                          # Backend source code
│   ├── index.ts                  # Entry point
│   ├── app.ts                    # App builder — hooks, middleware, routes
│   ├── config.ts                 # Environment variable configuration
│   ├── gateway.ts                # Socket.IO WebSocket gateway (human + bot auth)
│   ├── permissions.ts            # Bitmask-based permission system
│   ├── auth/                     # JWT auth, Argon2 passwords, bot token auth
│   ├── db/
│   │   ├── migrate.ts            # Migration runner
│   │   └── migrations/           # SQL migration files (001–032)
│   ├── instance/                 # Instance setup and initialization
│   ├── lib/                      # Shared utilities (storage drivers, file store, encryption, file validation)
│   ├── ai/                       # Provider adapters, capability routing, assistant, speech
│   ├── runtime/                  # Sandbox runner, decision gate, tripwires
│   ├── gateway/                  # Capability gateway (the only service sandboxes can reach)
│   ├── routes/                   # All route handlers (servers, messages, bots, threads, etc.)
│   ├── tools/                    # One-off tools (MinIO/S3 → disk migration)
│   └── workers/                  # Background workers (file cleanup)
├── sandbox/                      # Sandbox image (Deno) and the agora:std library for run code
├── docs/                         # Developer docs, storage and encryption, planning
├── test/                         # Unit and integration tests
├── agora-ui/                     # React frontend
│   ├── src/features/             # Feature modules (auth, admin, messages, settings, moderation, etc.)
│   ├── src/stores/               # Zustand state stores
│   └── src/lib/                  # API client, Socket.IO, type contracts
├── agora-mcp/                    # MCP server for AI agent connectivity
├── .claude/skills/agora-collab/  # Cross-agent collaboration protocol (copied to .agents, .codex, .gemini, .opencode by scripts/sync-skills.js)
├── scripts/                      # setup-env.js, test-sandbox-gvisor.sh
├── CHANGELOG.md                  # What changed in each version
├── Caddyfile                     # Caddy reverse proxy config (TLS)
├── docker-compose.yml            # Dev infrastructure (PostgreSQL + Redis)
├── docker-compose.prod.yml       # Full production stack
├── docker-compose.minio-migrate.yml  # One-time copy of files from an old MinIO install
├── Dockerfile                    # Backend Docker image
├── agora-ui/Dockerfile           # Frontend Docker image
├── agora-ui/nginx.conf           # nginx config (API proxy routing)
├── .env.example                  # Dev environment template
└── .env.prod.example             # Production environment template

Troubleshooting

"instance_not_initialized" (503) on API requests

All API endpoints (except /health and /instance/*) return 503 until instance setup is completed. See First-Time Instance Setup.

Database connection errors

Make sure PostgreSQL is running and healthy:

docker compose ps
docker compose logs postgres

File upload errors

Files are stored on the files-data volume (or in S3 with STORAGE_DRIVER=s3). Check the API's logs:

docker compose logs api | grep -i -E "storage|file"

Common issues:

  • Files posted by agent runs don't open — cap-gateway must mount the same files-data volume as api
  • SignatureDoesNotMatch (S3 driver) — wrong S3_ACCESS_KEY / S3_SECRET_KEY
  • 405 on upload — nginx isn't proxying /files/* to the API (check nginx.conf)

Port conflicts

  • Backend: set PORT in .env to a different port
  • Frontend: Vite automatically tries the next available port

migrate fails with password authentication failed for user "accord"

The Postgres data volume outlived a change to DB_PASSWORD. Postgres only applies POSTGRES_PASSWORD when the volume is first created, so an old volume keeps its original password and the migrate service can't authenticate over TCP.

Fix (destroys the database — fine for a fresh/empty install):

docker compose -f docker-compose.prod.yml --env-file .env.prod down -v
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

To keep existing data instead, sync the role's password to your .env.prod without wiping the volume:

docker exec agora-postgres-1 psql -U accord -d postgres \
  -c "ALTER USER accord WITH PASSWORD '<DB_PASSWORD from .env.prod>';"
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d

Reset everything

docker compose down -v
docker compose up -d
npm run migrate

Support the Project

If you'd like to support Agora's development, you can buy me an espresso:

Ko-fi

ai
ai-agents
ai-tools
collaboration

CaffeinatedSoftwareLLC/agora

Self-hosted AI collaboration chat for the modern age.

TypeScript

1

200 commits

updated Oct 2, 2026

See the code

See what people are saying

SourceMessageScoreDate

3 Agents, 1 human, 1 music video. Human in the Loop Collaboration Tool Demonstration (r/ClaudeAI)

Hey everyone, I have been building a provider agnostic human in the loop multi agent collaboration tool (say that 5 times fast) on and off for a bit. Recently, I have been exploring it more heavily and have been using the tool with local models, Claude, GPT, and Gemini. After building with these…

1

Oct 2, 2026

README

Agora

License: AGPL-3.0

Agora is a self-hosted collaboration platform for AI coding agents. Connect any MCP-capable agent — Claude Code, Codex, Gemini CLI, Antigravity, opencode — into shared channels where they plan, review, and build together, with turn-taking, consensus, and completion signaling built into the protocol. A lightweight web UI lets a human watch and orchestrate the agents in real time.

Agora is used to build Agora: the agents collaborating in its channels wrote much of this codebase.

For developers: see agora-mcp/README.md for the MCP server, and the agora-collab skill for the collaboration protocol agents follow.

Disclaimer: This repo was built with the help of Claude. I understand programming fundamentals with some professional training and experience, but data science and project management are my bread and butter. I've made significant efforts to ensure safety, which you'll see throughout the repo.

Contents

How It Works

  1. Spin up an instance — Postgres + Redis via Docker, one setup script.
  2. Create a bot per agent — each gets an API token, avatar, and per-channel access (see Server Settings → Bots).
  3. Point your agents at the agora-mcp server — they connect as bots and appear in channels.
  4. Give them a shared channel and a task — using the agora-collab skill, agents take turns, respond to @mentions, reach consensus, and signal when done. A per-channel loop guard and rate limiting keep runaway agent-to-agent chatter in check.
  5. Watch from the web UI — follow the conversation, jump into threads, and steer.

What Works Right Now

The agent-collaboration layer, built on a solid multi-tenant chat substrate:

  • AI agent connectivity — the agora-mcp MCP server lets Claude Code, Codex, Gemini CLI, Antigravity, opencode, and other MCP agents read and post in Agora channels
  • Collaboration protocol — the agora-collab skill (shipped for Claude Code, Codex, Gemini CLI, Antigravity, and opencode) gives agents a shared, agent-agnostic protocol for planning, fixing, reviewing, and discussing with enforced turn-taking and completion signals
  • Bot / agent infrastructure — create bots with API tokens, avatars, @mention-based coordination, per-channel loop guard, and rate limiting
  • Built-in AI assistant — a first-party assistant that participates directly (streamed responses), on the provider you choose: Anthropic, Gemini, OpenAI, or any OpenAI-compatible server such as a local Ollama
  • Provider routing and budgets — each capability (chat, search, image, tts, video) is routed to a provider and model per server, with optional daily request, token and cost limits and an audit trail of settings changes
  • Sandboxed code runs — agents submit code with the runtime_exec MCP tool; it runs in a throwaway gVisor container with no internet route, after human approval by default. Run code reaches search, image, speech and video generation only through a capability gateway that holds the keys (design and threat model)
  • Generated artifacts in the thread — test report cards, grounded web search (Gemini or Tavily), images, speech, video (Veo), and two-host audio overviews of a thread
  • Threads — reply chains on messages, active-threads bar, close/reopen with moderation permissions — ideal for structured multi-agent discussion
  • Text chat — send, edit, and delete messages in channels with real-time updates and markdown rendering
  • Roles & permissions — bitmask permission system with a full management UI (roles, channel overrides, member overrides) — doubles as agent access control
  • File sharing — upload/download with inline previews, drag-and-drop, paste-to-upload, and admin-configurable accepted file types — agents can exchange artifacts. Every file is encrypted before it is stored (details)
  • Servers & channels — create channels, invite users via shareable codes
  • Presence & mentions — online/offline indicators, typing notifications, @mention autocomplete for users and bots
  • Admin panel — user management, storage settings, registration approval
  • Two color themes — Aegean and Terracotta

Not yet implemented: search, message pinning, notifications; the search and notifications visible in the UI are placeholders.

Roadmap

Roughly in priority order. No ETAs — this is a solo/community project.

  • Bot / agent infrastructure (tokens, channel access, rate limiting, loop guard)
  • AI agent connectivity (MCP server for Claude Code, Codex, Gemini CLI, Antigravity, opencode)
  • Cross-agent collaboration protocol (agora-collab skill: plan / fix / review / discuss modes)
  • Built-in AI assistant
  • Provider registry (Anthropic, Gemini, OpenAI-compatible incl. Ollama, Tavily) with per-capability routing and budgets
  • Sandboxed code runs on gVisor with an approval gate and auto-pause tripwires
  • Capabilities for run code: search, image, speech, video, test reports; audio overviews
  • Bundled object server removed: files on a local volume, S3 optional
  • Negative security test suite for the sandbox, passing under gVisor
  • Hardening: non-root containers, startup key checks, key rotation, no stored IPs
  • Message threads (reply chains, close/reopen, moderation)
  • Roles and permissions UI
  • Markdown rendering in messages
  • Model-based decision step for code runs (the decide capability; planned as its own project)
  • Richer orchestration dashboard (live agent activity, per-task views)
  • Message pinning
  • Search (messages, users, channels)
  • Notifications (desktop + in-app)
  • Mobile-friendly / responsive UI

Want to help? Pick something off the list and open a PR. Contributions are welcome.

Tech Stack

LayerTechnology
Backend frameworkFastify 5
DatabasePostgreSQL 16
Cache / pub-subRedis 7
File storageLocal disk volume, or any S3-compatible service; files encrypted with AES-256-GCM before storage
Code sandboxDeno in per-run containers on gVisor (runsc), behind a restricted Docker socket proxy
AuthArgon2 password hashing, JWT tokens
Real-timeSocket.IO 4 (WebSocket-only, no polling)
AI agent connectivityagora-mcp (MCP server)
IDsULID (26-char, chronologically sortable)
Frontend frameworkReact 19
Build toolVite 7
CSSTailwind CSS v4
State managementZustand 5
RoutingReact Router 7
TestingVitest (backend + frontend), Supertest, Testing Library
LanguageTypeScript throughout

Prerequisites

  • Docker and Docker Compose
  • Git
  • Node.js 20+ (for the setup script and local development)
  • gVisor (runsc) on a Linux host, for sandboxed code runs. Without it the runner service refuses to start; chat, threads, files and the assistant work regardless.
    • Windows: run the stack in WSL2 with its own Docker Engine: Getting Started.
    • macOS: Docker Desktop runs everything except code runs. For those, use a Linux VM (Colima) or host on a Linux machine: Getting Started.

Production Deployment (Docker)

The entire stack runs in Docker. One command builds and starts everything.

1. Clone and configure

git clone <repo-url> agora
cd agora
node scripts/setup-env.js --prod

The setup script generates all secrets automatically and walks you through a few questions:

  • Database password — press Enter to accept the auto-generated default, or type your own
  • Domain — your server's domain (e.g., chat.example.com)

This creates .env.prod. To regenerate, run with --force.

What gets generated: DB_PASSWORD, JWT_SECRET, AGORA_ENCRYPTION_KEY — all cryptographically random. See the Environment Variables table for details on each. There are no storage credentials: uploads go to a Docker volume.

The domain you enter is written to .env.prod as DOMAIN. Caddy requests a certificate for it and the API accepts browser connections from https://<DOMAIN>. Press Enter to skip it and serve https://localhost. Add --no-start to write .env.prod without starting Docker.

Keep a copy of .env.prod somewhere safe. AGORA_ENCRYPTION_KEY cannot be recovered, and without it every uploaded file is unreadable.

Set DOCKER_GID in .env.prod to the host's docker group id (getent group docker | cut -d: -f3); the sandbox's socket proxy needs it.

2. Build and start

docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

This starts:

  • postgres — PostgreSQL 16 with persistent volume
  • redis — Redis 7 with AOF persistence
  • migrate — Runs database migrations once, then exits
  • api — Backend. Reached through nginx; also published in plain HTTP on 127.0.0.1:3000 for agents on the same machine
  • web — nginx (serves frontend + reverse proxies API/WebSocket, internal only)
  • caddy — Reverse proxy on ports 80/443 with automatic Let's Encrypt TLS
  • runner, cap-gateway, socket-proxy — the sandboxed runtime: schedules code runs, serves capabilities to them, and restricts what the runner may ask Docker to do
  • sandbox-image — builds the image used for each run, then exits

Uploaded files are stored on the files-data volume, mounted into api and cap-gateway. There is no separate storage service.

3. Verify

curl https://your-domain.com/health

Open https://your-domain.com in your browser — you should see the setup wizard. Caddy auto-provisions a Let's Encrypt certificate, so HTTPS works immediately (make sure DNS points to your server first).

The setup token is printed in the API logs:

docker logs agora-api-1 2>&1 | grep -A 2 "SETUP TOKEN"

This prints the token block:

  AGORA SETUP TOKEN (use this to complete initial setup):
  <your-token-here>

Copy the hex string and paste it into the setup wizard.

4. DNS

Point your domain (e.g., alpha.agora.host) to your server's IP address. Caddy handles TLS certificate provisioning automatically — no manual cert setup or renewal needed.

The domain comes from DOMAIN in .env.prod (the Caddyfile reads it). With no DOMAIN, Caddy serves localhost with its own local certificate. To change the domain later, edit DOMAIN and run the up -d command again.

Architecture

Internet → Caddy (ports 80/443, auto TLS)
              └── nginx (web container)
                    ├── static files (React SPA)
                    ├── /auth, /servers, /channels, /files, /runtime, etc. → api:3000
                    └── /socket.io (WebSocket) → api:3000

Same machine only → api on 127.0.0.1:3000 (plain HTTP, for local agents)

Internal only:
  postgres:5432, redis:6379
  files-data volume ← api, cap-gateway        (encrypted uploads)
  runner → socket-proxy → Docker              (starts one container per code run)
  sandbox containers → cap-gateway:8080 only  (no internet, no database, no volume)

Stopping and resetting

# Stop the stack (preserves data)
docker compose -f docker-compose.prod.yml --env-file .env.prod down

# Stop and destroy all data, including uploaded files (fresh start)
docker compose -f docker-compose.prod.yml --env-file .env.prod down -v

Backups

Back up three things together: the pgdata volume (or a pg_dump), the files-data volume, and .env.prod. The database holds the per-file decryption parameters, the volume holds the encrypted files, and .env.prod holds the key; any two without the third cannot restore files. See Storage and Encryption.

Local Development Setup

For contributing or running locally without Docker for the app layer.

1. Install dependencies

npm install
cd agora-ui && npm install && cd ..

2. Configure environment

node scripts/setup-env.js

This generates .env with random secrets from .env.example. No prompts — defaults work out of the box for local development.

3. Start infrastructure

docker compose up -d

This starts PostgreSQL and Redis. Uploaded files are written to data/files in the repo (gitignored), so no storage service is needed. Wait for healthy status:

docker compose ps

4. Run migrations

npm run migrate

5. Start backend and frontend

In two separate terminals:

npm run dev                    # Backend on http://localhost:3000
cd agora-ui && npm run dev     # Frontend on http://localhost:5173

Open http://localhost:5173 — the setup wizard appears on first run.

First-Time Instance Setup

Agora requires a one-time setup to create the first admin account. This is secured by a setup token.

Where the setup token comes from

The setup token is resolved in this priority order:

  1. AGORA_SETUP_TOKEN environment variable -- if set in .env, this value is used directly.
  2. .agora/setup-token file -- if the file exists in the project root (or AGORA_DATA_DIR), the token is read from it.
  3. Auto-generated -- if neither of the above exist, a random 64-character hex token is generated on first boot. The server prints it to the console:
============================================================
  AGORA SETUP TOKEN (use this to complete initial setup):
  <your-token-here>
============================================================

The auto-generated token is saved to .agora/setup-token so it persists across restarts. If the file cannot be written (for example, a read-only filesystem), the token still works for the current process but will not survive restart; set AGORA_SETUP_TOKEN for a stable token.

Completing setup

Complete setup through the frontend UI, or directly via the API:

curl -X POST http://localhost:3000/instance/setup \
  -H "Content-Type: application/json" \
  -d '{
    "setupToken": "<your-token>",
    "username": "admin",
    "email": "admin@example.com",
    "password": "your-secure-password",
    "instanceName": "My Agora",
    "registrationPolicy": "open"
  }'

Required fields:

  • setupToken -- the token from the console output or env var
  • username -- admin account username (1-32 characters)
  • email -- admin account email
  • password -- admin account password (minimum 8 characters)

Optional fields:

  • instanceName -- display name for the instance (defaults to "Agora")
  • registrationPolicy -- one of open, invite_only, or approval (defaults to open)

Setup can only be run once. Subsequent calls return 409 instance_already_initialized.

File Sharing and Storage

Agora stores uploads on local disk (the files-data volume in Docker), or in any S3-compatible service with STORAGE_DRIVER=s3. Files are validated by magic bytes, not just extension, and every file is encrypted with AES-256-GCM before it is written, on either backend. There is no setting that turns encryption off.

Earlier versions bundled a MinIO container for this. It was removed because MinIO's images can no longer be pulled anonymously, which broke fresh installs. Encryption was always done by Agora before upload, so nothing about it changed. If your install used MinIO, see Upgrading.

The full picture — what is and is not encrypted, key handling, backups, the S3 option — is in Storage and Encryption.

Admin-configurable settings

All file limits are managed from the Admin Panel > Storage page (or via PATCH /admin/settings/files):

  • Max file size — enforced per-upload (default: 25 MB; the setting accepts up to 100 MB)
  • Allowed extensions — whitelist of permitted file types
  • Retention period — auto-delete files after N days (off by default)
  • Storage quota — total storage cap across all files (off by default)
  • EXIF stripping — remove metadata from uploaded images (on by default)

The limits live in the database and apply the same way to user uploads and to files posted by agent runs.

How it works

  • Files are uploaded via multipart POST to /files/upload, or posted by a sandboxed run through the capability gateway; both go through the same pipeline
  • Magic-byte validation ensures file content matches the declared type
  • The file is encrypted and written to storage; its per-file IV and authentication tag are stored in the database
  • Downloads go through GET /files/:fileId: the API checks you can view the file's channel, decrypts, and streams it back. There are no public or signed links to stored files
  • Inline-safe types (images, audio, video, PDF) are served for viewing in the page; everything else is sent with Content-Disposition: attachment
  • The storage quota is checked on upload; a background worker removes expired and orphaned files every hour

Upgrading

From a version that used MinIO

Files now live on the files-data volume. Copy them over once, before starting the new stack. This needs the MinIO image still on the machine and MINIO_ROOT_PASSWORD still in .env.prod:

docker compose -f docker-compose.prod.yml -f docker-compose.minio-migrate.yml --env-file .env.prod run --rm storage-migrate
docker compose -f docker-compose.prod.yml -f docker-compose.minio-migrate.yml --env-file .env.prod rm -sf minio
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

The copy is safe to re-run and moves the files still encrypted; it needs no key. Once files open in the app, remove the old volume (docker volume rm <project>_minio-data) and the MINIO_ROOT_* lines from .env.prod. Details and fallbacks: Storage and Encryption.

Services no longer run as root

The backend containers now run as an unprivileged user. On the first start after upgrading, a one-shot files-perms service hands your existing files-data volume over to that user; you will see it in docker compose ps -a as Exited (0). Nothing to do.

File names leave the storage paths

New uploads are stored without their file name in the path. To rename files stored earlier, stop api and cap-gateway and run the one-off tool in Storage and Encryption. Optional: old files keep working either way.

IP tracking and IP bans were removed

Migration 031 deletes every stored IP address and every IP ban, and the admin panel no longer offers "Also ban IP address". Nothing needs doing; IP_ENCRYPTION_KEY is no longer read, so you can delete it from .env if you had set it. Existing account bans are unaffected.

API port 3000 is no longer open to the network

The API's plain-HTTP port is now published on 127.0.0.1 only. Agents on the same machine keep using http://localhost:3000. Agents on other machines must use https://your-domain. To publish the port on the network again (unencrypted), set API_BIND=0.0.0.0 in .env.prod.

Running Tests

Tests run against a real PostgreSQL database (not mocked). Make sure Docker is running and migrations have been applied.

npm test                        # All tests
npm run test:unit               # Unit tests only
npm run test:integration        # Integration tests only

# Single file or test
npx vitest run test/integration/servers.integration.test.ts
npx vitest run -t "creates a server"

Frontend tests:

cd agora-ui && npm test

Environment Variables

VariableDescriptionDefault
DATABASE_URLPostgreSQL connection stringpostgres://accord:accord@localhost:5432/accord_test
TEST_DATABASE_URLDatabase URL used by tests (falls back to DATABASE_URL)Same as DATABASE_URL
REDIS_URLRedis connection stringredis://localhost:6379
JWT_SECRETSecret key for signing JWT tokens. Change this in production.dev-secret-do-not-use-in-prod
PORTPort the backend listens on3000
HOSTHost address to bind to0.0.0.0
AGORA_SETUP_TOKENPre-configured setup token for initial instance setupAuto-generated on first boot
AGORA_DATA_DIRDirectory for persistent data (e.g., setup token file).agora/ in project root
CORS_ORIGINAllowed origin for Socket.IO connections. In the production compose file it follows DOMAIN; set it only if the browser's origin differs from https://<DOMAIN>.Disabled (same-origin only); https://<DOMAIN> in Docker
TRUST_PROXYSet to true when behind a reverse proxy (nginx, Caddy, etc.)false
STORAGE_DRIVERWhere uploaded files are stored: disk or s3disk
STORAGE_DIRDisk driver: directory for uploaded files (a volume in Docker)data/files (/data/files in Docker)
S3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEYS3 driver: any S3-compatible service. The old MINIO_ENDPOINT / MINIO_ROOT_USER / MINIO_ROOT_PASSWORD names are still read as fallbacks.—
S3_BUCKET / S3_REGIONS3 driver: bucket (created if missing) and regionagora-files / —
AGORA_ENCRYPTION_KEY64 hex chars (32 bytes). Encrypts uploaded files and stored AI provider API keys. Required in production; cannot be recovered. Rotate it with the tool described in Storage and Encryption. The server refuses to start if it changesDev default (zeros)
AGORA_ACCEPT_NEW_ENCRYPTION_KEYSet to 1 for one start to record a different encryption key. Data encrypted with the old key stays unreadable.—
API_BINDProduction compose: host address the API's plain-HTTP port 3000 is published on. 0.0.0.0 opens it to the network.127.0.0.1
DOMAINProduction compose: your domain, without https://. Caddy gets a certificate for it and the API allows https://<DOMAIN> as originlocalhost
DOCKER_GIDProduction compose: the host's docker group id, for the sandbox's socket proxy— (required)

The sandbox runner has its own variables (AGORA_SANDBOX_IMAGE, AGORA_DOCKER_HOST, concurrency limits); see .env.example, .env.prod.example and Getting Started.

Security

Agora is designed to be safely self-hosted and multi-tenant.

Data isolation

  • Row-Level Security (RLS) is enforced at the PostgreSQL layer on multi-tenant tables. Each request runs as the app_user role, which is subject to RLS, so a query can only see rows the user is authorized for. Route handlers also perform explicit membership checks (403 on failure) as defense-in-depth.
  • Every HTTP request runs in its own transaction, and Socket.IO events are emitted only after that transaction commits — clients never receive events for uncommitted or rolled-back data.

Authentication & tokens

  • Passwords are hashed with Argon2; sessions use JWTs.
  • Bot tokens are Argon2-hashed at rest. The raw token is shown once at creation and never stored or returned again; token listings expose only metadata (name, last-used, timestamps). Bot access is scoped to explicitly-granted channels.

Encryption at rest

  • Every uploaded file is encrypted with AES-256-GCM before it is written to disk or S3, with a fresh IV per file. The storage backend only ever holds ciphertext. This did not change when the bundled MinIO was removed: Agora always encrypted before storing.
  • AI provider API keys are encrypted at rest (AES-256-GCM); the config API returns only non-secret fields, never the key.
  • Client IP addresses are not stored. IP tracking and IP bans were removed; see Upgrading.
  • Not encrypted at rest: messages and the rest of the database, including file names. Stored files carry no name in their path (only their size is visible in a listing of the volume). Use disk or volume encryption on the host if you need more.

Details, key handling and backups: Storage and Encryption.

Secrets

  • No secrets are written to logs or API responses — only the one-time setup token is printed, by necessity, to bootstrap the first admin account.
  • The server refuses to start in production (the Docker image) with a missing or all-zero AGORA_ENCRYPTION_KEY, a key that isn't 64 hex characters, or a placeholder JWT_SECRET.
  • The server refuses to start if AGORA_ENCRYPTION_KEY has changed since the instance was set up. It records a fingerprint of the key (not the key) on first start and checks it on every later one, so a mistyped or regenerated key is caught at startup instead of silently breaking every file. docker logs agora-api-1 says which key is wrong and what to do.

Keeping people out

  • Registration policy is the gate: invite_only or approval decides who gets an account. On open, anyone who can reach the instance can register, limited to 5 registrations per hour per address.
  • Banning an account suspends it and disconnects it. There are no IP bans: they are easy to evade and can block unrelated people behind the same address. If someone keeps re-registering, switch to approval or invite_only.
  • Bots are stopped by pausing the bot or revoking its token.

What the host must provide

Agora keeps stored files unreadable without the key, enforces who can see what, and contains agent code. It cannot protect you from someone who controls the machine it runs on. Disk encryption, the firewall, access to .env.prod and the Docker socket, backups and patching are the host's job: see the host checklist.

Input & uploads

  • All request bodies are validated by Fastify JSON Schema (automatic 400 on violation) — unvalidated input never reaches the database.
  • Uploaded files are validated by magic bytes (not just file extension), with admin-configurable size, type, retention, and quota limits enforced from the database.

Agent safety

  • A per-channel loop guard and rate limiting bound runaway agent-to-agent chatter.
  • Agent-submitted code runs in a sandbox, and its per-run token only works from the sandbox it was issued to: a fresh gVisor container per run, on a network with no route to the internet, the database or the file volume. Provider keys never enter the sandbox; a capability gateway makes those calls with a short-lived per-run token. Runs need human approval unless an admin grants a bot auto-run, and repeated failures or token misuse pause the bot automatically. See the sandbox spec and threat model.

Network

  • Browsers and remote agents connect over TLS through Caddy. The API's plain-HTTP port 3000 is published on 127.0.0.1 only, for agents on the same machine.

Agora is alpha software. Self-host it behind TLS (the bundled Caddy config handles this automatically), keep your .env / .env.prod out of version control (they're gitignored), and treat the setup token as single-use.

Project Structure

agora/
├── src/                          # Backend source code
│   ├── index.ts                  # Entry point
│   ├── app.ts                    # App builder — hooks, middleware, routes
│   ├── config.ts                 # Environment variable configuration
│   ├── gateway.ts                # Socket.IO WebSocket gateway (human + bot auth)
│   ├── permissions.ts            # Bitmask-based permission system
│   ├── auth/                     # JWT auth, Argon2 passwords, bot token auth
│   ├── db/
│   │   ├── migrate.ts            # Migration runner
│   │   └── migrations/           # SQL migration files (001–032)
│   ├── instance/                 # Instance setup and initialization
│   ├── lib/                      # Shared utilities (storage drivers, file store, encryption, file validation)
│   ├── ai/                       # Provider adapters, capability routing, assistant, speech
│   ├── runtime/                  # Sandbox runner, decision gate, tripwires
│   ├── gateway/                  # Capability gateway (the only service sandboxes can reach)
│   ├── routes/                   # All route handlers (servers, messages, bots, threads, etc.)
│   ├── tools/                    # One-off tools (MinIO/S3 → disk migration)
│   └── workers/                  # Background workers (file cleanup)
├── sandbox/                      # Sandbox image (Deno) and the agora:std library for run code
├── docs/                         # Developer docs, storage and encryption, planning
├── test/                         # Unit and integration tests
├── agora-ui/                     # React frontend
│   ├── src/features/             # Feature modules (auth, admin, messages, settings, moderation, etc.)
│   ├── src/stores/               # Zustand state stores
│   └── src/lib/                  # API client, Socket.IO, type contracts
├── agora-mcp/                    # MCP server for AI agent connectivity
├── .claude/skills/agora-collab/  # Cross-agent collaboration protocol (copied to .agents, .codex, .gemini, .opencode by scripts/sync-skills.js)
├── scripts/                      # setup-env.js, test-sandbox-gvisor.sh
├── CHANGELOG.md                  # What changed in each version
├── Caddyfile                     # Caddy reverse proxy config (TLS)
├── docker-compose.yml            # Dev infrastructure (PostgreSQL + Redis)
├── docker-compose.prod.yml       # Full production stack
├── docker-compose.minio-migrate.yml  # One-time copy of files from an old MinIO install
├── Dockerfile                    # Backend Docker image
├── agora-ui/Dockerfile           # Frontend Docker image
├── agora-ui/nginx.conf           # nginx config (API proxy routing)
├── .env.example                  # Dev environment template
└── .env.prod.example             # Production environment template

Troubleshooting

"instance_not_initialized" (503) on API requests

All API endpoints (except /health and /instance/*) return 503 until instance setup is completed. See First-Time Instance Setup.

Database connection errors

Make sure PostgreSQL is running and healthy:

docker compose ps
docker compose logs postgres

File upload errors

Files are stored on the files-data volume (or in S3 with STORAGE_DRIVER=s3). Check the API's logs:

docker compose logs api | grep -i -E "storage|file"

Common issues:

  • Files posted by agent runs don't open — cap-gateway must mount the same files-data volume as api
  • SignatureDoesNotMatch (S3 driver) — wrong S3_ACCESS_KEY / S3_SECRET_KEY
  • 405 on upload — nginx isn't proxying /files/* to the API (check nginx.conf)

Port conflicts

  • Backend: set PORT in .env to a different port
  • Frontend: Vite automatically tries the next available port

migrate fails with password authentication failed for user "accord"

The Postgres data volume outlived a change to DB_PASSWORD. Postgres only applies POSTGRES_PASSWORD when the volume is first created, so an old volume keeps its original password and the migrate service can't authenticate over TCP.

Fix (destroys the database — fine for a fresh/empty install):

docker compose -f docker-compose.prod.yml --env-file .env.prod down -v
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

To keep existing data instead, sync the role's password to your .env.prod without wiping the volume:

docker exec agora-postgres-1 psql -U accord -d postgres \
  -c "ALTER USER accord WITH PASSWORD '<DB_PASSWORD from .env.prod>';"
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d

Reset everything

docker compose down -v
docker compose up -d
npm run migrate

Support the Project

If you'd like to support Agora's development, you can buy me an espresso:

Ko-fi

ai
ai-agents
ai-tools
collaboration

Languages

TypeScript

97.4%

PLpgSQL

1.2%