Add database setup guide and clean up spec formatting

Add doc/DATABASE.md documenting the three database modes: embedded
PGlite, local Docker PostgreSQL, and hosted production. Fix markdown
table alignment and minor whitespace in SPEC.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-16 20:21:15 -06:00
parent d2f76585b8
commit 54f3526252
2 changed files with 147 additions and 18 deletions

38
SPEC.md
View File

@@ -10,13 +10,13 @@ A company is a first-order object. One Paperclip instance runs multiple companie
### Fields (Draft)
| Field | Type | Notes |
| --- | --- | --- |
| `id` | uuid | Primary key |
| `name` | string | Company name |
| `goal` | text/markdown | The company's top-level objective |
| `createdAt` | timestamp | |
| `updatedAt` | timestamp | |
| Field | Type | Notes |
| ----------- | ------------- | --------------------------------- |
| `id` | uuid | Primary key |
| `name` | string | Company name |
| `goal` | text/markdown | The company's top-level objective |
| `createdAt` | timestamp | |
| `updatedAt` | timestamp | |
### Board Governance [DRAFT]
@@ -42,6 +42,7 @@ The board has **unrestricted access** to the entire system at all times:
The board is not just an approval gate — it's a live control surface. The human can intervene at any level at any time.
**Future governance models** (not V1):
- Hiring budgets (auto-approve hires within $X/month)
- Multi-member boards
- Delegated authority (CEO can hire within limits)
@@ -104,7 +105,7 @@ Configurable per agent. Two ends of the spectrum:
#### Minimum Contract
The minimum requirement to be a Paperclip agent: **be callable.** That's it. Paperclip can invoke you via command or webhook. No requirement to report back — Paperclip infers basic status from process liveness.
The minimum requirement to be a Paperclip agent: **be callable.** That's it. Paperclip can invoke you via command or webhook. No requirement to report back — Paperclip infers basic status from process liveness when it can.
#### Integration Levels
@@ -182,10 +183,10 @@ The heartbeat is a protocol, not a runtime. Paperclip defines how to initiate an
Agent configuration includes an **adapter** that defines how Paperclip invokes the agent. Initial adapters:
| Adapter | Mechanism | Example |
| --- | --- | --- |
| `process` | Execute a child process | `python run_agent.py --agent-id {id}` |
| `http` | Send an HTTP request | `POST https://openclaw.example.com/hook/{id}` |
| Adapter | Mechanism | Example |
| --------- | ----------------------- | --------------------------------------------- |
| `process` | Execute a child process | `python run_agent.py --agent-id {id}` |
| `http` | Send an HTTP request | `POST https://openclaw.example.com/hook/{id}` |
More adapters can be added.
@@ -355,6 +356,7 @@ The key constraint: it must be trivial to go from "I'm trying this on my machine
Agents need to register and authenticate with the Paperclip server to get an API key that identifies them.
Flow:
1. Agent "signs up" — requests access to the Paperclip instance
2. Human board member approves/onboards the agent
3. Agent receives credentials (API key) and can now interact with the control plane
@@ -363,12 +365,12 @@ This is the same pattern as agent hiring — an agent can't just show up, it nee
### Tech Stack
| Layer | Technology |
| --- | --- |
| Frontend | React + Vite |
| Backend | TypeScript + Hono (REST API, not tRPC — need non-TS clients) |
| Database | PostgreSQL (embedded for dev, hosted for production) |
| Auth | Standard React auth library (not Supabase-dependent) |
| Layer | Technology |
| -------- | ------------------------------------------------------------ |
| Frontend | React + Vite |
| Backend | TypeScript + Hono (REST API, not tRPC — need non-TS clients) |
| Database | PostgreSQL (embedded for dev, hosted for production) |
| Auth | Standard React auth library (not Supabase-dependent) |
### Concurrency Model: Atomic Task Checkout

127
doc/DATABASE.md Normal file
View File

@@ -0,0 +1,127 @@
# Database
Paperclip uses PostgreSQL via [Drizzle ORM](https://orm.drizzle.team/). There are three ways to run the database, from simplest to most production-ready.
## 1. Embedded (PGlite) — zero config
If you don't set `DATABASE_URL`, the server automatically starts an embedded PostgreSQL instance powered by [PGlite](https://pglite.dev/). No installation, no Docker, no setup.
```sh
pnpm dev
```
That's it. On first start the server:
1. Creates a `./data/pglite/` directory for storage
2. Pushes the Drizzle schema to create all tables
3. Starts serving requests
Data persists across restarts in the `./data/pglite/` directory. To reset the database, delete that directory.
**Limitations:**
- Single connection only (no concurrent access from multiple processes)
- Slower than native PostgreSQL (runs in WebAssembly)
- Not suitable for production
This mode is ideal for getting started, local development, and demos.
## 2. Local PostgreSQL (Docker)
For a full PostgreSQL server locally, use the included Docker Compose setup:
```sh
docker compose up -d
```
This starts PostgreSQL 17 on `localhost:5432`. Then set the connection string:
```sh
cp .env.example .env
# .env already contains:
# DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip
```
Run migrations (once the migration generation issue is fixed) or use `drizzle-kit push`:
```sh
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
npx drizzle-kit push
```
Start the server:
```sh
pnpm dev
```
## 3. Hosted PostgreSQL (Supabase)
For production, use a hosted PostgreSQL provider. [Supabase](https://supabase.com/) is a good option with a free tier.
### Setup
1. Create a project at [database.new](https://database.new)
2. Go to **Project Settings > Database > Connection string**
3. Copy the URI and replace the password placeholder with your database password
### Connection string
Supabase offers two connection modes:
**Direct connection** (port 5432) — use for migrations and one-off scripts:
```
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
```
**Connection pooling via Supavisor** (port 6543) — use for the application:
```
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
```
### Configure
Set `DATABASE_URL` in your `.env`:
```sh
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
```
If using connection pooling (port 6543), the `postgres` client must disable prepared statements. Update `packages/db/src/client.ts`:
```ts
export function createDb(url: string) {
const sql = postgres(url, { prepare: false });
return drizzlePg(sql, { schema });
}
```
### Push the schema
```sh
# Use the direct connection (port 5432) for schema changes
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
npx drizzle-kit push
```
### Free tier limits
- 500 MB database storage
- 200 concurrent connections
- Projects pause after 1 week of inactivity
See [Supabase pricing](https://supabase.com/pricing) for current details.
## Switching between modes
The database mode is controlled entirely by the `DATABASE_URL` environment variable:
| `DATABASE_URL` | Mode |
|---|---|
| Not set | Embedded PGlite (`./data/pglite/`) |
| `postgres://...localhost...` | Local Docker PostgreSQL |
| `postgres://...supabase.com...` | Hosted Supabase |
Your Drizzle schema (`packages/db/src/schema/`) stays the same regardless of mode.