PoietesCMD

Documentation

Self-host guide

There are two ways to run PoietesCMD: with Docker Compose (the intended deployment), or directly with Node for development.

Docker Compose

You need Docker with the Compose plugin.

1. Configure

cp .env.example .env

Open .env and set three values. Generate each secret with:

node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
VariableWhat it is
POSTGRES_PASSWORDPassword for the bundled PostgreSQL. It is placed in a connection URL, so use letters and digits only.
PCMD_ENCRYPTION_KEY32 random bytes (base64 or 64 hex characters). Encrypts provider keys stored through the console. Back it up separately from the database.
PCMD_PROXY_SECRETShared secret between the console and the API.

Provider keys can go in .env too (OPENAI_API_KEY, ANTHROPIC_API_KEY). See Provider setup.

2. Start

docker compose up -d --build

This builds two images and starts four services: postgres, api, worker and web. The API applies database migrations when it starts. Only web is published, on 127.0.0.1:3000 by default.

3. Create the owner account

There is no default password. Until an owner exists, the API prints a one-time setup token when it starts:

docker compose logs api

Look for the line starting with [setup] Setup token:. Open http://localhost:3000/setup, enter the token and choose a password of at least 12 characters. The token changes every time the API starts until setup is complete. To fix it in advance instead, set PCMD_SETUP_TOKEN in .env.

4. Connect a model and run a task

Open Settings → Providers, add a connection and run its test. Then create a task from Tasks → New task. Until a connection has passed its test, the console says "Connect a model to run your first task" and nothing can be queued.

Exposing the console

By default the console listens on this machine only. To reach it from elsewhere, put a reverse proxy with TLS in front of it and tell PoietesCMD its public address:

PCMD_PUBLIC_ORIGIN=https://agent.example.com

With that set, the session cookie is marked Secure and state-changing requests from any other origin are rejected. Your reverse proxy should set X-Forwarded-For and X-Forwarded-Proto; per-address login throttling relies on the first of those.

A minimal Caddy example:

agent.example.com {
  reverse_proxy 127.0.0.1:3000
}

Updating

docker compose up -d --build

Migrations are applied on start. Running tasks go back to the queue when the worker stops and continue when it is back.

Useful commands

docker compose ps                 # state and health of the four services
docker compose logs -f worker     # what the worker is doing
docker compose stop worker        # tasks wait; nothing is lost
docker compose down               # stop everything, keep the volumes

Development without Docker

You need Node 22 or newer and pnpm 9.

pnpm install
pnpm dev

pnpm dev starts a local PostgreSQL 17 on 127.0.0.1:15791 (real PostgreSQL binaries from the embedded-postgres package, data in .local/pg-dev), the API on port 19320, the worker, and the console on http://localhost:19300. The setup token appears in the output, prefixed with [api].

To use your own PostgreSQL instead, set DATABASE_URL in .env.

Other commands:

CommandWhat it does
pnpm testServer tests against a real PostgreSQL 17
pnpm e2e:localThe six end-to-end workflows against real API and worker processes
pnpm typecheckTypeScript in all packages
pnpm buildProduction build of the server and the console
pnpm migrateApply database migrations and exit
pnpm model:doubleA deterministic stand-in for a model endpoint, for trying the console without a key
pnpm smoke:providerOne real request to a real provider, using keys from your environment

Configuration reference

Every variable is described in .env.example. The ones that change behaviour most:

VariableDefaultEffect
PCMD_WORKER_CONCURRENCY2Tasks one worker runs at once
PCMD_LEASE_SECONDS60How long after a worker dies its task can be taken over
PCMD_HEARTBEAT_SECONDS10Worker heartbeat; also how fast cancel reaches a running task. Use the same value for the API and the worker
PCMD_SCHEDULERtrueRun the scheduler in this worker
PCMD_MODEL_TIMEOUT_MS300000Longest single model request
PCMD_FETCH_ALLOWED_PORTS80,443Ports fetch_url may contact
PCMD_SESSION_TTL_HOURS168Session lifetime without activity

Where data lives

DataLocation in Compose
Tasks, events, memory, schedules, settings, encrypted keyspgdata volume
Workspace filesdata volume, /data/workspace
Generated artifactsdata volume, /data/artifacts

To let the agent work on a folder of your own, mount it as the workspace. In docker-compose.yml the api and worker services share the x-server block at the top, so one change covers both:

x-server: &server
  # …
  volumes:
    - data:/data
    - /path/on/host/documents:/data/workspace

The API needs write access to it (uploads, deletions); the worker only reads it.

See Backup and restore for what to back up.

NextProvider setup