Format version 1 · api pdo-v0
Inside a .obj file.
An object file is an ordinary SQLite database. Open it with sqlite3 and you can
read all of it. Seven tables whose names start with _obj_ belong to the runtime.
Every other table belongs to the object.
The runtime’s half
Object code cannot read or write these. The runtime and the MCP tools can. Migrations cannot touch them either.
-
_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.
The object’s half
Data tables are created by migrations and nothing else. Object code reaches them through
ctx.storage.sql.exec; agents read them with query_data.
A declared PRIMARY KEY on every table. A migration that creates one without is refused.
No AUTOINCREMENT. Use UUIDv7 text keys, so copies of a file can merge changesets later. The to-do example ships a 14-line uuidv7().
Forward only. Migrations run in order, are recorded with the check-in that brought them, and are never undone.
Code and history
A check-in is a manifest from path to blob hash, a parent, a message and a time. Its id is
the SHA-256 of those. _obj_meta.live_checkin names the one that runs.
Releasing writes one squashed check-in, runs its migrations and moves
live_checkin, all in one transaction. Rolling back moves
live_checkin to an older check-in after comparing the schema it was released
against with the tables as they are now.
-- what runs SELECT value FROM _obj_meta WHERE key = 'live_checkin'; -- its files SELECT manifest FROM _obj_checkins WHERE id = :live; -- {"src/index.ts":"9f2c…","src/uuid.ts":"41ab…"} SELECT data FROM _obj_blobs WHERE hash = '9f2c…';
The preview copy
propose_change copies the live file to <file>.preview, data
included, and applies the change there. test_call and
query_data with target: "preview" work on that copy.
release_change replays the migrations on the live file, and is refused if
live moved since the preview was made.
Writes leave a trail
Each event is one SQLite transaction with a session attached. When it writes, its
changeset goes into _obj_changes before commit, labelled with the event kind
(fetch, rpc, alarm) and the check-in that ran.