System configuration

Route more LLM providers through the gateway, reach local model servers, and tune PeerDB replication and the Temporal workers of a self-hosted install.

In this page

Three parts of the stack take more than a key or two in .env: the LLM gateway, which routes the platform’s model calls; PeerDB, which copies Postgres changes into ClickHouse on Distributed; and the Temporal workers that run background jobs. Where each one runs, and what you can change, depends on the setup. The keys named here are set as described in Environment variables.

📝
TL;DR

A provider key in .env and docker compose up -d is enough to start. Copy the gateway’s example config only to route more providers through it. A model server that a workspace adds in the UI on a private address needs AGENTCC_ALLOW_PRIVATE_PROVIDER_URLS=true. PeerDB runs on Distributed only and sets itself up on first boot. Standalone runs one Temporal worker inside the API; Distributed adds a worker per queue with the workers profile.

LLM gateway

The gateway is a Go proxy that routes the model calls the platform makes. It also serves an OpenAI-compatible endpoint that your own apps can call, on http://localhost:8090 (AGENTCC_GATEWAY_PORT) in both Compose setups.

SetupWhere the gateway runs
StandaloneA process inside the app container
DistributedThe agentcc-gateway container
HelmThe <fullname>-agentcc-gateway Deployment: futureagi-agentcc-gateway for a release named futureagi

Set a provider key in .env (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY or the AWS pair) and run docker compose up -d. One provider is enough to start, and workspaces can also add their own keys in the UI.

The gateway’s bundled config, agentcc-gateway/config.example.yaml, routes OpenAI. To route more providers through it:

Copy the example config

cp agentcc-gateway/config.example.yaml agentcc-gateway/config.yaml

Enable providers

Uncomment the providers you want (Anthropic, Gemini, Bedrock, Vertex and others) in agentcc-gateway/config.yaml. Every provider entry needs a base_url and an api_format, or the gateway refuses to start. Reference keys as ${VAR} so the real key never has to live in the file. See the provider examples.

Point the stack at your copy

Add the path to .env. A path that does not exist stops the stack from starting.

AGENTCC_CONFIG_PATH=agentcc-gateway/config.yaml

Set the provider keys

Add the matching keys to .env, for example:

ANTHROPIC_API_KEY=sk-...

Apply and check

docker compose up -d
curl -sf http://localhost:8090/healthz

docker compose up -d recreates app in Standalone. In Distributed it recreates agentcc-gateway and every other service that reads .env, the backend and workers included. Later edits to config.yaml alone need a restart: docker compose restart app (Distributed: docker compose restart agentcc-gateway).

Warning

Treat config.yaml as a secret even when it only holds ${VAR} references, and never commit it. Keep it out of git by excluding it in your checkout: echo "agentcc-gateway/config.yaml" >> .git/info/exclude. On Distributed the gateway runs as uid 65532, so the file must be readable by that user (mode 0644).

On Helm, the gateway’s config is the chart value agentccGateway.config, and provider keys come from secrets.llm. See Configure.

Provider examples

The models lists below are examples: list the models your provider account serves.

providers:
  openai:
    base_url: "https://api.openai.com"
    api_key: "${OPENAI_API_KEY}"
    api_format: "openai"
    models: [gpt-4o, gpt-4o-mini]

  anthropic:
    base_url: "https://api.anthropic.com"
    api_key: "${ANTHROPIC_API_KEY}"
    api_format: "anthropic"
    models: [claude-sonnet-4-6, claude-opus-4-7]

  gemini:
    base_url: "https://generativelanguage.googleapis.com"
    api_key: "${GEMINI_API_KEY}"
    api_format: "gemini"
    models: [gemini-3.5-flash, gemini-3.5-flash-lite]

Compose passes GOOGLE_API_KEY from .env to the gateway as GEMINI_API_KEY.

providers:
  bedrock:
    base_url: "https://bedrock-runtime.us-east-1.amazonaws.com"
    api_format: "bedrock"
    aws_access_key_id: "${AWS_ACCESS_KEY_ID}"
    aws_secret_access_key: "${AWS_SECRET_ACCESS_KEY}"
    aws_region: "us-east-1"
    models: [us.anthropic.claude-sonnet-4-6]

