System Architecture Overview

React 19 + Vite frontend, Express REST API backend, PostgreSQL + Drizzle ORM persistence, and the adapter layer. How the four layers connect and where each concern lives.

System Architecture Overview

What you'll learn

The four-layer architecture of Paperclip AI, how each layer communicates, and where to look when something goes wrong.


The four layers

┌─────────────────────────────────────────┐
│  Layer 1: React 19 + Vite Frontend      │  Dashboard UI
│  localhost:5173                          │
└──────────────────┬──────────────────────┘
                   │ HTTP/REST
┌──────────────────▼──────────────────────┐
│  Layer 2: Express.js REST API            │  Business logic
│  localhost:3100                          │
└──────────────────┬──────────────────────┘
                   │ Drizzle ORM queries
┌──────────────────▼──────────────────────┐
│  Layer 3: PostgreSQL (or PGlite dev)    │  Persistence
│  7 tables, all scoped by company_id     │
└──────────────────┬──────────────────────┘
                   │ adapter.invoke()
┌──────────────────▼──────────────────────┐
│  Layer 4: Adapter Layer                  │  AI execution
│  Claude Code CLI / OpenAI API /          │
│  HTTP webhook / Shell process            │
└─────────────────────────────────────────┘

Layer 1 — React Frontend

The frontend is a React 19 SPA built with Vite. It connects to the API via fetch calls and provides:

The frontend is read/write through the REST API. It never touches the database directly.

Key files:

apps/web/
  src/
    pages/        ← one component per page (Dashboard, Agents, Issues, …)
    components/   ← shared UI pieces (AgentCard, IssueRow, CostChart, …)
    api/          ← typed fetch wrappers for every API endpoint

Layer 2 — Express REST API

The API is a Node.js Express server. It handles:

Every request is validated and scoped to a company. The API never returns data from one company to a request authenticated for another.

Key files:

apps/server/
  src/
    routes/       ← one router per entity (agents.ts, issues.ts, …)
    services/     ← heartbeat.service.ts, cost.service.ts, …
    adapters/     ← claude.adapter.ts, openai.adapter.ts, …
    db/           ← drizzle schema and migration files

Layer 3 — PostgreSQL / PGlite

The persistence layer uses Drizzle ORM — a TypeScript-first ORM that generates type-safe queries from your schema definition. No raw SQL in application code.

The schema has 7 tables:

Table Purpose
companies Workspace/tenant root
agents Agent definitions with adapter config and budget
issues Tasks with hierarchy, priority, and status
heartbeat_runs One record per agent work cycle
cost_events One record per token spend
approvals Pending human approval gates
activity_log Immutable audit trail

Layer 4 — Adapter Layer

Adapters are the translation layer between Paperclip's abstract task model and a real execution environment. Every adapter implements three methods:

interface Adapter {
  invoke(task: Task): Promise<string>      // start work, return runId
  status(runId: string): Promise<RunStatus> // check if done
  cancel(runId: string): Promise<void>      // abort if needed
}

Current adapters:
- ClaudeCodeAdapter — spawns the claude CLI as a child process
- OpenAIAdapter — calls the OpenAI chat completions API
- HttpAdapter — POSTs to an external webhook and polls for completion
- ShellAdapter — runs an arbitrary shell command or script


Request flow example — triggering a heartbeat

User clicks "Run Now" in dashboard
   ↓
POST /api/heartbeat/trigger   (Layer 2)
   ↓
SELECT agents WHERE status='active' AND next_run_at <= NOW()   (Layer 3)
   ↓
SELECT issues WHERE assigned_to=agentId AND status='open' ORDER BY priority DESC   (Layer 3)
   ↓
adapter.invoke(issue)   (Layer 4)
   ↓
INSERT heartbeat_runs, cost_events, activity_log   (Layer 3)
   ↓
WebSocket push → dashboard updates in real time   (Layer 1)

Checkpoint ✓

Sign in for progress tracking & Q&A →