Skip to content

Abloatai/ablo

Repository files navigation

Ablo

Ablo Sync is a schema-first state control layer for AI agents and collaborative apps.

Use it when human UI, server actions, and AI agents need to edit the same typed state with realtime fanout, stale-write protection, active-work coordination, and audit.

schema -> ablo.<model>.create/load/edit/update(...)

Install

npm install @abloatai/ablo

Requires Node 22+ and TypeScript 5+.

Get a Test Key

Create an Ablo sandbox and copy an sk_test_* API key. Keep API keys in trusted server runtimes only.

export ABLO_API_KEY=sk_test_...

Browser apps should use a scoped capability/session route through the React provider. Do not ship ABLO_API_KEY in a browser bundle.

Quick Start

import Ablo from '@abloatai/ablo';
import { defineSchema, model, z } from '@abloatai/ablo/schema';

const schema = defineSchema({
  weatherReports: model({
    location: z.string(),
    status: z.enum(['pending', 'ready']),
    forecast: z.string().optional(),
  }),
});

const ablo = Ablo({
  schema,
  apiKey: process.env.ABLO_API_KEY,
});

await ablo.ready();

const created = await ablo.weatherReports.create({
  location: 'Stockholm',
  status: 'pending',
});

const updated = await ablo.weatherReports.update(created.id, {
  status: 'ready',
  forecast: 'Light rain, 13C',
});

console.log({ id: updated.id, status: updated.status });

await ablo.dispose();
```c

Expected output:

```txt
{ id: '...', status: 'ready' }

Pass schema for typed model resources. Omit it only for advanced server-side resource clients such as custom agents and MCP routes.

Run the package example from this directory:

cd examples
ABLO_API_KEY=sk_test_... npx tsx quickstart.ts

For a production integration with React, an existing backend, Data Source, and future agents, read Integration Guide.

AI Activity on Existing State

When AI or background work will touch an existing row for more than a quick write, coordinate through ablo.<model>.intent(id) — the coordination accessor that sits beside create/update/retrieve. It returns a handle synchronously, so you can see who's already working on a row before you start.

const report = ablo.weatherReports.intent('weather_stockholm');

// Read side: is someone already on it? Wait for them to finish.
if (report.current) {
  report.current.heldBy; // 'agent:forecaster'
  await report.settled();
}

// Write side: acquire so other participants yield while we work.
await report.acquire({ action: 'checking_weather', field: 'forecast', ttl: '2m' });

// Your existing weather tool or agent call. While this runs, other clients see
// that weather_stockholm is being checked.
const row = ablo.weatherReports.retrieve('weather_stockholm');
const weather = await weatherAgent.getWeather(row.location);

await report.update({
  status: 'ready',
  forecast: weather.summary,
});

Ablo does not fetch the weather. It keeps the activity visible while the work runs, rejects report.update(...) with AbloStaleContextError if the row changed under you, and releases the intent automatically once the write lands.

Multiplayer

There is no separate multiplayer mode. When human UI, server actions, and agent workers use the same schema client and write through ablo.<model>, they are on the same shared resource stream.

  • ablo.<model>.create/update/delete fan out confirmed deltas to subscribers.
  • useAblo(...) gives React clients the live row plus active intents.
  • ablo.<model>.intent(id) lets humans and agents see and coordinate active work before a write lands.
  • ablo.intents remains available for custom lower-level coordination across resources.

If a team writes directly to its own database outside Ablo, that write bypasses the multiplayer stream until the app reports it through Data Source events.

Under the hood, capabilities, tasks, leases, intents, commits, and receipts are real protocol primitives. They exist so agent work is scoped, coordinated, attributable, and cleaned up if a runtime disappears. They should not be ceremony in the first integration.

Load vs Retrieve

For schema clients, load and retrieve are intentionally different:

  • ablo.weatherReports.load({ where }) is async. It hydrates matching rows from the local store and server, then returns them.
  • ablo.weatherReports.retrieve(id) is sync. It reads one already-loaded row from the local pool and returns undefined if it is not loaded yet.
  • ablo.resource('weatherReports').retrieve(id) is the lower-level resource API. It returns { data, stamp, intents } for custom runtimes that need raw read stamps and receipts.

Activity and Busy State

Model edit activity is the live coordination signal. If another participant is reading, editing, or updating an entity, Ablo can return that state, wait for it to clear, or fail fast with AbloBusyError.

const busy = ablo.intents.list({
  resource: 'weatherReports',
  id: 'weather_stockholm',
});

if (busy.length > 0) {
  console.log(`${busy[0].actor} is ${busy[0].action}`);
}

await ablo.intents.waitFor({ resource: 'weatherReports', id: 'weather_stockholm' });

Policy names are literal:

  • ifBusy: 'return' returns immediately with intents.
  • ifBusy: 'wait' waits on the live intent stream. Plain HTTP callers must provide their own explicit polling policy instead of getting hidden SDK polling.
  • ifBusy: 'fail' throws AbloBusyError with the active intents attached.

Persistence

Ablo defaults to volatile local persistence. That keeps the SDK focused on shared state coordination instead of silently adding an IndexedDB storage product to every browser app.

Opt into browser durable local cache and offline queueing when you need it:

const ablo = Ablo({
  schema,
  apiKey: process.env.ABLO_API_KEY,
  persistence: 'indexeddb',
});

Node, SSR, tests, and agents use volatile in-memory persistence automatically.

Connect Your Database

Every schema model has a backing store. By default, Ablo stores rows for the models you declare, so ablo.weatherReports.create(...) and ablo.weatherReports.update(...) write to Ablo-managed state.

If your existing database remains the source of truth, connect it with a signed Data Source endpoint. Your app keeps the database credentials; Ablo sends signed commit requests to your route.

ABLO_API_KEY=sk_live_...

See Connect Your Database for the route and commit shape.

Agent Runs

Most agent workers should import the same schema and use ablo.<model>.load(...) plus ablo.<model>.update(...). The schema-less agent.run(...) wrapper exists for advanced workers that intentionally cannot import the app schema.

Production Reference

  • Guarantees — confirmed writes, stale-write protection, intent coordination, and agent lifecycle.
  • Integration Guide — pick the backing mode and integrate React, Data Source, multiplayer, and agents.
  • Client Behavior — options, errors, retries, timeouts, and public imports.
  • Connect Your Database — keep canonical rows in your app database without giving Ablo database credentials.
  • Existing Python Backend — migrate existing Python endpoints to multiplayer and agent-safe writes gradually.
  • AI SDK Tool — use Ablo inside an AI SDK tool call.
  • Server Agent — schema-backed worker plus advanced schema-less run.

License

Apache License 2.0. See LICENSE and NOTICE.

About

State control for AI agents. Persist, coordinate, and audit every human and agent write to shared application state.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors