docs/deployment.md

Deployment

Run locally, use remote S3, deploy services, and publish releases.

Markdown

Four deployment scenarios, from simplest to most complex.

1. Single Developer, Local

Everything runs on your machine. SQLite for metadata, MinIO (Docker) for file storage.

Prerequisites

Setup

bash
# Install agent-fs
bun add -g @desplega.ai/agent-fs
# Initialize (starts MinIO container, creates DB, registers local user)
agent-fs init --local
# Verify
agent-fs config show
agent-fs write /hello.md --content "Hello from agent-fs"
agent-fs cat /hello.md

This creates ~/.agent-fs/ with:

  • agent-fs.db — SQLite database (metadata, FTS5 index, embeddings)
  • config.json — S3 endpoint, credentials, embedding settings
  • agent-fs.pid / agent-fs.log — daemon PID and logs (when running as daemon)

Running as a daemon

bash
agent-fs daemon start # Start background daemon
agent-fs daemon status # Check if running
agent-fs daemon stop # Stop daemon

The daemon serves both the HTTP REST API and the MCP endpoint on 127.0.0.1:7433. The CLI and agent-fs mcp proxy both require a running daemon.

2. Single Developer, Remote S3

Use Cloudflare R2, AWS S3, or any S3-compatible storage instead of local MinIO.

Setup

bash
agent-fs init --local
# Then configure remote S3
agent-fs config set s3.endpoint "https://<account-id>.r2.cloudflarestorage.com"
agent-fs config set s3.bucket "agent-fs"
agent-fs config set s3.accessKeyId "<your-access-key>"
agent-fs config set s3.secretAccessKey "<your-secret-key>"
agent-fs config set s3.region "auto"

S3 Provider Notes

ProviderendpointregionforcePathStyle
MinIO (local)http://localhost:9000us-east-1true
Cloudflare R2https://<account>.r2.cloudflarestorage.comautotrue
AWS S3https://s3.<region>.amazonaws.comyour regionfalse
DigitalOcean Spaceshttps://<region>.digitaloceanspaces.comyour regionfalse

S3 Versioning

Enable S3 versioning on your bucket for full diff and revert support. Without versioning, these operations degrade (no content-level diffs, revert creates from latest only).

bash
# AWS
aws s3api put-bucket-versioning --bucket agent-fs --versioning-configuration Status=Enabled
# MinIO
mc version enable myminio/agent-fs

3. Team, Shared Server

Deploy the HTTP server so multiple developers or agents can share the same filesystem.

Setup

bash
# On the server
agent-fs init --local
agent-fs server --host 0.0.0.0 --port 7433

Important: The default bind address is 127.0.0.1 (localhost only). Use --host 0.0.0.0 to accept external connections.

Register users

bash
# Each team member gets their own identity
curl -X POST http://your-server:7433/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com"}'
# Returns: { "apiKey": "..." }

Client configuration

Each team member configures their CLI or MCP client:

bash
agent-fs config set api.url "http://your-server:7433"
agent-fs config set api.key "<their-api-key>"

Or via environment variables:

bash
export AGENT_FS_API_URL="http://your-server:7433"
export AGENT_FS_API_KEY="<their-api-key>"

RBAC

Users have roles per-organization and per-drive:

RolePermissions
viewerRead files, search, list
editorRead + write, edit, delete files
adminFull access + manage users, drives, orgs

Key rules:

  • Drive membership is explicit. A drive is only visible and usable for users with a drive membership row. New drives grant the creator admin membership automatically; invite other users per drive (or rely on org-invite's default-drive grant).
  • Member management is admin-only. Inviting, listing, updating, and removing org members requires org admin. Managing drive members requires drive admin or admin of the owning org. Creating drives in an org requires org admin.
  • Write paths all enforce editor-or-better — the JSON ops route, the binary PUT /raw route, and FUSE mounts share the same check. Viewers can read everywhere they're a member but cannot write through any surface.
  • Org/drive IDs are bound. A request that addresses a drive under the wrong org — or any org/drive the caller has no membership in — returns 404, indistinguishable from a nonexistent ID.

4. Multi-Agent, Hosted

Deploy agent-fs as shared infrastructure for autonomous agents.

Architecture

Agent A (Claude Code) ──┐
Agent B (Cursor)     ───┤──→ agent-fs server ──→ SQLite + S3
Agent C (custom)     ───┘        :7433

Setup

  1. Deploy server with remote S3 (see scenario 2 for S3 config)
  2. Register each agent as a user with its own API key
  3. Create shared drives and assign access via RBAC
  4. Configure each agent's MCP client with its API key
bash
# Register agents
curl -X POST http://agent-fs:7433/auth/register -d '{"email": "agent-a@agents.local"}'
curl -X POST http://agent-fs:7433/auth/register -d '{"email": "agent-b@agents.local"}'

Each agent gets its own identity, so file operations are attributed to the agent that performed them. Use log to see who wrote what.

Multi-tenant isolation model

When mutually distrustful users or agents share one server, understand what the boundary is — and is not:

  • Isolation is enforced at the application layer by RBAC: every HTTP, MCP, raw, and FUSE operation proves the caller has an explicit role on the target org/drive before touching data. Cross-tenant org/drive/comment IDs resolve to 404, so tenants can't probe each other's resources.
  • Storage is a single shared S3 bucket, namespaced by <orgId>/drives/<driveId>/... key prefixes. There is no per-tenant bucket, credential, or encryption key — anyone holding the *server's* S3 credentials (or the server's SQLite DB) can read all tenants' data. Tenant isolation holds only as long as the server host and its credentials are trusted.
  • Signed URLs are an intentional escape hatch. Generation is RBAC-checked (viewer-or-better on the drive), but the resulting presigned S3 URL is an unauthenticated bearer secret until it expires. A tenant who shares a signed URL is sharing read access to that file with anyone who has the URL.

If you need isolation that survives a server-credential leak, run separate agent-fs instances (or buckets) per tenant.

Embedding Providers

Semantic search requires an embedding provider. Configure via environment variable or config.json.

ProviderEnv VariableCostNotes
OpenAIOPENAI_API_KEY~$0.02/1M tokensBest quality, requires API key
Google GeminiGEMINI_API_KEYFree tier availableGood quality, generous free tier
Local (llama.cpp)FreeRequires local model download, slower

Priority: environment variable > config.json > none (semantic search disabled).

Configuring in config.json

json
{
"embedding": {
"provider": "openai",
"model": "text-embedding-3-small",
"apiKey": "sk-..."
}
}

Configuration Reference

The config file lives at ~/.agent-fs/config.json (or $AGENT_FS_HOME/config.json).

json
{
"s3": {
"endpoint": "http://localhost:9000",
"bucket": "agent-fs",
"region": "us-east-1",
"accessKeyId": "minioadmin",
"secretAccessKey": "minioadmin",
"forcePathStyle": true
},
"embedding": {
"provider": "openai",
"model": "text-embedding-3-small",
"apiKey": "sk-..."
},
"server": {
"host": "127.0.0.1",
"port": 7433
}
}

Troubleshooting

"SQLITEERROR: no such module: fts5"

On macOS, Apple's bundled SQLite doesn't support extensions. Install via Homebrew:

bash
brew install sqlite

Bun will use the Homebrew version automatically.

MinIO container won't start

Check if port 9000 is already in use:

bash
lsof -i :9000

The MinIO container is named agent-fs-minio. Check its status:

bash
docker ps -a --filter name=agent-fs-minio
docker logs agent-fs-minio

Daemon won't start

Check for stale PID file:

bash
cat ~/.agent-fs/agent-fs.pid
kill -0 $(cat ~/.agent-fs/agent-fs.pid) 2>/dev/null && echo "running" || echo "stale"

If stale, remove the PID file and restart:

bash
rm ~/.agent-fs/agent-fs.pid
agent-fs daemon start