The gateway signs Bedrock requests with the aws_access_key_id and aws_secret_access_key of the provider entry, so reference the AWS pair from .env there. A model ID that starts with us., eu., ap. or global. names an inference profile.

providers:
  vertex:
    base_url: "https://aiplatform.googleapis.com"
    api_format: "gemini"
    credentials_file: "${GOOGLE_APPLICATION_CREDENTIALS}"
    headers:
      x-gcp-project: "${GCP_PROJECT_ID}"
      x-gcp-location: "global"
    models: [gemini-3.5-flash]

Vertex authenticates with a GCP service account, not an API key. Set GOOGLE_APPLICATION_CREDENTIALS in .env to the absolute host path of the service account’s JSON key, and GCP_PROJECT_ID to your project. Compose mounts the key read-only into the gateway’s container and points GOOGLE_APPLICATION_CREDENTIALS there, and the gateway refreshes its access token from the key itself. On Distributed, the backend and the workers other than worker-simulation-runner receive only the host path: if they need the key, mount it at that path with a compose override listed in COMPOSE_FILE. Never commit the key or bake it into an image.

For routing rules, rate limits, caching and the full config reference, see Self-hosted deployment.

Local and private model servers

A model server such as Ollama or vLLM that you list under providers: in the gateway’s config.yaml needs nothing more: those entries are trusted, have no address check, and serve the platform’s own calls. One that a workspace adds in the UI under Gateway > Providers, on a private address such as host.docker.internal or a Docker service name, needs AGENTCC_ALLOW_PRIVATE_PROVIDER_URLS=true (Helm: agentccGateway.allowPrivateProviderURLs=true): Private and local provider URLs has the steps, Linux hosts, the risks and the errors you may see.

Keys and org settings sync

The gateway loads the API keys and organization settings created in the UI from the app. It starts serving at once with the keys in its config.yaml, loads the rest in the background, retrying until the app answers, and then re-reads them every AGENTCC_SYNC_INTERVAL: 60s in both compose files, and on Helm the same through agentccGateway.config.control_plane.sync_interval. Its log shows control plane startup sync succeeded once the first load works, and the control plane sync enabled line shows the interval it runs with.

Sync on Gateway > API Keys pushes the organization’s live keys that the gateway lacks, and revokes on the gateway any key that was revoked while the gateway could not be reached.

PeerDB replication

Only Distributed runs PeerDB. It copies Postgres changes into ClickHouse, the analytics store behind traces, dashboards and evaluation queries. Standalone and Helm use capture triggers drained by a Temporal schedule instead, so neither runs PeerDB. On Standalone the drain runs in the API process, with the embedded Temporal worker; on Helm, in the all-queues worker Deployment.

On Distributed, the PeerDB services start with the stack whatever the profile, and one-shot jobs set up the peers and mirrors on first boot: peerdb-init runs once the Postgres and ClickHouse schema jobs have finished, and clickhouse-cdc-bootstrap waits for the mirrors before the backend starts.

The PeerDB UI comes with the peerdb or all profile, on http://localhost:3001 (PEERDB_UI_PORT). The installer’s summary lists its login, peerdb / peerdb.

Warning

The PeerDB UI and the Temporal UI (observability or all profile) listen on every interface of the host, not only 127.0.0.1. The Temporal UI has no sign-in, and the PeerDB UI’s login, if it asks for one, is the published one above. Anyone who can reach ports 3001 and 8085 can read workflow payloads or change the mirrors. Turn these profiles on only on a network where everyone is trusted, or bind the UIs to 127.0.0.1 in docker-compose.override.yml:

services:
  peerdb-ui:
    ports: !override
      - "127.0.0.1:${PEERDB_UI_PORT:-3001}:3000"
  temporal-ui:
    ports: !override
      - "127.0.0.1:${TEMPORAL_UI_PORT:-8085}:8080"

Compose reads the override only when COMPOSE_FILE in .env lists it: COMPOSE_FILE=docker-compose.distributed.yml:docker-compose.override.yml.

