Skip to content

asoules/pi-worker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-worker

A pi coding agent running inside a Cloudflare Worker with a persistent virtual filesystem and git — no servers required.

Each session gets its own isolated Durable Object with a SQLite-backed filesystem (@cloudflare/shell) and a full git repository (isomorphic-git). The agent can read, write, edit, search, and version-control files across requests.

┌─────────────────────────────────────────────────────────┐
│                    Worker (router)                       │
│              Routes requests by session ID               │
└────────────┬────────────────────────┬───────────────────┘
             │                        │
┌────────────▼──────────┐  ┌─────────▼──────────────────┐
│  Durable Object (A)   │  │  Durable Object (B)        │
│                       │  │                             │
│  Workspace (SQLite)   │  │  Workspace (SQLite)         │
│    └─ /src/index.ts   │  │    └─ /app/main.py          │
│    └─ /package.json   │  │    └─ /requirements.txt     │
│    └─ ...             │  │    └─ ...                    │
│  isomorphic-git       │  │  isomorphic-git              │
│    └─ .git/           │  │    └─ .git/                  │
│  pi-agent-core        │  │  pi-agent-core               │
│    └─ Claude API      │  │    └─ Claude API             │
│    └─ 14 tools        │  │    └─ 14 tools               │
└───────────────────────┘  └──────────────────────────────┘

Quick start

# Install
npm install

# Set your Anthropic API key
echo 'ANTHROPIC_API_KEY=sk-ant-...' > .dev.vars

# Run locally
npm run dev

Then talk to it:

