

A named entity you address by type and key. Writes are serialized by its single owner, the log survives a crash, and every client sees each change live. The same idea as Cloudflare Durable Objects and Restate Virtual Objects — with a live subscription and replay built in.
State is the materialized view. On restart the server rebuilds it by replaying the log — nothing lost.
object('run', 'run-abc123') routes to exactly one process, anywhere in the cluster, that owns this key. No table row, no cache key, no channel name to coordinate.
const pc = new PingerchipsServer(KEY, SECRET);
const run = pc.object('run', 'run-abc123');
const status = await run.get('status', 'queued');Each write is appended to the log and serialized against every other write to the same object. Two workers writing at once can't clobber each other — no locks in your code.
await run.set('status', 'streaming');
await run.increment('step');
await run.append('history', { at: Date.now() });Read-then-write atomically. The object runs the whole function start to finish before anyone else touches the state.
await run.transaction(async (obj) => {
if (!await obj.get('owner')) {
await obj.set('owner', 'worker-3');
}
});Every write gets a logId. A client that drops and reconnects passes the last one it saw and gets only what happened since — no full replay, no gap.
const run = await client.object('run', 'run-abc123', {
afterLogId: loadCheckpoint(),
});
run.on('change', (e) => saveCheckpoint(e.logId));const pc = new PingerchipsServer(KEY, SECRET);
const run = pc.object('run', 'run-abc123');
const status = await run.get('status', 'queued');await run.set('status', 'streaming');
await run.increment('step');
await run.append('history', { at: Date.now() });await run.transaction(async (obj) => {
if (!await obj.get('owner')) {
await obj.set('owner', 'worker-3');
}
});const run = await client.object('run', 'run-abc123', {
afterLogId: loadCheckpoint(),
});
run.on('change', (e) => saveCheckpoint(e.logId));const pc = new PingerchipsServer(KEY, SECRET);
const run = pc.object('run', 'run-abc123');
const status = await run.get('status', 'queued');Every write gets a logId. A client that drops sends its last one on reconnect and the server replays only the gap — no full history, no missed events. This is how an agent survives a crash.
The log is the source of truth. Anything written while you were gone is still there, in order.
You resume from your checkpoint, not from entry 1. Cheap on reconnect, cheap on the server.
The SDK saves the logId on every change. Reconnect passes it back. That's the whole protocol.
Agent presence isn't a separate API — it's a slot on the object. A worker claims it on join with a transaction and updates its status as it works. Every change streams to whoever is watching the run.
// claim a slot
await run.transaction(async (obj) => {
const slots = await obj.get('agent_slots', {});
slots[agentId] = { status: 'active', startedAt: Date.now() };
await obj.set('agent_slots', slots);
});
// update as you go
await run.set(`agents.${agentId}.status`, 'done');waiting for workers to claim a slot…
For human presence — who's viewing, with avatar and cursor — Spaces has a dedicated presence API.
No schema migration, no lock service, no pub/sub wiring. One object per key and the SDK.
One run object. Workers claim slots, checkpoint each step, write partial results. The orchestrator watches change:status and renders the board live. A crashed worker rejoins from its logId.
an afternoonobject('order', id) holds status, ETA, and a history array. Your backend writes on every warehouse event; the customer's tab shows each change with no polling.
~1 hourOne object per board, columns and cards as slots. transaction() serializes concurrent moves so two people dragging the same card can't corrupt it.
an afternoonobject('quota', userId), increment() per request, a scheduled reset. The count is in RocksDB, not a Redis you have to run.
20 minutesA run object as the journal, one slot per step, append-only history. Resume from the last completed step after any failure — Temporal-shaped, on infra you already have.
a dayobject('match', code) with players, ready flags, and settings. Everyone in the lobby subscribes; the host writes; the match starts when all ready slots flip.
~2 hoursCloudflare and Restate proved the primitive. Pingerchips adds the subscription, the replay log, and an on-prem story.
A thread is an object of type thread — messages are append('messages', …), status and assignment are slots.
See the page →A run object holds step, partial results, and which worker owns the task. Claim with a transaction, checkpoint every step.
Game rooms, live docs, dashboards your frontend watches without polling.
Serialized writes, a replay log, a live subscription. Self-hostable, free while in beta.