Virtual filesystem backed by a single Postgres table.
Give your agent sandbox a working directory that persists in Postgres. The agent reads and writes files. It doesn't know a database exists underneath.
export PGFILES_DSN=postgres://db/app
export PGFILES_PREFIX=agents/agent-7f3a/
pgfiles mount .
echo '{"result": 42}' > output.json # that's a Postgres INSERT
cat output.json # that's a SELECTbrew install pgfiles# Set connection once — everything else reads from it
export PGFILES_DSN=postgres://db/app
# Bootstrap the schema (once)
pgfiles init
# Each agent gets its own prefix
AGENT_ID=$(uuidgen)
PGFILES_PREFIX="agents/$AGENT_ID/" pgfiles mount .
# The agent just sees a normal directory
mkdir output
echo '{"status": "done"}' > output/result.json
cat output/result.json
# Tear down — files live on in Postgres
pgfiles unmount .Everything in . is a file or directory synced to Postgres. Every edit creates a new version.
One table. Four columns. That's the entire filesystem.
CREATE TABLE pgfiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key TEXT NOT NULL,
meta JSONB NOT NULL DEFAULT '{}',
data BYTEA
);| Column | What it does |
|---|---|
id |
Immutable identity per version |
key |
Object key — project/readme.md — flat, /-delimited |
meta |
Version, timestamps, etag, content-type, your custom tags |
data |
File body. NULL for directory markers. |
There are no real directories. project/readme.md is a key string.
The / is a convention, not a hierarchy. When you ls, pgfiles does a
prefix scan with delimiter grouping — the same way S3 works.
Every write creates a new row. The latest non-deleted version is "the file." Delete is a tombstone. Restore is always possible.
pgfiles init [dsn] Create schema (reads PGFILES_DSN if omitted)
pgfiles mount [dsn] <path> Mount filesystem (path can be ".")
pgfiles unmount <path> Unmount and flush
pgfiles where Show mount, prefix, cwd, key
pgfiles status Mount info, cache stats
pgfiles version <path> List versions
pgfiles restore <path> <version> Promote old version
pgfiles purge <path> Hard-delete all versions
pgfiles purge-prefix <prefix> Hard-delete a subtree
pgfiles batch Atomic multi-op from stdin
pgfiles cache clear Evict cache
pgfiles cache stats Hit rate, size
pgfiles config show Print resolved config
The connection can be set three ways, in priority order:
# 1. CLI argument (highest priority)
pgfiles mount postgres://localhost/mydb .
# 2. Environment variable (recommended for sandboxes)
export PGFILES_DSN=postgres://localhost/mydb
pgfiles mount .
# 3. Config file (convenient for laptops)
# ~/.config/pgfiles/config.yaml| Variable | Default | Notes |
|---|---|---|
PGFILES_DSN |
— | Connection string. The only required setting. |
PGFILES_POOL_SIZE |
10 |
Postgres connection pool size |
PGFILES_PREFIX |
"" |
Key prefix for scoping |
PGFILES_READ_ONLY |
false |
Reject all writes |
PGFILES_CACHE_MAX_SIZE |
1GB |
Local data cache cap |
PGFILES_CACHE_METADATA_TTL |
30s |
How long metadata is trusted |
PGFILES_LOG_LEVEL |
warn |
debug, info, warn, error |
| OS | Backend | Notes |
|---|---|---|
| Linux | FUSE3 | fuser crate, talks to /dev/fuse directly |
| macOS | NFS v4.1 | Embedded NFS server on localhost, no kernel extension |
Auto-detected. Single binary.
MIT