Skip to main content
Most server agents should import the app schema and use the same model methods as the product UI.
import Ablo from '@abloatai/ablo';
import { defineSchema, model, z } from '@abloatai/ablo/schema';

const schema = defineSchema({
  tasks: model({
    title: z.string(),
    status: z.enum(['todo', 'doing', 'done']),
    summary: z.string().optional(),
  }),
});

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

export async function completeTask(taskId: string) {
  await ablo.ready();

  // Claim grants exclusive, ordered access and hands back the fresh row.
  await using claim = await ablo.tasks.claim({ id: taskId, action: 'update' });
  if (!claim.data) return { status: 'not_found' };

  const updated = await ablo.tasks.update({
    id: taskId,
    data: { status: 'done' },
    wait: 'confirmed',
  });

  return { status: 'done', task: updated };
  // claim auto-releases as the function returns
}

Schema-less writes

When a worker intentionally cannot import the app schema, use the protocol client (Ablo({ apiKey }), no schema) and commit operations directly. There is no separate “run” envelope — a write is just a commit, and attribution is stamped server-side from the capability.
const ablo = Ablo({ apiKey: process.env.ABLO_API_KEY }); // protocol client

await ablo.commits.create({
  operations: [
    { action: 'update', model: 'tasks', id: 'task_123', data: { status: 'done' } },
  ],
  wait: 'confirmed',
});
When two writers may touch the same row, reach for the typed client’s claim — it serializes writers, re-reads the fresh row on promotion, and is the causal link recorded on every write it guards:
await using claim = await ablo.tasks.claim({ id: 'task_123', action: 'update' });
await ablo.tasks.update({ id: 'task_123', data: { status: 'done' }, wait: 'confirmed' });
Use the schema-backed version first. The schema-less commit path is for generic agent infrastructure, MCP routes, and platform code.