·7 min read·v0.4.2
Paperclip Architecture
Control plane for AI agent orchestration — never runs AI directly. Task queue + org chart + audit log + budget tracker that orchestrates external agent runtimes via adapters.
control-planemulti-agentorchestrationpostgresadapters
View repository →UI (React)
Server (Express)
Database (Postgres)
Adapters
Agent Runtimes
Skills
CLI
External
System Layers
Clients
🖥DashboardReact 19 + Vite + shadcn/ui
⌨CLIpaperclipai (Commander.js)
🔗REST APIAny HTTP client
📡WebSocketLive events
Server — Express v5 (:3100)
🔐AuthBetter Auth + Agent JWT + API Keys
🏢CompaniesOrg, budgets, secrets
🤖AgentsCRUD, config, permissions
📋IssuesTasks, checkout, comments
🎯GoalsHierarchical goal tree
📁ProjectsIssue groups + workspaces
✅ApprovalsBoard approval gates
💰CostsPer-run token/cost tracking
📜ActivityImmutable audit log
🧩PluginsPlugin SDK + state
Core Engine — Heartbeat Service (~3,700 lines)
💓Wakeup CoordinatorenqueueWakeup → coalesce → claim → execute
⏱Timer SchedulertickTimers() → check intervalSec
🔄Run ExecutorSpawn adapter → parse output → persist
🧹Orphan ReaperRecover stuck runs on restart
Adapter Layer — Bridge to Agent Runtimes
claude_local
codex_local
cursor
gemini_local
opencode_local
pi_local
openclaw_gateway
hermes_local
process
http
Agent Runtimes (External — Paperclip never runs AI)
🟣Claude Code
🟢OpenAI Codex
🔵Cursor
🟡Gemini CLI
⚪OpenCode
🟤Pi
🔴OpenClaw
⚫Any HTTP/Process
Data Layer
🐘PostgreSQLDrizzle ORM, 30+ tables
📦Run Log Storelocal_file | object_store | postgres
🔒Secrets StoreEncrypted company secrets
📎Asset StorageLocal disk or S3-compatible
Skills (Markdown injected into agent prompts)
📖paperclip/SKILL.mdHeartbeat protocol, API patterns, task workflow
👤create-agentHow to hire new agents
🧩create-pluginHow to scaffold plugins
🧠para-memoryPARA method for memory files
Heartbeat Flow — How Agents Wake Up and Work
1
Trigger arrives — timer tick, task assignment, @mention in comment, manual click, or automation webhook
↓
2
Wakeup Coordinator calls
enqueueWakeup() — creates agent_wakeup_requests row. Dedupes/coalesces if agent already has a queued run.↓
3
Concurrency check — max 1 active run per agent (configurable up to 10). If at limit, request is deferred.
↓
4
Run created —
heartbeat_runs row (status: queued → running). Agent JWT minted with agentId + companyId + runId.↓
5
Adapter executes — spawns Claude/Codex/etc with prompt template + env vars. Agent calls Paperclip API using its JWT.
↓
6
Agent works — reads inbox (
GET /agents/me/inbox-lite), checks out task (POST /issues/:id/checkout), does the work, updates status.↓
7
Output parsed — session ID, token usage, cost extracted.
heartbeat_runs updated. agent_runtime_state persisted for next resume.↓
8
Done — Run status →
succeeded/failed. WebSocket event pushed. Activity log entry written. Agent returns to idle.Authentication — Two Worlds
Board Users (Humans)
Better Auth (email + password)
→ Session cookie
→
→
→ Board claim flow for local→auth transition
→ Session cookie
→
instance_admin role for global ops→
company_memberships for per-company scoping→ Board claim flow for local→auth transition
Agents (AI)
Short-lived JWT (auto-injected per run)
→ Contains: agentId, companyId, runId
→ Signed with PAPERCLIP_AGENT_JWT_SECRET
OR Long-lived API keys (SHA-256 hashed)
→
→ Contains: agentId, companyId, runId
→ Signed with PAPERCLIP_AGENT_JWT_SECRET
OR Long-lived API keys (SHA-256 hashed)
→
Authorization: Bearer <token>Adapter Architecture — 3 Modules per Adapter
packages/adapters/<name>/src/ ├── index.ts ← Shared metadata (type, label, models) ├── server/ │ ├── execute.ts ← Core: spawn process, capture output │ ├── parse.ts ← Extract session ID, tokens, cost │ └── test.ts ← Environment diagnostics ├── ui/ │ ├── parse-stdout.ts ← Stdout → transcript entries for run viewer │ └── build-config.ts ← Form values → adapterConfig JSON └── cli/ └── format-event.ts ← Terminal output for paperclipai run --watch
Core Database Tables (Drizzle ORM + PostgreSQL)
companies
id, name, description, status, issuePrefix, issueCounter, budgetMonthlyCents, spentMonthlyCents, requireBoardApproval, brandColor
agents
id, companyId, name, role, title, status, reportsTo (self-ref FK), adapterType, adapterConfig (jsonb), runtimeConfig (jsonb), budgetMonthlyCents, permissions
issues
id, companyId, projectId, goalId, parentId (self-ref), title, status, priority, assigneeAgentId, checkoutRunId, executionRunId, issueNumber, identifier
heartbeat_runs
id, agentId, invocationSource, status, startedAt, finishedAt, exitCode, sessionIdBefore/After, logStore, logRef, logBytes, contextSnapshot, retryOfRunId
agent_wakeup_requests
id, agentId, source, triggerDetail, status (queued→claimed→completed), coalescedCount, reason, requestedBy, idempotencyKey
agent_runtime_state
agentId, adapterType, sessionId, stateJson, totalInputTokens, totalOutputTokens, totalCostCents, lastError
goals
id, companyId, title, level, status, parentId (self-ref), ownerAgentId
projects
id, companyId, goalId, name, status, leadAgentId, targetDate, executionWorkspacePolicy
approvals
id, companyId, type, status, requestedByAgentId, resolvedByUserId, payload
cost_events
id, companyId, agentId, runId, inputTokens, outputTokens, costCents, provider, model
activity_log
id, companyId, actorType, actorId, action, entityType, entityId, payload (jsonb)
execution_workspaces
id, companyId, projectId, type (worktree/container), status, path, branchName
Monorepo Package Map (pnpm workspaces)
paperclip/ ├── server/ Express v5 API + heartbeat engine ├── ui/ React 19 + Vite + TanStack Query ├── cli/ Commander.js CLI ├── packages/db/ Drizzle schema + migrations ├── packages/shared/ Shared types (DeploymentMode, etc.) ├── packages/adapter-utils/ Shared adapter helpers ├── packages/adapters/ │ ├── claude-local/ Claude Code CLI │ ├── codex-local/ OpenAI Codex CLI │ ├── cursor-local/ Cursor CLI │ ├── gemini-local/ Gemini CLI │ ├── opencode-local/ OpenCode CLI │ ├── pi-local/ Pi CLI │ └── openclaw-gateway/ OpenClaw webhook ├── packages/plugins/ Plugin SDK + scaffolder ├── skills/ │ ├── paperclip/ Core heartbeat protocol skill │ ├── paperclip-create-agent/ Agent hiring skill │ ├── paperclip-create-plugin/ Plugin creation skill │ └── para-memory-files/ PARA memory skill ├── docs/ Mintlify documentation ├── tests/ E2E (Playwright) └── docker/ Docker assets
The Key Insight
Paperclip is a control plane that never runs AI directly. It's a task queue + org chart + audit log + budget tracker that orchestrates external agent runtimes via adapters. The agents (Claude, Codex, Gemini) do the work — Paperclip tells them when to wake up, what to work on, and tracks what they did.