# Paperclip Administrator & Developer Guide

This guide is the operational and architectural reference for **Paperclip** — a self-hosted platform that orchestrates teams of AI coding agents to do work autonomously. It covers the system architecture, every configurable subsystem, deployment topologies, security posture, the data model, the API surface, and the internals a developer would need to extend or operate the platform.

It is written against source commit `03e1e3a` (package version `0.3.1`, MIT). For end-user/operator tasks (hiring agents, filing issues, running the CLI day-to-day), see the **Paperclip User Guide**; this document assumes that material and goes deeper.

---

## 1. Executive Summary

Paperclip is a **single-deployment control plane for an AI workforce**. Operators stand up one server that hosts:

- a **REST API** (Express) with ~300+ endpoints and an auto-generated OpenAPI document,
- a **React/Vite single-page UI** served from the same process,
- a **`paperclipai` CLI** for setup, operations, and developer worktrees,
- an **embedded PostgreSQL** (or an external one) accessed through Drizzle ORM,
- a **plugin runtime** (sandboxed JSON-RPC workers) and a **plugin SDK**,
- an **MCP server** so external LLM clients can drive the instance, and
- a fleet of **interchangeable agent adapters** and **sandbox providers**.

The mental model is a company: **companies** contain **agents** (backed by **adapters**) that work **issues** in **environments/sandboxes**, governed by **budgets**, **approvals**, **routines**, **secrets**, and an **org chart**.

| Attribute | Value |
|-----------|-------|
| Language | TypeScript (ESM), Node ≥ 20 |
| Monorepo | pnpm workspaces (`pnpm@9.15.4`) |
| Server | Express + OpenAPI registry + Zod |
| Persistence | PostgreSQL via Drizzle ORM (embedded `embedded-postgres`, or external) |
| Auth | better-auth (sessions) + API keys + agent JWTs |
| UI | React 19 + Vite (+ Storybook, 40-locale i18n) |
| Default port | `3100` |
| Distribution | npm package `paperclipai` (CLI), plus publishable workspace packages |

---

## 2. Architecture

### 2.1 Component diagram

```
                          ┌───────────────────────────────────────────────┐
   Browser ──HTTP/WS────► │                Paperclip Server                │
   (React SPA, Vite)      │                  (Express)                     │
                          │                                                │
   CLI ──────HTTP───────► │   middleware/ ─► routes/ ─► services/          │
   (paperclipai)          │     (auth,        (40 files)   (117 files:     │
                          │      logging,                    agents,        │
   MCP client ─stdio────► │      redaction)                  issues,        │
   (paperclip-mcp-server) │                                  runs,          │
                          │                                  plugins,       │
                          │                                  costs, …)      │
                          │        │              │              │          │
                          │   Drizzle ORM     storage (local/S3)  secrets   │
                          │        │                                        │
                          └────────┼────────────────────────────────────────┘
                                   ▼
                            PostgreSQL (embedded or external; 86 schema files)

   Agent execution (dispatched by services through adapters):
   ┌───────────────────────────── adapters/ ─────────────────────────────┐
   │ claude-local · codex-local · cursor-local · cursor-cloud ·           │
   │ gemini-local · grok-local · opencode-local · pi-local ·              │
   │ acpx-local · openclaw-gateway        (each: index/server/ui/cli)     │
   └──────────────────────────────────────────────────────────────────────┘

   Execution surface (sandbox provider plugins + bridge):
   ┌──────────────── sandbox-providers/ ────────────────┐
   │ e2b · daytona · modal · cloudflare · exe-dev ·      │
   │ fake-sandbox      ⇄  sandbox-callback bridge        │
   └─────────────────────────────────────────────────────┘
```

### 2.2 Monorepo layout

| Package | Path | Responsibility |
|---------|------|----------------|
| **CLI** | `cli/` | `paperclipai` binary (Commander). Onboarding, run, doctor, configure, db backup, worktrees, heartbeat, routines |
| **Server** | `server/` | Express API: `routes/` (40 files), `services/` (117 files), `middleware/`, `adapters/`, `storage/` |
| **UI** | `ui/` | React 19 + Vite SPA: `pages/` (84), `components/` (200), `api/` (40), `lib/` (162), `i18n/` (40 locales) |
| **Shared** | `packages/shared/` | Zod validators, shared types, telemetry, document-anchors |
| **DB** | `packages/db/` | Drizzle schema (86 files), 94 migrations, embedded-postgres, backup/restore |
| **Adapter Utils** | `packages/adapter-utils/` | Execution targets, sandbox-callback bridge, SSH, server utils — the shared runtime adapters build on |
| **Adapters** | `packages/adapters/*` | 10 agent adapters (claude/codex/cursor-local/cursor-cloud/gemini/grok/opencode/pi/acpx/openclaw-gateway) |
| **Plugin SDK** | `packages/plugins/sdk/` | Plugin protocol (JSON-RPC), host-client factory, capability model, UI kit |
| **Sandbox providers** | `packages/plugins/sandbox-providers/*` | e2b, daytona, modal, cloudflare (+bridge-template), exe-dev |
| **Example/utility plugins** | `packages/plugins/*` | workspace-diff, llm-wiki, file-browser, hello-world, kitchen-sink, orchestration-smoke, fake-sandbox, `create-paperclip-plugin` |
| **MCP server** | `packages/mcp-server/` | `paperclip-mcp-server` bin (MCP SDK + Zod) |
| **Skills catalog** | `packages/skills-catalog/` | Bundled + optional agent skills, catalog builder |

All workspace packages publish under the `@paperclipai/*` scope; the CLI publishes as `paperclipai`. Internal dependencies use `workspace:*` so the CLI bundles the adapters, db, server, and shared packages it needs.

### 2.3 Request lifecycle

1. A request reaches Express; middleware runs first — request logging, **log redaction** (`server/src/log-redaction.ts` scrubs user/host identifiers), and **authentication** (`middleware/auth.ts`).
2. Auth resolves the **actor** — a board (human) user, an agent, or anonymous — from a session cookie, an API key, an agent JWT, or a cloud-tenant token.
3. The matched route (`server/src/routes/*`) validates input with Zod and delegates to one or more services (`server/src/services/*`).
4. Services read/write PostgreSQL via Drizzle, persist files via the storage layer, and resolve credentials via the secrets manager.
5. Agent work is dispatched through the relevant **adapter**, which runs the underlying coding agent — locally or inside a **sandbox** via a provider plugin, mediated by the **callback bridge**.
6. Plugins receive events and expose tools over the **JSON-RPC plugin bridge**.

### 2.4 The adapter contract

Every adapter package exports four entry points so the same adapter can plug into each layer of the stack:

| Export | Consumed by |
|--------|-------------|
| `.` (index) | Core registration / capability metadata |
| `./server` | Server-side execution logic |
| `./ui` | UI surface (config form, model pickers) |
| `./cli` | CLI integration (e.g. quota probes) |