# Create a session
SESSION=$(curl -s -X POST http://localhost:8787/session/new | jq -r .sessionId)

# Chat
curl -s -X POST "http://localhost:8787/session/$SESSION/chat" \
  -H "Content-Type: application/json" \
  -d '{"message": "List all files and show git log"}' | jq .

# Files persist across requests
curl -s -X POST "http://localhost:8787/session/$SESSION/chat" \
  -H "Content-Type: application/json" \
  -d '{"message": "Add a multiply function to src/index.ts, then git add and commit"}' | jq .

# Streaming (SSE)
curl -N -X POST "http://localhost:8787/session/$SESSION/chat" \
  -H "Content-Type: application/json" \
  -d '{"message": "Refactor src/utils.ts", "stream": true}'

API

POST /session/new

Create a new session. Returns { sessionId: string }.

POST /session/:id/chat

Send a message to the agent in a session. Creates the session on first use.

Request body:

{
  "message": "Your prompt here",
  "stream": false,
  "model": "claude-sonnet-4-20250514"
}

Response (non-streaming):

{
  "response": "Agent's text response",
  "toolCalls": [
    { "tool": "read", "args": { "path": "/src/index.ts" } },
    { "tool": "git_commit", "args": { "message": "Add feature" } }
  ]
}

Response (streaming): Server-Sent Events with text_delta, tool_start, tool_end, done, and error events.

GET /session/:id/info

Get workspace metadata (file count, directory count, total bytes).

GET /health

Returns ok.

Tools

The agent has 14 tools available:

Filesystem tools (backed by @cloudflare/shell Workspace)

Tool Description
read Read file contents with optional offset/limit
write Create or overwrite a file (auto-creates parent dirs)
edit Find-and-replace exact text in a file
ls List directory contents
grep Search for text patterns across files
tree Show filesystem tree structure

Git tools (backed by isomorphic-git)

Tool Description
git_status Show working tree status
git_add Stage files (or "." for all)
git_commit Commit staged changes
git_log Show commit history
git_diff Show changed files since last commit
git_branch List or create branches
git_checkout Switch branches
git_init Initialize a new repository

Architecture

How it works

  1. Worker receives POST /session/:id/chat and routes to a Durable Object keyed by session ID
  2. Durable Object owns a Workspace (SQLite-backed virtual filesystem) and an isomorphic-git instance
  3. On first access, the workspace is seeded with a sample project and git init + initial commit
  4. pi-agent-core runs the agent loop: sends the prompt to Claude, executes tool calls against the workspace, loops until done
  5. All state persists in the DO's SQLite storage — files, git objects, refs — across requests

Storage model

Durable Object SQLite
┌──────────────────────────────────────────┐
│  cf_workspace_default table              │
│  ┌──────────────────────────────────┐    │
│  │ path (PK)  │ content  │ type     │    │
│  │ /src/app.ts│ "export…"│ file     │    │  ← small files inline
│  │ /src/      │  NULL    │ directory│    │
│  │ /.git/...  │ <binary> │ file     │    │  ← git objects
│  └──────────────────────────────────┘    │
└──────────────────────────────────────────┘
         │
         │ files > 1.5 MB spill to
         ▼
┌──────────────────────┐
│  R2 Bucket (optional)│
│  └─ {doId}/default/… │
└──────────────────────┘
  • Files < 1.5 MB are stored inline in SQLite (the content column)
  • Files ≥ 1.5 MB spill to R2 if an R2_BUCKET binding is configured
  • Each DO instance is fully isolated — its own database, its own git repo
  • D1 max row size is 2 MB; the 1.5 MB threshold keeps inline storage safe

Build system

The pi SDK (@mariozechner/pi-agent-core, @mariozechner/pi-ai) was designed for Node.js. Running it in a Cloudflare Worker requires a custom esbuild step (build.mjs) that bundles everything into a single ESM file with no nodejs_compat dependency:

  1. Patches config.js — the SDK's config module uses import.meta.url and fs.readFileSync at module scope to locate package assets. We replace it with static stubs since Workers have no filesystem at boot time.

  2. Removes new Function() callsajv (used by @sinclair/typebox for tool parameter validation) compiles JSON schemas via new Function(). Workers block eval/new Function at parse time. We patch the compiled output to disable runtime codegen (the SDK falls back to interpretation-based validation).

  3. Stubs Node-only dependenciesnode-fetch (Workers have native fetch), supports-color/debug (no terminal), https-proxy-agent (no proxies), node:diagnostics_channel (observability). These are transitive deps from @google/genai and gaxios that aren't needed at runtime.

  4. Shims node:crypto — mapped to Web Crypto API (crypto.subtle.digest). Used by @cloudflare/shell for file hashing.

  5. Polyfills Buffer — isomorphic-git uses Node's Buffer; we inject the buffer polyfill globally.

  6. Externalizes cloudflare:workers — the Durable Object base class import (provided by the Workers runtime).

Project structure

src/
├── index.ts          # Worker entry point — thin router to DOs
├── workspace-do.ts   # Durable Object — owns Workspace + git + agent
├── tools.ts          # Filesystem tools (read, write, edit, ls, grep, tree)
└── git-tools.ts      # Git tools (status, add, commit, log, diff, branch, checkout, init)

build.mjs             # Custom esbuild build with patches for Workers compat
wrangler.toml         # Cloudflare Workers config

Configuration

Environment variables

Variable Description
ANTHROPIC_API_KEY Anthropic API key (set in .dev.vars for local dev, or via wrangler secret for production)

Optional bindings

Binding Type Description
R2_BUCKET R2 Bucket Large file storage overflow (files > 1.5 MB spill here)

To enable R2, add to wrangler.toml:

[[r2_buckets]]
binding = "R2_BUCKET"
bucket_name = "pi-workspace-files"

Deploying

# Set your API key
wrangler secret put ANTHROPIC_API_KEY

# Deploy
npm run deploy

Limitations

  • No bash/shell execution — the agent can read, write, and edit files but cannot run arbitrary commands. The filesystem is a virtual JS implementation, not a real OS.
  • No network from tools — tools operate on the local virtual filesystem only. Git clone/push could be wired up with auth tokens but is not yet exposed to the agent.
  • Stateless conversation — each request is a fresh agent context (no multi-turn memory across requests). The filesystem persists, but the conversation history does not. Adding conversation persistence in the DO's SQLite is a natural next step.
  • Single model — currently hardcoded to Anthropic Claude. The pi-ai transport layer supports OpenAI, Gemini, and others; wiring them up is straightforward.

License

MIT

About

Pi coding agent running in a Cloudflare Worker with persistent filesystem and git

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors