GuideDevelopment Guide

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 / admin

That 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.

.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.

VariantWhat you getBase ports
sqlite (default)SQLite, single-user, no RBAC. Fastest to boot.daemon: 3000 + worktree.unique_id, UI: 5000 + …
postgresPostgres backend (closer to multi-user prod). RBAC off.same as sqlite
fullPostgres + worktree RBAC + strict Unix-user impersonation. Requires the sudoers config under docker/sudoers/.same as sqlite
docsThe Nextra docs site (apps/agor-docs) in its own self-contained container. Standalone — no daemon, no DB.7000 + worktree.unique_id

Ports derive from worktree.unique_id so multiple worktrees 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 worktree’s environment settings — or 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 worktree.unique_id}}
    UI_PORT={{add 5000 worktree.unique_id}}
    docker compose -p agor-{{worktree.name}} up -d
  stop:   docker compose -p agor-{{worktree.name}} down
  nuke:   docker compose -p agor-{{worktree.name}} down -v
  logs:   docker compose -p agor-{{worktree.name}} logs --tail=100
  health: http://localhost:{{add 3000 worktree.unique_id}}/health
  app:    http://localhost:{{add 5000 worktree.unique_id}}

Other variants extends: sqlite to inherit the unchanged blocks — the schema enforces single-level extension only (no chains).

The whole point of .agor.yml is that Agor can develop itself. The natural workflow:

  1. Run a “host” Agor (any way you like — npm i -g agor-live, docker compose up, anything).
  2. Register the agor repo in your host Agor — clone or point it at your local checkout.
  3. Create a worktree per feature. Each worktree gets a unique worktree.unique_id → unique ports → fully isolated dev stack.
  4. Pick a variant in the worktree’s environment settings (sqlite for most things, postgres if you’re touching DB code, full if you’re testing RBAC, docs for documentation work).
  5. 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 — without you ever leaving the canvas.

This is also the only sane way to develop multiple Agor features in parallel: each worktree’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 install

Two 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:5173

Custom 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>.tgz

build.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, eslint-linux, etc.). If you commit from the host, Husky pre-commit hooks try to execute those Linux binaries and fail.

Two fixes:

  1. Commit inside the container (recommended)docker compose exec agor-dev git add . && docker compose exec agor-dev git commit -m "msg". Hooks run in Linux.
  2. Reinstall on host — run pnpm install from the host once to get host-OS binaries; commit normally afterward. (You’ll end up with both binary sets in node_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/health

Project 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, Worktree, 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 designs

Monorepo & 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/cli depends 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 dev

How it works:

  • Reads turbo.json for 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 hooks

Code Quality

Linting:

  • biome - Fast linter and formatter
  • Config: biome.json
  • Run: pnpm lint or pnpm 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 install

Tech Stack

See the Architecture Guide for the complete tech stack (FeathersJS, Drizzle, React, Ant Design, etc.).

Development Patterns

Code Standards

  1. Type-driven - Use branded types for IDs, strict TypeScript
  2. Centralize types - ALWAYS import from packages/core/src/types/ (never redefine)
  3. Read before edit - Always read files before modifying
  4. Prefer Edit over Write - Modify existing files when possible
  5. Git operations - ALWAYS use simple-git (NEVER subprocess execSync, spawn, etc.)
  6. Error handling - Clean user-facing errors, no stacktraces in CLI

Important Rules

Git Library:

  • ✅ Use simple-git for ALL git operations
  • ❌ NEVER use execSync, spawn, or bash for git commands
  • Location: packages/core/src/git/index.ts

Watch Mode:

  • User runs pnpm dev in 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, Worktrees, Messages, Repos, Boards, Users, etc.
  • Never redefine canonical types

Worktree-Centric Architecture:

  • Boards display Worktrees as primary cards (NOT Sessions)
  • Sessions reference worktrees via required FK
  • Read context/concepts/worktrees.md before touching boards

Key Documentation

Before diving into code, familiarize yourself with the architecture:

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/health

CLI 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 PostgreSQL

Troubleshooting

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 build

If 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 dev

tsx watch not picking up changes

cd apps/agor-daemon
rm -rf node_modules/.tsx
pnpm dev

Daemon hanging

lsof -ti:3030 | xargs kill -9
cd apps/agor-daemon && pnpm dev

What to Contribute

Browse the roadmap issues for contribution ideas, or propose your own!

Getting Help

Next Steps

  • Architecture - System design and internals
  • Run agor --help for complete CLI documentation
  • API Reference - REST endpoints and WebSocket events
  • AGENTS.md - Development patterns and project structure
BSL 1.1 © 2026 Maxime Beauchemin