PeerDB’s init job stores CH_PASSWORD in its ch_dest peer when it creates it. A later change to CH_PASSWORD stops the sync until that peer carries the new password: see Secrets the installer generates.

Temporal workers

Temporal runs the platform’s background work: evaluations, optimizations, scheduled jobs and, on Standalone and Helm, the Postgres to ClickHouse sync.

Standalone

Standalone runs a Temporal dev server on SQLite inside the app container, without its web UI, and the worker runs inside the API process. It polls every queue except the simulation runner’s, with these slots per queue:

KeyDefaultWhat it sets
FI_APP_TEMPORAL_MAX_CONCURRENT_ACTIVITIES8Activity slots. Each running activity can hold a Postgres connection, so raise it only on a larger host.
FI_APP_TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASKS8Workflow-task slots

Standalone ignores TEMPORAL_MAX_CONCURRENT_ACTIVITIES and TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASKS. Its Temporal server keeps 32 schedules busy, six of which fire every 10 seconds, and the API shares its process with the worker, so a host short of CPU slows both. For more concurrent users or a steady eval load, use Distributed: see Choose a setup.

Distributed

Two workers always run:

  • worker polls every queue except the exact-aggregation queue and those in TEMPORAL_EXCLUDED_QUEUES (default simulation_runner), while TEMPORAL_ALL_QUEUES=true, the default. Its slots are TEMPORAL_MAX_CONCURRENT_ACTIVITIES and TEMPORAL_MAX_CONCURRENT_WORKFLOW_TASKS, 50 each.
  • worker-exact-aggregation serves the exact-analytics queue with a single slot.

The workers (or all) profile adds one worker per queue. Set TEMPORAL_ALL_QUEUES=false with it, so that worker polls only the default queue instead of every queue alongside them. Never set it without workers or all: the other queues would then have no worker. Add both lines to .env, then run docker compose up -d:

COMPOSE_PROFILES=workers
TEMPORAL_ALL_QUEUES=false
ServiceQueueSlot keys (activities, workflow tasks)Defaults
worker-defaultdefaultTEMPORAL_DEFAULT_MAX_ACTIVITIES, TEMPORAL_DEFAULT_MAX_WORKFLOWS100, 100
worker-tasks-stasks_sTEMPORAL_TASKS_S_MAX_ACTIVITIES, TEMPORAL_TASKS_S_MAX_WORKFLOWS200, 200
worker-tasks-ltasks_lTEMPORAL_TASKS_L_MAX_ACTIVITIES, TEMPORAL_TASKS_L_MAX_WORKFLOWS50, 50
worker-tasks-xltasks_xlTEMPORAL_TASKS_XL_MAX_ACTIVITIES, TEMPORAL_TASKS_XL_MAX_WORKFLOWS10, 20
worker-trace-ingestiontrace_ingestionTEMPORAL_TRACE_MAX_ACTIVITIES, TEMPORAL_TRACE_MAX_WORKFLOWS100, 100
worker-agent-compassagent_compassTEMPORAL_COMPASS_MAX_ACTIVITIES, TEMPORAL_COMPASS_MAX_WORKFLOWS50, 50
worker-simulation-runnersimulation_runnerALK_RUNNER_MAX_CONCURRENCY, TEMPORAL_SIMRUNNER_MAX_WORKFLOWS4, 8

worker-simulation-runner runs its own image, futureagi/future-agi-simulation-runner, for hosted voice simulations, and workers and all always start it. That image is linux/amd64 only: on an arm64 host, such as Apple Silicon or AWS Graviton, Docker runs it under emulation, which on Linux needs qemu-user-static. See Per-queue workers.

The observability or all profile adds the Temporal UI on http://localhost:8085 (TEMPORAL_UI_PORT) and the temporal-admin-tools container. The Temporal UI has no sign-in: see the warning under PeerDB replication.

Helm

worker.allQueues is the all-queues worker, with maxConcurrentActivities and maxConcurrentWorkflowTasks of 50 each. Each worker.queues entry gives a busy queue its own Deployment, which the all-queues worker then stops polling. See Configure.

Dive deeper

Was this page helpful?

Questions & Discussion