Local Development
Run Trinity on your own machine for development and experimentation. All services run in Docker; nothing is installed on the host except Docker and Git.
Prerequisites
- •Docker Desktop (includes Docker Compose v2) — version 24 or later recommended. On Linux, Docker Engine plus the
docker composeplugin (docker-composev1 is not supported). - •Git
- •
opensslon your PATH (comes with macOS and most Linux distributions) - •8 GB RAM available to Docker
1. Clone the Repository
git clone https://github.com/abilityai/trinity.git
cd trinity2. Configure .env
cp .env.example .envThe only edit a local install needs is ADMIN_PASSWORD. Everything else below is either generated by start.sh or optional.
Required for a working dev boot
| Variable | Where used | How to set |
|---|---|---|
| ADMIN_PASSWORD | Backend — admin login; MCP server — legacy password auth | Choose a strong password (12+ characters). start.sh refuses to start while it is blank; under --unattended it generates one and prints it once (an ADMIN_PASSWORD exported in your shell is written into .env when the file has none). The backend re-applies this value on every boot, so change the admin password here and recreate the backend container (docker compose up -d backend) — a plain restart does not re-read .env. |
| SECRET_KEY | Backend — JWT signing | Generated by start.sh if blank |
| INTERNAL_API_SECRET | Scheduler-to-backend calls | Generated by start.sh if blank (falls back to SECRET_KEY only if you bring the stack up without the script) |
| CREDENTIAL_ENCRYPTION_KEY | Backend — encrypts OAuth tokens, channel bot tokens, subscription credentials | Generated by start.sh if blank; do not change once set |
| AGENT_AUTH_SECRET | Backend — derives each agent's in-container auth token | Generated by start.sh if blank; do not change once set — every running agent's token stops working until the agent is recreated |
| REDIS_PASSWORD | Redis default ACL user | Generated by start.sh on a fresh install; see note below |
| REDIS_BACKEND_PASSWORD | Redis backend and scheduler ACL users; embedded in REDIS_URL | Generated by start.sh on a fresh install; see note below |
| DOCKER_GID | Backend — group of /var/run/docker.sock inside the container | Detected by start.sh by probing the socket from a throwaway container (Docker Desktop, Colima and rootless report 0; a native Linux daemon reports the host docker group). Set it yourself only to override. |
Redis passwords and start.sh: On a fresh install (no redis-data volume), start.sh fills in missing REDIS_PASSWORD / REDIS_BACKEND_PASSWORD values. If the volume already exists and the passwords are missing, the script exits with an error and points you at the migration guide — re-keying a populated Redis would lock the platform out of its own data.
CREDENTIAL_ENCRYPTION_KEY: once generated, do not change or delete it — all encrypted credentials (channel bot tokens, OAuth secrets, subscription credentials) become unrecoverable if the key changes.
Optional but recommended
| Variable | Purpose |
|---|---|
| ANTHROPIC_API_KEY | Lets agents use Claude. Can be left blank and configured in Settings after login; start.sh warns in its summary when no model key (ANTHROPIC_API_KEY, GOOGLE_API_KEY or CLAUDE_CODE_OAUTH_TOKEN) is set. |
| EMAIL_PROVIDER | How verification codes are sent. .env.example sets console (codes print to the backend log); the compose default when the line is absent is resend. |
| RESEND_API_KEY | Required if EMAIL_PROVIDER=resend. |
| GITHUB_PAT | Forwarded to the backend; needed to clone private template repos. |
| GEMINI_API_KEY | Platform image generation, voice. |
| SLACK_SIGNING_SECRET | Required only if you configure the Slack channel adapter. |
| PUBLIC_CHAT_URL | Public-facing URL for chat links and webhooks. Leave empty for local dev. |
| FRONTEND_URL | Used for OAuth redirect callbacks. Leave empty for local dev. |
| TRINITY_DEFAULT_SYSTEM_MANIFEST | Set to disabled to skip the starter fleet a fresh install seeds (see First-Time Setup). |
Keys the dev compose does not forward
Every key in .env.example is forwarded by docker-compose.yml except these, which only the production and hosted compose files read: SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, NOTION_CLIENT_ID, NOTION_CLIENT_SECRET, TUNNEL_TOKEN, TRINITY_DATA_PATH, HOST_TEMPLATES_PATH. TRINITY_IMAGE_TAG and ADMIN_PASSWORD_SOURCE are read only by the hosted compose, and the VITE_* keys are build arguments of the production frontend image (for the dev Vite server, set them in src/frontend/.env instead). Setting any of these in a dev .env does nothing. The complete key-by-key reference lives in Single-Server Deployment → .env reference.
Port conflict
The frontend binds port 80 by default. If another process already holds :80, add FRONTEND_PORT=8090 (or any free port) to .env. start.shprobes the web UI port, 8000, 8080 and 6379 before starting and prints a warning if any is busy (a re-run over a running Trinity is expected to hit this — those are Trinity's own containers).
3. Build the Base Agent Image
./scripts/deploy/build-base-image.shThis builds trinity-agent-base:latest — the Docker image every agent container inherits. It includes Python 3.13, Node.js 20, Go 1.23, and Claude Code. The platform can start without this, but you cannot create any agents until the image exists.
The image is tagged both trinity-agent-base:latest and trinity-agent-base:<VERSION> (read from the VERSION file). First build takes 5–10 minutes.
start.sh detects a missing base image and calls build-base-image.sh before starting services. You can skip this step if you prefer. (start.sh --hosted pulls the published image from GHCR instead of building — see Single-Server Deployment.)
4. Start Services
./scripts/deploy/start.sh./quickstart.sh is an alias for the same script (--defaults is translated to --unattended; every other flag passes through).
The script does the following, in order:
- 1Pre-flight. Exits with one consolidated message if the Docker daemon is unreachable or Compose v2 is missing. Warns (does not stop) if the web UI port, 8000, 8080 or 6379 is already in use.
- 2If
.envdoes not exist, copies.env.exampleto.env. - 3Generates
CREDENTIAL_ENCRYPTION_KEY,SECRET_KEY,INTERNAL_API_SECRETandAGENT_AUTH_SECRETif blank. - 4Checks
ADMIN_PASSWORD: if.envhas none but your shell exports one, writes it in; otherwise blank → exits with an error (interactive) or generates a 24-character password (--unattended). A quoted-empty value (ADMIN_PASSWORD="") counts as blank. (The one exception,ADMIN_PASSWORD_SOURCE=browser, is set only by a marketplace image's first boot and never applies locally.) - 5Notes whether a model API key is present (a warning in the final summary, never a stop).
- 6Generates
REDIS_PASSWORDandREDIS_BACKEND_PASSWORDif both are blank and noredis-datavolume exists yet; if the volume exists and passwords are missing, exits with an error. - 7Creates the data directory used by the prod/hosted bind mount (
./trinity-databy default) and, on Linux, makes it owned by UID 1000. - 8Detects
DOCKER_GID(see the table above) and writes it to.env. - 9Data-switch guard. Refuses to start if the checkout holds a database this file set cannot see — a
trinity-databind-mount directory written by the prod/hosted stack while you are starting the dev stack, or the reverse under--hosted— and prints the copy command. If both stores exist, it warns which one the stack will use. - 10Checks for
trinity-agent-base:latest; builds it if missing (pulls and retags it under--hosted). - 11On Docker Desktop, creates
docker-compose.override.ymlfromdocker-compose.override.example.ymlso Vector tails log files instead of busy-looping on the Docker socket (opt out withTRINITY_LOCAL_LOG_SOURCE=docker). - 12Runs
docker compose up -d. - 13Polls
http://localhost:8000/healthfor up to 180 seconds — the backend answers only after migrations and startup complete — then prints the access URLs and a next-steps card. A timeout is a warning, not a failure: checkdocker compose logs -f backendand re-run the script once it settles.
One-shot / unattended install: pass --unattended (or set TRINITY_UNATTENDED=1) — ./scripts/deploy/start.sh --unattended — and the happy path never blocks on a prompt. It generates ADMIN_PASSWORD (and any missing secrets) and prints the admin password in the final summary. Save it — it is stored in .env and shown only once.
Let an AI agent install it for you: an AI coding agent (Claude Code) can run the whole local install by following the deterministic runbook at docs/AGENT_INSTALL_GUIDE.md. Point your agent at it and it drives the steps above end to end.
Once complete, the platform services are running:
| Service | Container | Port |
|---|---|---|
| Backend (FastAPI) | trinity-backend | 8000 |
| Frontend (Vite dev server) | trinity-frontend | 80 (or $FRONTEND_PORT) |
| MCP Server | trinity-mcp-server | 8080 |
| Scheduler | trinity-scheduler | 8001 inside the container (health only; not published to the host) |
| Redis | trinity-redis | 127.0.0.1:6379 (loopback only) |
| Vector (logs) | trinity-vector | 8686 |
| OTel collector | trinity-otel-collector | 4317, 4318, 8889, 13133 |
Two one-shot containers (trinity-logs-init, trinity-archives-init) prepare the log directories and exit; they are expected to show as exited.
5. Open the Web UI
Navigate to http://localhost and log in with username admin and the ADMIN_PASSWORD from .env. There is no setup screen: setting ADMIN_PASSWORD provisions the admin account at boot. The Dashboard then opens the first-run setup sequence — Connect Claude (a subscription token or API key) is its one required step; the rest can be skipped and picked up later from Settings → General → Re-run setup. See First-Time Setup.
| URL | Purpose |
|---|---|
| http://localhost | Web UI |
| http://localhost:8000/docs | Backend API (Swagger) |
| http://localhost:8080/mcp | MCP Server endpoint (the dev Vite server does not proxy /mcp; that route exists on the production nginx only) |
| http://localhost:8686/health | Vector log aggregator health |
Hot Reload
The dev compose mounts source trees into the containers:
- •Backend:
./src/backendis bind-mounted insidetrinity-backend. Uvicorn runs with--reload, so any change to a.pyfile restarts the backend worker automatically. - •Frontend:
./src/frontendis bind-mounted intotrinity-frontend. Vite watches for.vueand.jschanges and hot-reloads automatically.
If you add or remove a Python dependency or change package.json, rebuild the affected image:
docker compose build backend # after requirements.txt change
docker compose build frontend # after package.json change
docker compose up -d backend # restart after rebuildViewing Logs
# All services
docker compose logs -f
# One service
docker compose logs -f backend
docker compose logs -f frontend
# Structured platform logs via Vector
docker exec trinity-vector sh -c "tail -50 /data/logs/platform.json" | jq .
# Structured agent logs via Vector
docker exec trinity-vector sh -c "tail -50 /data/logs/agents.json" | jq .Stopping Services
./scripts/deploy/stop.shstop.sh runs docker compose stop — containers and the agent network stay in place, and start.sh brings the same containers back. It reads the running stack's compose label and adds -f docker-compose.hosted.yml when the stack was started with --hosted.
Never use docker compose down on a Trinity checkout you want to keep. down removes the trinity-agent-network; agent containers are created with Docker's unless-stopped restart policy, so after a down they loop in Restarting against a network that no longer exists. Recovery is docker compose up -d (recreates the network), then docker rm -f each stale agent container and start it again from the UI — the workspace volume is untouched.
Data Persistence
All platform state is stored in Docker named volumes. These survive a docker compose stop / start cycle, and even docker compose down (unless you pass -v).
| Volume | Contents |
|---|---|
| trinity_trinity-data | trinity.db — agents, schedules, chat history, credentials metadata — plus backups/ (automatic database backups) |
| trinity_redis-data | Redis AOF journal |
| trinity_agent-configs | Agent configuration files |
| trinity_trinity-logs | Vector log aggregation output |
| trinity_trinity-archives | Compressed log archives (when LOG_ARCHIVE_ENABLED is on) |
The volume name prefix trinity_ comes from the Compose project name (the directory name, lowercased). start.sh derives the same name compose does, so a checkout called trinity-dev or project_trinity gets trinity-dev_trinity-data / project_trinity_trinity-data.
Optional: PostgreSQL Backend
SQLite is the zero-config default for local development, but the dev compose ships a bundled PostgreSQL container behind a profile. Note: SQLite support ends 2026-09-01 — production instances should run PostgreSQL (see the Single-Server guide).
# In .env:
# POSTGRES_PASSWORD=your-postgres-password
# DATABASE_URL=postgresql://trinity:your-postgres-password@postgres:5432/trinity
docker compose --profile postgres up -dThe host in the URL is the compose service name postgres (Docker DNS). POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD are read by the dev compose only (the bundled container does not exist in the prod or hosted files). The switch is non-destructive — comment DATABASE_URL out and the next restart is back on SQLite (each backend keeps its own data).
Troubleshooting
ModuleNotFoundError on backend startup
A Python dependency was added since your last build. Rebuild:
docker compose build backend && docker compose up -d backendPort 80 already in use
Add FRONTEND_PORT=8090 to .env, then restart.
Agents can't be created
The base image is missing. Run:
./scripts/deploy/build-base-image.shRefusing to start: this checkout has a bind-mounted database that the dev stack cannot see
The checkout was last run with the production or hosted file set, whose database lives in the trinity-data directory, not the dev volume. Bring it up the way it was installed (docker compose -f docker-compose.prod.yml up -d or ./scripts/deploy/start.sh --hosted), or copy the data into the dev volume with the command the message prints. To start fresh on purpose, move the directory aside.
Redis password errors on startup
Passwords changed after the redis-data volume was created. See docs/migrations/REDIS_AUTH.md for the upgrade path.
Vector pegging the CPU on Docker Desktop
The on-disk log source override was not applied. Run start.sh again (it creates docker-compose.override.yml), or copy docker-compose.override.example.yml to docker-compose.override.yml yourself.