Development Guide
Complete developer documentation for contributing to Agor.
Quick start
git clone https://github.com/preset-io/agor
cd agor
docker compose up
# Visit http://localhost:5173 → Login: admin@agor.live / adminThat gives you SQLite, single-user, no RBAC, the fastest path to a running daemon. For Postgres, multiplayer + RBAC, or the docs site, see the variant system below.
Seeding test data
Two optional, composable env switches pre-populate the database on first boot:
# Real runnable branch (clones the agor repo) + a rich set of demo data
SEED=true LOAD_FIXTURES=true docker compose upSEED=trueclones the realagorrepo and creates one runnabletest-branch.LOAD_FIXTURES=trueinserts hardcoded,demo--prefixed fake data (pure DB inserts, no git/network): 4 loginable users, 2 repos, a board with 4 zones, 5 branches, 4 sessions with fork/spawn genealogy + transcripts, kanban cards, and a live Sandpack artifact. Idempotent and dev-only. You can also run it manually withpnpm load:fixtures.
The SEED=true LOAD_FIXTURES=true docker compose up form is for running compose
directly on a host. In an Agor-managed env the start command comes pre-rendered
from a .agor.yml variant, so pick a demo variant instead of injecting env vars:
the sqlite-demo and postgres-demo variants below are the sqlite / postgres
variants with LOAD_FIXTURES=true added next to SEED=true. Select one via the
Branch → Environment tab variant picker, the MCP call
agor_environment_set({ branchId, variant: "sqlite-demo", andStart: true }), or at
branch creation with variant: "sqlite-demo".
Both are off by default and can be enabled independently. With LOAD_FIXTURES,
extra demo logins are available (passwords printed in the seed log):
| Password | Role | |
|---|---|---|
demo.alice@agor.live | demo-password-alice | admin |
demo.bob@agor.live | demo-password-bob | member |
demo.carol@agor.live | demo-password-carol | member |
demo.dave@agor.live | demo-password-dave | viewer |
See packages/core/src/seed/README.md for the full inventory.
.agor.yml (the variant system)
Agor’s repo ships .agor.yml, a declarative schema describing how to spawn an environment for this codebase. The same schema you’d write for any repo you load into Agor: Agor uses it to develop itself. Each variant is a Handlebars-templated start / stop / nuke / logs / health / app block.
| Variant | What you get | Base ports |
|---|---|---|
sqlite (default) | SQLite, single-user, no RBAC. Fastest to boot. | daemon: 3000 + branch.unique_id, UI: 5000 + … |
postgres | Postgres backend (closer to multi-user prod). RBAC off. Requires Postgres 13+ (migrations use gen_random_uuid() from pg_catalog). | same as sqlite |
full | Postgres + branch RBAC + strict Unix-user impersonation. Requires the sudoers config under docker/sudoers/. Same PG13+ floor as postgres. | same as sqlite |
docs | The Nextra docs site (apps/agor-docs) in its own self-contained container. Standalone (no daemon, no DB). | 7000 + branch.unique_id |
sqlite-demo | sqlite plus LOAD_FIXTURES=true, which boots a populated demo board with 4 loginable demo users for instant E2E testing. Dev-only. | same as sqlite |
postgres-demo | postgres plus LOAD_FIXTURES=true, with the same demo fixtures on a Postgres backend. Dev-only. | same as sqlite |
Ports derive from branch.unique_id so multiple branches run side-by-side without colliding. The docs variant uses {{host.ip_address}} in its app URL so the dev server is reachable from your laptop, not just localhost on the daemon host.
Pick a variant by setting AGOR_VARIANT in your environment or selecting it in the branch’s environment settings. You can also render it manually with the appropriate docker compose invocation from the file.
A representative variant block, abridged from the file:
sqlite:
description: Single-user dev with SQLite. No RBAC. Fastest to boot.
start: >-
DAEMON_PORT={{add 3000 branch.unique_id}}
UI_PORT={{add 5000 branch.unique_id}}
docker compose -p agor-{{branch.name}} up -d
stop: docker compose -p agor-{{branch.name}} down
nuke: docker compose -p agor-{{branch.name}} down -v
logs: docker compose -p agor-{{branch.name}} logs --tail=100
health: http://localhost:{{add 3000 branch.unique_id}}/health
app: http://localhost:{{add 5000 branch.unique_id}}Other variants extends: sqlite to inherit the unchanged blocks. The schema enforces single-level extension only (no chains).
Agor in Agor (recommended)
The whole point of .agor.yml is that Agor can develop itself. The natural workflow:
- Run a “host” Agor (any way you like, via
npm i -g agor-live,docker compose up, anything). - Register the agor repo in your host Agor. Clone or point it at your local checkout.
- Create a branch per feature. Each branch gets a unique
branch.unique_id→ unique ports → fully isolated dev stack. - Pick a variant in the branch’s environment settings (sqlite for most things, postgres if you’re touching DB code, full if you’re testing RBAC, docs for documentation work).
- Spawn an agent session. The session has Agor MCP access, so it can spin up environments, monitor health and logs, run tasks against the running stack, and tear it down, all without you ever leaving the canvas.
This is also the only sane way to develop multiple Agor features in parallel: each branch’s variant picks its own ports, mounts source for HMR, and persists node_modules in a named volume.
When to use pnpm dev instead
Skip Docker if you’re working on the environment-spawning system itself. Running Agor in Docker while it tries to spawn docker compose for environments creates docker-in-docker entanglements that aren’t worth fighting.
git clone https://github.com/preset-io/agor && cd agor && pnpm installTwo terminals:
# Terminal 1: Daemon (watches @agor/core + daemon, auto-restarts)
cd apps/agor-daemon && pnpm dev
# Terminal 2: UI dev server (Vite HMR)
cd apps/agor-ui && pnpm dev
# Visit http://localhost:5173Custom builds
To test the packaged artifact end-to-end (the way users actually install Agor), build the agor-live npm package locally:
cd packages/agor-live
./build.sh
npm i -g . # or: npm i -g ./agor-live-<version>.tgzbuild.sh produces a single self-contained package: the UI is built as static assets and bundled into agor-live so the daemon serves it directly. After npm i -g ., the global agor and agor-live commands run your local build instead of the published version.
Useful when you want to:
- Verify the production-style flow (no Vite dev server, no separate UI process)
- Test CLI behavior against a built package
- Hand a colleague a tarball without publishing to npm
Run npm uninstall -g agor-live to remove your local build and fall back to whatever you had installed before.
Committing from inside Docker
When the daemon runs in Docker, pnpm install populates node_modules/ with Linux binaries (turbo-linux-arm64, @biomejs/cli-linux-x64, etc.). If you commit from the host, Husky pre-commit hooks try to execute those Linux binaries and fail.
Two fixes:
- Commit inside the container (recommended). Run
docker compose exec agor-dev git add . && docker compose exec agor-dev git commit -m "msg". Hooks run in Linux. - Reinstall on host. Run
pnpm installfrom the host once to get host-OS binaries; commit normally afterward. (You’ll end up with both binary sets innode_modules, which is harmless but bloats the tree.)
Checking which database is active
Open Settings → About (admin only):
- 💾 SQLite shows the database file path
- 🐘 PostgreSQL shows the connection URL (password masked)
Or hit the daemon directly:
curl http://localhost:3030/healthProject Structure
agor/
├── apps/
│ ├── agor-daemon/ # FeathersJS backend (REST + WebSocket)
│ ├── agor-cli/ # CLI tool (oclif-based)
│ ├── agor-ui/ # React UI (Ant Design + React Flow)
│ └── agor-docs/ # Documentation website (Nextra)
├── packages/
│ ├── core/ # Shared @agor/core package
│ │ ├── types/ # TypeScript types (Session, Task, Branch, etc.)
│ │ ├── db/ # Drizzle ORM + repositories + schema
│ │ ├── git/ # Git utils (simple-git only, no subprocess)
│ │ ├── claude/ # Claude Code session loading utilities
│ │ └── api/ # FeathersJS client utilities
│ └── agor-live/ # Published npm package
└── context/ # 📚 Architecture documentation (READ THIS!)
├── concepts/ # Core design docs
└── explorations/ # Experimental designsMonorepo & Development Tooling
Agor is a pnpm workspace monorepo with several automation tools:
pnpm Workspaces
Structure:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'Workspace protocol:
- Packages reference each other via
workspace:* - Example:
@agor/clidepends on@agor/core@workspace:* - pnpm symlinks workspace packages (no need to rebuild on every change)
Turbo
Parallel builds and task orchestration:
# Build all packages in dependency order
pnpm build
# Run typecheck across all packages in parallel
pnpm typecheck
# Run dev servers (daemon + UI)
pnpm devHow it works:
- Reads
turbo.jsonfor task definitions - Understands package dependencies
- Runs tasks in parallel when possible
- Caches build outputs for speed
Git Hooks (Husky + lint-staged)
Pre-commit checks:
- Runs automatically before
git commit - Only checks staged files (fast!)
- Runs: biome (linting), prettier (formatting), typecheck
Setup:
pnpm prepare # Installs git hooksCode Quality
Linting:
- biome - Fast linter and formatter
- Config:
biome.json - Run:
pnpm lintorpnpm lint:fix
Formatting:
- prettier - Code formatter
- Config:
.prettierrc - Run:
pnpm format
Root Scripts
Common commands from root directory:
# Development
pnpm dev # Start daemon + UI
pnpm docs:dev # Start docs site
# Code quality
pnpm typecheck # Type check all packages
pnpm lint # Lint all packages
pnpm lint:fix # Lint and auto-fix
pnpm format # Format all files
pnpm check # typecheck + lint + build
pnpm check:fix # lint:fix + typecheck + build
# Building
pnpm build # Build all packages
pnpm clean # Clean all build artifacts
# CLI (from root)
pnpm agor <command> # Run CLI without global installTech Stack
See the Architecture Guide for the complete tech stack (FeathersJS, Drizzle, React, Ant Design, etc.).
Development Patterns
Code Standards
- Type-driven - Use branded types for IDs, strict TypeScript
- Centralize types - ALWAYS import from
packages/core/src/types/(never redefine) - Read before edit - Always read files before modifying
- Prefer Edit over Write - Modify existing files when possible
- Git operations - ALWAYS use
simple-git(NEVER subprocessexecSync,spawn, etc.) - Error handling - Clean user-facing errors, no stacktraces in CLI
Important Rules
Git Library:
- ✅ Use
simple-gitfor ALL git operations - ❌ NEVER use
execSync,spawn, or bash for git commands - Location:
packages/core/src/git/index.ts
Watch Mode:
- User runs
pnpm devin daemon (watches core + daemon) - DO NOT run builds unless explicitly asked or you see compilation errors
- DO NOT start background processes
Type Reuse:
- Import types from
packages/core/src/types/ - Sessions, Tasks, Branches, Messages, Repos, Boards, Users, etc.
- Never redefine canonical types
Branch-Centric Architecture:
- Boards display Branches as primary cards (NOT Sessions)
- Sessions reference branches via required FK
- Read
context/concepts/branches.mdbefore touching boards
Key Documentation
Before diving into code, familiarize yourself with the architecture:
- AGENTS.md - Development patterns and project structure
- CONTRIBUTING.md - Complete contribution guide
- context/README.md - Architecture documentation index
- context/concepts/core.md - Core primitives and design philosophy
Testing
Database Operations
SQLite:
# Query database directly
sqlite3 ~/.agor/agor.db "SELECT COUNT(*) FROM messages"
sqlite3 ~/.agor/agor.db "SELECT * FROM sessions LIMIT 5"PostgreSQL:
# Connect to postgres container
docker compose exec postgres psql -U agor -d agor
# Example queries
docker compose exec postgres psql -U agor -d agor -c "SELECT COUNT(*) FROM messages"
docker compose exec postgres psql -U agor -d agor -c "SELECT * FROM sessions LIMIT 5"Health Checks
# Daemon health
curl http://localhost:3030/health
# Check which database is active (admin auth required)
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:3030/healthCLI Commands
# Test CLI (ensure clean exit, no hanging)
pnpm agor session list
pnpm agor repo list
# CLI auto-detects database from environment
# Works with both SQLite and PostgreSQLTroubleshooting
Build fails with ERR_WORKER_OUT_OF_MEMORY / JS heap out of memory
@agor/core exports ~40 entry points and emits TypeScript declarations for
both CJS and ESM. tsup’s DTS pipeline peaks around 3 GB of memory, which
exceeds Node’s default heap ceiling on smaller hosts (4 GB RAM containers,
Raspberry Pis, etc.).
Agor’s standard build paths already raise the ceiling to 4 GB. If you wrap
the build in your own script (or invoke tsup directly) and hit the OOM, set
it yourself:
export NODE_OPTIONS="--max-old-space-size=4096"
pnpm --filter @agor/core buildIf 4 GB isn’t enough on a particularly large feature branch, raise to 8192.
”Method is not a function” after editing @agor/core
Should NOT happen with new 2-process workflow (daemon watches core and auto-restarts).
If it still happens:
cd packages/core && pnpm build
cd apps/agor-daemon && pnpm devtsx watch not picking up changes
cd apps/agor-daemon
rm -rf node_modules/.tsx
pnpm devDaemon hanging
lsof -ti:3030 | xargs kill -9
cd apps/agor-daemon && pnpm devWhat to Contribute
Browse the open GitHub issues for contribution ideas, or propose your own.
Getting Help
- Discord - Join our Discord community for support and discussion
- GitHub Discussions - Ask questions, share ideas
- GitHub Issues - Report bugs, request features
Next Steps
- Architecture - System design and internals
- Run
agor --helpfor complete CLI documentation - API Reference - REST endpoints and WebSocket events
- AGENTS.md - Development patterns and project structure