Adapters are registered in a server-side registry (`server/src/adapters/registry.ts`) and managed at runtime through `/api/adapters` — install, enable, reload, reinstall, and fetch a config schema. Unknown adapter references raise `Unknown adapter type: <type>`. Adapters can communicate over a child-**process** or **HTTP** transport (`server/src/adapters/process`, `server/src/adapters/http`).

> [!NOTE]
> Adapters are hot-manageable: `POST /adapters/:type/reload` re-reads configuration and `POST /adapters/:type/reinstall` performs a full teardown/re-init, both without restarting the server.

---

## 3. System Requirements

| Component | Requirement |
|-----------|-------------|
| Node.js | ≥ 20 |
| Package manager (dev) | pnpm `9.15.4` |
| PostgreSQL | Embedded (bundled `embedded-postgres`, 18.x beta) by default; external Postgres for production |
| OS | Linux / macOS / Windows (WSL recommended) |
| TypeScript (dev) | 5.7.x |
| Build tooling | esbuild (CLI/plugins), Vite (UI), drizzle-kit (migrations), tsc |

### Provider credentials

Agents and sandboxes need third-party credentials, supplied as environment variables or Paperclip secrets:

| Purpose | Variable(s) |
|---------|-------------|
| Anthropic / Claude | `ANTHROPIC_API_KEY` |
| OpenAI / Codex | `OPENAI_API_KEY` |
| E2B sandbox | `E2B_API_KEY` |
| Daytona sandbox | `DAYTONA_API_KEY` |
| Modal sandbox | `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET` |
| exe-dev sandbox | `EXE_API_KEY` |

---

## 4. Deployment Topologies

Paperclip is a **single-server** application; there is no built-in horizontal scaling (the embedded Postgres and bridge queue assume one process owns the state). Scale vertically and offload the database/storage for heavier loads.

### 4.1 Reachability and deployment posture

Two orthogonal axes control exposure. The default for a fresh instance is **`local_trusted` + `private`**.

| Axis | Env var | Values |
|------|---------|--------|
| Mode (auth) | `PAPERCLIP_DEPLOYMENT_MODE` | `local_trusted` (loopback, no login) · `authenticated` (login required) |
| Exposure | `PAPERCLIP_DEPLOYMENT_EXPOSURE` | `private` · `public` |
| Bind preset | `PAPERCLIP_BIND` / `--bind` | `loopback` · `lan` · `tailnet` |

The server enforces the safe combinations at startup: `local_trusted` **requires** loopback host binding and `private` exposure; `authenticated` + `public` **requires** an explicit public base URL. Anything reachable off-host should therefore run `authenticated`.

> [!CAUTION]
> Never run `local_trusted` mode off loopback — it performs no authentication, and the server will refuse to start (`local_trusted mode requires loopback host binding`). The onboarding wizard switches to `authenticated` automatically for `lan`/`tailnet` binds; preserve that when scripting deployments.

### 4.2 Local / single user

```bash
npx paperclipai onboard --yes --run        # local_trusted + loopback, embedded Postgres
```

Zero external dependencies — the embedded Postgres and local storage are provisioned automatically under `~/.paperclip`.

### 4.3 Docker (quickstart)

`docker/docker-compose.quickstart.yml` builds the image and runs the server on port **3100** with a bind-mounted data volume:

```bash
BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
  docker compose -f docker/docker-compose.quickstart.yml up --build
```

Key container environment (from the compose file):

| Var | Value |
|-----|-------|
| `HOST` | `0.0.0.0` |
| `PAPERCLIP_HOME` | `/paperclip` |
| `PAPERCLIP_DEPLOYMENT_MODE` | `authenticated` |
| `PAPERCLIP_DEPLOYMENT_EXPOSURE` | `private` |
| `BETTER_AUTH_SECRET` | **required** (compose fails fast without it) |

### 4.4 AWS ECS Fargate

`docker/ecs-task-definition.json` is a ready template for a public, authenticated deployment:

- Fargate task: **2 vCPU / 4 GB**, container port **3100**.
- Environment: `NODE_ENV=production`, `SERVE_UI=true`, `PAPERCLIP_DEPLOYMENT_MODE=authenticated`, `PAPERCLIP_DEPLOYMENT_EXPOSURE=public`, `PAPERCLIP_MIGRATION_AUTO_APPLY=true`, `HEARTBEAT_SCHEDULER_ENABLED=true`.
- Secrets pulled from **AWS Secrets Manager**: `DATABASE_URL`, `BETTER_AUTH_SECRET`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`.
- Persistent data on **EFS** (encrypted in transit) mounted at `/paperclip`.
- Health check: `curl -f http://localhost:3100/api/health`.
- Logs to CloudWatch (`/ecs/paperclip`).

> [!IMPORTANT]
> For public deployments, point `PAPERCLIP_PUBLIC_URL` (and, if you fix the auth base URL explicitly, `PAPERCLIP_AUTH_PUBLIC_BASE_URL` with `PAPERCLIP_AUTH_BASE_URL_MODE=explicit`) at the externally reachable URL, and set `BETTER_AUTH_TRUSTED_ORIGINS` for any additional front-end origins.

### 4.5 Podman / systemd (Quadlet)

`docker/quadlet/` provides Quadlet unit files for running the container as a systemd-managed service (Podman). Use this for rootless, auto-restarting host deployments without a full container orchestrator.

### 4.6 Untrusted review sandbox

`docker/docker-compose.untrusted-review.yml` + `docker/untrusted-review/Dockerfile` run a **hardened** container for reviewing agent-produced changes: `cap_drop: ALL`, `no-new-privileges`, a size-limited `tmpfs` for `/tmp`, and isolated volumes. Use it when you want to inspect or execute agent output away from any trusted context.

### 4.7 External PostgreSQL

```bash
export DATABASE_URL="postgresql://user:pass@db-host:5432/paperclip"
export PAPERCLIP_MIGRATION_AUTO_APPLY=true
paperclipai run
```

Provide `DATABASE_MIGRATION_URL` separately if migrations need a more privileged connection than the runtime user. Embedded Postgres data does **not** auto-migrate to an external database — dump and restore it deliberately.

### 4.8 Reverse proxy

Terminate TLS at nginx/Traefik/Caddy and proxy to the server port (3100), forwarding `Upgrade`/`Connection` headers for WebSocket and using long read timeouts (agent runs are long-lived). Set `PAPERCLIP_PUBLIC_URL` and `BETTER_AUTH_TRUSTED_ORIGINS` to the external origin.

### 4.9 Startup validation errors

The server validates the deployment configuration on boot and exits with a precise message if it is unsafe or incomplete:

