- Every AI agent gets its own Postgres.
- Install one Skill. Your coding agent handles the database.
- No Docker required · Native Mac & Linux · Automatic cleanup
-
@@ -56,55 +66,24 @@ PGX is the database engine behind PGRun branches.
## Why PGX
+
+
+
+
Agent workflows, test suites, CI jobs and preview environments create
databases constantly and throw them away minutes or hours later. Giving
each one its own PostgreSQL server costs a process, tens of megabytes and
-a cold start every time. Traditional PostgreSQL is built to keep one
-database running for years; PGX is built to create one in milliseconds,
-use it briefly and delete it.
-
-Instead of
+a cold start every time. PostgreSQL is built to keep one database running
+for years; PGX is built to create one in milliseconds, use it briefly and
+delete it.
```text
-1 database → 1 PostgreSQL server
-```
-
-PGX runs
-
-```text
-1 PGX runtime → hundreds or thousands of isolated databases
+1 PostgreSQL server → 1 database
+1 PGX runtime → hundreds or thousands of isolated databases
```
Immutable state is shared. Each database pays mainly for what makes it
-different.
-
-## At a glance
-
-```text
-~6.2 ms warm database mint, p50
-~70 KB memory per untouched idle database
-~128 KB memory per idle database that has been queried
-1,000 isolated databases on a 4 vCPU / 8 GB VM
-128–155 MB total runtime memory with 1,000 untouched databases
-~213 MB after all 1,000 have been used
-
-10 GB template
-~674 ms copy-on-write branch ready
-~13.6 MB initial additional physical storage
-
-219 / 231 PostgreSQL src/test/regress byte-exact against PG 18.6 expected output
-0 result differences (the other 12 differ in EXPLAIN plan text only)
-```
-
-These describe measured configurations and workloads, not a claim that
-PGX is faster than PostgreSQL in general. The regression figure is
-PostgreSQL's own test suite on one platform under the regress profile
-(see Benchmarks), not a compatibility percentage for arbitrary SQL. The detail and the scripts are
-under Benchmarks.
-
-## Disposable Postgres
-
-PGX is built around a different database lifecycle:
+different, and follows a lifecycle built for being thrown away:
```text
sealed template
@@ -116,184 +95,196 @@ agent / CI / test minutes or hours
discard dropped a grace period after the last session
```
-A database is created from a sealed template without copying it. On
-Linux, ZFS block cloning lets a branch share every unchanged block with
+On Linux, ZFS block cloning lets a branch share every unchanged block with
its template, so a branch of a 10 GB database is ready in under a second
-and starts at about 14 MB of its own storage.
-
-## Built for agents
+and starts at about 14 MB of its own storage. Every agent, every pull
+request and every test run can have its own real Postgres-compatible
+database.
-A coding agent's database workload looks like this, and many agents do it
-at once:
+## Disposable by design
-```text
-create database → run migrations → change schema → load fixtures
-→ run tests → inspect results → throw the database away
-```
+Postgres was built to run for years. An AI agent needs a database for two
+minutes: create → migrate → test → destroy. Multiply that by 1,000 agents
+running in parallel and you do not need 1,000 Postgres servers. You need
+one runtime holding thousands of isolated, disposable databases, each
+dropped the moment its agent, pull request or CI job is done.
-PGX optimizes for that lifecycle rather than for a long-running
-production primary. Every agent, every pull request and every test run
-can have its own real Postgres-compatible database.
+
+
+
## Benchmarks
Measured on a 4 vCPU / 8 GB / $8.50-a-month VPS with ZFS block cloning. The
load generator ran on the same CPUs, so these are architecture numbers,
-not capacity limits. Every figure has a script and a committed raw result
-([`benchmarks/pgx/linux/README.md`](benchmarks/pgx/linux/README.md)).
+not capacity limits.
-### Density
+```text
+~6.2 ms warm database mint, p50 (cold copy-on-write clone: 74 ms)
+~70 KB memory per untouched idle database (~128 KB once queried)
+128–155 MB one runtime holding 1,000 idle databases; 213 MB after all were used
+111 MB runtime memory after 30,000 create → use → drop lifecycles
+~674 ms copy-on-write branch of a 10 GB template, ~13.6 MB new storage
+0 result differences on PostgreSQL's src/test/regress (219 / 231 byte-exact vs PG 18.6)
+36 / 36 framework runs: Rails 8.1, Django 5.2, Prisma 6, SQLAlchemy 2, node-postgres
+```
-| | PGX |
-|---|---|
-| 1000 idle databases in one runtime | **128–155 MB** in total |
-| 1000 databases, each queried once | **213 MB** |
-| Per idle database | ~70 KB untouched, ~128 KB once queried |
-| 1000 databases with 100 active | 283 MB |
-| Idle CPU with 1000 databases | 0.33–0.43 % of one core |
+PGX is not trying to beat Postgres on single queries. Short statements are
+about 0.05–0.15 ms slower than PostgreSQL 18, large single-threaded scans
+1.5–2.2× slower, COPY and 4-client throughput equal; whole framework
+lifecycles take 0–15 % longer. Transactional behaviour follows PostgreSQL,
+including transactional DDL, savepoints and temporary tables.
-For comparison, one idle PostgreSQL 18 server uses 16.6 MB.
+These describe measured configurations and workloads, not a claim that
+PGX is faster than PostgreSQL in general. The regression figure is
+PostgreSQL's own test suite on one platform under the regress profile,
+not a compatibility percentage for arbitrary SQL. Every figure has a
+script and a committed raw result. The tables, the isolation results and
+how to reproduce them: [`docs/pgx/benchmarks.md`](docs/pgx/benchmarks.md).
+The full report: [`docs/pgx/linux-results.md`](docs/pgx/linux-results.md).
-### Creating databases
+## Architecture
-| | p50 | p99 |
-|---|---|---|
-| Database from the warm pool | 6.2 ms | 9.4 ms |
-| Cold database (copy-on-write clone of a 600-file template) | 74 ms | 102 ms |
-| 500 simultaneous new-database requests | all served, 0 errors | drained in 12.6–13.8 s |
+The central idea: **share everything immutable, pay for divergence.** It
+applies to memory and to storage alike.
-Times run from the client's connection to its first `SELECT 1` answer,
-on the same host.
+One process, one shared SQL engine, and one isolated database per client:
+each agent, CI job or app connects over the ordinary Postgres wire protocol
+and gets a database branched copy-on-write from a sealed template. The
+engine, catalog, caches and warm pool are shared; only each database's own
+changed blocks are written. Nothing is shared between databases.
-### Branch storage (ZFS copy-on-write)
+### 1,000 databases, one PGX process
-| Template | Branch ready in | New physical storage |
-|---|---|---|
-| 1 GB | 135 ms | 2.3 MB |
-| 2 GB | 198 ms | 3.7 MB |
-| 5.2 GB | 417 ms | 7.0 MB |
-| 10 GB | 674 ms | 13.6 MB |
+One idle PostgreSQL server: 16.6 MB. One idle PGX database: about 70 KB.
+Databases are isolated by name, roles and limits inside one process with a
+thread per connection. A database costs its catalog caches, not a server:
+databases cloned from the same template share identical relation-cache
+entries, and idle databases are skipped by autovacuum. Measured on a
+4 vCPU / 8 GB VM:
-A branch of a 10 GB database is ready in under a second and initially uses
-hundreds of times less physical storage than a full copy.
+```text
+one idle PostgreSQL 18 server 16.6 MB
+one idle PGX database ~70 KB (~128 KB once it has been queried)
+1,000 idle PGX databases 128–155 MB in one process
+1,000 databases, each queried once 213 MB
+1,000 databases, 100 active 283 MB
+```
-### Running for a long time
+[Benchmarks →](docs/pgx/benchmarks.md)
-| | Runtime memory |
-|---|---|
-| After 10,000 create → use → drop lifecycles | 108 MB |
-| After 30,000 lifecycles | 111 MB (~0.2 KB retained per lifecycle) |
+### 1,000 databases on a MacBook in 46.6 seconds
-### Compatibility
+Not a cluster, not Docker, not the cloud: PGX on a 16 GB M1 Pro, cloning a
+31 MB app database 1,000 times with `pgx clone`. All 1,000 clones in
+46.6 s, 41 ms median per clone, 0 errors, and the engine idles at 20 MB
+RSS with all 1,000 present. Under load it grows with the sessions, not the
+databases: 340 MB at 100 concurrent readers, back to 20 MB when they
+disconnect.
-| | Result |
-|---|---|
-| PostgreSQL regression suite (`src/test/regress`, PG 18.6 expected output) | 219 / 231 byte-exact; 12 differ only in EXPLAIN plan text; **0 result differences**. Measured under the PGX profile with parallel workers and `synchronous_commit` restored (`configs/pgx-regress-overlay.conf`), the two settings the suite asserts on; under the shipped ephemeral profile those two settings alone make `select_parallel` and `stats` differ. One suite, one platform: not a compatibility score. |
-| Real Rails 8.1 app (migrations, fixtures, model tests, schema change, `db:schema:load`) | passes; within 0–5 % of PostgreSQL 18 per phase |
-| Django 5.2, Prisma 6, SQLAlchemy 2, node-postgres, Rails-shaped SQL | pass |
-| All framework runs | **36 / 36** |
-
-Transactional behaviour follows PostgreSQL, including transactional DDL
-and temporary tables:
-
-```sql
-BEGIN;
-CREATE TEMP TABLE pgx_test (id integer PRIMARY KEY, value text);
-INSERT INTO pgx_test VALUES (1, 'PGX works');
-SAVEPOINT before_update;
-UPDATE pgx_test SET value = 'changed' WHERE id = 1;
-ROLLBACK TO SAVEPOINT before_update;
-SELECT value FROM pgx_test WHERE id = 1; -- 'PGX works'
-ROLLBACK;
-SELECT to_regclass('pg_temp.pgx_test'); -- NULL: the table was rolled back
+
+
+
+
+### Mint-on-connect
+
+Connecting to `__` creates that database from a
+sealed template and connects you to it, in about 6 ms from a warm pool of
+pre-created spares and 74 ms cold. A janitor drops it a grace period after
+its last session ends, unless it is pinned.
+
+```text
+psql -d tdb_tpl_app__agent42 # does not exist yet
+ # → minted from tpl_app, 6.2 ms p50
+psql -d tdb_tpl_app__agent43 # a second, isolated one
+ # → 0 rows: it sees none of agent42's writes
+ …last session ends…
+ # → dropped after the grace period
```
-### Isolation
+[Sealing templates and minting by hand →](docs/pgx/local-dev/build-from-source.md#make-a-template-and-mint-databases)
-- **Memory limits.** Session, database and runtime limits refuse runaway
- work with SQLSTATE 53200. The session stays usable, other databases see
- 0 errors, and the runtime does not crash.
-- **Connection limits.** A neighbour running a 50-connection migration
- mix pushes another database's p99 latency to 9.8 ms. Capped at 1 / 2 / 5
- connections per database, it stays at 1.2 / 2.1 / 4.0 ms.
-- **cgroups.** The kernel is the last line: under a cgroup memory limit,
- the worst case is that one runtime is killed. The host survives.
+### Copy-on-write storage
-### Query speed
+`file_copy_method = clone` turns database creation into ZFS block clones.
+A branch shares every unchanged block with its template, so its cost grows
+with divergence: a branch of a 10 GB template is ready in 674 ms and starts
+at 13.6 MB of its own storage. On a filesystem without block cloning a
+branch is a full copy.
-PGX is not trying to beat Postgres on single queries.
+```text
+template (10 GB)
+├── branch A → only changed blocks 674 ms, +13.6 MB
+├── branch B → only changed blocks
+└── branch C → only changed blocks
+
+ 1 GB template → 135 ms, +2.3 MB
+ 2 GB → 198 ms, +3.7 MB
+ 5 GB → 417 ms, +7.0 MB
+10 GB → 674 ms, +13.6 MB
+```
-- **Short statements:** about 0.05–0.15 ms slower than PostgreSQL 18; a
- point SELECT takes 0.25 vs 0.13 ms.
-- **Large single-threaded scans:** 1.5–2.2× slower.
-- **Equal:** COPY and 4-client throughput.
+[ZFS copy-on-write →](docs/pgx/zfs-cow.md)
-Whole framework lifecycles take 0–15 % longer than on PostgreSQL 18.
+### Limits at every level
-PGX concentrates on database creation latency, density, memory
-efficiency, copy-on-write storage, high churn and isolation between
-tenants. For application lifecycle workloads it stays close enough to
-PostgreSQL while making large numbers of isolated databases dramatically
-cheaper to keep alive.
+Session, database and runtime memory limits inside the engine, connection
+limits per database, and a Linux cgroup around the runtime. Memory-heavy
+work fails with a PostgreSQL-style error (SQLSTATE 53200) instead of
+taking the host down; the session stays usable and other databases see
+0 errors. `pgrust_runtime_status()` returns the whole picture as JSON:
+databases, sessions, memory against limits, warm-pool and mint counters.
-The full report:
-[`docs/pgx/linux-results.md`](docs/pgx/linux-results.md). The frozen v0.1
-numbers: [`docs/pgx/v0.1-baseline.md`](docs/pgx/v0.1-baseline.md).
+```text
+session → database / runtime → Linux cgroup
-## How it works
+ERROR: out of memory SQLSTATE 53200
+HINT: The session reached pgrust.session_memory_limit.
+ (transaction aborted; session stays connected; other databases: 0 errors)
-The central idea: **share everything immutable, pay for divergence.** It
-applies to memory and to storage alike.
+SELECT pgrust_runtime_status();
+{"memory":{"rss_bytes":…,"session_limit_mb":256,"database_limit_mb":512,"runtime_limit_mb":3072},
+ "connections":42,
+ "databases":{"total":1003,"ephemeral":998,"spares":8,"pinned":2},
+ "janitor":{"prefix":"tdb_","pool_size":8,"pending_mints":0}}
+```
-- **Shared runtime.** Databases are isolated by name, roles and limits
- inside one process with a thread per connection. A database costs its
- catalog caches, not a server.
-- **Shared catalog state.** Databases cloned from the same template share
- identical relation-cache entries. Idle databases are skipped by
- autovacuum.
-- **Mint-on-connect.** Connecting to `__` creates
- that database from a sealed template and connects you to it. A warm pool
- of pre-created spares serves bursts. A janitor drops databases a grace
- period after their last session.
-- **Copy-on-write storage.** `file_copy_method = clone` turns database
- creation into ZFS block clones, so a branch shares every unchanged block
- with its template and its cost grows with divergence:
-
- ```text
- template
- ├── branch A → only changed blocks
- ├── branch B → only changed blocks
- └── branch C → only changed blocks
- ```
-- **Limits at every level.** Session, database and runtime memory limits
- inside the engine, connection limits per database, and a Linux cgroup
- around the runtime. Memory-heavy work fails with a PostgreSQL-style
- error instead of taking the host down:
-
- ```text
- session → database / runtime → Linux cgroup
- ```
-- **Machine-readable status.** `pgrust_runtime_status()` returns JSON with
- databases, sessions, memory against limits, and warm-pool and mint
- counters.
+[Memory limits and the cgroup backstop →](docs/pgx/linux-limits.md)
+
+### A branch per agent
+
+The PGX Skill and `pgx agent` put the lifecycle on hooks: each Claude Code
+session gets its own branch with `DATABASE_URL` already set, extended while
+the agent works and dropped after it stops, and every subagent gets a
+branch of its own. Two agents never see each other's changes, and your
+PostgreSQL is only ever read.
+
+```text
+session start → pgx branch myapp DATABASE_URL set
+ subagent → pgx branch myapp its own branch
+ agent works → TTL extended
+session end → branch dropped
+```
+
+[Agents →](docs/pgx/local-dev/agents.md)
## Status
PGX is at **v0.1.2**, an early release with known limitations listed in the
[release notes](https://github.com/pgrundev/pgx/releases/tag/v0.1.2). It
-already powers PGX branches in PGRun. The current focus is PostgreSQL compatibility,
-Rails/application compatibility, concurrency semantics, lifecycle
-correctness, predictable resource containment and real agent workloads.
-Correctness and reproducibility come before benchmark marketing: if PGX
-behaves differently from PostgreSQL for a supported workload, we want a
-reproducible test case (see Contributing).
+already powers PGX branches in [PGRun](https://pgrun.dev), which adds the
+control plane around it: safe masked copies of a source database, sealed
+templates, one branch per agent, PR or CI job, routing, access and
+automatic expiry. The current focus is PostgreSQL and application
+compatibility, concurrency semantics, lifecycle correctness, predictable
+resource containment and real agent workloads. If PGX behaves differently
+from PostgreSQL for a supported workload, we want a reproducible test case
+(see Contributing).
**PGX v0.1 is for disposable databases:** agent, CI, PR, test and preview
databases built from sanitized copies of production. Use it where losing
-a branch is acceptable.
-
-It is **not** a production primary, a system of record, or an HA or PITR
-database:
+a branch is acceptable. It is **not** a production primary, a system of
+record, or an HA or PITR database:
- **Not crash-safe.** The PGX profile runs with `fsync = off`.
- **One process.** A runtime is one crash domain.
@@ -302,32 +293,7 @@ database:
PL/Perl and PL/Tcl are not available.
Keep Postgres as the source of truth for data you can't afford to lose.
-If you need a long-lived production database, use PostgreSQL or a managed
-PostgreSQL service. If you need hundreds or thousands of databases that
-live for minutes or hours, PGX is the workload we are building for. The
-full list is in [`docs/pgx/v0.1-readiness.md`](docs/pgx/v0.1-readiness.md).
-
-## PGX + PGRun
-
-PGX is the database engine behind PGX branches in [PGRun](https://pgrun.dev).
-PGRun adds the control plane around it:
-
-```text
-production / source database
- ↓
-safe copy with masking
- ↓
-sealed template
- ↓
-PGX
- ↓
-one branch per agent, PR or CI job
- ↓
-automatic expiry
-```
-
-PGRun handles orchestration, routing, templates, lifecycle, access and
-branch management. PGX is the runtime. They are separate projects.
+The full list is in [`docs/pgx/v0.1-readiness.md`](docs/pgx/v0.1-readiness.md).
## Quickstart
@@ -346,59 +312,37 @@ PGX is ready.
```
Point `DATABASE_URL` at that URL. `npx pgxdb` with no arguments is `pgx run`;
-every other `pgx` command works the same way through npx:
-
-```bash
-npx pgxdb url app # its connection URL
-npx pgxdb ls # databases, TTL, expiry, size
-npx pgxdb branch app agent-001 # a disposable 2-hour branch for one agent
-npx pgxdb clone app pr-123 --ttl 5m # a 5-minute copy-on-write clone
-npx pgxdb --json ls # machine-readable output for agents
-npx pgxdb stop
-```
-
-The binary is downloaded once (SHA-256 verified) into `~/.pgx/dist/`
-and reused. `npm install -g pgxdb` puts `pgx` on your PATH for good. Supported:
-Apple silicon Macs and Linux (arm64, x86-64). Intel Macs are not supported yet.
-
-Without Node, the shell installer does the same install:
+every other `pgx` command works the same way through npx, and
+`npm install -g pgxdb` puts `pgx` on your PATH for good:
```bash
-curl -fsSL https://pgx.dev/install | sh
-pgx run
+pgx url app # its connection URL
+pgx ls # databases, TTL, expiry, size
+pgx branch app agent-001 # a disposable 2-hour branch for one agent
+pgx clone app pr-123 --ttl 5m # a 5-minute copy-on-write clone
+pgx create scratch --ttl 2h # a throwaway database, gone in 2 hours
+pgx --json ls # machine-readable output for agents
+pgx stop
```
-Any PostgreSQL client or ORM works against `pgx url `. The full CLI,
-install options, integrations (Rails, Node, Python, Go, psql), storage model
-and troubleshooting are under [`docs/pgx/local-dev/`](docs/pgx/local-dev/); the
-test matrix and release decision are in
-[`docs/pgx/local-dev/validation-report.md`](docs/pgx/local-dev/validation-report.md).
-
-### Branching and temporary databases
-
-Clones are fast branches: copy-on-write on a backend with block cloning
-(such as ZFS), a full copy elsewhere. Any database can also carry a TTL so
-it cleans itself up — a branch per agent, pull request or CI job that you
-never have to remember to delete.
-
-```bash
-pgx branch myapp agent-1 # an agent's own database; expires in 2 hours
-pgx branch myapp agent-2 --ttl 30m # same, with a shorter TTL
-pgx clone myapp pr-123 --ttl 30m # a clone; permanent unless you give a TTL
-pgx create scratch --ttl 2h # a throwaway database, gone in 2 hours
-pgx ls # shows each database's TTL and expiry
-pgx ttl pr-123 unlimited # changed your mind — keep it for good
-```
-
-`pgx branch` is `pgx clone` for agents: a 2-hour TTL by default and the
-connection URL as the last line of output, so an agent can be handed
-`DATABASE_URL="$(pgx branch myapp agent-1 | tail -1)"` or read `--json`.
-
-Databases created without `--ttl` are permanent. A background supervisor
-drops each temporary database shortly after its deadline, never touches
-permanent or system databases, and resumes cleanup automatically after a
-`pgx stop` / `pgx start`. Details and the identity-safety model are in
-[`docs/pgx/local-dev/ttl.md`](docs/pgx/local-dev/ttl.md).
+Without Node, `curl -fsSL https://pgx.dev/install | sh` does the same
+install. Supported: Apple silicon Macs and Linux (arm64, x86-64); Intel
+Macs are not supported yet. Any PostgreSQL client or ORM works against
+`pgx url `.
+
+Clones are copy-on-write on a backend with block cloning (such as ZFS), a
+full copy elsewhere. `pgx branch` is `pgx clone` for agents: a 2-hour TTL by
+default and the connection URL as the last line of output, so an agent can
+be handed `DATABASE_URL="$(pgx branch myapp agent-1 | tail -1)"`. Databases
+created without `--ttl` are permanent; a background supervisor drops each
+temporary one shortly after its deadline and never touches permanent or
+system databases ([`ttl.md`](docs/pgx/local-dev/ttl.md)).
+
+The full CLI, install options, integrations (Rails, Node, Python, Go, psql),
+storage model and troubleshooting are under
+[`docs/pgx/local-dev/`](docs/pgx/local-dev/). Building the engine yourself,
+running a bare runtime and minting from a sealed template by hand:
+[`build-from-source.md`](docs/pgx/local-dev/build-from-source.md).
### Every AI agent gets its own Postgres
@@ -410,13 +354,6 @@ Install the Skill, then ask your agent. In Claude Code, from inside a session:
/pgx:pgx Set up isolated Postgres databases for this project.
```
-[`pgrundev/pgx-plugin`](https://github.com/pgrundev/pgx-plugin) holds only
-the plugin (about 110 KB, generated from this repository on every change to
-`main`), so the install takes seconds. Adding this repository itself as the
-marketplace still works (`/plugin marketplace add pgrundev/pgx`, then
-`/plugin install pgx@pgrundev`) but clones about 2 GB. Install from one of
-the two, not both.
-
For Cursor, Codex or several hosts at once, the Agent Skills CLI installs the
same Skill into the project:
@@ -426,24 +363,15 @@ npx skills add pgrundev/pgx --skill pgx
> Use PGX to give this project isolated databases for coding agents. Set everything up.
-The plugin is the Skill packaged for Claude Code's plugin manager (`/plugin`
-lists, updates and removes it; the skill is `/pgx:pgx` and Claude also picks
-it up on its own when a task touches the database). The Skills CLI copies
-the Skill into `.claude/skills/pgx` and `.agents/skills/pgx` for every host.
-Both contain no hooks and install no database engine: the `pgx` binary is
-installed when the Skill first needs it, with your approval. Details and the
-differences: [`docs/pgx/local-dev/claude-plugin.md`](docs/pgx/local-dev/claude-plugin.md).
-
The agent detects your database setup, installs PGX if needed, imports your
development database **schema-only** into a template (`pgx copy`), writes a
committed `.pgx.toml`, installs lifecycle hooks (`pgx agent setup`) and
-proves a branch. From then on each Claude Code session gets its own branch
-with `DATABASE_URL` already set, extended while the agent works and removed
-after it stops, and every subagent it launches gets a branch of its own,
-with its shell commands routed there; two agents never see each other's
-changes and your PostgreSQL is never written. Installing the Skill executes
-nothing by itself: the agent does the setup when the Skill is activated and
-your permissions allow it.
+proves a branch. From then on each session gets its own branch with
+`DATABASE_URL` already set, extended while the agent works and removed after
+it stops, and every subagent gets a branch of its own; two agents never see
+each other's changes and your PostgreSQL is never written. Installing the
+Skill executes nothing by itself: the agent does the setup when the Skill is
+activated and your permissions allow it.
| Host | Automatic isolation | Status |
|---|---|---|
@@ -451,9 +379,13 @@ your permissions allow it.
| Cursor | hooks installed; `pgx agent exec --` when `PGX_ISOLATION` is unset | env propagation pending live verification |
| Codex | `pgx agent exec -- codex` (Codex hooks cannot set environment) | wrapper verified |
-Requires PGX 0.1.2 or newer ([release v0.1.2](https://github.com/pgrundev/pgx/releases/tag/v0.1.2); 0.1.1 isolates sessions but lets subagents share the parent's branch); the Skill checks the installed version before it does anything.
-Details, the lifecycle, security rules and the validation notes:
-[`docs/pgx/local-dev/agents.md`](docs/pgx/local-dev/agents.md). The Skill
+Requires PGX 0.1.2 or newer; the Skill checks the installed version before it
+does anything. [`pgrundev/pgx-plugin`](https://github.com/pgrundev/pgx-plugin)
+holds only the plugin (about 110 KB, generated from this repository), so
+the install takes seconds; adding this repository itself as the marketplace
+works too but clones about 2 GB. The lifecycle, security rules and host
+differences: [`agents.md`](docs/pgx/local-dev/agents.md),
+[`claude-plugin.md`](docs/pgx/local-dev/claude-plugin.md); the Skill
itself: [`skills/pgx/SKILL.md`](skills/pgx/SKILL.md).
### Keep your Postgres, branch it in PGX (by hand)
@@ -474,128 +406,25 @@ pgx branch myapp agent-2
pgx agent exec -- bin/rails test # or: any command in a branch of its own
```
-```console
-$ pgx branch myapp agent-1
-✓ Branch created: agent-1
-✓ Source: myapp
-✓ Expires in: 2 hours
-
-DATABASE_URL:
-postgres://postgres:…@127.0.0.1:5433/agent-1
-```
-
`--schema-only` copies the structure without the rows (migration bookkeeping
-such as `schema_migrations` is kept, so frameworks see their migrations as
-applied). The copy needs `pg_dump`/`pg_restore` on the machine (a PostgreSQL
-install has them); it refuses sources that use extensions PGX does not ship
-(PostGIS, TimescaleDB, …) and never overwrites an existing database. Details,
-limitations and measurements: [`docs/pgx/local-dev/copy.md`](docs/pgx/local-dev/copy.md).
-
-### Build from source
-
-You need the Rust toolchain (`rust-toolchain.toml` pins Rust 1.96.0;
-rustup fetches it), the RE2 regex library, and the PostgreSQL 18 client
-tools for `initdb` and `psql`:
-
-- Debian/Ubuntu: `sudo apt-get install -y build-essential pkg-config libre2-dev`,
- plus `postgresql-18` and `postgresql-client-18` from the
- [PGDG apt repo](https://www.postgresql.org/download/linux/ubuntu/)
- (then `export PATH="/usr/lib/postgresql/18/bin:$PATH"`).
-- macOS: `brew install re2 pkg-config postgresql@18`, then
- `export PATH="$(brew --prefix postgresql@18)/bin:$PATH"`.
-
-```bash
-cargo build --release --locked --bin postgres
-```
-
-The server binary is `target/release/postgres`. Release builds refuse to
-compile without RE2 on purpose: the fallback regex engine is much slower.
-
-At run time the server loads two libraries with `dlopen` rather than
-linking them: libxml2 for the `xml` type and SQL/XML functions, and ICU for
-ICU collations. The PostgreSQL 18 packages above install both. A host
-without libxml2 still starts, but XML functions raise `unsupported XML
-feature` (SQLSTATE 0A000) with a hint naming the library, and
-`pgrust_runtime_status()` reports `libraries.libxml2.available = false`.
-Debian/Ubuntu package: `libxml2` (`libxml2-16` from Ubuntu 25.10); macOS
-ships libxml2 with the system.
-
-### Run a PGX runtime
-
-```bash
-# Create a cluster and apply the PGX profile.
-initdb -D /tmp/pgx-data --no-locale --encoding UTF8 -U postgres
-cat configs/pgx-ephemeral.conf >> /tmp/pgx-data/postgresql.conf
-
-# The server reads timezone and other data files from a Postgres share dir.
-export PGRUST_PGSHAREDIR=/usr/share/postgresql/18 # macOS: "$(brew --prefix postgresql@18)/share/postgresql"
-export PGRUST_TZDIR=/usr/share/zoneinfo # macOS: "$PGRUST_PGSHAREDIR/timezone"
-
-ulimit -s 65520
-RUST_MIN_STACK=33554432 target/release/postgres \
- -D /tmp/pgx-data -k /tmp -p 5432 \
- -c listen_addresses= -c io_method=sync -c max_stack_depth=60000 \
- -c pgrust.ephemeral_db_prefix=tdb_ \
- -c pgrust.ephemeral_db_mint_roles=postgres
-```
-
-Put the data directory on a ZFS dataset with block cloning enabled to get
-copy-on-write branches (the profile sets `file_copy_method = clone`). On a
-filesystem without block cloning, remove that line from the profile and
-databases are created by copying files.
-
-### Make a template and mint databases
-
-```bash
-# Build a template once, then seal it.
-psql -h /tmp -U postgres -c "CREATE DATABASE tpl_app"
-psql -h /tmp -U postgres -d tpl_app -c "CREATE TABLE users (id bigserial PRIMARY KEY, email text UNIQUE)"
-psql -h /tmp -U postgres -c "SELECT pgrust_seal_template('tpl_app')"
-
-# Connecting to __ creates that database.
-psql -h /tmp -U postgres -d tdb_tpl_app__agent42 -c "INSERT INTO users (email) VALUES ('a@example.com')"
-psql -h /tmp -U postgres -d tdb_tpl_app__agent43 -c "SELECT count(*) FROM users" # 0: isolated
-
-# Runtime status as JSON.
-psql -h /tmp -U postgres -Atc "SELECT pgrust_runtime_status()"
-```
-
-Minted databases are dropped `pgrust.ephemeral_db_grace` seconds after
-their last session ends, unless they are pinned with
-`pgrust_pin_database()`. All control functions are listed in
-[`docs/pgx/pgrun-interface.md`](docs/pgx/pgrun-interface.md).
-
-## Reproducing the benchmarks
-
-The harness is Python 3 standard library only and talks the wire protocol
-directly. On a Linux host with a ZFS pool:
-
-```bash
-PGXBENCH_WORKDIR=/tank/pgx-bench benchmarks/pgx/linux/run-suite.sh scale churn pool cow regress
-```
-
-Results land in `benchmarks/pgx/results/-/`.
-[`benchmarks/pgx/linux/README.md`](benchmarks/pgx/linux/README.md) maps
-every headline number above to its step, script and raw result.
-
-The regression suite is PostgreSQL's own `src/test/regress`, vendored
-unmodified at `crates/postgres-18.6-reference/src/test/regress`. It runs
-with stock `pg_regress` (`benchmarks/pgx/regress-baseline.py`) under the
-PGX profile plus `configs/pgx-regress-overlay.conf` (parallel workers and
-`synchronous_commit` back on, because the suite asserts on them), and each
-difference is classified as plan-only or semantic. The nightly run fails on
-any semantic difference (`classify-regress-diffs.py --fail-on-semantic`).
+such as `schema_migrations` is kept). The copy needs `pg_dump`/`pg_restore`
+on the machine, refuses sources that use extensions PGX does not ship
+(PostGIS, TimescaleDB, …) and never overwrites an existing database.
+Details, limitations and measurements: [`copy.md`](docs/pgx/local-dev/copy.md).
## Documentation
| Document | What's in it |
|---|---|
+| [`benchmarks.md`](docs/pgx/benchmarks.md) | every benchmark table, isolation results, and how to reproduce them |
+| [`linux-results.md`](docs/pgx/linux-results.md) | the full benchmark report |
| [`v0.1-baseline.md`](docs/pgx/v0.1-baseline.md) | the frozen v0.1 numbers and the build each came from |
| [`v0.1-readiness.md`](docs/pgx/v0.1-readiness.md) | what is ready, known limitations, approved use |
-| [`linux-results.md`](docs/pgx/linux-results.md) | the full benchmark report |
| [`compatibility-gate.md`](docs/pgx/compatibility-gate.md) | how to decide whether a database can run on PGX |
-| [`local-dev/claude-plugin.md`](docs/pgx/local-dev/claude-plugin.md) | the PGX plugin for Claude Code: `/plugin marketplace add pgrundev/pgx-plugin`, `/plugin install pgx@pgx-plugin`, the lightweight `pgrundev/pgx-plugin` repository and how it is generated, how it relates to the Skills CLI install, updates, removal, the Anthropic directory submission |
-| [`local-dev/agents.md`](docs/pgx/local-dev/agents.md) | the PGX Skill and `pgx agent`: automatic per-session isolation for Claude Code, Cursor and Codex, with the compatibility matrix |
+| [`local-dev/`](docs/pgx/local-dev/) | install, CLI, integrations, storage, TTL, troubleshooting |
+| [`local-dev/build-from-source.md`](docs/pgx/local-dev/build-from-source.md) | build the engine, run a bare runtime, seal a template and mint databases by hand |
+| [`local-dev/claude-plugin.md`](docs/pgx/local-dev/claude-plugin.md) | the PGX plugin for Claude Code and the lightweight `pgrundev/pgx-plugin` repository |
+| [`local-dev/agents.md`](docs/pgx/local-dev/agents.md) | the PGX Skill and `pgx agent`: automatic per-session isolation for Claude Code, Cursor and Codex |
| [`local-dev/copy.md`](docs/pgx/local-dev/copy.md) | `pgx copy`: import an existing PostgreSQL database and branch it per agent |
| [`pgrun-integration.md`](docs/pgx/pgrun-integration.md) | the host-agent API PGRun builds against |
| [`lifecycle-memory.md`](docs/pgx/lifecycle-memory.md), [`linux-churn.md`](docs/pgx/linux-churn.md) | memory under long churn, and what was fixed |
@@ -621,6 +450,10 @@ levels, transactional DDL, ORMs (Rails, Django, Prisma, SQLAlchemy),
schema migrations and client compatibility. Every real compatibility bug
becomes a permanent regression test.
+
+
+
+
## Origin
PGX is built on [pgrust](https://github.com/malisper/pgrust), a
diff --git a/docs/pgx/benchmarks.md b/docs/pgx/benchmarks.md
new file mode 100644
index 0000000000..337c7dc120
--- /dev/null
+++ b/docs/pgx/benchmarks.md
@@ -0,0 +1,127 @@
+# PGX benchmarks
+
+The numbers behind the README's Benchmarks section, with every table and the isolation and query-speed results. The full report is [`linux-results.md`](linux-results.md) and the frozen v0.1 numbers are [`v0.1-baseline.md`](v0.1-baseline.md).
+
+Measured on a 4 vCPU / 8 GB / $8.50-a-month VPS with ZFS block cloning. The
+load generator ran on the same CPUs, so these are architecture numbers,
+not capacity limits. Every figure has a script and a committed raw result
+([`benchmarks/pgx/linux/README.md`](../../benchmarks/pgx/linux/README.md)).
+
+## Density
+
+| | PGX |
+|---|---|
+| 1000 idle databases in one runtime | **128–155 MB** in total |
+| 1000 databases, each queried once | **213 MB** |
+| Per idle database | ~70 KB untouched, ~128 KB once queried |
+| 1000 databases with 100 active | 283 MB |
+| Idle CPU with 1000 databases | 0.33–0.43 % of one core |
+
+For comparison, one idle PostgreSQL 18 server uses 16.6 MB.
+
+## Creating databases
+
+| | p50 | p99 |
+|---|---|---|
+| Database from the warm pool | 6.2 ms | 9.4 ms |
+| Cold database (copy-on-write clone of a 600-file template) | 74 ms | 102 ms |
+| 500 simultaneous new-database requests | all served, 0 errors | drained in 12.6–13.8 s |
+
+Times run from the client's connection to its first `SELECT 1` answer,
+on the same host.
+
+## Branch storage (ZFS copy-on-write)
+
+| Template | Branch ready in | New physical storage |
+|---|---|---|
+| 1 GB | 135 ms | 2.3 MB |
+| 2 GB | 198 ms | 3.7 MB |
+| 5.2 GB | 417 ms | 7.0 MB |
+| 10 GB | 674 ms | 13.6 MB |
+
+A branch of a 10 GB database is ready in under a second and initially uses
+hundreds of times less physical storage than a full copy.
+
+## Running for a long time
+
+| | Runtime memory |
+|---|---|
+| After 10,000 create → use → drop lifecycles | 108 MB |
+| After 30,000 lifecycles | 111 MB (~0.2 KB retained per lifecycle) |
+
+## Compatibility
+
+| | Result |
+|---|---|
+| PostgreSQL regression suite (`src/test/regress`, PG 18.6 expected output) | 219 / 231 byte-exact; 12 differ only in EXPLAIN plan text; **0 result differences**. Measured under the PGX profile with parallel workers and `synchronous_commit` restored (`configs/pgx-regress-overlay.conf`), the two settings the suite asserts on; under the shipped ephemeral profile those two settings alone make `select_parallel` and `stats` differ. One suite, one platform: not a compatibility score. |
+| Real Rails 8.1 app (migrations, fixtures, model tests, schema change, `db:schema:load`) | passes; within 0–5 % of PostgreSQL 18 per phase |
+| Django 5.2, Prisma 6, SQLAlchemy 2, node-postgres, Rails-shaped SQL | pass |
+| All framework runs | **36 / 36** |
+
+Transactional behaviour follows PostgreSQL, including transactional DDL
+and temporary tables:
+
+```sql
+BEGIN;
+CREATE TEMP TABLE pgx_test (id integer PRIMARY KEY, value text);
+INSERT INTO pgx_test VALUES (1, 'PGX works');
+SAVEPOINT before_update;
+UPDATE pgx_test SET value = 'changed' WHERE id = 1;
+ROLLBACK TO SAVEPOINT before_update;
+SELECT value FROM pgx_test WHERE id = 1; -- 'PGX works'
+ROLLBACK;
+SELECT to_regclass('pg_temp.pgx_test'); -- NULL: the table was rolled back
+```
+
+## Isolation
+
+- **Memory limits.** Session, database and runtime limits refuse runaway
+ work with SQLSTATE 53200. The session stays usable, other databases see
+ 0 errors, and the runtime does not crash.
+- **Connection limits.** A neighbour running a 50-connection migration
+ mix pushes another database's p99 latency to 9.8 ms. Capped at 1 / 2 / 5
+ connections per database, it stays at 1.2 / 2.1 / 4.0 ms.
+- **cgroups.** The kernel is the last line: under a cgroup memory limit,
+ the worst case is that one runtime is killed. The host survives.
+
+## Query speed
+
+PGX is not trying to beat Postgres on single queries.
+
+- **Short statements:** about 0.05–0.15 ms slower than PostgreSQL 18; a
+ point SELECT takes 0.25 vs 0.13 ms.
+- **Large single-threaded scans:** 1.5–2.2× slower.
+- **Equal:** COPY and 4-client throughput.
+
+Whole framework lifecycles take 0–15 % longer than on PostgreSQL 18.
+
+PGX concentrates on database creation latency, density, memory
+efficiency, copy-on-write storage, high churn and isolation between
+tenants. For application lifecycle workloads it stays close enough to
+PostgreSQL while making large numbers of isolated databases dramatically
+cheaper to keep alive.
+
+The full report:
+[`docs/pgx/linux-results.md`](linux-results.md). The frozen v0.1
+numbers: [`docs/pgx/v0.1-baseline.md`](v0.1-baseline.md).
+
+## Reproducing the benchmarks
+
+The harness is Python 3 standard library only and talks the wire protocol
+directly. On a Linux host with a ZFS pool:
+
+```bash
+PGXBENCH_WORKDIR=/tank/pgx-bench benchmarks/pgx/linux/run-suite.sh scale churn pool cow regress
+```
+
+Results land in `benchmarks/pgx/results/-/`.
+[`benchmarks/pgx/linux/README.md`](../../benchmarks/pgx/linux/README.md) maps
+every headline number in this document to its step, script and raw result.
+
+The regression suite is PostgreSQL's own `src/test/regress`, vendored
+unmodified at `crates/postgres-18.6-reference/src/test/regress`. It runs
+with stock `pg_regress` (`benchmarks/pgx/regress-baseline.py`) under the
+PGX profile plus `configs/pgx-regress-overlay.conf` (parallel workers and
+`synchronous_commit` back on, because the suite asserts on them), and each
+difference is classified as plan-only or semantic. The nightly run fails on
+any semantic difference (`classify-regress-diffs.py --fail-on-semantic`).
diff --git a/docs/pgx/img/arch-density.gif b/docs/pgx/img/arch-density.gif
new file mode 100644
index 0000000000..12720c539c
Binary files /dev/null and b/docs/pgx/img/arch-density.gif differ
diff --git a/docs/pgx/img/arch-density.jpg b/docs/pgx/img/arch-density.jpg
new file mode 100644
index 0000000000..79c7d457bf
Binary files /dev/null and b/docs/pgx/img/arch-density.jpg differ
diff --git a/docs/pgx/img/architecture.gif b/docs/pgx/img/architecture.gif
new file mode 100644
index 0000000000..c754a347a9
Binary files /dev/null and b/docs/pgx/img/architecture.gif differ
diff --git a/docs/pgx/img/disposable.gif b/docs/pgx/img/disposable.gif
new file mode 100644
index 0000000000..02497e7963
Binary files /dev/null and b/docs/pgx/img/disposable.gif differ
diff --git a/docs/pgx/img/disposable.jpg b/docs/pgx/img/disposable.jpg
new file mode 100644
index 0000000000..36a1c59d6d
Binary files /dev/null and b/docs/pgx/img/disposable.jpg differ
diff --git a/docs/pgx/img/star-history.png b/docs/pgx/img/star-history.png
new file mode 100644
index 0000000000..69742db173
Binary files /dev/null and b/docs/pgx/img/star-history.png differ
diff --git a/docs/pgx/local-dev/build-from-source.md b/docs/pgx/local-dev/build-from-source.md
new file mode 100644
index 0000000000..28d1bb2143
--- /dev/null
+++ b/docs/pgx/local-dev/build-from-source.md
@@ -0,0 +1,77 @@
+# Building PGX from source
+
+The prebuilt distribution (`npx pgxdb@latest` or `curl -fsSL https://pgx.dev/install | sh`, see [`install.md`](install.md)) is the normal way to run PGX. This page is for building the engine yourself, running a bare runtime without the `pgx` CLI, and minting databases from a sealed template by hand.
+
+## Build
+
+You need the Rust toolchain (`rust-toolchain.toml` pins Rust 1.96.0;
+rustup fetches it), the RE2 regex library, and the PostgreSQL 18 client
+tools for `initdb` and `psql`:
+
+- Debian/Ubuntu: `sudo apt-get install -y build-essential pkg-config libre2-dev`,
+ plus `postgresql-18` and `postgresql-client-18` from the
+ [PGDG apt repo](https://www.postgresql.org/download/linux/ubuntu/)
+ (then `export PATH="/usr/lib/postgresql/18/bin:$PATH"`).
+- macOS: `brew install re2 pkg-config postgresql@18`, then
+ `export PATH="$(brew --prefix postgresql@18)/bin:$PATH"`.
+
+```bash
+cargo build --release --locked --bin postgres
+```
+
+The server binary is `target/release/postgres`. Release builds refuse to
+compile without RE2 on purpose: the fallback regex engine is much slower.
+
+At run time the server loads two libraries with `dlopen` rather than
+linking them: libxml2 for the `xml` type and SQL/XML functions, and ICU for
+ICU collations. The PostgreSQL 18 packages above install both. A host
+without libxml2 still starts, but XML functions raise `unsupported XML
+feature` (SQLSTATE 0A000) with a hint naming the library, and
+`pgrust_runtime_status()` reports `libraries.libxml2.available = false`.
+Debian/Ubuntu package: `libxml2` (`libxml2-16` from Ubuntu 25.10); macOS
+ships libxml2 with the system.
+
+## Run a PGX runtime
+
+```bash
+# Create a cluster and apply the PGX profile.
+initdb -D /tmp/pgx-data --no-locale --encoding UTF8 -U postgres
+cat configs/pgx-ephemeral.conf >> /tmp/pgx-data/postgresql.conf
+
+# The server reads timezone and other data files from a Postgres share dir.
+export PGRUST_PGSHAREDIR=/usr/share/postgresql/18 # macOS: "$(brew --prefix postgresql@18)/share/postgresql"
+export PGRUST_TZDIR=/usr/share/zoneinfo # macOS: "$PGRUST_PGSHAREDIR/timezone"
+
+ulimit -s 65520
+RUST_MIN_STACK=33554432 target/release/postgres \
+ -D /tmp/pgx-data -k /tmp -p 5432 \
+ -c listen_addresses= -c io_method=sync -c max_stack_depth=60000 \
+ -c pgrust.ephemeral_db_prefix=tdb_ \
+ -c pgrust.ephemeral_db_mint_roles=postgres
+```
+
+Put the data directory on a ZFS dataset with block cloning enabled to get
+copy-on-write branches (the profile sets `file_copy_method = clone`). On a
+filesystem without block cloning, remove that line from the profile and
+databases are created by copying files.
+
+## Make a template and mint databases
+
+```bash
+# Build a template once, then seal it.
+psql -h /tmp -U postgres -c "CREATE DATABASE tpl_app"
+psql -h /tmp -U postgres -d tpl_app -c "CREATE TABLE users (id bigserial PRIMARY KEY, email text UNIQUE)"
+psql -h /tmp -U postgres -c "SELECT pgrust_seal_template('tpl_app')"
+
+# Connecting to __ creates that database.
+psql -h /tmp -U postgres -d tdb_tpl_app__agent42 -c "INSERT INTO users (email) VALUES ('a@example.com')"
+psql -h /tmp -U postgres -d tdb_tpl_app__agent43 -c "SELECT count(*) FROM users" # 0: isolated
+
+# Runtime status as JSON.
+psql -h /tmp -U postgres -Atc "SELECT pgrust_runtime_status()"
+```
+
+Minted databases are dropped `pgrust.ephemeral_db_grace` seconds after
+their last session ends, unless they are pinned with
+`pgrust_pin_database()`. All control functions are listed in
+[`docs/pgx/pgrun-interface.md`](../pgrun-interface.md).
diff --git a/docs/pgx/local-dev/install.md b/docs/pgx/local-dev/install.md
index 7d64cd1117..94f51001c3 100644
--- a/docs/pgx/local-dev/install.md
+++ b/docs/pgx/local-dev/install.md
@@ -82,7 +82,7 @@ that reaps processes.
## From source
-See the repository README "Quickstart → Build": the Rust toolchain (pinned by
+See [`build-from-source.md`](build-from-source.md): the Rust toolchain (pinned by
`rust-toolchain.toml`), RE2, and PostgreSQL 18 client tools, then
`cargo build --release --bin postgres --bin pgx` and `dist/assemble.sh`.