# AutoGPT — Admin Guide

This guide is for the people who **deploy, operate, secure, and extend** AutoGPT: administrators, platform engineers, architects, and block developers. It covers the AutoGPT Platform (`autogpt_platform/`) in depth and the legacy `classic/` tree briefly. End-user tasks (building agents, AutoPilot, the Marketplace, scheduling) are in the companion **User Guide**.

---

## 1. Executive Summary

| Aspect | Summary |
|---|---|
| **What it is** | A platform for building and running AI agents as visual graphs of **blocks**, plus a conversational builder (**AutoPilot**), a **Marketplace**, schedules, webhook triggers, credits/billing, and chat-bot bridges |
| **Two products, one repo** | `autogpt_platform/` (current, Polyform Shield license) and `classic/` (original 2023 autonomous agent + `forge` + benchmark, MIT, **unsupported**) |
| **Backend** | Python (`>=3.10,<3.14`), FastAPI, Prisma ORM on PostgreSQL + pgvector, RabbitMQ, a 3-node Redis cluster, FalkorDB (Graphiti memory), ClamAV, APScheduler-backed scheduler, Pyro-style internal RPC between services |
| **Frontend** | Next.js (React, TypeScript) with an **embedded Better Auth** service issuing ES256 JWTs verified by the backend via JWKS |
| **Deployment shapes** | (1) Multi-container Docker Compose from source; (2) experimental single-container appliance `significantgravitas/autogpt`; (3) the hosted cloud (`BEHAVE_AS=cloud`) |
| **Scale of code** | ~7,000 files; 90 Prisma models; 219 migrations; 115 entries under `backend/blocks/` |
| **State** | PostgreSQL (everything durable), RabbitMQ (execution queues), Redis (event bus, locks, caches), FalkorDB (memory graph), workspace/media storage (local or GCS) |
| **Operational burden** | Moderate-to-high: ~10 backend service roles, 6 infrastructure services, secret management (Fernet `ENCRYPTION_KEY`), Prisma migrations on every upgrade, OAuth apps per integration |

> [!IMPORTANT]
> The three most consequential operator facts: **(1)** the backend refuses to start without `ENCRYPTION_KEY`, and losing it makes every stored integration credential unreadable; **(2)** the appliance persists every generated secret in `/data/config/runtime.env` and has no in-place rotation; **(3)** `BEHAVE_AS=local` (the self-host default) bypasses subscription entitlement gating and is only appropriate for single-tenant installs.

---

## 2. Architecture

### 2.1 Component diagram (Compose deployment)

```text
                         Browser
                            │ :3000
                ┌───────────▼────────────┐
                │ frontend (Next.js)     │  Better Auth (sessions, ES256 JWT,
                │  /api/auth/jwks        │  JWKS in platform."UserAuthJwks")
                └──────┬───────────┬─────┘
          REST :8006   │           │  WS :8001
        ┌──────────────▼──┐   ┌────▼─────────────┐
        │ rest_server     │   │ websocket_server │◄── Redis event bus
        │ FastAPI (/api,  │   │ (execution       │    (execution_event,
        │ /external-api,  │   │  updates)        │     notification_event)
        │ /health)        │   └──────────────────┘
        └──┬───────┬──────┘
           │ RPC   │ publish
           │       ▼
           │   ┌─────────┐   consume  ┌──────────────┐   ┌──────────────────┐
           │   │RabbitMQ │──────────► │ executor     │   │ copilot_executor │
           │   │  :5672  │──────────► │ :8002        │   │ :8008 (AutoPilot)│
           │   └─────────┘            └──────┬───────┘   └────────┬─────────┘
           │                                 │ RPC                │
     ┌─────▼──────────────┐       ┌──────────▼─────────┐  ┌───────▼───────┐
     │ database_manager   │◄──────┤ scheduler :8003    │  │ FalkorDB      │
     │ :8005 (DB RPC)     │       │ notification :8007 │  │ :6380 Graphiti│
     └─────┬──────────────┘       │ linking mgr :8009  │  └───────────────┘
           │                      └────────────────────┘
   ┌───────▼───────────┐   ┌──────────────────┐   ┌──────────────┐
   │ PostgreSQL :5432  │   │ Redis cluster    │   │ ClamAV :3310 │
   │ pgvector pg15     │   │ :17000-17002     │   │ (uploads)    │
   │ schema "platform" │   └──────────────────┘   └──────────────┘
   └───────────────────┘
```

### 2.2 Service inventory

All backend roles are the **same image** started with a different Poetry script.

| Compose service | Command | Port | Responsibility |
|---|---|---|---|
| `migrate` | Prisma migrate | — | Applies migrations; other services wait for `service_completed_successfully` |
| `rest_server` | `rest` | `8006` | FastAPI REST (`/api/*`), External API (`/external-api`), `/health`, webhook ingress, chat-bot webhook adapters |
| `websocket_server` | `ws` | `8001` | Pushes execution events to browsers (`/`, `/health`) |
| `executor` | `executor` | `8002` | Graph execution manager: consumes RabbitMQ, runs nodes, charges credits, resolves credentials |
| `copilot_executor` | `python -m backend.copilot.executor` | `8008` | AutoPilot turn execution |
| `database_manager` | `db` | `8005` | DB access service reached over internal RPC |
| `scheduler_server` | `scheduler` | `8003` | Cron schedules (SQLAlchemy job store, pool of 3), periodic cleanup jobs |
| `notification_server` | `notification` | `8007` | Email (Postmark) and push (VAPID) notifications |
| `platform_linking_manager` | `platform-linking-manager` | `8009` | Links chat-platform users to accounts; **`bot` profile only** |
| `frontend` | Next.js | `3000` | UI and Better Auth |
| `db` | `pgvector/pgvector:pg15` | `5432` | PostgreSQL |
| `redis-0/1/2` + `redis-init` | `redis:7` cluster | `17000`–`17002` | Event bus, locks, caches |
| `rabbitmq` | `rabbitmq:4.1.4` | `5672` | Execution queues |
| `falkordb` | `falkordb/falkordb:latest` | `6380` (protocol), `3001` (UI) | Graphiti temporal knowledge graph |
| `clamav` | `clamav/clamav-debian:latest` | `3310` | Virus scanning for uploads |

Additional roles defined in code (not separate Compose services by default): `batch-executor` (`batch_executor_port` 8011, polls LLM batch APIs), and the chat bridge (`copilot_chat_bridge_port` 8010).

`poetry run app` starts **all** roles in one process tree, in this order: DatabaseManager, Scheduler, BatchExecutor, NotificationManager, PlatformLinkingManager, WebsocketServer, AgentServer (REST), ExecutionManager, CoPilotChatBridge, CoPilotExecutor (the last runs in the foreground).

### 2.3 Repository layout

| Path | Contents |
|---|---|
| `autogpt_platform/backend/backend/api/` | FastAPI app (`rest_api.py`), WebSocket app (`ws_api.py`), `features/*` routers, `external/` (External API v1), `middleware/` |
| `.../backend/blocks/` | Auto-discovered block modules (one package per provider) |
| `.../backend/executor/` | Execution manager, scheduler, billing, cost tracking, cluster lock, simulator, automod |
| `.../backend/data/` | Data access and domain logic (graphs, executions, credits, event bus, `llm_registry/catalog.py`, ...) |
| `.../backend/integrations/` | `ProviderName`, OAuth handlers (`oauth/`), webhook managers (`webhooks/`), credential store |
| `.../backend/copilot/` | AutoPilot: executor, tools, Graphiti memory, chat bots (`bot/`), evals |
| `.../backend/sdk/` | `ProviderBuilder`, block SDK re-exports |
| `.../backend/cli/` | Admin CLI (`cli`), `oauth_tool`, OpenAPI export |
| `.../backend/util/settings.py` | The `Config` and `Secrets` settings models |
| `autogpt_platform/backend/schema.prisma` + `migrations/` | 90 models, 32 enums, 219 migrations |
| `autogpt_platform/frontend/` | Next.js app, Better Auth (`src/lib/auth`), Playwright tests |
| `autogpt_platform/autogpt_libs/` | Shared auth (JWKS verification, dependencies), utils |
| `autogpt_platform/single-container/` | Appliance build (`docker-bake.hcl`), `runtime_config.py`, `probe.py`, supervisor listeners, nginx |
| `autogpt_platform/installer/` | Appliance installer scripts |
| `autogpt_platform/graph_templates/` | Built-in graph templates |
| `autogpt_platform/analytics/` | Analytics SQL views |
| `classic/` | `forge/`, `original_autogpt/`, `direct_benchmark/` |
| `docs/` | `platform/`, `integrations/` (generated block docs), `content/` (classic/forge) |

### 2.4 Execution flow

1. A run is requested via REST (`POST /api/graphs/{graph_id}/execute/{graph_version}`), the External API, a schedule, a webhook trigger, or AutoPilot.
2. The REST layer validates inputs and credentials, enforces the payment paywall where applicable, records an `AgentGraphExecution`, and publishes to RabbitMQ.
3. The **executor** consumes the message (up to `num_graph_workers`, default 10), resolves starting nodes, and queues downstream nodes as their required inputs validate.
4. Each node execution loads its block, injects credentials (never persisted into node I/O), charges credits (`execution_cost_*` settings), and records `AgentNodeExecution` + `AgentNodeExecutionInputOutput`.
5. Progress is published on the Redis event bus (`execution_event`); the WebSocket server forwards it to subscribed browsers.
6. A block failure yields on its `error` pin; errors inheriting from `ValueError` (`BlockInputError`, `BlockExecutionError`) are classified as user-fixable "expected failures".

Concurrency guards in `Config`:

| Setting | Default | Meaning |
|---|---|---|
| `num_graph_workers` | `10` | Executor worker concurrency |
| `max_concurrent_graph_executions_per_user` | `25` | Per-user running graph cap |
| `num_copilot_workers` | `5` | AutoPilot executor concurrency |
| `max_inflight_copilot_turns_per_user` | `15` | Queued + running AutoPilot turns per user |
| `max_running_copilot_turns_per_user` | `5` | Concurrently running AutoPilot turns per user |
| `cluster_lock_timeout` | `300` s | Distributed lock TTL |
| `requeue_by_republishing` | `True` | Requeue strategy for RabbitMQ messages |

### 2.5 Data Layer

- **ORM:** Prisma (Python client), schema `postgres/schema.prisma`-style layout at `backend/schema.prisma`, default DB schema `platform` (`DB_SCHEMA`).
- **Database:** PostgreSQL 15 with `pgvector` (embeddings for hybrid search).
- **Key model groups (90 models):**

| Group | Models |
|---|---|
| Auth (Better Auth) | `AuthUser`, `AuthSession`, `AuthAccount`, `AuthVerification`, `AuthJwks` |
| Users | `User`, `UserOnboarding`, `UserBalance`, `UserWorkspace`, `UserWorkspaceFile`, `UserWorkspaceFolder`, `UserAttribution`, `UserBriefing` |
| Graphs | `AgentGraph`, `AgentNode`, `AgentNodeLink`, `AgentBlock`, `AgentPreset` |
| Executions | `AgentGraphExecution`, `AgentNodeExecution`, `AgentNodeExecutionInputOutput`, `AgentNodeExecutionKeyValueData`, `PendingHumanReview` |
| Library | `LibraryAgent`, `LibraryFolder` |
| Store / Marketplace | `Profile`, `StoreListing`, `StoreListingVersion`, `StoreListingReview`, `SkillListing`, `SkillListingVersion`, `SkillListingFile`, `UnifiedContentEmbedding`, `SearchTerms` |
| Billing | `CreditTransaction`, `CreditRefundRequest`, `PlatformCostLog`, `SubscriptionTrial`, `SubscriptionTrialClaim`, `OrganizationSubscription`, `OrgBalance`, `OrgCreditTransaction` |
| Orgs & teams | `Organization`, `OrganizationAlias`, `OrganizationProfile`, `OrgMember`, `OrgInvitation`, `Team`, `TeamMember`, `TeamInvite`, `OrganizationSeatAssignment`, `TransferRequest`, `AuditLog` |
| Integrations | `IntegrationCredential`, `IntegrationWebhook`, `APIKey` |
| OAuth provider | `OAuthApplication`, `OAuthAuthorizationCode`, `OAuthAccessToken`, `OAuthRefreshToken` |
| AutoPilot / chat | `ChatSession`, `ChatMessage`, `SharedChatFile`, `ChatLinkedShare`, `CoPilotUnderstanding`, `OnboardingBrainDump` |
| Experts | `Expert`, `ExpertWorkflow`, `ExpertRoutine`, `ExpertSkillListing`, `ExpertCredential`, `ExpertPauseEvent`, `ExpertPod` |
| Chat bots | `PlatformLink`, `PlatformUserLink`, `PlatformLinkToken`, `BotEvent`, `BotGuild`, `BotInstall` |
| Notifications | `NotificationEvent`, `UserNotificationBatch`, `PushSubscription`, `AlertCondition` |
| Analytics / experiments | `AnalyticsDetails`, `AnalyticsMetrics`, `ActivityEvent`, `ExperimentAssignment` |
| Model registry | `LlmModelMigration` |

> [!WARNING]
> Don't hand-edit rows that carry encrypted payloads (`IntegrationCredential`) or Better Auth key material (`AuthJwks`, exposed as `platform."UserAuthJwks"` in the upgrade instructions). Use the CLI (`rotate-encryption-key`) or the documented `DELETE FROM platform."UserAuthJwks";` reset only.

**Search infrastructure.** `api/features/search/` provides a unified embedding table (`UnifiedContentEmbedding`) and `unified_hybrid_search` (semantic + BM25 lexical + recency + category scoring) across store agents, blocks, docs, library agents, workspace files, and chat sessions, exposed at `/api/search/global`. New content types add a `ContentType` enum value, a `ContentHandler` subclass registered in `CONTENT_HANDLERS`, and a background `ensure_content_embedding` call on write. Embeddings use `store_embedding_model` (default `text-embedding-3-small`).

### 2.6 Caching

| Layer | What is cached |
|---|---|
| Redis cluster | Event bus channels (`execution_event`, `notification_event`), cluster locks, chat-bot thread subscriptions (7-day TTL), Teams activity-id dedup claims, PostHog flag definitions (`posthog_flag_definition_cache=redis`, refresh 30 s, TTL 600 s) |
| HTTP | `SecurityHeadersMiddleware` sets **cache-protection** headers so sensitive API responses are not cached by browsers or proxies (only explicitly cacheable paths are allowed) |
| Appliance `/data/cache` | Regenerable backend and Next.js caches; excluded from backups |

---

## 3. System Requirements

| Component | Requirement |
|---|---|
| Container runtime | Docker Engine or Docker Desktop; **Podman is not supported** |
| Docker Compose | v2 (`docker compose`) for the from-source deployment |
| CPU architecture (appliance) | `linux/amd64` or `linux/arm64` Linux containers |
| Backend Python (dev) | `>=3.10,<3.14`, Poetry |
| Frontend (dev) | Node.js + pnpm |
| Classic | Python 3.12+, Poetry |
| PostgreSQL | 15 with `pgvector` (image `pgvector/pgvector:pg15`) |
| RabbitMQ | `rabbitmq:4.1.4` |
| Redis | `redis:7` in cluster mode (3 nodes) |
| Memory (appliance) | Measured 5–6 GiB at startup/steady health, **plus** headroom |
| Storage (appliance) | Image + growing `/data` + space for one full compressed backup; SSD-class recommended |
| Windows hosts | Docker Desktop with **WSL 2** engine (Hyper-V causes an unhealthy `db`) |

> [!NOTE]
> There is no measured minimum CPU count or supported concurrent-run ceiling for the appliance; performance depends on enabled services and workload.

**Appliance fixed ceilings**

| Resource | Limit |
|---|---|
| Prisma pool per backend role | ≤ 5 connections (`DB_CONNECTION_LIMIT` range `1`–`5`) |
| Scheduler job-store pools | 2 × 3 SQLAlchemy connections |
| FalkorDB | ≤ 25 queued queries, 1-second query timeout |
| Valkey / FalkorDB memory | **No** appliance-level ceiling or eviction policy — monitor |

---

## 4. Configuration Mastery

### 4.1 How configuration is loaded

| Source | Precedence (low → high) |
|---|---|
| `.env.default` (tracked, public, no secrets) | 1 |
| `.env` (per install, gitignored) | 2 |
| Compose `environment:` blocks (YAML anchors) | 3 |
| Shell environment | 4 |

The backend settings are two Pydantic settings models in `backend/util/settings.py`: **`Config`** (behavior) and **`Secrets`** (credentials). Field names map to environment variables of the same name (conventionally upper-case, e.g. `num_graph_workers` ↔ `NUM_GRAPH_WORKERS`). `.env.default` only lists variables that **must** be set; everything else falls back to the code defaults below.

A `./secrets` directory is also supported: each file `./secrets/<name>` holds one secret value, loaded at startup — useful for Docker secrets.

### 4.2 Required settings

| Variable | Default | Notes |
|---|---|---|
| `DB_USER` / `DB_PASS` / `DB_NAME` / `DB_HOST` / `DB_PORT` | `postgres` / placeholder / `postgres` / `localhost` / `5432` | Change `DB_PASS` from the published placeholder |
| `DB_SCHEMA` | `platform` | |
| `DB_CONNECTION_LIMIT` | `12` (`.env.default`); appliance `1`–`5`, default `5` | Per-role Prisma pool |
| `DB_CONNECT_TIMEOUT` | `60` | Seconds |
| `DB_POOL_TIMEOUT` | `300` | Seconds |
| `DATABASE_URL` / `DIRECT_URL` | built from the `DB_*` vars | Includes `?schema=${DB_SCHEMA}&connect_timeout=...` |
| `PRISMA_SCHEMA` | `postgres/schema.prisma` | |
| `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` | `localhost` / `17000` (`.env.default`; code default `6379`) / empty | |
| `RABBITMQ_DEFAULT_USER` / `RABBITMQ_DEFAULT_PASS` | dev values in `.env.default` | Change for any shared deployment; `guest` is refused by the appliance |
| `JWT_JWKS_URL` | `http://localhost:3000/api/auth/jwks` | Must be HTTPS across untrusted networks |
| `JWT_JWKS_ALGORITHMS` | `ES256,RS256,EdDSA` | Asymmetric only |
| `JWKS_ALLOW_INSECURE_TRANSPORT` | `false` | Allows cleartext JWKS to a non-local host (logs a warning) |
| `ENCRYPTION_KEY` | **blank → generated** | Fernet key; backend refuses to start without it |
| `UNSUBSCRIBE_SECRET_KEY` | **blank → generated** | Signs email unsubscribe links |
| `BETTER_AUTH_SECRET` (frontend) | **blank → generated** | Better Auth signing secret |

> [!CAUTION]
> `VAPID_PRIVATE_KEY` / `VAPID_PUBLIC_KEY` ship as a **dev-only keypair** in `.env.default`. Regenerate before any non-local deployment:
>
> ```bash
> poetry run python -c "
> from py_vapid import Vapid; import base64
> from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
> v = Vapid(); v.generate_keys()
> raw_priv = v.private_key.private_numbers().private_value.to_bytes(32, 'big')
> print('VAPID_PRIVATE_KEY=' + base64.urlsafe_b64encode(raw_priv).rstrip(b'=').decode())
> raw_pub = v.public_key.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
> print('VAPID_PUBLIC_KEY=' + base64.urlsafe_b64encode(raw_pub).rstrip(b'=').decode())
> "
> ```
>
> Also set `VAPID_CLAIM_EMAIL` to a real mailbox (default `mailto:[email removed]` in `.env.default`).

### 4.3 `Config` reference (behavior)

**Service ports and hosts**

| Setting | Default |
|---|---|
| `agent_api_host` / `agent_api_port` | `0.0.0.0` / `8006` |
| `websocket_server_host` / `websocket_server_port` | `0.0.0.0` / `8001` |
| `execution_manager_port` | `8002` |
| `execution_scheduler_port` | `8003` |
| `database_api_port` | `8005` |
| `notification_service_port` | `8007` |
| `copilot_executor_port` | `8008` |
| `platform_linking_service_port` | `8009` |
| `copilot_chat_bridge_port` | `8010` |
| `batch_executor_port` | `8011` |
| `pyro_host` | `localhost` |
| `rabbitmq_host` / `rabbitmq_port` / `rabbitmq_vhost` | `localhost` / `5672` / `/` |
| `redis_host` / `redis_port` | `localhost` / `6379` |
| `clamav_service_host` / `clamav_service_port` | `localhost` / `3310` |
| `ollama_host` | `localhost:11434` |

**Execution and concurrency**

| Setting | Default |
|---|---|
| `num_graph_workers` | `10` |
| `num_copilot_workers` | `5` |
| `fastapi_thread_pool_size` | `60` |
| `max_concurrent_graph_executions_per_user` | `25` |
| `max_inflight_copilot_turns_per_user` | `15` |
| `max_running_copilot_turns_per_user` | `5` |
| `requeue_by_republishing` | `True` |
| `cluster_lock_timeout` | `300` |
| `rpc_client_call_timeout` | `300` |
| `pyro_client_comm_timeout` / `pyro_client_comm_retry` / `pyro_client_max_wait` | `15` / `100` / `30.0` |
| `llm_request_timeout_seconds` | `600` |
| `max_message_size_limit` | `16 MiB` |
| `execution_late_notification_threshold_secs` | `300` |
| `execution_late_notification_checkrange_secs` | `3600` |

**Codex / ChatGPT subscription processes**

| Setting | Default |
|---|---|
| `codex_max_active_processes` | `4` |
| `codex_capacity_timeout_seconds` | `10` |
| `codex_startup_timeout_seconds` | `30` |
| `codex_control_timeout_seconds` | `60` |
| `codex_invocation_timeout_seconds` | `180` |
| `codex_copilot_turn_timeout_seconds` | `21600` |
| `codex_copilot_tool_timeout_seconds` | `900` |
| `codex_login_timeout_seconds` | `900` |
| `codex_auth_checkpoint_interval_seconds` | `0.25` |
| `codex_temp_root` | `""` |

**Credits and billing**

| Setting | Default |
|---|---|
| `enable_auth` | `True` |
| `enable_credit` | `False` |
| `enable_subscription_credit_grant` | `False` |
| `refund_credit_tolerance_threshold` | `500` |
| `low_balance_threshold` | `500` |
| `execution_cost_count_threshold` | `100` |
| `execution_cost_per_threshold` | `1` |
| `execution_counter_expiration_time` | `86400` |
| `expert_weekly_credit_budget_default` | `500` |
| `expert_spend_approval_threshold_default` | `250` |
| `expert_spend_approval_window` | `week` |
| `refund_notification_email` | `[email removed]` |
| `stripe_tier_reconcile_interval_hours` | `6` |

**Files, scanning, moderation**

| Setting | Default |
|---|---|
| `upload_file_size_limit_mb` | `256` |
| `max_file_size_mb` | `100` |
| `clamav_service_enabled` | `True` |
| `clamav_service_timeout` | `60` |
| `clamav_max_concurrency` | `10` |
| `clamav_mark_failed_scans_as_clean` | `False` |
| `automod_enabled` | `False` |
| `automod_api_url` | `""` |
| `automod_timeout` / `automod_retry_attempts` / `automod_retry_delay` | `30` / `3` / `1.0` |
| `automod_fail_open` | `False` |
| `media_gcs_bucket_name` | `""` (local `store-media/`) |
| `workspace_storage_dir` | `""` |

**URLs, CORS, environment**

| Setting | Default |
|---|---|
| `app_env` | `local` |
| `behave_as` | `local` |
| `platform_base_url` | `""` (`.env.default`: `http://localhost:8006`) |
| `frontend_base_url` | `""` (`.env.default`: `http://localhost:3000`) |
| `trusted_frontend_origins` | `[]` |
| `backend_cors_allow_origins` | `["http://localhost:3000"]` |
| `external_oauth_callback_origins` | `["http://localhost:3000"]` |
| `platform_link_base_url` | `https://platform.agpt.co/link` (`.env.default`: `http://localhost:3000/link`) |

**Feature flags and toggles**

| Setting | Default |
|---|---|
| `feature_flag_backend` | `launchdarkly` |
| `posthog_flag_definition_cache` | `redis` |
| `enable_example_blocks` | `False` |
| `enable_agent_input_subtype_blocks` | `True` |
| `use_agent_image_generation_v2` | `True` |
| `marketplace_require_canonical_category` | `False` |
| `scheduler_startup_embedding_backfill` | `True` |
| `autopilot_bot_teams_allow_unverified` | `False` |

Force individual flags locally with `FORCE_FLAG_<NAME>=true` (or `FORCE_ALL_FLAGS=true` when `APP_ENV=local`); frontend equivalents use `NEXT_PUBLIC_FORCE_FLAG_<NAME>`.

**Periodic cleanup jobs**

| Setting | Default (hours) |
|---|---|
| `cloud_storage_cleanup_interval_hours` | `6` |
| `oauth_token_cleanup_interval_hours` | `6` |
| `push_subscription_cleanup_interval_hours` | `24` |
| `platform_link_token_cleanup_interval_hours` | `6` |
| `block_error_rate_check_interval_secs` | `86400` (seconds) |
| `execution_accuracy_check_interval_hours` | `24` |

**Alerts and email senders**

| Setting | Default |
|---|---|
| `block_error_rate_threshold` | `0.5` |
| `block_error_include_top_blocks` | `3` |
| `platform_alert_discord_channel` | `local-alerts` |
| `product_alert_discord_channel` | `product-alerts` |
| `postmark_sender_email` | `[email removed]` |
| `billing_sender_email` | `AutoGPT <[email removed]>` |
| `product_sender_email` | `AutoGPT <[email removed]>` |
| `ops_sender_email` | `AutoGPT Platform <[email removed]>` |
| `postmark_transactional_stream` | `outbound` |

### 4.4 `Secrets` reference (credentials)

All default to empty. Only set what you use.

| Category | Variables |
|---|---|
| Core | `ENCRYPTION_KEY`, `UNSUBSCRIBE_SECRET_KEY`, `RABBITMQ_DEFAULT_USER`, `RABBITMQ_DEFAULT_PASS`, `VAPID_PRIVATE_KEY`, `VAPID_PUBLIC_KEY`, `VAPID_CLAIM_EMAIL` |
| LLM providers | `OPENAI_API_KEY`, `OPENAI_INTERNAL_API_KEY`, `ANTHROPIC_API_KEY`, `GROQ_API_KEY`, `OPEN_ROUTER_API_KEY`, `LLAMA_API_KEY`, `AIML_API_KEY`, `V0_API_KEY`, `NVIDIA_API_KEY` |
| OAuth apps | `GITHUB_*`, `GOOGLE_*`, `NOTION_*`, `TWITTER_*`, `DISCORD_*`, `REDDIT_*`, `LINEAR_*`, `TODOIST_*` (`_CLIENT_ID` + `_CLIENT_SECRET`) |
| Email | `POSTMARK_SERVER_API_TOKEN`, `POSTMARK_WEBHOOK_TOKEN`, `SMTP_SERVER`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `MAILERLITE_API_TOKEN` |
| Billing | `STRIPE_API_KEY`, `STRIPE_WEBHOOK_SECRET` |
| Chat bots | `AUTOPILOT_BOT_DISCORD_TOKEN`, `AUTOPILOT_BOT_DISCORD_CLIENT_ID`, `AUTOPILOT_BOT_DISCORD_PERMISSIONS`, `AUTOPILOT_BOT_SLACK_TOKEN`, `AUTOPILOT_BOT_SLACK_SIGNING_SECRET`, `AUTOPILOT_BOT_SLACK_CLIENT_ID`, `AUTOPILOT_BOT_SLACK_CLIENT_SECRET`, `AUTOPILOT_BOT_TELEGRAM_TOKEN`, `AUTOPILOT_BOT_TELEGRAM_WEBHOOK_SECRET`, `AUTOPILOT_BOT_TELEGRAM_USERNAME`, `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`, `MICROSOFT_TENANT_ID`, `DISCORD_BOT_TOKEN` |
| Block providers | `REPLICATE_API_KEY`, `FAL_API_KEY`, `IDEOGRAM_API_KEY`, `DID_API_KEY`, `ELEVENLABS_API_KEY`, `UNREAL_SPEECH_API_KEY`, `E2B_API_KEY`, `EXA_API_KEY`, `JINA_API_KEY`, `MEM0_API_KEY`, `OPENWEATHERMAP_API_KEY`, `GOOGLE_MAPS_API_KEY`, `SCREENSHOTONE_API_KEY`, `MEDIUM_API_KEY`, `MEDIUM_AUTHOR_ID`, `APOLLO_API_KEY`, `SMARTLEAD_API_KEY`, `ZEROBOUNCE_API_KEY`, `ENRICHLAYER_API_KEY`, `AGENTMAIL_API_KEY`, `AYRSHARE_API_KEY`, `AYRSHARE_JWT_KEY`, `TALLY_API_KEY`, `WEBSHARE_PROXY_USERNAME`, `WEBSHARE_PROXY_PASSWORD` |
| Observability & flags | `SENTRY_DSN`, `LAUNCH_DARKLY_SDK_KEY`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST` (`https://cloud.langfuse.com`), `LANGFUSE_TRACING_ENVIRONMENT` (`local`), `POSTHOG_API_KEY`, `POSTHOG_HOST` (`https://eu.i.posthog.com`), `POSTHOG_PERSONAL_API_KEY` |
| Moderation | `AUTOMOD_API_KEY` |
| Copilot feature requests | `COPILOT_LINEAR_API_KEY`, `LINEAR_FEATURE_REQUEST_PROJECT_ID`, `LINEAR_FEATURE_REQUEST_TEAM_ID` |

### 4.5 AutoPilot and memory settings

| Variable | Default | Purpose |
|---|---|---|
| `CHAT_USE_LOCAL` | `false` | Route AutoPilot through an OpenAI-compatible endpoint |
| `CHAT_USE_OPENROUTER` | appliance profile setting | `true` → OpenRouter; `false` with `ANTHROPIC_API_KEY` → direct Anthropic |
| `CHAT_BASE_URL` | empty | e.g. `http://host.docker.internal:11434/v1` |
| `CHAT_API_KEY` | empty | Must be non-empty for local servers |
| `CHAT_FAST_STANDARD_MODEL` (and other `CHAT_*_MODEL`) | — | Bare model names for local backends |
| `CHAT_DAILY_COST_LIMIT_MICRODOLLARS` / `CHAT_WEEKLY_COST_LIMIT_MICRODOLLARS` | `-1` in the appliance | Spend caps; HTTP 429 when exceeded |
| `GRAPHITI_FALKORDB_HOST` / `_PORT` / `_PASSWORD` | `localhost` / `6380` / `local-dev-password` | FalkorDB connection |
| `GRAPHITI_LLM_MODEL` | `gpt-4.1-mini` | Extraction LLM |
| `GRAPHITI_EMBEDDER_MODEL` | `text-embedding-3-small` | Embeddings |
| `GRAPHITI_RERANKER_MODEL` | — | Reranker |
| `GRAPHITI_SEMAPHORE_LIMIT` | `5` | Graphiti concurrency |

Key fallbacks: Graphiti's LLM key falls back to `CHAT_API_KEY`, then `OPEN_ROUTER_API_KEY`, then (local) the chat config; its embedder key falls back to `CHAT_OPENAI_API_KEY`, then `OPENAI_API_KEY`, then (local) the chat config. Under `CHAT_USE_LOCAL=true` cloud-slug Graphiti defaults are rewritten to local-friendly slugs unless you set custom values.

> [!NOTE]
> On the hosted cloud (`BEHAVE_AS=cloud`), `CHAT_*_MODEL` is the **bottom** layer of AutoPilot model resolution — the catalog routing cells in `backend/data/llm_registry/catalog.py` and the per-user LaunchDarkly `copilot-model-routing` flag take precedence. On self-hosted installs the catalog cells are skipped and the env vars are authoritative.

### 4.6 Frontend settings

| Variable | Default |
|---|---|
| `BETTER_AUTH_SECRET` | blank → generated |
| `BETTER_AUTH_URL` | `http://localhost:3000` |
| `DATABASE_URL` | `postgresql://postgres:...@localhost:5432/postgres` |
| `AUTH_DB_SCHEMA` | `platform` |
| `AUTH_REQUIRE_EMAIL_VERIFICATION` | `false` |
| `AUTH_ALLOW_NEW_ACCOUNTS` / `AUTH_SIGNUP_ALLOWLIST` | commented (open signup) |
| `AUTH_GOOGLE_*`, `AUTH_GITHUB_*`, `AUTH_DISCORD_*` | commented (social sign-in) |
| `SUPABASE_JWT_SECRET` / `SUPABASE_BRIDGE_MAX_TOKEN_AGE_DAYS` | commented / `30` (legacy session bridge) |
| `NEXT_PUBLIC_AGPT_SERVER_URL` | `http://localhost:8006/api` |
| `NEXT_PUBLIC_AGPT_WS_SERVER_URL` | `ws://localhost:8001/ws` |
| `NEXT_PUBLIC_FRONTEND_BASE_URL` | `http://localhost:3000` |
| `NEXT_PUBLIC_APP_ENV` / `NEXT_PUBLIC_BEHAVE_AS` | `local` / `LOCAL` |
| `NEXT_PUBLIC_LAUNCHDARKLY_ENABLED` | `false` |
| `NEXT_PUBLIC_TURNSTILE` | `disabled` |
| `NEXT_PUBLIC_REACT_QUERY_DEVTOOL` / `NEXT_PUBLIC_TOKEN_DEVTOOL` | `true` |
| `NEXT_PUBLIC_POSTHOG_KEY` / `NEXT_PUBLIC_POSTHOG_HOST` | empty / `https://eu.i.posthog.com` |
| `NEXT_PUBLIC_GOOGLE_CLIENT_ID`, `NEXT_PUBLIC_GOOGLE_API_KEY`, `NEXT_PUBLIC_GOOGLE_APP_ID` | empty (Google Picker) |
| `NEXT_PUBLIC_VAPID_PUBLIC_KEY` | dev key (replace with yours) |
| `OPENAI_API_KEY` | empty (server-side transcription) |

> [!WARNING]
> `NEXT_PUBLIC_*` values are compiled into the browser bundle. Never put a secret in a `NEXT_PUBLIC_` variable.

### 4.7 Single-container appliance settings

| Variable | Purpose |
|---|---|
| `AUTOGPT_PUBLIC_URL` | Exact browser origin (scheme + host + non-default port, no path) |
| `AUTH_SIGNUP_ALLOWLIST`, `AUTH_ALLOW_NEW_ACCOUNTS`, `AUTH_REQUIRE_EMAIL_VERIFICATION` | Account policy |
| `AUTOGPT_ENABLE_LEGACY_AUTH` | Keep `false` except for an intentional legacy symmetric-JWT migration |
| `AUTOGPT_ENABLE_BOT_SERVICES` | `true` starts bot services and the linking manager |
| `BEHAVE_AS` | `local` (default) or `cloud` |
| `DB_CONNECTION_LIMIT` / `DB_CONNECT_TIMEOUT` / `DB_POOL_TIMEOUT` | `1`–`5` (5) / `1`–`600` (60) / `1`–`3600` (300) |
| `POSTMARK_*` | Password reset and email-change messages |
| Provider keys, `CHAT_*`, `GRAPHITI_*` | As in the backend |

`runtime_config.py` validates the environment at boot and raises precise errors (see [Troubleshooting](#11-troubleshooting)). Generated secrets are written once to `/data/config/runtime.env` (`root:root`, `0600`); supplying a different value later fails instead of silently rotating.

### 4.8 Migrations

| Task | Command |
|---|---|
| Apply (Compose) | `docker compose run --rm migrate` |
| Apply (local dev) | `make migrate` (`prisma migrate deploy` + `prisma generate` + `gen-prisma-stub`) |
| Create during development | `poetry run prisma migrate dev` |
| Reset local DB | `make reset-db` (**deletes** `data/db/data`) |
| Appliance | Applied automatically on boot before readiness is published |

> [!CAUTION]
> Never stop the appliance during a boot-time migration, and **never run an older image against a volume migrated by a newer one**. Rollback = old image + its matching pre-upgrade backup restored into a new volume.

Notable migrations: `20260716120000_copy_supabase_users_to_better_auth` copies Supabase `auth.users` into Better Auth tables during the Supabase → Better Auth upgrade.

**Model retirement** (catalog-as-code): set `is_enabled: False` in `backend/data/llm_registry/catalog.py`, then migrate existing graph nodes:

```bash
python -m backend.data.llm_registry.retire <slug> --replacement <slug> --yes
```

It is a dry run by default and revertable.

---

## 5. Deployment

### 5.1 Docker Compose (multi-container, from source)

```bash
git clone https://github.com/Significant-Gravitas/AutoGPT.git
cd AutoGPT/autogpt_platform
make init-env
docker compose up -d --build
```

```text
✔ Container autogpt_platform-migrate-1       Exited
✔ Container autogpt_platform-rest_server-1   Started
✔ Container autogpt_platform-frontend-1      Started
```

Compose profiles:

| Profile / target | Use |
|---|---|
| default | Full stack |
| `deps` service (`make start-core`) | Postgres, Redis, RabbitMQ only — for running backend on the host |
| `--profile local up deps_backend` | Backend dependencies for running the frontend on the host |
| `--profile bot` | Adds `platform_linking_manager` for chat bots |

Deployment checklist:

- [ ] `make init-env` run; `ENCRYPTION_KEY`, `UNSUBSCRIBE_SECRET_KEY`, `BETTER_AUTH_SECRET` populated
- [ ] `DB_PASS`, `RABBITMQ_DEFAULT_*`, VAPID keys replaced from published defaults
- [ ] `FRONTEND_BASE_URL`, `PLATFORM_BASE_URL`, `BACKEND_CORS_ALLOW_ORIGINS` match real origins
- [ ] `JWT_JWKS_URL` is HTTPS if backend and frontend are not on one host
- [ ] OAuth apps registered with callback `<frontend>/auth/integrations/oauth_callback`
- [ ] Only `3000` (and, if needed, `8006`/`8001`) exposed through a TLS proxy; infrastructure ports not public
- [ ] Backups scheduled for Postgres (and FalkorDB if memory matters)

### 5.2 Docker: single-container appliance

```bash
docker run --detach --name autogpt \
  --restart unless-stopped \
  --shm-size 2g \
  --ulimit nofile=65536:65536 \
  --log-driver json-file \
  --log-opt max-size=50m \
  --log-opt max-file=5 \
  --env-file autogpt.env \
  --publish 127.0.0.1:3000:3000 \
  --volume autogpt-data:/data \
  "${IMAGE}"
```

Internals:

- **Supervisor** runs process groups; names are group-qualified (`runtime:rest`, `state:postgres`). `supervisorctl status` lists them.
- **Shutdown** is tuned to fit Docker's stock 10-second timeout: runtime processes are signalled first; PostgreSQL, RabbitMQ, Valkey, and FalkorDB get at most five seconds each; the event listener stops last (measured ~8.4 s). Do not shorten Docker's timeout.
- A **watchdog** allows 600 seconds for initial full health, then stops the container so `--restart unless-stopped` retries.
- A fatal-event listener (`fatal_listener.py`) and probes (`probe.py`: HTTP, AMQP, Redis/cluster) back `autogpt-healthcheck`.
- nginx and Next.js run under separate Unix identities; the frontend reaches PostgreSQL via a passwordless local **peer** role restricted to Better Auth tables/columns.

**`/data` layout**

| Path | Contents |
|---|---|
| `/data/config` | `runtime.env` (generated secrets), `backend.json` |
| `/data/postgres` | Auth and platform data |
| `/data/rabbitmq` | Queue state (`mnesia`) |
| `/data/valkey` | Three Valkey nodes (`17000`, `17001`, `17002`) |
| `/data/falkordb` | Graphiti memory |
| `/data/workspaces` | User workspaces |
| `/data/store-media` | Marketplace media when GCS isn't configured |
| `/data/home`, `/data/frontend-home` | Application homes |
| `/data/cache` | Regenerable caches (excluded from backups) |

> [!WARNING]
> Never mount one `/data` volume into two running containers, and never selectively mix service directories from different backups.

**Optional processes:** FalkorDB is mandatory. Chat-bot services default off; set `AUTOGPT_ENABLE_BOT_SERVICES=true` after configuring an adapter (Discord: `AUTOPILOT_BOT_DISCORD_TOKEN`; Telegram: token + webhook secret + a public HTTPS webhook).

**Local models:** on Docker Engine add `--add-host host.docker.internal:host-gateway` (Docker Desktop provides the hostname already).

### 5.3 Reverse Proxy / Load Balancing

**Appliance.** Keep the container bound to loopback and terminate TLS in a reverse proxy **outside** the container, forwarding to `127.0.0.1:3000`; the appliance routes internally. Set `AUTOGPT_PUBLIC_URL` to the HTTPS origin:

```dotenv
AUTOGPT_PUBLIC_URL=https://agents.example.com
```

Example (nginx on the host):

```nginx
server {
    listen 443 ssl;
    server_name agents.example.com;
    ssl_certificate     /etc/ssl/agents.crt;
    ssl_certificate_key /etc/ssl/agents.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}
```

> [!IMPORTANT]
> The bundled nginx records your proxy as the client. Your proxy must keep its own client-attribution logs and enforce per-client IP rate limits and access rules.

> [!CAUTION]
> On Linux, Docker-managed port forwarding can bypass firewall rules expressed only through UFW. The `127.0.0.1:` in `--publish` **is** the exposure control; publishing on a non-loopback address can expose the app regardless of INPUT-chain rules.

**Compose.** Route `https://<host>/` → frontend `:3000`, and expose the backend API (`:8006`) and WebSocket (`:8001`) either on their own hostnames or paths, then set:

| Setting | Value |
|---|---|
| `NEXT_PUBLIC_AGPT_SERVER_URL` | `https://api.example.com/api` |
| `NEXT_PUBLIC_AGPT_WS_SERVER_URL` | `wss://ws.example.com/ws` |
| `FRONTEND_BASE_URL` / `NEXT_PUBLIC_FRONTEND_BASE_URL` / `BETTER_AUTH_URL` | `https://app.example.com` |
| `PLATFORM_BASE_URL` | `https://api.example.com` |
| `BACKEND_CORS_ALLOW_ORIGINS` | `["https://app.example.com"]` |
| `JWT_JWKS_URL` | `https://app.example.com/api/auth/jwks` |

> [!NOTE]
> `backend/api/utils/cors.py` rejects localhost origins in production (`Production environment cannot allow localhost origins`), including via regex patterns.

Horizontal scaling: webhook-style chat adapters ride the N-replica REST deployment (no dedicated pod); socket adapters (Discord Gateway, Slack Socket Mode) run in a separate `copilot-bot` process. Executors scale by adding executor replicas consuming the same RabbitMQ queues; cluster locks live in Redis.

---

## 6. Security

### 6.1 Authentication

| Layer | Mechanism |
|---|---|
| End-user sign-in | **Better Auth** embedded in the Next.js frontend (email/password; optional social providers via `AUTH_*_CLIENT_ID/SECRET`) |
| Backend request auth | Asymmetric JWTs (ES256 by default) verified against `JWT_JWKS_URL` by `autogpt_libs.auth` |
| Legacy bridge | Old Supabase JWT cookies are exchanged for a Better Auth session while `SUPABASE_JWT_SECRET` is set |
| API keys | `X-API-Key` header, stored as `APIKey` rows with **permissions** (e.g. `EXECUTE_GRAPH`); validated by `api/utils/api_key_auth.py` |
| OAuth provider | AutoGPT is itself an OAuth 2.0 authorization server (`/api/oauth/token`, `/introspect`, `/revoke`) with mandatory PKCE `S256`; tokens `agpt_xt_...` (1 h), refresh `agpt_rt_...` (30 d), codes (10 min) |
| Admin CLI for OAuth apps | `poetry run oauth-tool generate-app ...` |

> [!CAUTION]
> The backend trusts **whatever keys** `JWT_JWKS_URL` returns. Over any untrusted network use HTTPS; a cleartext fetch lets an attacker substitute keys and forge tokens for any user. The backend refuses to boot with a cleartext non-local JWKS URL unless `JWKS_ALLOW_INSECURE_TRANSPORT=true`.

Account policy (appliance): allowlist by exact email or `@domain`; passwords ≥ 12 characters; `AUTH_ALLOW_NEW_ACCOUNTS=false` after bootstrap. Allowlisting compares the email asserted at signup — without email verification it is **not proof of mailbox ownership**.

### 6.2 Authorization & Access Control

| Control | Detail |
|---|---|
| Route guards | `Security(requires_user)` / admin dependencies on FastAPI routers (e.g. presets router) |
| Admin role | Granted by `autogpt-admin promote` (appliance); admin routers under `/api/admin` |
| API key permissions | Per-route `require_permission(APIKeyPermission.X)` on the External API |
| OAuth scopes | `IDENTITY`, `EXECUTE_GRAPH`, `READ_GRAPH`, `WRITE_GRAPH`, `EXECUTE_BLOCK`, `READ_BLOCK`, `READ_STORE`, `WRITE_LIBRARY`, `USE_TOOLS`, `MANAGE_INTEGRATIONS`, `READ_INTEGRATIONS`, `DELETE_INTEGRATIONS` |
| Org scoping | Org-scoped API keys attribute executions to their org; team-restricted keys pin to that team |
| Entitlements | `BEHAVE_AS=cloud` enforces subscription entitlements; `local` exempts every defined entitlement |
| Paywall | `enforce_payment_paywall()` on execute routes (returns 503 on tier-lookup failure rather than failing open) |
| Marketplace | Submissions require human review; listings are read-only in libraries until duplicated |
| Experts | Per-expert credential allowlists (`ExpertCredential`) and spend approval thresholds |

> [!WARNING]
> `BEHAVE_AS=local` also enables blocks disabled in hosted mode, self-host model routing, and different diagnostics/telemetry. Any multi-tenant deployment must use `BEHAVE_AS=cloud` — and even then the appliance frontend is compiled in local mode, so the appliance is **not** a turnkey multi-tenant distribution.

### 6.3 TLS / Certificates

- AutoGPT does not terminate TLS itself; terminate at a reverse proxy.
- For LAN splits, use locally trusted certificates (e.g. mkcert) and point `JWT_JWKS_URL` at the `https://` URL.
- Production OAuth redirect URIs must be HTTPS.
- Telegram and Teams webhooks require public HTTPS endpoints.

### 6.4 Secrets handling

| Secret | Protection |
|---|---|
| Integration credentials | Encrypted at rest with Fernet `ENCRYPTION_KEY`; injected into blocks at run time |
| `.env.default` files | Must stay secret-free; enforced by `.gitleaks.toml`, `detect-secrets`, the `check-env-defaults-blank` pre-commit hook, and a unit test over `BLANK_IN_ENV_DEFAULT` |
| Appliance runtime secrets | Generated on first boot into `/data/config/runtime.env` (`0600`, root) |
| Frontend process | Receives only an allowlisted env: `BETTER_AUTH_SECRET`, `AUTH_*` client secrets, optional `OPENAI_API_KEY`, `TRANSCRIPTION_API_KEY`, legacy `SUPABASE_JWT_SECRET` — never the Postgres superuser, RabbitMQ/Valkey/FalkorDB passwords, or encryption keys |
| Templates / exports | Store credential **references** only |

**Suspected exposure of appliance secrets:** there is no in-place rotation. Isolate the installation, revoke external provider and OAuth credentials, build a new installation on a new volume, move only non-secret agent definitions via export/import, and reconnect credentials. Restoring an older backup does not rotate anything.

### 6.5 Input validation and network exposure

| Risk | Mitigation in source |
|---|---|
| SSRF from blocks | `backend.util.request.Requests` validates URLs, resolves DNS, blocks private ranges (`BLOCKED_IP_NETWORKS`), disables redirects by default, supports `trusted_origins` |
| Malicious uploads | ClamAV scanning (`clamav_*` settings; failed scans are **not** marked clean by default). **The appliance has no antivirus** — treat uploads as trusted input there |
| Content moderation | Optional `automod_*` integration (fail-closed by default) |
| Browser caching of sensitive data | `SecurityHeadersMiddleware` |
| CORS | Production rejects localhost origins |
| Webhook forgery | Providers with signatures override `verify_signature` using `hmac.compare_digest` (GitHub `X-Hub-Signature-256`, Airtable `X-Airtable-Content-MAC`, Slant3D HMAC-SHA256 with ms timestamp, Slack signing secret); unsigned providers rely on the unguessable webhook UUID |
| Teams bot | Inbound Bot Framework JWT validated; `serviceUrl` claim bound to the token and outbound hosts allowlisted |
| Internal RPC | Appliance binds AppService RPC to container loopback; Valkey requires auth |
| Shell tool | Local `bash_exec` fallback relies on Bubblewrap user/network namespaces — do not make the appliance privileged to work around a host that disables them |
| Postgres config tampering | Restore validation rejects non-loopback `listen_addresses`, include files, preload libraries, WAL archiving, and weakened `pg_hba.conf` |

### 6.6 Security hardening checklist

- [ ] Loopback-only publish + external TLS proxy
- [ ] Signup allowlist during bootstrap, then `AUTH_ALLOW_NEW_ACCOUNTS=false`
- [ ] No published defaults in use (`DB_PASS`, RabbitMQ creds, VAPID keypair, pre-v0.7.4 `ENCRYPTION_KEY`)
- [ ] HTTPS `JWT_JWKS_URL` for any split deployment
- [ ] `BEHAVE_AS=cloud` for anything multi-tenant
- [ ] ClamAV enabled (Compose) or uploads restricted (appliance)
- [ ] Unauthenticated local model servers not exposed to the internet
- [ ] Backups encrypted (they contain plaintext secrets and user content)
- [ ] Webhook URLs for unsigned providers treated as secrets

---

## 7. Operations

### 7.1 Start, stop, restart

| Task | Compose | Appliance |
|---|---|---|
| Start | `docker compose up -d` | `docker run ...` (Quick start) or `docker start autogpt` |
| Stop | `docker compose down` | `docker stop autogpt` |
| Restart one role | `docker compose restart rest_server` | `docker exec autogpt supervisorctl restart runtime:rest` |
| Rebuild after upgrade | `git pull && docker compose up -d --build` | Replace container with new image (see Upgrade) |
| Status | `docker compose ps` | `docker inspect --format '{{.State.Health.Status}}' autogpt` |

> [!NOTE]
> Agent runs executing when services stop are **abandoned**: queue messages can be dropped and rows can stay `RUNNING`. They do not resume.

### 7.2 Upgrades

**Compose:**

1. `git pull` the release.
2. Diff each `.env` against its `.env.default` and copy new keys (`make init-env` won't merge them).
3. `docker compose build migrate rest_server && docker compose run --rm migrate`.
4. `docker compose up -d --build`.

**Appliance:**

1. Record the running image reference and ID: `docker inspect --format '{{.Config.Image}} {{.Image}}' autogpt`.
2. Pull/build the new image while the old one runs.
3. `export RESTART_AFTER_BACKUP=false`, run the cold-backup block once, then `unset RESTART_AFTER_BACKUP`.
4. `docker rm autogpt` (stopped container only).
5. Re-run the original `docker run` with the new image reference.
6. Wait for health; test login, memory, one agent execution, streaming, WebSockets, and persistence across one restart.

**Secret-generation upgrade** (installs from before per-install secrets): stop, export `OLD_ENCRYPTION_KEY` (for never-set keys it's the `.env.default` value at tag `autogpt-platform-beta-v0.7.4`), blank the three secret lines, `make init-env`, build + migrate, run `cli rotate-encryption-key` (dry run) then `--apply`, clear `platform."UserAuthJwks"` if `BETTER_AUTH_SECRET` changed, and start. Do the re-encryption **before** starting the stack on the new key.

**Supabase → Better Auth upgrade:** the database moves to `pgvector/pgvector:pg15` at `autogpt_platform/data/db/data`; old data remains at `autogpt_platform/db/docker/volumes/db/data`. Either reuse the data directory (same major version; may fail on missing Supabase extensions/roles) or dump/restore with `--no-owner --no-privileges`, dropping the `auth` shim schema first. Users are copied by migration `20260716120000_copy_supabase_users_to_better_auth`. `frontend/scripts/migrate-supabase-auth.ts` is only for live cutovers. Neither data path has been validated against a real old volume — back up first.

### 7.3 Logging

| Where | How |
|---|---|
| Compose | `docker compose logs -f <service>`; `make logs-core` for core deps |
| Appliance | `docker logs --follow --tail 100 autogpt`; json-file driver capped at 5 × 50 MB |
| Error tracking | `SENTRY_DSN` |
| LLM tracing / prompts | Langfuse (`LANGFUSE_*`); also manages the AutoPilot system prompt externally |
| Product analytics | PostHog (`POSTHOG_*`) |
| Chat inspection | `poetry run cli chat list` / `cli chat view SESSION_ID [RANGE] --json` |

Log style (backend convention): `%s` deferred interpolation in `debug`, f-strings elsewhere; error messages use `os.path.basename()` to avoid leaking paths.

### 7.4 Monitoring & Health Checks

| Endpoint / command | What it proves |
|---|---|
| `GET :8006/health` | REST server up **and** DB connected (`{"status":"healthy"}`; raises `UnhealthyServiceError("Database is not connected")` otherwise) |
| `GET :8001/health` (and `/`) | WebSocket server up |
| Appliance `GET /healthz` | **nginx only** — not whole-appliance readiness |
| `docker exec autogpt autogpt-healthcheck` | Every bundled dependency and application role |
| Compose healthchecks | `rabbitmq-diagnostics -q ping`, DB and Redis checks gate dependent services (`service_healthy`) |

```bash
curl -s http://localhost:8006/health
```

```text
{"status":"healthy"}
```

Built-in alerting (via configured Discord channels / notifications):

| Signal | Setting |
|---|---|
| Block error rate | `block_error_rate_threshold` `0.5`, checked every `block_error_rate_check_interval_secs` (24 h), top `3` blocks reported |
| Late executions | Queued longer than `execution_late_notification_threshold_secs` (300 s) within a 1-hour window |
| Low balance | `low_balance_threshold` (500) |
| Execution accuracy | Every `execution_accuracy_check_interval_hours` (24) |

Load testing: `autogpt_platform/backend/load-tests/` (k6). Targets: core API P95 < 2 s, marketplace P95 < 5 s, success > 95 %.

```bash
cd autogpt_platform/backend/load-tests
node generate-tokens.js --count=160
node orchestrator/orchestrator.js DEV local
```

### 7.5 Backup & Restore

**Compose.** Back up PostgreSQL (and FalkorDB if you rely on memory) with service-native tools:

```bash
docker compose exec db pg_dump -U postgres -d postgres -Fc -f /tmp/autogpt.dump
docker compose cp db:/tmp/autogpt.dump ./autogpt.dump
```

Also preserve `backend/.env` — especially `ENCRYPTION_KEY`, without which restored credentials are unreadable.

**Appliance cold backup.** The documented block (in `docs/platform/single-container.md`):

- refuses anonymous volumes and a stopped/crashed appliance;
- takes an atomic lock (a stopped helper container named `autogpt-backup-lock`);
- captures the exact image ID/digest, stops the appliance, and tars `/data` read-only with `--network none`, excluding `./cache`;
- writes `autogpt-data-<UTC timestamp>.tgz` plus `.sha256`, promoting from `.partial` only on success;
- restarts the appliance unless `RESTART_AFTER_BACKUP=false`.

> [!CAUTION]
> Backups are **plaintext** and contain user content, provider credentials, auth keys, and database passwords. Encrypt them and delete staging copies. Stale `*.partial` files from interrupted runs may also contain secrets. After a `SIGKILL`/host crash, confirm no backup is running, then remove a stale lock with `docker rm --volumes autogpt-backup-lock`.

There is **no supported hot backup** for the appliance's coupled volume.

**Restore** always goes into a **new** named volume (`autogpt-data-restored-<ts>-<pid>-<random>`), verifies the SHA-256, labels the volume with a per-run owner, extracts with `--network none`, then validates the layout offline (`runtime.env`, `backend.json`, `PG_VERSION`, Postgres configs, RabbitMQ `mnesia`, three Valkey dirs, FalkorDB, workspaces, store-media, homes) and rejects unsafe Postgres configuration. Only then launch with the recorded image, env file, publish spec, and optional `ADD_HOST_SPEC`.

> [!WARNING]
> `tar` runs as root during restore and preserves ownership and setuid bits. Only restore archives from a trusted backup process. A full recovery rehearsal boots live schedules and credentials — do it on an egress-filtered host after revoking production credentials.

---

## 8. Performance Tuning

| Lever | Setting | Default | Guidance |
|---|---|---|---|
| Graph worker concurrency | `num_graph_workers` | `10` | Raise with CPU and DB headroom; add executor replicas to scale out |
| AutoPilot workers | `num_copilot_workers` | `5` | Bounded also by per-user caps |
| Per-user graph cap | `max_concurrent_graph_executions_per_user` | `25` | Protects shared capacity |
| AutoPilot per-user caps | `max_inflight_copilot_turns_per_user` / `max_running_copilot_turns_per_user` | `15` / `5` | |
| FastAPI threadpool | `fastapi_thread_pool_size` | `60` | For sync handlers |
| DB pool | `DB_CONNECTION_LIMIT` | `12` (Compose), `5` max (appliance) | Every role holds its own pool — total = roles × limit; stay under Postgres `max_connections` |
| DB timeouts | `DB_CONNECT_TIMEOUT` / `DB_POOL_TIMEOUT` | `60` / `300` | Lowering pool timeout fails faster; it doesn't add capacity |
| LLM calls | `llm_request_timeout_seconds` | `600` | |
| RPC | `rpc_client_call_timeout`, `pyro_client_*` | `300`, `15`/`100`/`30.0` | |
| Graphiti | `GRAPHITI_SEMAPHORE_LIMIT` | `5` | |
| ClamAV | `clamav_max_concurrency`, `clamav_service_timeout` | `10`, `60` | |
| Uploads | `upload_file_size_limit_mb`, `max_file_size_mb` | `256`, `100` | |
| Codex processes | `codex_max_active_processes` | `4` | Bounded by `--shm-size` in the appliance |

> [!TIP]
> If requests stall under concurrent runs, inspect backend and PostgreSQL logs for pool exhaustion **before** changing limits. On the appliance, the fixed pools are the ceiling — move to a distributed deployment when they're insufficient.

Batch LLM work: the `BatchExecutor` polls submitted LLM batches (Anthropic) so slow batch polls never starve the scheduler's cron tick; the memory "dream pass" uses it when an Anthropic key is configured and falls back to a synchronous baseline otherwise.

---

## 9. Integrations

### 9.1 API Internals

The backend is one FastAPI app (`backend/api/rest_api.py`) with feature routers mounted under `/api`, the External API mounted as a sub-app at `/external-api`, and `/health`.

**Router mount map (selected)**

| Prefix | Feature |
|---|---|
| `/api` | Graphs (`/api/graphs...`), executions, blocks, v1 features, experts, memory, home, desktop preview, trials |
| `/api/library` | Library agents, folders, presets |
| `/api/store` | Marketplace (profiles, agents, creators, submissions, media) |
| `/api/store/skills` | Skill listings |
| `/api/builder` | Builder helpers |
| `/api/credits` | Credits and billing |
| `/api/executions` | Admin execution analytics |
| `/api/integrations` | Credentials, OAuth callbacks, webhook ingress (`/generic_webhook/webhooks/{id}/ingress`), providers |
| `/api/api-keys` | API key management |
| `/api/skills` | Skills |
| `/api/copilot` | AutoPilot |
| `/api/admin` | Admin routes |
| `/api/review` | Human review (pending reviews) |
| `/api/auth/email` | Auth email actions |
| `/api/analytics`, `/api/experiments` | Analytics and experiments |
| `/api/oauth/*` | OAuth provider (`token`, `introspect`, `revoke`) |
| `/api/copilot-webhooks/teams/messages` | Teams bot messaging endpoint |
| `/external-api/v1` | Public External API |

**Graph routes (`/api`)**

| Method | Path |
|---|---|
| `GET` | `/api/graphs` |
| `POST` | `/api/graphs` |
| `GET` | `/api/graphs/{graph_id}` |
| `PUT` | `/api/graphs/{graph_id}` |
| `DELETE` | `/api/graphs/{graph_id}` |
| `GET` | `/api/graphs/{graph_id}/versions` |
| `GET` | `/api/graphs/{graph_id}/versions/{version}` |
| `PUT` | `/api/graphs/{graph_id}/versions/active` |
| `PATCH` | `/api/graphs/{graph_id}/settings` |
| `POST` | `/api/graphs/{graph_id}/execute/{graph_version}` |

**Library routes (`/api/library`)**

| Method | Path | Handler |
|---|---|---|
| `GET` | `/agents` | `list_library_agents` |
| `GET` | `/agents/favorites` | `list_favorite_library_agents` |
| `GET` | `/agents/{library_agent_id}` | `get_library_agent` |
| `GET` | `/agents/by-graph/{graph_id}` | `get_library_agent_by_graph_id` |
| `GET` | `/agents/marketplace/{store_listing_version_id}` | `get_library_agent_by_store_listing_version_id` |
| `POST` | `/agents` | `add_marketplace_agent_to_library` |
| `PATCH` | `/agents/{library_agent_id}` | `update_library_agent` |
| `DELETE` | `/agents/{library_agent_id}` | `delete_library_agent` |
| `POST` | `/agents/{library_agent_id}/fork` | `fork_library_agent` |
| `GET` | `/agents/{library_agent_id}/triggers` | `list_trigger_agents` |
| `GET` | `/folders` | `list_folders` |
| `GET` | `/folders/tree` | `get_folder_tree` |
| `GET` | `/folders/{folder_id}` | `get_folder` |
| `POST` | `/folders` | `create_folder` |
| `PATCH` | `/folders/{folder_id}` | `update_folder` |
| `POST` | `/folders/{folder_id}/move` | `move_folder` |
| `DELETE` | `/folders/{folder_id}` | `delete_folder` |
| `POST` | `/folders/agents/bulk-move` | `bulk_move_agents` |
| `GET` | `/presets` | `list_presets` |
| `GET` | `/presets/{preset_id}` | `get_preset` |
| `POST` | `/presets` | `create_preset` |
| `POST` | `/presets/setup-trigger` | `setup_trigger` |
| `PATCH` | `/presets/{preset_id}` | `update_preset` |
| `DELETE` | `/presets/{preset_id}` | `delete_preset` |
| `POST` | `/presets/{preset_id}/execute` | `execute_preset` |

**External API v1 (`/external-api/v1`)**

| Method | Path | Permission / scope |
|---|---|---|
| `GET` | `/me` | `IDENTITY` |
| `GET` | `/blocks` | `READ_BLOCK` |
| `POST` | `/blocks/{block_id}/execute` | `EXECUTE_BLOCK` |
| `POST` | `/graphs` | `WRITE_GRAPH` + `WRITE_LIBRARY` |
| `POST` | `/graphs/{graph_id}/execute/{graph_version}` | `EXECUTE_GRAPH` — body `{"node_input": {...}}`, returns `{"id": "<graph_exec_id>"}` |
| `GET` | `/graphs/{graph_id}/executions/{graph_exec_id}/results` | `READ_GRAPH` — includes `status`; 404 if graph or execution missing |
| `GET` | `/store/agents` | Any valid auth (public data; auth required as anti-DDoS) |
| `GET` | `/store/agents/{username}/{agent_name}` | Any valid auth |
| `GET` | `/store/creators` | Any valid auth |
| `GET` | `/store/creators/{username}` | Any valid auth |

Additional External API modules (`external/v1/integrations.py`, `tools.py`) expose integration management and platform tools under the same prefix; the Swagger UI at `/external-api/docs` is authoritative.

Export the OpenAPI schema:

```bash
cd autogpt_platform/backend
poetry run export-api-schema --output openapi.json --pretty
```

The frontend's typed client is generated with `pnpm generate:api-client`.

**Error semantics:** `400` bad parameters, `401` invalid/expired credentials, `403` missing scope/permission, `404` missing resource, `429` AutoPilot spend cap, `503` paywall tier-lookup failure on execute.

### 9.2 Webhooks

**Inbound triggers.** A webhook-triggered block declares `webhook_config` (`BlockWebhookConfig`), an **event filter** input (a Pydantic model of booleans whose field name matches `webhook_config.event_filter_input`), a `payload` input, and a `credentials` input with scopes sufficient to manage webhooks. A provider's `WebhooksManager` (subclass of `BaseWebhooksManager` in `integrations/webhooks/_base.py`) registers/deregisters webhooks with the provider and validates payloads; register it in `load_webhook_managers` in `integrations/webhooks/__init__.py`. Webhook records live in `IntegrationWebhook`.

Generic ingress URL:

```text
https://<backend>/api/integrations/generic_webhook/webhooks/<webhook-id>/ingress
```

> [!IMPORTANT]
> Override `verify_signature` with `hmac.compare_digest` for any provider that signs deliveries and raise `fastapi.HTTPException(403)` on failure. For unsigned providers, the webhook UUID is a bearer secret — document that.

**Stripe billing webhooks** use `STRIPE_WEBHOOK_SECRET`; **Postmark** webhooks use `POSTMARK_WEBHOOK_TOKEN`.

**Chat-bot webhooks.** `copilot/bot/` implements a connector taxonomy:

| Adapter type | Platforms | Runs in |
|---|---|---|
| `SocketAdapter` | Discord Gateway, Slack Socket Mode | `copilot-bot` process (`start`/`stop`) |
| `WebhookAdapter` | Slack Events API, Telegram, Microsoft Teams (WhatsApp next) | Mounted on the main REST API via `register_webhook_adapters` |

Factories: `app.py::_build_socket_adapters`, `webhook_routes.py::_build_webhook_adapters` — each gated on its tokens being set. Messaging: link check → **Link Account** prompt or thread creation → Redis-subscribed thread (7-day TTL) → streamed AutoPilot reply chunked at `chunk_flush_at` → messages arriving mid-stream batched into one follow-up turn.

Teams specifics: signed Bot Framework JWT (auth the sender, not the body — `serviceurl` claim binding closes replay); replies go to the activity's `serviceUrl` (host-allowlisted); single-tenant only (`MICROSOFT_TENANT_ID` mandatory since 2025-07-31); no native slash commands in channels (must @mention); group chats accepted and ignored; **at-most-once** delivery via a Redis activity-id claim; the learned `serviceUrl` map is per process. Local testing with the M365 Agents Playground requires both `APP_ENV=local` and `AUTOPILOT_BOT_TEAMS_ALLOW_UNVERIFIED=true`.

### 9.3 Plugin / Extension System: blocks

Blocks are the extension point. Every `.py` under `backend/blocks/` is auto-imported at startup (except `__init__.py` and `test_*` files); no registration needed.

**Block anatomy**

| Element | Requirement |
|---|---|
| Class | Subclass `Block` (`backend.blocks._base` or `backend.sdk`) |
| `Input` | `BlockSchemaInput` with `SchemaField(...)` fields (`advanced=True` hides from basic UI; `ge`/`le` constraints) |
| `Output` | `BlockSchemaOutput` (the `error` pin is predefined) |
| `id` | A real `uuid.uuid4()` — never invented |
| `categories` | `BlockCategory` values (e.g. `SEARCH`, `AI`, `PRODUCTIVITY`) |
| `run()` | `async def run(self, input_data, *, credentials=..., **kwargs) -> BlockOutput`, yielding `(name, value)` |
| Tests | `test_input`, `test_output` (values or types), `test_mock`, `test_credentials` |

**Runtime kwargs available to `run()`:** `user_id`, `graph_id`, `graph_exec_id`, `node_exec_id`, `node_id`, `execution_context` (`ExecutionContext`: user_id, graph_exec_id, workspace_id, session_id — required for file handling).

**Yield rules:** prefer one result object at a time; for lists, yield each item **and** optionally the whole list. Yielding `error` stops the node and marks it failed; prefer raising.

**Errors:** raise `BlockInputError` (invalid input) or `BlockExecutionError` (user-addressable runtime failure) from `backend.util.exceptions`; bare `ValueError` is auto-wrapped. Don't wrap logic in blanket try/except, and don't catch infrastructure failures (out of credits, DB down, internal 5xx) — let them surface.

**Files:** `store_media_file(file=..., execution_context=..., return_format=...)`:

| `return_format` | Returns | Use |
|---|---|---|
| `for_local_processing` | Local path | ffmpeg, MoviePy, PIL |
| `for_external_api` | Data URI | Replicate, OpenAI |
| `for_block_output` | `workspace://` in AutoPilot, data URI in graphs | **Always** for outputs |

**Credentials**

| Pattern | How |
|---|---|
| API key only (recommended) | Add `ProviderName` member → `_auth.py` (`CredentialsMetaInput[Literal[ProviderName.X], Literal["api_key"]]`, a field factory, `TEST_CREDENTIALS`, `TEST_CREDENTIALS_INPUT`) → `_config.py` with `ProviderBuilder(...).with_description(...).with_supported_auth_types("api_key").build()` |
| SDK provider | `ProviderBuilder("x").with_api_key(ENV, title).with_base_cost(1, BlockCostType.RUN).build()`; fields via `provider.credentials_field()` |
| OAuth2 | `OAuthHandler` subclass of `BaseOAuthHandler`, added to `HANDLERS_BY_NAME` (`integrations/oauth/__init__.py`) and `{provider}_client_id/secret` in `Secrets`; or `.with_oauth(Handler, scopes=..., client_id_env_var=..., client_secret_env_var=...)` |
| Multiple credentials | Field names end with `_credentials` and match `run()` parameter names |
| Other | `.with_user_password()`, `.with_webhook_manager()` |

Use `credentials.auth_header()` or `credentials.api_key.get_secret_value()` / `credentials.access_token.get_secret_value()`. Blocks that need OAuth with no API-key fallback should disable themselves when OAuth isn't configured (Google uses `GOOGLE_OAUTH_IS_CONFIGURED`).

**Frontend:** add `frontend/public/integrations/{provider_name}.png` (missing icon = broken image in the Blocks menu). Provider names, descriptions, and auth types are fetched from `GET /integrations/providers` — no frontend registration needed.

**Networking:** always use `backend.util.request.Requests` / `backend.sdk.Requests` (SSRF protection).

**Docs sync (CI-enforced):**

```bash
cd autogpt_platform/backend
poetry run python scripts/generate_block_docs.py
```

Writes `docs/integrations/block-integrations/{provider}/{module}.md`; the "Block Documentation Sync Check" workflow fails PRs whose docs drift. Preserve hand-written content between `<!-- MANUAL: use_case -->` … `<!-- END MANUAL -->` (sections `how_it_works`, `use_case`, `extras`, `file_description`, `additional_content`).

**Integration checklist**

- [ ] Provider name registered (`ProviderName` or `ProviderBuilder`)
- [ ] Credentials in `_auth.py` / SDK config; description + auth types in `_config.py`
- [ ] Input/Output schemas, real UUID, categories, async `run()`
- [ ] `BlockInputError` / `BlockExecutionError` for user-fixable errors
- [ ] Inline tests and/or `{name}_test.py`
- [ ] Icon PNG added
- [ ] Block docs regenerated

Reference implementations: `blocks/firecrawl/` (simple API key), `blocks/linear/` (OAuth + API key), `blocks/exa/` (webhooks), `blocks/github/` (triggers + OAuth), `blocks/zerobounce/` (`_auth.py`/`_config.py` pattern), `blocks/typesafe/` and `blocks/slant3d/` (documented provider packages).

### 9.4 LLM model catalog

Models, costs, and AutoPilot routing are catalog-as-code in `backend/data/llm_registry/catalog.py`. Block-selectable models also need a line in `backend/data/llm_registry/llm_models.py` (an import-time check enforces pairing). Catalog-only diffs may ship via `hotfix/*` → `master`.

### 9.5 MCP integrations

Hosted MCP services appear in **Settings → Integrations** with an **MCP** badge; catalog maintenance is documented in `docs/platform/contributing/hosted-mcp-catalog.md`. Open issue #15032 notes that AutoPilot's `find_capability` doesn't yet search user-connected custom MCP servers.

---

## 10. Developer Internals

### 10.1 Branching and contribution

| Rule | Detail |
|---|---|
| Main dev branch | `dev` (all PRs target it) |
| Production | `master` |
| Hotfix exception | LLM-catalog-only diffs via `hotfix/*` → `master` |
| PR structure | Why / What / How; one concern per PR; conventional commits |
| TDD | Failing test first (`@pytest.mark.xfail` / Playwright `.fixme`), then fix, then remove marker |

### 10.2 Backend code conventions (from `backend/AGENTS.md`)

- Top-level, absolute imports (single-dot relative allowed within a package).
- No duck typing for dispatch; Pydantic models over dicts/dataclasses.
- No linter suppressors (`# type: ignore`, `# noqa`).
- Files under ~300 lines; functions under ~40 lines; top-down ordering.
- `Security()` (not `Depends()`) for auth dependencies so OpenAPI shows security.
- Redis pipelines with `transaction=True` for multi-step atomicity; TOCTOU awareness for file access and credit charging; `max(0, value)` guards.
- SSE: `data:` lines for parsed events (must match the frontend Zod schema), `: comment` lines for heartbeats.

### 10.3 Key classes

| Class | Module | Role |
|---|---|---|
| `AgentServer` | `api/rest_api.py` | REST process |
| `WebsocketServer` | `api/ws_api.py` | WebSocket process |
| `ExecutionManager`, `Scheduler` | `executor/` | Graph execution and scheduling |
| `BatchExecutor` | `executor/batch_executor.py` | LLM batch polling |
| `DatabaseManager` | `data/db_manager.py` | DB RPC service |
| `NotificationManager` | `notifications/notifications.py` | Email/push |
| `PlatformLinkingManager` | `platform_linking/manager.py` | Chat account linking |
| `CoPilotExecutor` / `CoPilotChatBridge` | `copilot/executor/manager.py` / `copilot/bot/app.py` | AutoPilot turns / chat bridge |
| `SecurityHeadersMiddleware` | `api/middleware/security.py` | Cache-protection headers |
| `Block`, `BlockSchemaInput`, `BlockSchemaOutput`, `BlockWebhookConfig` | `blocks/_base.py` | Block framework |
| `ProviderBuilder` | `sdk/` | Provider registration |
| `BaseOAuthHandler` / `BaseWebhooksManager` | `integrations/oauth/base.py` / `integrations/webhooks/_base.py` | Extension interfaces |
| `Config`, `Secrets` | `util/settings.py` | Settings |

### 10.4 Classic AutoGPT internals

| Part | Summary |
|---|---|
| `forge/` | Component-based agent framework: `BaseAgent` auto-discovers `AgentComponent` attributes; `ConfigurableComponent[BM]` for Pydantic-configured components; built-in components (file manager, action history, watchdog, system, archive handler, data processor) |
| `original_autogpt/` | CLI (`autogpt run/serve/config`) and Agent Protocol server (`AP_SERVER_PORT`, `AP_SERVER_DB_URL`, `AP_SERVER_CORS_ALLOWED_ORIGINS`) |
| `direct_benchmark/` | Benchmark harness (strategies, models, categories, external benchmarks `gaia`/`swe-bench`/`agent-bench`) |
| Config | `.env` → `.autogpt/autogpt.yaml` → `.autogpt/agents/{id}/permissions.yaml`; `SMART_LLM` (`gpt-4o`), `FAST_LLM` (`gpt-4o-mini`), `EMBEDDING_MODEL`, `DATABASE_STRING` (`sqlite:///agent.db`), `PORT` (`8000`), `FILE_STORAGE_BACKEND` (`local`/`s3`/`gcs`), `DISABLED_COMMANDS`, `RESTRICT_TO_WORKSPACE` (`True`), `TELEMETRY_OPT_IN` |
| Permissions | `command_name(glob)` patterns; `{workspace}`, `**`, `*`; agent deny → workspace deny → agent allow → workspace allow → prompt |
| URL safety | `forge/utils/url_validator.py` blocks local files and private addresses |

> [!CAUTION]
> Classic has known vulnerabilities in its dependencies and will not be updated. Don't deploy it on a network-reachable host.

### 10.5 Testing Strategy

| Layer | Tooling | Command |
|---|---|---|
| Backend unit/integration | pytest, colocated `*_test.py`, snapshot tests | `poetry run test` or `poetry run pytest path::test -xvs` |
| Snapshots | pytest snapshot | `poetry run pytest path --snapshot-update` (review diffs) |
| All blocks | Inline block fixtures | `poetry run pytest backend/blocks/test/test_block.py -xvs` |
| One block | | `poetry run pytest 'backend/blocks/test/test_block.py::test_available_blocks[MyBlock]' -xvs` |
| Block helper | `execute_block_test(block)` | In a `*_test.py` |
| Frontend | `pnpm test`; Playwright E2E (`PLAYWRIGHT_BASE_URL`, `PLAYWRIGHT_WORKERS`, `PLAYWRIGHT_RETRIES`) | `pnpm test` |
| Load | k6 orchestrator (12 tests; local 5 VUs × 30 s, cloud 80–160 VUs × 3–6 min) | `node orchestrator/orchestrator.js DEV local` |
| Appliance | `single-container/tests/` (incl. `test_runtime_config.py` enforcing blank secrets) | pytest |
| AutoPilot quality | Expert style eval | `poetry run expert-style-eval --dry-run` / `--write-baseline` (paid, roughly $1–2 per expert) |
| Classic | pytest | `poetry run pytest forge/tests/` etc. |

Mocking rule: patch where a symbol is **used** (e.g. `backend.blocks.my_provider.my_block.Requests.post`), use `AsyncMock` for async. Block test files ending `_test.py` are auto-imported at startup — keep them side-effect-free, or name them `test_*.py` to opt out.

---

## 11. Troubleshooting

### Boot and configuration

### Error: `ENCRYPTION_KEY is not set`

**Cause:** Blank `ENCRYPTION_KEY`. **Solution:** `make init-env`, restart. **Prevention:** Include `make init-env` in provisioning.

### Error: `ENCRYPTION_KEY is set to a value that was published…`

**Cause:** Pre-v0.7.4 default key. **Solution:** Run the secret-generation upgrade with `OLD_ENCRYPTION_KEY` and `cli rotate-encryption-key --apply`.

### Error: `ENCRYPTION_KEY is not set, so there is no key to re-encrypt under.`

**Cause:** `rotate-encryption-key` run before a new key was set. **Solution:** Set the new `ENCRYPTION_KEY` first.

### Error: `--old-key is the configured ENCRYPTION_KEY. Change ENCRYPTION_KEY first, then pass the previous value here.`

**Solution:** Put the **new** key in `ENCRYPTION_KEY`; pass the **old** one via `--old-key`/`OLD_ENCRYPTION_KEY`.

### Error: `Not a valid encryption key: ...`

**Solution:** Generate a Fernet key: `poetry run cli gen-encrypt-key`.

### Error: `ENCRYPTION_KEY must be a Fernet-compatible key` / `must decode to exactly 32 bytes`

**Solution:** Same — use a Fernet key, not an arbitrary string.

### Error: `JWT_JWKS_URL must be set to the JWKS endpoint of the platform auth service ...`

**Solution:** Set `JWT_JWKS_URL` (e.g. `http://frontend:3000/api/auth/jwks` inside Compose).

### Error: `JWT_JWKS_URL fetches keys over cleartext http:// from a non-local host ('...')`

**Solution:** Use HTTPS, or on a trusted isolated network set `JWKS_ALLOW_INSECURE_TRANSPORT=true`.

### Error: `Invalid JWT_JWKS_ALGORITHMS entry: '...'`

**Solution:** Only asymmetric algorithms (`ES256`, `RS256`, `EdDSA`).

### Error: `'cryptography' package is required for JWT verification but not installed`

**Solution:** Reinstall backend dependencies (`poetry install`).

### Error: `Production environment cannot allow localhost origins`

Also `... via regex: <pattern>` and `Production regex pattern matches localhost/127.0.0.1: <pattern>`.
**Solution:** Remove localhost entries from `BACKEND_CORS_ALLOW_ORIGINS` in production.

### Error: `<Class>.expected_token is not set; either specify it or provide a custom validator`

**Cause:** An API-key-protected internal route has no expected token configured. **Solution:** Set the corresponding token (e.g. `POSTMARK_WEBHOOK_TOKEN` for Postmark webhooks).

### Error: `Database is not connected` (HTTP 5xx from `/health`)

**Cause:** Postgres unreachable or wrong `DATABASE_URL`. **Solution:** Check `db` health and credentials; confirm `migrate` completed.

### Error: `the specified Containerfile or Dockerfile does not exist`

**Cause:** Podman. **Solution:** Use Docker.

### `db` unhealthy on Windows

**Solution:** Switch Docker Desktop to the WSL 2 engine.

### Nobody can reach the backend after rotating `BETTER_AUTH_SECRET`

**Solution:** `DELETE FROM platform."UserAuthJwks";` then restart; keys regenerate on next sign-in.

### Credentials marked revoked after starting on a new key

**Solution:** Stop, run `rotate-encryption-key --apply`, restart; users who reconnected in between must reconnect older credentials.

### Supabase data directory restart-loops on `pgvector/pgvector:pg15`

**Solution:** Use the dump/restore fallback; drop the `auth` shim schema before `pg_restore`; verify `select count(*) from auth.users` before migrating.

### Frontend (outside Docker) can't authenticate

**Cause:** Next.js reads only `.env`. **Solution:** `make init-env`; ensure `DATABASE_URL` and `BETTER_AUTH_SECRET` are in `frontend/.env`.

### Appliance runtime configuration

### Error: `AUTOGPT_PUBLIC_URL must use http or https`

Siblings: `must include a host`, `must not include credentials`, `must be an origin without path, query, or fragment`, `has an invalid port`, `has an invalid host`. **Solution:** A bare origin.

### Error: `<NAME> differs from the value persisted on first boot; restore the original value or start with a new data volume`

**Solution:** Remove the override; generated secrets cannot be rotated in place.

### Error: `runtime configuration has missing or unknown keys` / `unsupported runtime configuration version` / `invalid runtime configuration line N`

**Cause:** `/data/config/runtime.env` was edited or is from an incompatible version. **Solution:** Don't edit `runtime.env`; restore it from backup or start a new volume.

### Error: `runtime configuration is not a regular file: <path>` / `refusing symlink at <path>`

**Solution:** Replace the symlink/special file with the original regular file.

### Error: `RABBITMQ_DEFAULT_USER may not use the built-in guest account` / `contains unsupported characters`

**Solution:** Pick another username.

### Error: `<NAME> must be at least 32 URL-safe characters` / `invalid URL-safe base64 value`

**Solution:** Provide a longer URL-safe value, or let the appliance generate it.

### Error: `VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY must be set together`

Also `VAPID_PRIVATE_KEY must decode to exactly 32 bytes`, `VAPID_PUBLIC_KEY must be an uncompressed P-256 public key`, `cryptography is required to generate VAPID keys`. **Solution:** Generate a proper pair or set neither.

### Startup rejects `DB_CONNECTION_LIMIT` / `DB_CONNECT_TIMEOUT` / `DB_POOL_TIMEOUT`

**Solution:** Ranges `1`–`5`, `1`–`600`, `1`–`3600`.

### Startup rejects legacy JWT secrets

**Solution:** Remove `JWT_VERIFY_KEY` and `SUPABASE_JWT_SECRET` on fresh installs; for legacy migration set `AUTOGPT_ENABLE_LEGACY_AUTH=true` and both to the same ≥32-character secret.

### Startup stops because email verification is enabled

**Solution:** `AUTH_REQUIRE_EMAIL_VERIFICATION=false` (unsupported in the appliance).

### Health probe errors: `AMQP credentials are missing` / `AMQP connection failed`

**Solution:** Check RabbitMQ state in `/data/rabbitmq` and logs for the `state:rabbitmq` process.

### Health probe errors: `Redis authentication failed` / `Redis cluster is not healthy` / `Redis did not return PONG` / `Redis closed the connection`

**Solution:** Inspect Valkey process logs; confirm the three nodes (`17000`–`17002`) started; check disk space in `/data/valkey`.

### Health probe error: `HTTP <status> from <url>`

**Solution:** The named internal HTTP service is failing; read its supervisor log.

### Supervisor errors: `Supervisor event payload is too large` / `ended unexpectedly` / `has an unsupported type` / `has an invalid payload length`

**Cause:** The fatal-event listener received malformed events. **Solution:** Restart the container; if recurring, capture logs for an issue report.

### Container stays `starting` / becomes `unhealthy`

**Solution:** `docker exec autogpt autogpt-healthcheck`, then the logs for the first failed service. The watchdog stops the container after 600 s.

### OOM-killed during startup

**Solution:** More memory; the appliance needs more than 5–6 GiB.

### Interrupted migration blocks startup

**Solution:** Empty install → discard volume; existing → restore backup. `prisma migrate resolve` only from a separate recovery environment after verifying the DB state.

### Data missing after replacing the container

**Solution:** Reattach the original named volume (check with the `/data` mount inspect command).

### Backup refuses to run

Messages: `Refusing backup because /data uses an anonymous volume`, `... another backup may be running or its lock is stale`, `... the autogpt container is not running`, `Refusing to overwrite an existing backup`. **Solution:** Use a named volume; clear a stale `autogpt-backup-lock` only after confirming no backup is running; start and verify health first.

### Restore refuses to run

Messages: `Backup checksum verification failed`, `Refusing to reuse existing volume`, `... ownership cannot be verified`. **Solution:** Use a trusted archive/checksum pair and a fresh volume name.

### Runtime and capacity

### Runs stuck in `RUNNING` after restart

**Solution:** Start new runs; abandoned runs never resume.

### Requests stall under concurrent runs

**Solution:** Check for pool exhaustion; reduce concurrency or scale out (appliance pools are capped at 5).

### Valkey or FalkorDB memory/disk keeps growing

**Cause:** No appliance-level ceiling or eviction. **Solution:** Monitor, back up, and move to a distributed deployment for growth.

### `/setup` fails with `httpx.ConnectError`

**Solution:** Start `platform_linking_manager` (`--profile bot`).

### Teams bot replies 404 for some tenants

**Cause:** Per-process `serviceUrl` map; a cold replica falls back to the default host. **Solution:** Keep replies reactive; persist the mapping in Redis before enabling proactive sends.

### Teams bot drops a turn

**Cause:** At-most-once delivery (claim before processing). **Solution:** Ask the user to resend; release the claim on failure if you prefer retries over duplicates.

### AutoPilot `401` / `429`

**Solution:** `401` → set the transport's key (`OPEN_ROUTER_API_KEY` for the default route, `OPENAI_API_KEY` for memory); `429` → spend cap reached.

### Local memory ingestion fails

**Solution:** Pull the embedding model; set `GRAPHITI_LLM_MODEL`, `GRAPHITI_RERANKER_MODEL`, `GRAPHITI_EMBEDDER_MODEL` to slugs your server serves.

### `ValueError: graph_exec #<id> not found for user #<user>` (WebSocket subscribe)

**Cause:** Client subscribed to an execution it doesn't own or that doesn't exist. **Solution:** Verify the execution ID and user.

### `ValueError: Execution <id> not found`

**Solution:** The execution was deleted or belongs to another user.

### Schedule fires after its expert workflow was removed

**Cause:** Open issue #14607. **Solution:** Delete the schedule manually.

### Development

### Block shows a broken icon

**Solution:** Add `frontend/public/integrations/{provider}.png`.

### CI "Block Documentation Sync Check" fails

**Solution:** `poetry run python scripts/generate_block_docs.py`, commit.

### Block test fails with a type error on `credentials`

**Cause:** Missing `CredentialsMetaInput[Literal[ProviderName.X], Literal["api_key"]]` type hint. **Solution:** Add the full type hint.

### Block module side effects run at startup

**Cause:** `*_test.py` files are auto-imported. **Solution:** Keep them side-effect-free or rename to `test_*.py`.

### `prisma generate` writes to the global interpreter

**Solution:** `pip uninstall prisma`, rerun `poetry run prisma generate`.

### OAuth tool: `At least one scope is required` / `Invalid scope: ...`

**Solution:** Pass `--scopes` with valid scope names (e.g. `EXECUTE_GRAPH,READ_GRAPH`).

### CLI: `Invalid --range '...'; use forms like '7', '6-10', '6-', '-10'.` / `start is after end`

**Solution:** Fix the `cli chat view` range.

### Load tests: `No valid authentication token available` / `Token has expired`

**Solution:** `node generate-tokens.js --count=160` (24-hour tokens).

### Load tests show 5 VUs in k6 cloud

**Solution:** `k6 cloud run --env K6_ENVIRONMENT=DEV --env VUS=160 test.js`.

### Direct benchmark: `Unknown benchmark: <name>. Available: ...` / `Workspace not initialized`

**Solution:** Use `gaia`, `swe-bench`, or `agent-bench`; pass a valid `--workspace`.

---

## 12. Potential Improvements

| Area | Observation | Suggestion |
|---|---|---|
| Scheduling correctness | Issue #14607: a schedule survives removal of its workflow from an expert because execution doesn't check membership | Validate expert-workflow membership at fire time; delete schedules on workflow removal |
| Block authoring | Issue #15025: no scaffolder; block ID collisions are possible and the pairing of structural and dynamic tests is manual | Add a generator that mints the UUID, checks collisions, and creates tests + docs stubs |
| AutoPilot discovery | Issues #15032 / #14829: `find_capability` doesn't search user-connected custom MCP servers (skills search was a prior gap) | Index connected MCP tools in the capability search |
| Appliance resource bounds | Valkey and FalkorDB have no memory ceiling/eviction | Ship default `maxmemory` policies and alerts |
| Appliance backup | Cold backup only; coupled volume | Offer per-service hot backup (e.g. `pg_basebackup`) for larger installs |
| Appliance secrets | No in-place rotation of generated secrets | Add a supported rotation command mirroring `rotate-encryption-key` |
| Appliance uploads | No malware scanning | Optional bundled ClamAV profile |
| Email verification | Unsupported in the appliance | Support required verification via Postmark |
| Appliance → distributed migration | No in-place conversion; accounts, runs, schedules, credentials, memory aren't migrated | Provide an export/import path for full state |
| Supabase upgrade | Both data-migration routes are unvalidated against real old volumes | Add an automated, tested migration tool |
| Teams bot | At-most-once delivery; per-process `serviceUrl` map | Persist mapping in Redis; configurable retry semantics |
| Published dev keys | `.env.default` still ships a dev VAPID keypair and a RabbitMQ dev password | Move them to `BLANK_IN_ENV_DEFAULT` and generate at `init-env` |
| Pool sizing | Per-role pools multiply connections; appliance capped at 5 | Add pgbouncer or a shared pool for the appliance |
| In-flight runs | Abandoned on shutdown; rows remain `RUNNING` | Reconcile orphaned executions on startup |
| Classic | Unsupported with known vulnerable dependencies | Archive into a separate repository or pin/scan dependencies |
| Hosted installer | Not yet live; README points to the manual path | Complete release gates for the appliance installer |

---

## 13. Quick Reference

**Paths**

| Item | Path |
|---|---|
| Compose project | `autogpt_platform/` |
| Backend env | `autogpt_platform/backend/.env` |
| Frontend env | `autogpt_platform/frontend/.env` |
| Postgres data (Compose) | `autogpt_platform/data/db/data` |
| Old Supabase data | `autogpt_platform/db/docker/volumes/db/data` |
| Blocks | `autogpt_platform/backend/backend/blocks/` |
| Settings model | `autogpt_platform/backend/backend/util/settings.py` |
| Prisma schema | `autogpt_platform/backend/schema.prisma` |
| Model catalog | `autogpt_platform/backend/backend/data/llm_registry/catalog.py` |
| Appliance secrets | `/data/config/runtime.env` |

**Ports**

| Port | Service |
|---|---|
| 3000 | Frontend / appliance nginx |
| 8001 | WebSocket |
| 8002 | Executor |
| 8003 | Scheduler |
| 8005 | Database manager |
| 8006 | REST API |
| 8007 | Notifications |
| 8008 | AutoPilot executor |
| 8009 | Platform linking manager |
| 8010 / 8011 | Chat bridge / batch executor (config defaults) |
| 5432 | PostgreSQL |
| 5672 | RabbitMQ |
| 17000–17002 | Redis cluster |
| 6380 / 3001 | FalkorDB / FalkorDB UI |
| 3310 | ClamAV |

**Commands**

| Task | Command |
|---|---|
| Initialize env | `make init-env` |
| Start stack | `docker compose up -d --build` |
| Migrate | `docker compose run --rm migrate` |
| Health | `curl http://localhost:8006/health` |
| New encryption key | `poetry run cli gen-encrypt-key` |
| Rotate key | `docker compose run --rm --no-deps -e OLD_ENCRYPTION_KEY rest_server cli rotate-encryption-key --apply` |
| Reset JWKS | `docker compose exec db psql -U postgres -c 'DELETE FROM platform."UserAuthJwks";'` |
| Register OAuth app | `poetry run oauth-tool generate-app --name ... --redirect-uris ... --scopes ...` |
| Export OpenAPI | `poetry run export-api-schema --output openapi.json --pretty` |
| Test all blocks | `poetry run pytest backend/blocks/test/test_block.py -xvs` |
| Regenerate block docs | `poetry run python scripts/generate_block_docs.py` |
| Appliance admin | `docker exec autogpt autogpt-admin promote EMAIL` |
| Appliance health | `docker exec autogpt autogpt-healthcheck` |
| Appliance processes | `docker exec autogpt supervisorctl status` |

**Critical settings**

| Setting | Value |
|---|---|
| `ENCRYPTION_KEY` | Generated Fernet key — back it up |
| `JWT_JWKS_URL` | `https://<frontend>/api/auth/jwks` when split |
| `BEHAVE_AS` | `local` single-tenant / `cloud` multi-tenant |
| `AUTOGPT_PUBLIC_URL` | Exact browser origin |
| `AUTH_ALLOW_NEW_ACCOUNTS` | `false` after bootstrap |
| `DB_CONNECTION_LIMIT` | ≤ 5 on the appliance |

---

_Generated September 2026 from Significant-Gravitas/AutoGPT at c035ab1. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
