# pdo (portable durable objects) > One SQLite file holding a Durable Object’s code and data. The `pdo` binary (Rust, V8 via deno_core, SQLite via rusqlite) > runs the object locally, exposes it to agents over MCP, and exports it to > Cloudflare with the class source unchanged. Version 0.1.0. File format_version 1, api_version pdo-v0. ## The file An object file is a plain SQLite database. Tables named `_obj_*` are reserved for the runtime; every other table is the object's own data, created only by migrations and reached by object code through `ctx.storage.sql`. - `_obj_meta` (key, value): format_version (1), api_version (pdo-v0), name, class, main (src/index.ts), created_at and live_checkin, the id of the code that runs. - `_obj_blobs` (hash, size, data): Every source file version, keyed by its SHA-256. A file that does not change between check-ins is stored once. - `_obj_checkins` (id, parent, manifest, message, created_at, schema): Fossil-style check-ins: a manifest maps each path to a blob hash, parent links the history, and the id is the SHA-256 of the check-in. schema snapshots the data tables the code was released against, which is what rollback checks. - `_obj_migrations` (seq, name, sql, checkin, applied_at): Ordered SQL migrations, each tied to the check-in that brought it. They only move forward; nothing undoes one. - `_obj_kv` (key, value): The key-value half of storage. Values are V8-serialized, so anything structuredClone accepts round-trips. - `_obj_alarms` (id = 1, scheduled_at, retry_count): At most one row: when alarm() is due next. ctx.storage.setAlarm writes it. - `_obj_changes` (id, at, event, checkin, changeset): Every event that writes runs under a SQLite session; its changeset (data tables and _obj_kv) is stored here in the same transaction, with the check-in that produced it. Data table rules: every table needs a declared PRIMARY KEY; AUTOINCREMENT is refused; use UUIDv7 TEXT keys (build v7 from Date.now() plus crypto.getRandomValues; crypto.randomUUID() is v4). Migrations are ordered, named, forward-only SQL. Preview: propose_change works on a sibling copy, `.preview`. ## Writing an object - Entry file `src/index.ts` exports the class named in `_obj_meta.class`, extending `DurableObject` from "cloudflare:workers". Constructor is (ctx, env); call super(ctx, env). - Single-threaded; each event (fetch, RPC, alarm) is one SQLite transaction. - fetch(request): every HTTP request to pdo run - public methods: RPC, also POST /.pdo/rpc/ with a JSON array of args - ctx.storage.sql.exec: cursor with toArray, one, raw, rowsWritten - ctx.storage.get / put / delete / list: the KV half - setAlarm / getAlarm / deleteAlarm, alarm(): fired on schedule by pdo run - transactionSync, blockConcurrencyWhile: atomic writes, guarded startup - Globals: Request, Response, Headers, URL, URLSearchParams, TextEncoder, TextDecoder, crypto.randomUUID, crypto.getRandomValues, setTimeout, structuredClone, atob, btoa, console. - A SQLite authorizer refuses, from object code: any access to `_obj_*` tables, DDL (CREATE/ALTER/DROP), BEGIN/COMMIT/SAVEPOINT, ATTACH, and all pragmas except read-only introspection (table_info, table_xinfo, table_list, index_list, index_info, index_xinfo, foreign_key_list). - Keep the class Cloudflare-compatible: `pdo export` deploys the same source. ## HTTP surface (pdo run, and the pdo export worker) - Every request goes to the class's fetch(). - `POST /.pdo/rpc/` with a JSON array body calls a public method and returns `{"result": ...}`. constructor, fetch, alarm, ctx, env and names starting with `_` are not RPC. ## MCP (`pdo mcp `, stdio, JSON-RPC 2.0, one message per line) Workflow: describe_object, read_code, propose_change (code + migrations on the preview), test_call (on the preview), release_change (live). rollback_code moves code back if it still fits the schema. call_object hits live. - `describe_object` [read]: Name, class, live check-in, tables with columns and row counts, files, next alarm, preview state. Start here. - `read_code` [read]: Source at the live check-in, the preview, or any check-in id (a 4+ character prefix works). - `list_checkins` [read]: History, newest first, with unified diffs and the migrations each check-in added. - `query_data` [read]: Read-only SQL over the object’s own tables, live or preview. At most 500 rows. - `propose_change` [write]: Code edits plus new migrations, applied to a preview copy of the file (live data included) and built there. Proposals stack; restart starts over from live. - `test_call` [write]: fetch, an RPC method or alarm() on the preview. Returns the result, console logs and the rows each table gained, changed or lost. - `release_change` [destructive]: Runs the preview’s migrations on the live file and moves live to its code, squashed into one check-in, in one transaction. Refused if the preview did not build or live moved. - `rollback_code` [destructive]: Moves live back to an earlier check-in. Code only, never schema: refused when that code no longer fits the tables. - `call_object` [destructive]: fetch or an RPC method on the live object. Writes change live data. Annotations: describe_object, read_code, list_checkins and query_data are readOnlyHint; release_change, rollback_code and call_object are destructiveHint. call_object takes kind fetch or rpc; test_call also takes kind alarm. Example client config: `claude mcp add todo -- pdo mcp todo.obj` ## CLI - `pdo new [--class C] [--name N] [--from DIR]`: Create an object file: an empty class, or src/** and migrations/*.sql from a directory. - `pdo run [--port 8787] [--host 127.0.0.1] [--no-alarms]`: Serve it over HTTP and fire its alarms. - `pdo mcp `: The MCP server, over stdio. - `pdo describe `: The description as JSON. - `pdo checkins [--limit 20] [--diffs]`: Code history. - `pdo call [json-args]`: Call an RPC method on the live object. - `pdo fetch [-X METHOD] [-d BODY]`: Send one request to the live object’s fetch(). - `pdo export [--worker-name W]`: Write the live code as a Wrangler project. `pdo export` writes `src/**` byte for byte, `pdo/worker.js` (a wrapper that applies migrations once per object, tracked in `_pdo_migrations`, and serves the RPC route), `pdo/migrations.js` and `wrangler.jsonc` with one SQLite-backed Durable Object binding. Deploy with `bunx wrangler deploy`. ## Out of scope in v0 - WebSockets - env bindings and outbound fetch - npm imports (relative imports between the object’s own files work) - a server mode hosting many object files - sync between copies of a file - moving state to or from Cloudflare (export moves code and migrations, not data) ## Pages - https://pdo.proc.io/ : overview and quick start - https://pdo.proc.io/format : the _obj_ tables