Skip to content
AbloAblo Docs
Esc
navigateopen⌘Jpreview
On this page

Interaction Model

The one write path every actor shares: load, claim, update, confirm.

When two agents, a server action, and a person can all write to the same row, you need one write path that stops them from clobbering each other. Ablo gives you exactly one: load the row, claim it while you work, update it, and wait for confirmation. This page walks through that path and the few primitives behind it.

Here’s the whole path in one block — claim a row, update it inside the claim, and let the claim release when your callback returns:

const report = await ablo.weatherReports.get({ id: 'report_stockholm' });

await using claim = await ablo.weatherReports.claim({ id: 'report_stockholm' });
await ablo.weatherReports.update({ id: claim.data.id, data: { status: 'ready' } });

Claims don’t lock. If another writer holds the row, claim waits for them, re-reads the fresh row, then hands it to you — so two writers serialize instead of clobbering.

Primitives

Primitive Plane Purpose
Schema State Declares typed models the app and agents can read and write.
Model State The generated ablo.<model> model. Use retrieve/list (async reads), local.get/local.list/local.count (the same verbs, synchronous and local-only), create, update, and delete.
Claim Coordination Who is working on a target. Taken via ablo.<model>.claim({ id }) and read via ablo.<model>.claim.state({ id }). Ephemeral — never persisted.
Commit Protocol The durable write underneath model updates. Most users do not call it directly.
Receipt Protocol The lower-level durable result for custom runtimes. Awaiting a schema write waits for confirmation.

Why each primitive is separate

Why are Claim, Commit, and Receipt separate things instead of one? Each does a job the others can’t. If you’re coming from Replicache or Yjs you’d expect just Commit; here’s what the other two buy you over that minimum:

  • Claim is not a read lock. Reads stay open. Claims serialize acting-on-the-row, so slow work can wait in FIFO order, re-read, and write from fresh state.
  • Receipt is not a 200 OK. It’s the durable artifact a commit produced — accepted commit id, server-assigned timestamps, stale-check outcome — addressable after the fact and replayable into a different client. A status code can’t be re-read by a sub-agent that wasn’t on the original call.

Run Loop

A normal schema-backed run is:

const report = await ablo.weatherReports.get({ id });
const active = ablo.weatherReports.claim.state({ id });
await using claim = await ablo.weatherReports.claim({ id });
await ablo.weatherReports.update({ id: claim.data.id, data: patch });

get({ id }) is an async server read (await it). claim.state({ id }) is a synchronous local read of who currently holds the row — it never blocks.

Coordination

Loop view only. Full claim reference — methods, the claim-state object, the claim.queue, errors — is Coordination.

Claims broadcast across the org. Call claim({ id }), do your writes with the normal update inside the await using scope, and the claim releases automatically when the scope exits:

await using claim = await ablo.weatherReports.claim({
  id: 'report_stockholm',
  description: 'editing',
});
await ablo.weatherReports.update({ id: claim.data.id, data: { status: 'ready' } }); // rejected if the row changed under the claim

ablo.weatherReports.claim.state({ id: 'report_stockholm' }) reads the live claim (or null) without blocking. Claims don’t lock: if another participant holds the row, claim waits for them to finish, re-reads, and then hands you the fresh row. The same signal is visible to every schema client through claim.state({ id }) and the live claim stream.

Conflict resolution

Schema updates can carry readAt and onStale. If the state advanced past readAt, Ablo applies the onStale disposition:

  • reject fails the commit, and the caller retries from scratch.
  • notify holds the write, leaving the row as it stands, and returns a StaleNotification with the current value for the actor to reconcile.
  • overwrite applies the write unconditionally, with no check.

The choice is per-commit, and there is no CRDT default: the policy is explicit. The full taxonomy lives in Concurrency Convention.

The contract in one sentence

Declare schema, load state, coordinate a claim, update the model, and wait for confirmation.

Was this page helpful?