| Error | Meaning / fix |
|-------|---------------|
| `local_trusted mode requires loopback host binding (received: <host>)` | You bound `local_trusted` to a non-loopback host. Bind loopback, or switch to `authenticated`. |
| `local_trusted mode only supports private exposure` | `local_trusted` + `public` is rejected. Use `authenticated` for public exposure. |
| `authenticated public exposure requires auth.baseUrlMode=explicit` | Set `PAPERCLIP_AUTH_BASE_URL_MODE=explicit`. |
| `auth.baseUrlMode=explicit requires auth.publicBaseUrl` | Set `PAPERCLIP_AUTH_PUBLIC_BASE_URL` to your public URL. |
| `reachable postgres does not use the expected embedded data directory` | Another Postgres occupies the embedded port. Stop it, change `PAPERCLIP_EMBEDDED_POSTGRES_PORT`, or set `DATABASE_URL`. |
| `Migrations are still pending after migration-history reconciliation` | Migration state couldn't auto-resolve. Back up, inspect the migrations history table, then resolve manually. |

> [!TIP]
> Run `paperclipai doctor` (with `--repair`) to surface and fix most of these before they block a start; `paperclipai env` prints the fully resolved deployment configuration so you can verify mode/exposure/host/port up front.

---

## 5. Configuration Reference

Configuration comes from environment variables plus a per-instance JSON config (`PAPERCLIP_CONFIG`, default under `~/.paperclip/instances/<id>/config.json`). The `onboard` wizard writes the config; `paperclipai configure -s <section>` edits sections (`llm`, `database`, `logging`, `server`, `storage`, `secrets`); `paperclipai env` prints the resolved environment. Environment variables override the file; the file overrides built-in defaults.

The tables below catalog the environment variables Paperclip reads, grouped by subsystem. Defaults are shown only where confirmed in source; where a default is environment-derived or set during onboarding, it is left blank — run `paperclipai env` to see the resolved value for your instance.

### 5.1 Core & server

| Variable | Default | Purpose |
|----------|---------|---------|
| `PAPERCLIP_HOME` | `~/.paperclip` | Root data directory |
| `PAPERCLIP_INSTANCE_ID` | `default` | Instance isolation key (validated; alphanumeric/dash) |
| `PAPERCLIP_CONFIG` | under `PAPERCLIP_HOME` | Path to the instance JSON config |
| `PAPERCLIP_CONTEXT` / profile | — | CLI context file/profile selection |
| `HOST` / `PAPERCLIP_BIND_HOST` / `PAPERCLIP_LISTEN_HOST` | `127.0.0.1` (in-process) | Bind host |
| `PORT` / `PAPERCLIP_LISTEN_PORT` | `3100` | Listen port |
| `PAPERCLIP_BIND` | — | Bind preset: `loopback`/`lan`/`tailnet` |
| `PAPERCLIP_TAILNET_BIND_HOST` | — | Explicit tailnet bind host |
| `SERVE_UI` | — | Serve the compiled React UI from the server |
| `PAPERCLIP_UI_DEV_MIDDLEWARE` | — | Enable Vite dev middleware (HMR) |
| `PAPERCLIP_OPEN_ON_LISTEN` | — | Open a browser when the server starts |
| `PAPERCLIP_LOG_DIR` | — | Log directory |
| `NODE_ENV` | — | `development` / `production` / `test` |

### 5.2 Authentication & authorization

| Variable | Purpose |
|----------|---------|
| `PAPERCLIP_DEPLOYMENT_MODE` | `local_trusted` (default) or `authenticated` |
| `PAPERCLIP_DEPLOYMENT_EXPOSURE` | `private` (default) or `public` |
| `BETTER_AUTH_SECRET` | **Required in authenticated mode.** Signs sessions |
| `BETTER_AUTH_URL` / `BETTER_AUTH_BASE_URL` | better-auth URLs |
| `BETTER_AUTH_TRUSTED_ORIGINS` | Comma-separated trusted CORS origins |
| `PAPERCLIP_PUBLIC_URL` | Public-facing base URL |
| `PAPERCLIP_AUTH_PUBLIC_BASE_URL` / `PAPERCLIP_AUTH_BASE_URL_MODE` | Explicit auth base URL + derivation mode (`auto`/`explicit`) |
| `PAPERCLIP_ALLOWED_HOSTNAMES` | Comma-separated hostnames permitted to reach the server |
| `PAPERCLIP_AUTH_DISABLE_SIGN_UP` | Disable new account registration |
| `PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN` | Cloud-tenant server auth token |

### 5.3 Agent identity (JWT)

| Variable | Purpose |
|----------|---------|
| `PAPERCLIP_AGENT_JWT_SECRET` | Signing key for agent JWTs (falls back to the auth secret) |
| `PAPERCLIP_AGENT_JWT_TTL_SECONDS` | Token lifetime |
| `PAPERCLIP_AGENT_JWT_ISSUER` | JWT `iss` claim |
| `PAPERCLIP_AGENT_JWT_AUDIENCE` | JWT `aud` claim |

### 5.4 Database & backups

| Variable | Default | Purpose |
|----------|---------|---------|
| `DATABASE_URL` | embedded | Postgres connection URL (set for external DB) |
| `DATABASE_MIGRATION_URL` | = `DATABASE_URL` | Separate connection for migrations |
| `PAPERCLIP_EMBEDDED_POSTGRES_PORT` | — | Port for the embedded Postgres |
| `PAPERCLIP_EMBEDDED_POSTGRES_VERBOSE` | — | Verbose embedded-Postgres logging |
| `PAPERCLIP_MIGRATION_AUTO_APPLY` | — | Apply pending migrations on startup |
| `PAPERCLIP_MIGRATION_PROMPT` | — | Prompt before applying migrations |
| `PAPERCLIP_PG_DUMP_PATH` / `PAPERCLIP_PSQL_PATH` | `pg_dump`/`psql` | Binary paths for backup/restore |
| `PAPERCLIP_DB_BACKUP_ENABLED` | — | Enable automated backups |
| `PAPERCLIP_DB_BACKUP_INTERVAL_MINUTES` | — | Backup cadence |
| `PAPERCLIP_DB_BACKUP_RETENTION_DAYS` | — | Backup retention window |
| `PAPERCLIP_DB_BACKUP_DIR` | — | Backup output directory |

### 5.5 Storage

| Variable | Purpose |
|----------|---------|
| `PAPERCLIP_STORAGE_PROVIDER` | `local` or `s3` |
| `PAPERCLIP_STORAGE_LOCAL_DIR` | Local storage directory |
| `PAPERCLIP_STORAGE_S3_BUCKET` / `_REGION` / `_ENDPOINT` / `_PREFIX` / `_FORCE_PATH_STYLE` | S3 (or S3-compatible: R2, MinIO) configuration |

### 5.6 Secrets

| Variable | Purpose |
|----------|---------|
| `PAPERCLIP_SECRETS_PROVIDER` | `local` (encrypted file) or AWS-backed |
| `PAPERCLIP_SECRETS_MASTER_KEY` / `_FILE` | Master encryption key (inline or file) |
| `PAPERCLIP_SECRETS_STRICT_MODE` | Fail on any secret-decryption error |
| `PAPERCLIP_SECRETS_AWS_DEPLOYMENT_ID` / `_AWS_REGION` / `_AWS_KMS_KEY_ID` | AWS KMS envelope-encryption configuration |
| (standard AWS env) | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_PROFILE`, `AWS_ROLE_ARN`, web-identity & container-credential vars are all honored by the AWS secrets path |

### 5.7 Scheduler, bridge, runtime & limits

| Variable | Default | Purpose |
|----------|---------|---------|
| `HEARTBEAT_SCHEDULER_ENABLED` | — | Enable the agent heartbeat scheduler |
| `HEARTBEAT_SCHEDULER_INTERVAL_MS` | `30000` | Scheduler tick interval |
| `PAPERCLIP_BRIDGE_QUEUE_DIR` | — | Sandbox-callback bridge queue directory |
| `PAPERCLIP_BRIDGE_TOKEN` | — | Bridge authentication token |
| `PAPERCLIP_BRIDGE_HOST` / `_PORT` | — | Bridge listener address |
| `PAPERCLIP_BRIDGE_POLL_INTERVAL_MS` | — | Bridge queue poll interval |
| `PAPERCLIP_BRIDGE_RESPONSE_TIMEOUT_MS` | — | Bridge RPC timeout |
| `PAPERCLIP_BRIDGE_MAX_QUEUE_DEPTH` / `_MAX_BODY_BYTES` | — | Bridge backpressure & size limits |
| `PAPERCLIP_RUNTIME_API_URL` / `PAPERCLIP_API_URL` / `PAPERCLIP_RUNTIME_API_CANDIDATES_JSON` | — | Agent↔server runtime API discovery |
| `PAPERCLIP_ALLOWED_ATTACHMENT_TYPES` / `PAPERCLIP_ATTACHMENT_MAX_BYTES` | — | Upload MIME allow-list & size cap |
| `RUN_LOG_BASE_PATH` / `WORKSPACE_OPERATION_LOG_BASE_PATH` | — | Base paths for run / workspace-operation logs |
| `PAPERCLIP_WORKTREES_DIR` / `PAPERCLIP_WORKTREE_START_POINT` / `PAPERCLIP_WORKTREE_NAME` | — | Developer worktree configuration |
| `PAPERCLIP_TELEMETRY_BACKEND_URL` / `_TOKEN`, `PAPERCLIP_FEEDBACK_EXPORT_BACKEND_URL` / `_TOKEN` | — | Telemetry / feedback export backends |
| `PAPERCLIP_ENABLE_COMPANY_DELETION` | — | Allow destructive company deletion via API |

---

## 6. Authentication & Authorization

### 6.1 Actor model

The auth middleware resolves a request to one of three actor kinds — **board** (human user), **agent**, or anonymous — from whichever credential is present:

| Credential | Actor | Notes |
|------------|-------|-------|
| better-auth session | board | Cookie-based browser session |
| Board API key | board | Issued per board, manageable via `/board-api-keys` |
| Agent API key | agent | Per-agent keys (`/agents/:id/keys`) |
| Agent JWT | agent | HS256, configurable TTL/issuer/audience |
| Cloud-tenant token | board (tenant) | `PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN` |
| Trusted-loopback implicit | board | Only in `local_trusted` mode on loopback |

### 6.2 Bootstrapping the first admin

On an authenticated instance, the first administrator (the "CEO") is created from a one-time invite:

```bash
paperclipai auth bootstrap-ceo --expires-hours 48
```

This prints an invite link (claimed via the `/bootstrap/claim` and `/invites/:token` flows). `--force` issues a new invite even if an admin already exists. The CLI reads `DATABASE_URL`, `PAPERCLIP_PUBLIC_URL`/`PAPERCLIP_AUTH_PUBLIC_BASE_URL`, and the better-auth URLs to build the link.

> [!NOTE]
> In `local_trusted` mode this is unnecessary — the CLI notes that the bootstrap invite is only required for `authenticated` mode (loopback users are implicitly the board).

### 6.3 Invites, board-claim & CLI auth

- **Invites** — companies issue invite tokens (`/invites/:token`, with onboarding text, logo, and skills index served per-invite); admins revoke them.
- **Board-claim** — a token-based flow (`/board-claim/:token`) to claim board access.
- **CLI auth** — challenge/response (`/cli-auth/challenges/:id`, `/cli-auth/me`, `/cli-auth/revoke-current`) lets the CLI obtain a session without a browser-stored cookie.
- **Join requests & members** — companies expose membership, join-requests, and a user directory.

### 6.4 Agent credentials

Agents authenticate with per-agent API keys or short-lived JWTs. Keys are created and revoked via `/agents/:id/keys`. JWTs are signed with `PAPERCLIP_AGENT_JWT_SECRET` (HS256) and carry the configured issuer/audience and TTL. The CLI's `heartbeat run` passes an agent bearer token via `--api-key`.

> [!WARNING]
> A known issue (#7450) exists where `run-stamped` writes can 500 when a gateway JWT's `run_id` has been pruned from `heartbeat_runs` (the FK lacked `ON DELETE SET NULL`). If you operate at high run volume, track this in your upgrade notes.

---

## 7. Agent Execution Model

### 7.1 Heartbeat runs

Agents execute in discrete **heartbeat runs**. A scheduler (`HEARTBEAT_SCHEDULER_ENABLED`, tick `HEARTBEAT_SCHEDULER_INTERVAL_MS`, default 30 s) wakes agents that have work; runs are also triggerable manually (`POST /agents/:id/wakeup`, or `POST /agents/:id/heartbeat/invoke`) and via the CLI `heartbeat run`. A run carries a **trigger** of `manual`, `ping`, `callback`, or `system`.

Run-related surfaces:

- `/companies/:companyId/heartbeat-runs`, `/companies/:companyId/live-runs`, `/instance/scheduler-heartbeats` — fleet-level views.
- `/heartbeat-runs/:runId` plus `/events`, `/log`, `/workspace-operations`, `/cancel`, `/watchdog-decisions` — per-run inspection and control.
- `/issues/:issueId/live-runs`, `/issues/:issueId/active-run` — runs scoped to an issue.

### 7.2 Watchdog & recovery

Runs are supervised by a **watchdog** (decisions recorded per run) and a **recovery** subsystem (`server/src/services/recovery/`) that handles stalled/dead runs. Issues carry **recovery actions** (`/issues/:id/recovery-actions`, `/recovery-actions/resolve`) and a **monitor** (`/issues/:id/monitor/check-now`, `/scheduled-retry/retry-now`).

> [!NOTE]
> Execution locks tie a run to an issue checkout. A frequently requested enhancement (#7458) is a server-side TTL that auto-clears execution locks left behind by dead runs without operator intervention. Until then, an admin can `POST /issues/:id/admin/force-release` a stuck issue.

### 7.3 Cross-instance / cloud upstreams

`/cloud-upstreams/*` provides a flow to connect an instance to an upstream and **push runs** to it (preview, push, cancel, activate). This underpins syncing work between a local instance and a hosted/cloud Paperclip.

---

## 8. Work Model: Issues, Approvals, Goals

### 8.1 Issues

Issues are the core work unit (`server/src/routes/issues.ts` — the single largest route module). Beyond basic CRUD and search, the issue surface includes:

- **Comments & interactions** — threaded discussion (`/issues/:id/comments`, `/interactions`), feedback votes and traces.
- **Documents & work products** — versioned documents with lock/unlock, revisions, and inline annotation threads (`/issues/:id/documents/:key/...`), plus work products (the deliverables).
- **Attachments** — uploaded files/images, gated by `PAPERCLIP_ALLOWED_ATTACHMENT_TYPES` / `PAPERCLIP_ATTACHMENT_MAX_BYTES`.
- **Hierarchy & planning** — children (`/issues/:id/children`) and accepted plan decompositions.
- **Lifecycle** — checkout/release, admin force-release, read/inbox-archive state, labels, and per-issue approvals.
- **Tree control** — `/issues/:id/tree-control/*` and `/tree-holds/*` coordinate work across an issue tree (holds prevent concurrent conflicting work on related issues).

> [!IMPORTANT]
> Several historical 500s on `GET /issues/:id/comments?after=<cursor>` (community issues #2612, #3661, #3830) stemmed from a `Date` value being passed where the Postgres driver expected a string in the comment-pagination path. Ensure you run a build where this is fixed; the bug surfaces specifically on the `after=` cursor.

### 8.2 Approvals

Approvals (`/companies/:companyId/approvals`, `/approvals/:id/...`) gate sensitive work: approve, reject, resubmit, and a comment thread per approval. They bind to issues (`/issues/:id/approvals`).

### 8.3 Goals & dashboard

Goals (`/companies/:companyId/goals`) track company objectives; the dashboard route aggregates company state for the UI overview.

---

## 9. Adapter Subsystem

Adapters are the pluggable agent backends. The registry resolves an adapter `type` to its implementation; agents reference a type and a model.

| Adapter | Backing SDK / mechanism |
|---------|-------------------------|
| `claude-local` | Local Claude agent (ships a quota probe CLI) |
| `codex-local` | Local Codex agent |
| `cursor-local` | Local Cursor agent |
| `cursor-cloud` | `@cursor/sdk` cloud API |
| `gemini-local` | Local Gemini agent |
| `grok-local` | Local Grok agent |
| `opencode-local` | Local OpenCode agent |
| `pi-local` | Local Pi agent |
| `acpx-local` | Agent Client Protocol (`acpx`, `@agentclientprotocol/claude-agent-acp`, `@zed-industries/codex-acp`) |
| `openclaw-gateway` | WebSocket (`ws`) gateway to remote agents |

Adapter management API (`server/src/routes/adapters.ts`): `GET /adapters`, `POST /adapters/install`, `GET/PATCH/DELETE /adapters/:type`, `PATCH /adapters/:type/override`, `POST /adapters/:type/reload`, `POST /adapters/:type/reinstall`, `GET /adapters/:type/config-schema`, `GET /adapters/:type/ui-parser.js`. Model discovery is per-company-per-adapter (`/companies/:companyId/adapters/:type/models`, `/model-profiles`, `/detect-model`). The `/llms/agent-configuration*.txt` endpoints expose adapter configuration to LLM clients.

> [!TIP]
> Document and operate adapters as **interchangeable backends**, not as bespoke integrations. A given agent's behavior is defined by its instructions + skills + model; the adapter is just which engine runs it. This is what lets you A/B different engines on the same work.

---

## 10. Environments, Workspaces & Sandbox Providers

### 10.1 The execution surface

- **Projects** (`/companies/:companyId/projects`) own **workspaces** (`/projects/:id/workspaces`).
- **Execution workspaces** (`/companies/:companyId/execution-workspaces`, `/execution-workspaces/:id`) are where commands and runtime services actually run; they expose runtime-service/command actions, a close-readiness check, and a workspace-operations log.
- **Environments** (`/companies/:companyId/environments`) describe sandbox capabilities, hand out **leases** (`/environments/:id/leases`, `/environment-leases/:leaseId`), and can be **probed** for health.

### 10.2 Sandbox providers (plugins)

Sandbox providers are plugins implementing a common provider interface:

| Provider | Package | Backing SDK |
|----------|---------|-------------|
| E2B | `@paperclipai/plugin-e2b` | `e2b` |
| Daytona | `@paperclipai/plugin-daytona` | `@daytonaio/sdk` |
| Modal | `@paperclipai/plugin-modal` | Modal (token id + secret) |
| Cloudflare | `@paperclipai/plugin-cloudflare-sandbox` | `@cloudflare/sandbox` + a deployed Worker bridge (`bridge-template`, wrangler) |
| exe-dev | sandbox-providers/exe-dev | exe.dev API (`EXE_API_KEY`) |
| fake-sandbox | `@paperclipai/plugin-fake-sandbox` | Deterministic in-process fake for tests |

### 10.3 The callback bridge

`packages/adapter-utils/src/sandbox-callback-bridge.ts` plus the `PAPERCLIP_BRIDGE_*` settings implement a queue-based bridge so code running **inside** a sandbox can call back into the Paperclip host (runtime API). The Cloudflare provider additionally ships a Worker **bridge-template** that proxies between the Cloudflare sandbox and the host. The bridge is the security boundary between untrusted sandboxed execution and the trusted host — protect it with `PAPERCLIP_BRIDGE_TOKEN`.

Relevant error classes: `EnvironmentRunError`, `PluginSandboxError`, `CloudflareBridgeError`, `ExeDevApiError`, plus provider SDK errors (`SandboxNotFoundError`, `TimeoutError`, `CommandExitError`).

---

## 11. Plugin Runtime & SDK

### 11.1 Architecture

Plugins extend the platform with UI, tools, jobs, and webhooks. Each plugin declares a manifest, a worker, and (optionally) a UI bundle:

```json
"paperclipPlugin": {
  "manifest": "./dist/manifest.js",
  "worker":   "./dist/worker.js",
  "ui":       "./dist/ui/"
}
```

The host runs the worker as a **sandboxed JSON-RPC process** (`packages/plugins/sdk/src/protocol.ts`), exchanging messages over the plugin bridge. A **capability model** (`host-client-factory.ts`) gates what a worker may do: requests beyond a plugin's grant raise `CapabilityDeniedError` or `InvocationScopeDeniedError`; malformed RPC raises `JsonRpcParseError` / `JsonRpcCallError`.

### 11.2 What plugins can contribute

| Surface | API |
|---------|-----|
| Tools (callable by agents) | `/plugins/tools`, `/plugins/tools/execute` |
| UI contributions | `/plugins/ui-contributions`, static UI at `/_plugins/:pluginId/ui/*` |
| Background jobs | `/plugins/:pluginId/jobs`, `/jobs/:jobId/runs`, `/jobs/:jobId/trigger` |
| Webhooks | `/plugins/:pluginId/webhooks/:endpointKey` |
| Bridge data/actions | `/plugins/:pluginId/bridge/data\|action`, `/bridge/stream/:channel` |
| Config | `/plugins/:pluginId/config`, `/config/test` |
| Local-folder dev plugins | `/plugins/:pluginId/companies/:companyId/local-folders/*` |
| Lifecycle | install, enable/disable, upgrade, health, logs, delete |

### 11.3 Authoring a plugin

Scaffold with the generator (`@paperclipai/create-paperclip-plugin`):

```bash
npm create paperclip-plugin@latest
```

A plugin dev server (`paperclip-plugin-dev-server`) serves the UI during development. First-party reference plugins demonstrate the surface — **plugin-kitchen-sink-example** intentionally exercises the full API, **plugin-hello-world-example** is the minimal widget, **plugin-orchestration-smoke-example** covers orchestration-grade APIs, and **plugin-workspace-diff** / **plugin-llm-wiki** / **plugin-file-browser-example** are production-style features.

> [!NOTE]
> Plugins can own database schema and migrations within their own scope (e.g. plugin-llm-wiki ships migrations), keeping plugin tables isolated from core tables.

---

## 12. Skills Catalog

`packages/skills-catalog/` is a build-time catalog of agent skills, split into **bundled** (always available — e.g. `paperclip-operations/issue-triage`, `task-planning`, `software-development/github-pr-workflow`, `quality/qa-acceptance`, `docs/doc-maintenance`, `product/wireframe`) and **optional** (e.g. `browser/agent-browser`, `content/release-announcement`, `product/design-critique`) sets. The catalog builder validates each skill package (it raises `${packageJsonPath} must declare a package version.` for malformed entries) and generates an index. Skills are served to agents and clients via `/skills/available`, `/skills/index`, `/skills/:skillName`, and per-company skill endpoints; repo-level skills under `skills/` (e.g. `paperclip-create-agent`, `paperclip-create-plugin`, `terminal-bench-loop`) document agent/plugin authoring workflows.

---

## 13. Routines & Triggers

Routines (`server/src/routes/routines.ts`) are scheduled/triggered company workflows with full revision history:

| Surface | Endpoint(s) |
|---------|-------------|
| CRUD | `/companies/:companyId/routines`, `/routines/:id` |
| Revisions | `/routines/:id/revisions`, `/revisions/:revisionId/restore` |
| Triggers | `/routines/:id/triggers`, `PATCH/DELETE /routine-triggers/:id` |
| Execution | `/routines/:id/run`, `/routines/:id/runs` |
| Public firing | `/routine-triggers/public/:publicId/fire` |

The CLI exposes `routines disable-all --company-id <id>` as an operational kill switch. A frequently requested feature (#7448) is letting **manager agents** create and manage routines for their subordinates.

---

## 14. Costs, Budgets & Finance

Paperclip records cost and finance events and aggregates them many ways (`server/src/routes/costs.ts`):

| Concern | Endpoints |
|---------|-----------|
| Ingest | `POST /companies/:companyId/cost-events`, `/finance-events` |
| Cost summaries | `/costs/summary`, `/costs/by-agent`, `/by-agent-model`, `/by-provider`, `/by-biller`, `/by-project`, `/issues/:id/cost-summary` |
| Finance ledger | `/costs/finance-summary`, `/finance-by-biller`, `/finance-by-kind`, `/finance-events` |
| Windows | `/costs/window-spend`, `/costs/quota-windows` |
| Budgets | `GET /companies/:companyId/budgets/overview`, `PATCH /companies/:companyId/budgets`, `PATCH /agents/:agentId/budgets` |

> [!NOTE]
> Budgets are spend-based today; **token-based agent budgets** are an open enhancement (#1756). Quota windows align cost accounting with provider rate/usage windows — useful when a provider meters by rolling window rather than absolute spend.

---

## 15. Secrets Management

Secrets are company-scoped and provider-backed (`server/src/routes/secrets.ts`, `server/src/secrets/`):

- **Providers** — `local` (encrypted file via `PAPERCLIP_SECRETS_MASTER_KEY[_FILE]`) or AWS KMS-backed (`PAPERCLIP_SECRETS_AWS_*` plus standard AWS credential resolution). List/configure providers, mark one default, and health-check them (`/secret-providers`, `/secret-provider-configs`).
- **Secrets** — create, update, rotate, delete (`/companies/:companyId/secrets`, `/secrets/:id`, `/secrets/:id/rotate`), with audit surfaces `/secrets/:id/usage` and `/secrets/:id/access-events`.
- **Strict mode** — `PAPERCLIP_SECRETS_STRICT_MODE` makes any decryption failure fatal, which surfaces key-rotation problems immediately instead of silently degrading.

> [!CAUTION]
> The local provider's security reduces to the secrecy of the master key. Protect `PAPERCLIP_SECRETS_MASTER_KEY_FILE` with strict file permissions and disk encryption, or use the AWS KMS provider so the data-encryption keys never live in plaintext on the host.

---

## 16. MCP Server

`packages/mcp-server/` publishes the `paperclip-mcp-server` binary (built on `@modelcontextprotocol/sdk` + Zod). It lets an external MCP-aware LLM client read and act on a Paperclip instance over stdio. Point your client's command at it and provide the instance base URL and an API key. Server-side, `/llms/*.txt` endpoints expose agent/adapter configuration in an LLM-friendly form.

---

## 17. Data Model & Database

The data layer is Drizzle ORM over PostgreSQL, with **86 schema files** in `packages/db/src/schema/` and **94 migrations** in `packages/db/src/migrations/`.

### 17.1 Major entity groups

| Group | Represents |
|-------|------------|
| Companies & access | companies, members, invites, board API keys, branding |
| Agents | agents, agent keys, configurations + revisions, runtime state, task sessions, skills, hires |
| Heartbeat runs | run records, events, logs, watchdog decisions, workspace operations |
| Issues | issues, comments, interactions, documents (+revisions/annotations), work products, attachments, approvals, labels, tree control/holds, read & inbox state |
| Projects & environments | projects, workspaces, execution workspaces, environments, leases, probes |
| Routines | routines, triggers, revisions, runs |
| Costs & finance | cost events, finance events, budgets, quota windows |
| Secrets | secrets, secret provider configs, access events |
| Plugins | plugin registrations, configs, jobs/runs, webhooks, data |
| Goals, approvals, activity | goals, approvals (+comments), activity feed |
| Auth | better-auth users/sessions/accounts |
| Instance | instance settings, database backups |

### 17.2 Migrations & embedded Postgres

Migrations are generated with drizzle-kit (`pnpm --filter @paperclipai/db generate`) and applied via `pnpm --filter @paperclipai/db migrate` or automatically on startup (`PAPERCLIP_MIGRATION_AUTO_APPLY`). A `check:migrations` guard enforces migration numbering. Embedded Postgres is a **patched** `embedded-postgres@18.1.0-beta.16` (see `patches/`); its port is set by `PAPERCLIP_EMBEDDED_POSTGRES_PORT`.

### 17.3 Backup & restore

`packages/db/src/backup-lib.ts` drives backups using `pg_dump`/`psql` (paths overridable via `PAPERCLIP_PG_DUMP_PATH` / `PAPERCLIP_PSQL_PATH`). The CLI `db:backup` supports `--dir`, `--retention-days`, `--filename-prefix`, and `--json`; the API exposes `POST /instance/database-backups`. Schedule `db:backup` via cron for unattended backups.

---

## 18. API Reference

The server mounts ~328 routes across 40 route modules under the `/api` prefix, with an auto-generated OpenAPI document at **`GET /api/openapi.json`** (built from an `OpenAPIRegistry`). Treat that document as the authoritative, always-current contract; the table below is the resource map.

| Resource module | Representative paths |
|-----------------|----------------------|
| `health` | `GET /health`, `POST /dev-server/restart` |
| `auth` | `GET /get-session`, `GET/PATCH /profile` |
| `access` | invites, board-claim, bootstrap claim, CLI-auth, board API keys, skills, members, admin users |
| `agents` | agent CRUD, hire (`POST /companies/:companyId/agent-hires`), pause/resume/approve/terminate, permissions, instructions bundle, config revisions/rollback, keys, wakeup, heartbeat invoke, org chart (`/companies/:companyId/org[.svg/.png]`) |
| `activity` | company & issue activity, issue runs, heartbeat-run issues |
| `adapters` | list/install/patch/reload/reinstall, config-schema, models/model-profiles/detect-model |
| `issues` | CRUD, search, comments, interactions, documents, work products, attachments, approvals, children, plan decompositions, checkout/release, admin force-release, tree-control/holds, recovery, monitor |
| `approvals` | CRUD, approve/reject/resubmit, comments |
| `companies` | CRUD, stats, export/import, branding, archive |
| `company-skills` / `instance-settings` | skill catalog, general/experimental instance settings |
| `projects` / `execution-workspaces` / `environments` | projects+workspaces, execution workspaces, environments+leases+probes |
| `routines` | CRUD, triggers, revisions, run, public fire |
| `costs` | cost/finance events, summaries, budgets, quota windows |
| `secrets` | providers, configs, secrets, rotate, usage, access events |
| `plugins` | install/enable/disable/upgrade, config, health, logs, tools, jobs, webhooks, bridge, UI |
| `goals` / `dashboard` / `assets` | goals, dashboard data, image/logo assets |
| `cloud-upstreams` | connect, push-runs lifecycle |
| `instance-database-backups` | trigger DB backups |
| `llms` | agent-configuration/icons text for LLM clients |
| `openapi` | `GET /openapi.json` |

> [!TIP]
> Generate clients from `/api/openapi.json` rather than hand-coding paths — the route surface is large and evolving, and the OpenAPI document is regenerated from the registry on every build. Many resources are mounted under a company scope (`/companies/:companyId/...`) while individual records are addressed by their own id (`/issues/:id`, `/agents/:id`, `/routines/:id`).

---

## 19. Security

### 19.1 Posture checklist

- [ ] `BETTER_AUTH_SECRET` set (256-bit random) for any non-loopback deployment.
- [ ] A distinct `PAPERCLIP_AGENT_JWT_SECRET` from the auth secret.
- [ ] `PAPERCLIP_DEPLOYMENT_MODE=authenticated` whenever exposure ≠ loopback.
- [ ] `PAPERCLIP_ALLOWED_HOSTNAMES` + `BETTER_AUTH_TRUSTED_ORIGINS` constrained to known hosts/origins.
- [ ] `PAPERCLIP_AUTH_DISABLE_SIGN_UP=true` once your initial users exist.
- [ ] `PAPERCLIP_BRIDGE_TOKEN` set so only authorized processes can use the sandbox-callback bridge.
- [ ] Secrets via AWS KMS (`PAPERCLIP_SECRETS_PROVIDER`=AWS) or a tightly-permissioned master key file; consider `PAPERCLIP_SECRETS_STRICT_MODE`.
- [ ] Attachment MIME allow-list and size cap configured.
- [ ] Untrusted agent code executed only in cloud sandboxes / the untrusted-review container, never directly on the host.

### 19.2 Built-in protections

- **Log redaction** (`server/src/log-redaction.ts`) scrubs identifiers (`USER`, `LOGNAME`, `USERNAME`, etc.) from logs.
- **Hardened review container** (`docker/untrusted-review/`) drops all capabilities and forbids privilege escalation.
- **Capability-gated plugins** — workers cannot exceed their declared grants (`CapabilityDeniedError`).
- **Hostname allow-listing** rejects requests from unexpected hosts; invalid hostnames raise `Invalid hostname: <raw>`.
- **Loopback enforcement** — `local_trusted` mode is rejected on any non-loopback host.
- **HTTP error mapping** — domain errors extend `HttpError` (`server/src/errors.ts`) for consistent status codes.

### 19.3 Handling a malicious or compromised agent

Agents are privileged actors. Pause or terminate a misbehaving agent immediately (`/agents/:id/pause`, `/terminate`), revoke its keys (`DELETE /agents/:id/keys/:keyId`), force-release any issues it holds, and review its `/secrets/:id/access-events` to see what it touched. Run untrusted execution only in isolated sandboxes so a compromised run cannot reach the host.

---

## 20. Operations Runbook

| Task | Command / endpoint |
|------|--------------------|
| Start an instance | `paperclipai run -i <instance>` |
| Diagnose / repair | `paperclipai doctor --repair` |
| Inspect resolved env | `paperclipai env` |
| Back up the DB | `paperclipai db:backup --dir <path> --retention-days <n> --json` |
| Allow a hostname | `paperclipai allowed-hostname add <host>` |
| Health check | `GET /api/health` |
| Pause all scheduled work | `paperclipai routines disable-all --company-id <id>` |
| Force-release a stuck issue | `POST /api/issues/:id/admin/force-release` |
| Cancel a run | `POST /api/heartbeat-runs/:runId/cancel` |

### 20.1 Developer worktrees

The `worktree` command family runs **isolated copies of an instance** side-by-side — each with its own repo-local config, isolated data dir, server port, and embedded-Postgres port — so developers can work on multiple branches without colliding state:

| Subcommand | Purpose |
|------------|---------|
| `worktree:make` | Create a new branch + isolated instance (seed from a source instance; `--seed-mode minimal\|full`) |
| `init` | Initialize an isolated instance in the current repo |
| `worktree:list` | List worktree instances |
| `env` | Print shell exports (or `--json`) for a worktree |
| `worktree:merge-history` | Import issues/comments from another worktree (`--scope`, `--dry`/`--apply`) |
| `reseed` | Re-seed a target worktree's DB from a source (guards against live targets) |
| `repair` | Repair/bootstrap a worktree's metadata/config |
| `worktree:cleanup` | Remove a worktree (with safety checks unless `--force`) |

> [!WARNING]
> `reseed` and `worktree:cleanup` are destructive. They guard against operating on a live target DB; `--allow-live-target` / `--force` override those guards — use them deliberately. `--preserve-live-work` keeps copied agent timers and assigned open issues instead of quarantining them.

### 20.2 env-lab

`env-lab up|status|down|doctor` manages local fixture environments for testing adapter/sandbox configurations without touching production data.

### 20.3 Performance tuning

- **Database is the first bottleneck.** Each heartbeat cycle queries active agents; under many concurrent agents, watch PostgreSQL connection count (`SELECT count(*) FROM pg_stat_activity;`). Move to an external Postgres with adequate `max_connections` before scaling agent count.
- **Scheduler cadence.** Lower `HEARTBEAT_SCHEDULER_INTERVAL_MS` for snappier response, raise it to reduce database churn.
- **Storage I/O.** Use S3 storage to remove local disk pressure for attachment-heavy workloads.
- **Embedded-Postgres diagnostics.** `PAPERCLIP_EMBEDDED_POSTGRES_VERBOSE=true` logs database activity for slow-query hunting.

---

## 21. Build, Test & Release

| Activity | Command |
|----------|---------|
| Install | `pnpm install` |
| Build all | `pnpm build` (runs workspace-link preflight, then `pnpm -r build`) |
| Typecheck | `pnpm typecheck` |
| Unit/integration tests | `pnpm test` (stable Vitest runner) |
| E2E | `pnpm test:e2e` (Playwright) |
| Release smoke | `pnpm test:release-smoke` |
| Dev server | `pnpm dev` (server with tsx watch) / `pnpm dev:ui` |
| DB generate/migrate | `pnpm db:generate` / `pnpm db:migrate` |
| Eval (promptfoo) | `pnpm evals:smoke` |

Releases are scripted (`scripts/release.sh` with `canary`/`stable` channels, GitHub release + rollback helpers) and the npm CLI is bundled via esbuild (`scripts/build-npm.sh`). The repo enforces guardrails such as `check:no-git-push` and forbidden-token checks. Test/CI surfaces include Storybook for UI components and a release-smoke Playwright suite that exercises Docker auth/onboarding.

### Error class hierarchy (selected)

```
Error
├── HttpError                  server/src/errors.ts          (HTTP status mapping)
├── EnvironmentRunError        services/environment-run-orchestrator.ts
├── PluginSandboxError         services/plugin-runtime-sandbox.ts
├── CapabilityDeniedError      plugins/sdk/host-client-factory.ts
├── InvocationScopeDeniedError plugins/sdk/host-client-factory.ts
├── JsonRpcParseError /        plugins/sdk/protocol.ts
│   JsonRpcCallError
├── CloudflareBridgeError      sandbox-providers/cloudflare
├── ExeDevApiError             sandbox-providers/exe-dev
├── PaperclipApiError          mcp-server/client.ts
└── ApiError / AuthApiError    ui/src/api
```

---

## 22. Code Quality & Conventions

- **Strict TypeScript, ESM throughout.** Every package is `"type": "module"`, targets TS 5.7, and publishes typed entry points; the CLI bundles workspace deps via esbuild.
- **Zod as the validation spine.** `packages/shared/src/validators/` (36 files) holds input schemas reused across server, CLI, and UI, and feeds the OpenAPI registry — request validation and API docs share one source of truth.
- **Service-oriented server.** 117 service files concentrate business logic behind thin route handlers; the `recovery/` subdirectory isolates run-recovery concerns.
- **Adapter/provider symmetry.** Both agent adapters and sandbox providers follow a registry + uniform-interface pattern, which is what makes them swappable.
- **Test breadth.** Vitest unit/integration (`server/src/__tests__` alone has 261 files), Playwright E2E + release-smoke, promptfoo evals, and Storybook stories.

> [!NOTE]
> The largest modules — `server/src/services` (~2.7M chars), `server/src/routes` (~1.06M chars), `ui/src/lib` (~0.68M chars), `packages/adapter-utils/src` (~0.44M chars), and `packages/plugins/sdk/src` (~0.36M chars) — are the natural focus points for anyone learning the codebase or planning refactors.

---

## 23. Known Limitations & Roadmap Signals

Drawn from the issue tracker at this commit:

| Area | Signal |
|------|--------|
| Execution locks | Server-side TTL to auto-clear dead-run locks without an operator (#7458) |
| Agent FK integrity | `run-stamped` 500 when a gateway JWT's `run_id` is pruned from `heartbeat_runs` (no `ON DELETE SET NULL`) (#7450) |
| Budgets | Token-based agent budgets as an alternative to spend-based (#1756) |
| Routines | Let manager agents create/manage routines for subordinates (#7448) |
| Providers | OpenRouter support (#1068) |
| Robustness | Agents can loop indefinitely with some Ollama models (#4678) — cap with budgets + heartbeat timeouts |
| Comments API | Date-as-bind-param 500 on `/issues/:id/comments?after=` (#2612/#3661/#3830) — fixed in current builds; verify on upgrade |

Scaling beyond a single node is the largest architectural limitation: shared embedded Postgres and the file-backed bridge queue assume one owning process. Production scale means external Postgres + S3 storage + vertical sizing, and (today) one active server.

---

## 24. Quick Reference

| Item | Value |
|------|-------|
| Default port | `3100` (`PORT` / `PAPERCLIP_LISTEN_PORT`) |
| In-process bind | `127.0.0.1` (presets: `loopback`/`lan`/`tailnet`) |
| Health | `GET /api/health` |
| OpenAPI | `GET /api/openapi.json` |
| Data root | `PAPERCLIP_HOME` (default `~/.paperclip`) |
| Auth modes | `local_trusted` / `authenticated` × `private` / `public` (default `local_trusted` + `private`) |
| DB | PostgreSQL via Drizzle (embedded or `DATABASE_URL`) |
| Scheduler tick | `HEARTBEAT_SCHEDULER_INTERVAL_MS` (default `30000`) |
| First admin | `paperclipai auth bootstrap-ceo` |
| Kill switch | `paperclipai routines disable-all --company-id <id>` |
| Force-release issue | `POST /api/issues/:id/admin/force-release` |

For user-facing workflows, the adapter/sandbox catalogs, and the CLI day-to-day, see the **Paperclip User Guide**.

---

_Generated June 2026 from paperclipai/paperclip at 03e1e3a. 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._
