@abloatai/ablo 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +242 -135
- package/dist/BaseSyncedStore.d.ts +2 -2
- package/dist/BaseSyncedStore.js +2 -2
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.js +1 -1
- package/dist/client/Ablo.d.ts +90 -93
- package/dist/client/Ablo.js +121 -60
- package/dist/client/ApiClient.d.ts +14 -14
- package/dist/client/ApiClient.js +81 -55
- package/dist/client/createInternalComponents.d.ts +2 -3
- package/dist/client/createInternalComponents.js +2 -3
- package/dist/client/createModelProxy.d.ts +90 -87
- package/dist/client/createModelProxy.js +124 -127
- package/dist/client/index.d.ts +6 -7
- package/dist/client/index.js +4 -5
- package/dist/client/validateAbloOptions.js +3 -3
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +7 -0
- package/dist/errors.d.ts +8 -8
- package/dist/errors.js +18 -10
- package/dist/index.d.ts +9 -8
- package/dist/index.js +7 -11
- package/dist/interfaces/index.d.ts +2 -10
- package/dist/mutators/Transaction.d.ts +2 -2
- package/dist/mutators/Transaction.js +2 -2
- package/dist/mutators/mutateActions.d.ts +44 -0
- package/dist/{react/useMutate.js → mutators/mutateActions.js} +11 -28
- package/dist/mutators/readerActions.d.ts +32 -0
- package/dist/{react/useReader.js → mutators/readerActions.js} +2 -18
- package/dist/query/types.d.ts +1 -1
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +3 -3
- package/dist/react/context.d.ts +4 -4
- package/dist/react/index.d.ts +4 -5
- package/dist/react/index.js +3 -7
- package/dist/react/useAblo.d.ts +14 -14
- package/dist/react/useAblo.js +26 -26
- package/dist/react/useIntent.d.ts +2 -2
- package/dist/react/useIntent.js +2 -2
- package/dist/react/useMutators.d.ts +1 -1
- package/dist/react/usePresence.d.ts +3 -3
- package/dist/react/usePresence.js +4 -4
- package/dist/react/useUndoScope.d.ts +1 -1
- package/dist/schema/diff.d.ts +161 -0
- package/dist/schema/diff.js +262 -0
- package/dist/schema/generate.d.ts +19 -0
- package/dist/schema/generate.js +87 -0
- package/dist/schema/index.d.ts +4 -1
- package/dist/schema/index.js +7 -1
- package/dist/schema/schema.d.ts +83 -32
- package/dist/schema/schema.js +58 -12
- package/dist/schema/serialize.d.ts +92 -0
- package/dist/schema/serialize.js +227 -0
- package/dist/sync/SyncWebSocket.d.ts +17 -0
- package/dist/sync/SyncWebSocket.js +46 -1
- package/dist/sync/awaitIntentGrant.d.ts +26 -0
- package/dist/sync/awaitIntentGrant.js +60 -0
- package/dist/sync/createIntentStream.js +43 -4
- package/dist/sync/createPresenceStream.js +1 -1
- package/dist/sync/participants.d.ts +2 -2
- package/dist/sync/participants.js +4 -4
- package/dist/types/global.d.ts +43 -52
- package/dist/types/global.js +16 -18
- package/dist/types/streams.d.ts +37 -9
- package/docs/api.md +68 -158
- package/docs/audit.md +5 -5
- package/docs/client-behavior.md +41 -42
- package/docs/coordination.md +294 -0
- package/docs/data-sources.md +14 -14
- package/docs/examples/agent-human.md +30 -32
- package/docs/examples/ai-sdk-tool.md +32 -33
- package/docs/examples/existing-python-backend.md +35 -33
- package/docs/examples/nextjs.md +24 -25
- package/docs/examples/server-agent.md +20 -61
- package/docs/guarantees.md +30 -55
- package/docs/identity.md +458 -0
- package/docs/index.md +12 -24
- package/docs/integration-guide.md +106 -116
- package/docs/interaction-model.md +29 -95
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +1 -1
- package/docs/mcp.md +11 -26
- package/docs/quickstart.md +43 -49
- package/docs/react.md +73 -23
- package/docs/roadmap.md +5 -7
- package/llms.txt +34 -39
- package/package.json +1 -1
- package/dist/react/useMutate.d.ts +0 -83
- package/dist/react/useQuery.d.ts +0 -123
- package/dist/react/useQuery.js +0 -145
- package/dist/react/useReader.d.ts +0 -69
- package/docs/capabilities.md +0 -163
|
@@ -9,7 +9,7 @@ that need multiplayer now and agent-safe writes later.
|
|
|
9
9
|
|
|
10
10
|
This also applies to any API-backed app, not only Python. A product like a YC
|
|
11
11
|
company's existing dashboard can keep its current endpoint/service/database
|
|
12
|
-
shape and migrate one coordinated
|
|
12
|
+
shape and migrate one coordinated model at a time.
|
|
13
13
|
|
|
14
14
|
```txt
|
|
15
15
|
Browser UI
|
|
@@ -30,10 +30,10 @@ Create a schema for the records that need realtime coordination.
|
|
|
30
30
|
import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
31
31
|
|
|
32
32
|
export const schema = defineSchema({
|
|
33
|
-
|
|
33
|
+
weatherReports: model({
|
|
34
34
|
id: z.string(),
|
|
35
|
-
|
|
36
|
-
status: z.enum(['
|
|
35
|
+
location: z.string(),
|
|
36
|
+
status: z.enum(['pending', 'ready']),
|
|
37
37
|
updatedAt: z.string(),
|
|
38
38
|
}),
|
|
39
39
|
});
|
|
@@ -51,7 +51,7 @@ export const ablo = Ablo({
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Mount the React provider near the app root so client components can subscribe to
|
|
54
|
-
model
|
|
54
|
+
model clients without importing server credentials.
|
|
55
55
|
|
|
56
56
|
```tsx
|
|
57
57
|
// web/app/providers.tsx
|
|
@@ -68,30 +68,32 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
68
68
|
## 2. Add Live Reads In The UI
|
|
69
69
|
|
|
70
70
|
Keep the first render backed by the existing Python endpoint. After that,
|
|
71
|
-
subscribe to the same model
|
|
71
|
+
subscribe to the same model client Ablo writes through.
|
|
72
72
|
|
|
73
73
|
```tsx
|
|
74
74
|
'use client';
|
|
75
75
|
|
|
76
76
|
import { useAblo } from '@abloatai/ablo/react';
|
|
77
77
|
|
|
78
|
-
export function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
78
|
+
export function ReportRow({
|
|
79
|
+
report: serverReport,
|
|
80
|
+
}: {
|
|
81
|
+
report: { id: string; location: string; status: string };
|
|
82
|
+
}) {
|
|
83
|
+
const report = useAblo((ablo) => ablo.weatherReports.retrieve(serverReport.id)) ?? serverReport;
|
|
84
|
+
const active = useAblo((ablo) => ablo.weatherReports.claimState(serverReport.id));
|
|
85
|
+
const claimed = Boolean(active);
|
|
84
86
|
|
|
85
87
|
return (
|
|
86
|
-
<button disabled={
|
|
87
|
-
{
|
|
88
|
+
<button disabled={claimed || report.status === 'ready'}>
|
|
89
|
+
{claimed ? 'Someone is editing' : report.location}
|
|
88
90
|
</button>
|
|
89
91
|
);
|
|
90
92
|
}
|
|
91
93
|
```
|
|
92
94
|
|
|
93
95
|
No string model key is needed in the first example. The selector reads from
|
|
94
|
-
`ablo.
|
|
96
|
+
`ablo.weatherReports`, so React uses the same model client as writes and agents.
|
|
95
97
|
|
|
96
98
|
## 3. Add One Python Data Source Endpoint
|
|
97
99
|
|
|
@@ -120,7 +122,7 @@ import os
|
|
|
120
122
|
import time
|
|
121
123
|
from fastapi import APIRouter, HTTPException, Request
|
|
122
124
|
|
|
123
|
-
from app.services.
|
|
125
|
+
from app.services.reports import get_report, list_reports, apply_report_operations
|
|
124
126
|
|
|
125
127
|
router = APIRouter()
|
|
126
128
|
|
|
@@ -160,15 +162,15 @@ async def ablo_source(request: Request):
|
|
|
160
162
|
body = json.loads(raw_body)
|
|
161
163
|
|
|
162
164
|
if body["type"] == "load":
|
|
163
|
-
if body["model"] == "
|
|
164
|
-
return {"row": await
|
|
165
|
+
if body["model"] == "weatherReports":
|
|
166
|
+
return {"row": await get_report(body["id"])}
|
|
165
167
|
|
|
166
168
|
if body["type"] == "list":
|
|
167
|
-
if body["model"] == "
|
|
168
|
-
return {"rows": await
|
|
169
|
+
if body["model"] == "weatherReports":
|
|
170
|
+
return {"rows": await list_reports(body.get("query", {}))}
|
|
169
171
|
|
|
170
172
|
if body["type"] == "commit":
|
|
171
|
-
rows = await
|
|
173
|
+
rows = await apply_report_operations(
|
|
172
174
|
operations=body["operations"],
|
|
173
175
|
client_tx_id=body.get("clientTxId"),
|
|
174
176
|
scope=body.get("scope", {}),
|
|
@@ -178,7 +180,7 @@ async def ablo_source(request: Request):
|
|
|
178
180
|
raise HTTPException(status_code=400, detail="unsupported request")
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
`
|
|
183
|
+
`apply_report_operations` should reuse the same transaction and validation logic
|
|
182
184
|
the existing Python endpoints already use. Dedupe by `clientTxId` so retries are
|
|
183
185
|
safe.
|
|
184
186
|
|
|
@@ -193,20 +195,20 @@ Button -> Python endpoint -> service -> database
|
|
|
193
195
|
Target button path:
|
|
194
196
|
|
|
195
197
|
```txt
|
|
196
|
-
Button -> ablo.
|
|
198
|
+
Button -> ablo.weatherReports.update(...)
|
|
197
199
|
Ablo -> Python Data Source endpoint
|
|
198
200
|
Python service -> database
|
|
199
201
|
Ablo -> realtime fanout and receipt
|
|
200
202
|
```
|
|
201
203
|
|
|
202
|
-
The app does not need a flag-day rewrite. Move one
|
|
204
|
+
The app does not need a flag-day rewrite. Move one model at a time.
|
|
203
205
|
|
|
204
206
|
```ts
|
|
205
|
-
const snap = ablo.snapshot({
|
|
207
|
+
const snap = ablo.snapshot({ weatherReports: reportId });
|
|
206
208
|
|
|
207
|
-
await ablo.
|
|
208
|
-
|
|
209
|
-
{ status: '
|
|
209
|
+
await ablo.weatherReports.update(
|
|
210
|
+
reportId,
|
|
211
|
+
{ status: 'ready' },
|
|
210
212
|
{ readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
|
|
211
213
|
);
|
|
212
214
|
```
|
|
@@ -235,12 +237,12 @@ and timestamp. If the change originated from an Ablo commit, include the same
|
|
|
235
237
|
Agents use the same model API as the UI:
|
|
236
238
|
|
|
237
239
|
```ts
|
|
238
|
-
const [
|
|
239
|
-
const snap = ablo.snapshot({
|
|
240
|
+
const [report] = await ablo.weatherReports.load({ where: { id: reportId } });
|
|
241
|
+
const snap = ablo.snapshot({ weatherReports: reportId });
|
|
240
242
|
|
|
241
|
-
await ablo.
|
|
242
|
-
|
|
243
|
-
{ status: '
|
|
243
|
+
await ablo.weatherReports.update(
|
|
244
|
+
reportId,
|
|
245
|
+
{ status: 'ready' },
|
|
244
246
|
{ readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
|
|
245
247
|
);
|
|
246
248
|
```
|
package/docs/examples/nextjs.md
CHANGED
|
@@ -7,11 +7,11 @@ Server Components, and live client subscriptions.
|
|
|
7
7
|
|
|
8
8
|
```txt
|
|
9
9
|
app/
|
|
10
|
-
|
|
10
|
+
reports/
|
|
11
11
|
[id]/
|
|
12
12
|
page.tsx # RSC: retrieve + render
|
|
13
13
|
actions.ts # Server Action: schema update with stale-state check
|
|
14
|
-
|
|
14
|
+
ReportEditor.tsx # Client: live updates
|
|
15
15
|
lib/
|
|
16
16
|
ablo.ts # Schema-backed Ablo client for server actions
|
|
17
17
|
```
|
|
@@ -19,40 +19,41 @@ app/
|
|
|
19
19
|
## RSC Initial Render
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
|
-
// app/
|
|
22
|
+
// app/reports/[id]/page.tsx
|
|
23
23
|
import { ablo } from '@/lib/ablo';
|
|
24
24
|
|
|
25
|
-
export default async function
|
|
25
|
+
export default async function ReportPage({
|
|
26
26
|
params,
|
|
27
27
|
}: { params: { id: string } }) {
|
|
28
28
|
await ablo.ready();
|
|
29
|
-
const [
|
|
30
|
-
if (!
|
|
29
|
+
const [report] = await ablo.weatherReports.load({ where: { id: params.id } });
|
|
30
|
+
if (!report) return null;
|
|
31
31
|
|
|
32
|
-
return <
|
|
32
|
+
return <ReportEditor report={report} />;
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Server Action Commit
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
// app/
|
|
39
|
+
// app/reports/[id]/actions.ts
|
|
40
40
|
'use server';
|
|
41
41
|
|
|
42
42
|
import { ablo } from '@/lib/ablo';
|
|
43
43
|
|
|
44
|
-
export async function
|
|
45
|
-
const
|
|
46
|
-
if (busy.length > 0) return { status: 'busy', intents: busy };
|
|
47
|
-
|
|
48
|
-
const snap = ablo.snapshot({ tasks: id });
|
|
49
|
-
const task = await ablo.tasks.update(
|
|
44
|
+
export async function markReady(id: string) {
|
|
45
|
+
const report = await ablo.weatherReports.claim(
|
|
50
46
|
id,
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
async (claimed) =>
|
|
48
|
+
ablo.weatherReports.update(
|
|
49
|
+
claimed.id,
|
|
50
|
+
{ status: 'ready' },
|
|
51
|
+
{ wait: 'confirmed' },
|
|
52
|
+
),
|
|
53
|
+
{ wait: false, action: 'marking_ready' },
|
|
53
54
|
);
|
|
54
55
|
|
|
55
|
-
return { status: '
|
|
56
|
+
return { status: 'ready', report };
|
|
56
57
|
}
|
|
57
58
|
```
|
|
58
59
|
|
|
@@ -66,16 +67,14 @@ rejects. The action can re-fetch and ask the user to retry.
|
|
|
66
67
|
|
|
67
68
|
import { useAblo } from '@abloatai/ablo/react';
|
|
68
69
|
|
|
69
|
-
export function
|
|
70
|
-
const data = useAblo((ablo) => ablo.
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
) ?? [];
|
|
74
|
-
const busy = intents.length > 0;
|
|
70
|
+
export function ReportEditor({ report: serverReport }: Props) {
|
|
71
|
+
const data = useAblo((ablo) => ablo.weatherReports.retrieve(serverReport.id)) ?? serverReport;
|
|
72
|
+
const active = useAblo((ablo) => ablo.weatherReports.claimState(serverReport.id));
|
|
73
|
+
const claimed = Boolean(active);
|
|
75
74
|
|
|
76
75
|
return (
|
|
77
|
-
<button disabled={
|
|
78
|
-
{
|
|
76
|
+
<button disabled={claimed || data.status === 'ready'}>
|
|
77
|
+
{claimed ? 'Someone is editing' : 'Mark ready'}
|
|
79
78
|
</button>
|
|
80
79
|
);
|
|
81
80
|
}
|
|
@@ -8,10 +8,10 @@ import Ablo from '@abloatai/ablo';
|
|
|
8
8
|
import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
9
9
|
|
|
10
10
|
const schema = defineSchema({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
status: z.enum(['
|
|
14
|
-
|
|
11
|
+
weatherReports: model({
|
|
12
|
+
location: z.string(),
|
|
13
|
+
status: z.enum(['pending', 'ready']),
|
|
14
|
+
forecast: z.string().optional(),
|
|
15
15
|
}),
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -20,67 +20,26 @@ const ablo = Ablo({
|
|
|
20
20
|
apiKey: process.env.ABLO_API_KEY,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
export async function
|
|
23
|
+
export async function completeReport(reportId: string) {
|
|
24
24
|
await ablo.ready();
|
|
25
25
|
|
|
26
|
-
const [
|
|
27
|
-
if (!
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
{ readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
|
|
26
|
+
const [report] = await ablo.weatherReports.load({ where: { id: reportId } });
|
|
27
|
+
if (!report) return { status: 'not_found' };
|
|
28
|
+
|
|
29
|
+
const updated = await ablo.weatherReports.claim(
|
|
30
|
+
reportId,
|
|
31
|
+
async (claimed) =>
|
|
32
|
+
ablo.weatherReports.update(
|
|
33
|
+
claimed.id,
|
|
34
|
+
{ status: 'ready' },
|
|
35
|
+
{ wait: 'confirmed' },
|
|
36
|
+
),
|
|
37
|
+
{ wait: false, action: 'completing' },
|
|
39
38
|
);
|
|
40
39
|
|
|
41
|
-
return { status: '
|
|
40
|
+
return { status: 'ready', report: updated };
|
|
42
41
|
}
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Use `agent.run(...)` when the worker intentionally cannot import the app schema.
|
|
48
|
-
It creates the run envelope and returns `done`, `failed`, or `cancelled`.
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
const api = Ablo({ apiKey: process.env.ABLO_API_KEY });
|
|
52
|
-
|
|
53
|
-
const result = await api.agent('task-writer', {
|
|
54
|
-
can: ['tasks.retrieve', 'tasks.update'],
|
|
55
|
-
syncGroups: ['workspace:acme'],
|
|
56
|
-
}).run(
|
|
57
|
-
{
|
|
58
|
-
prompt: 'Mark task_123 done.',
|
|
59
|
-
surface: 'agent_worker',
|
|
60
|
-
},
|
|
61
|
-
async ({ resource }) => {
|
|
62
|
-
const tasks = resource<{ title: string; status: string }>('tasks');
|
|
63
|
-
|
|
64
|
-
const { data, stamp, intents } = await tasks.retrieve('task_123', {
|
|
65
|
-
ifBusy: 'return',
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (intents.length > 0) {
|
|
69
|
-
return { skipped: true, reason: 'busy' };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return tasks.update(
|
|
73
|
-
'task_123',
|
|
74
|
-
{ status: 'done' },
|
|
75
|
-
{ readAt: stamp, onStale: 'reject', wait: 'confirmed' },
|
|
76
|
-
);
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (result.status === 'failed') throw result.error;
|
|
81
|
-
if (result.status === 'cancelled') return;
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Use the schema-backed version first. The schema-less version is for generic
|
|
85
|
-
agent infrastructure, MCP routes, and platform code.
|
|
86
|
-
|
|
44
|
+
Use the schema-backed version for server agents so the worker, app, and React UI
|
|
45
|
+
share the same model methods.
|
package/docs/guarantees.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Guarantees
|
|
2
2
|
|
|
3
|
-
This page is the short contract for what Ablo
|
|
3
|
+
This page is the short contract for what Ablo guarantees at the state
|
|
4
4
|
boundary.
|
|
5
5
|
|
|
6
6
|
## Confirmed Writes
|
|
@@ -9,19 +9,18 @@ boundary.
|
|
|
9
9
|
the authoritative sync cursor.
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
const updated = await ablo.
|
|
13
|
-
'
|
|
14
|
-
{ status: '
|
|
12
|
+
const updated = await ablo.weatherReports.update(
|
|
13
|
+
'report_stockholm',
|
|
14
|
+
{ status: 'ready' },
|
|
15
15
|
{ wait: 'confirmed' },
|
|
16
16
|
);
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
If the call resolves, the write was accepted by the server. If it rejects, the
|
|
20
20
|
error explains whether the write was rejected for auth, validation, stale state,
|
|
21
|
-
active
|
|
21
|
+
active claim conflict, idempotency, rate limit, or transport failure.
|
|
22
22
|
|
|
23
|
-
Schema model writes return the updated model row.
|
|
24
|
-
`commits.create(...)` return a receipt with the commit status and sync cursor.
|
|
23
|
+
Schema model writes return the updated model row.
|
|
25
24
|
|
|
26
25
|
## Optimistic Local State
|
|
27
26
|
|
|
@@ -42,11 +41,11 @@ Use `snapshot(...)` and `readAt` when a write depends on state the agent already
|
|
|
42
41
|
read:
|
|
43
42
|
|
|
44
43
|
```ts
|
|
45
|
-
const snap = ablo.snapshot({
|
|
44
|
+
const snap = ablo.snapshot({ weatherReports: 'report_stockholm' });
|
|
46
45
|
|
|
47
|
-
await ablo.
|
|
48
|
-
'
|
|
49
|
-
{ status: '
|
|
46
|
+
await ablo.weatherReports.update(
|
|
47
|
+
'report_stockholm',
|
|
48
|
+
{ status: 'ready' },
|
|
50
49
|
{ readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
|
|
51
50
|
);
|
|
52
51
|
```
|
|
@@ -61,45 +60,27 @@ Advanced policies exist for controlled product flows:
|
|
|
61
60
|
- `flag` accepts the write and marks it for product review.
|
|
62
61
|
- `merge` is reserved for server-defined merge behavior.
|
|
63
62
|
|
|
64
|
-
##
|
|
63
|
+
## Claim Coordination
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
Claims are live coordination signals. They are not database locks.
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
Claims are **advisory** and **cooperative**. `ablo.<model>.claim(id, ...)`
|
|
68
|
+
serializes on contention: if another human or agent already holds the row, the
|
|
69
|
+
claim waits for them to finish, then re-reads the row before handing it back, so
|
|
70
|
+
you proceed from fresh state. Reads are open by default —
|
|
71
|
+
`ablo.<model>.claimState(id)` returns the current claim state (or `null`) without
|
|
72
|
+
ever blocking. Server/model reads can opt into `ifClaimed: 'wait'` or
|
|
73
|
+
`ifClaimed: 'fail'` when they should not read through active work.
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Schema clients wait from the realtime intent stream. Schema-less HTTP callers
|
|
76
|
-
must pass an explicit `busyPollInterval` if they choose `ifBusy: 'wait'`; Ablo
|
|
77
|
-
does not hide a hard-coded polling loop. `busyTimeout` is only a maximum wait.
|
|
75
|
+
A claim does not reject or block other writers; it announces work so peers
|
|
76
|
+
serialize behind it rather than racing. While you hold a claim, the matching
|
|
77
|
+
`ablo.<model>.update(id, ...)` is stale-guarded and rejects with
|
|
78
|
+
`AbloStaleContextError` if the row advanced past your claim point.
|
|
78
79
|
|
|
79
80
|
## Agent Runs
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- `done` — the handler returned successfully.
|
|
85
|
-
- `failed` — the handler threw or the commit failed.
|
|
86
|
-
- `cancelled` — the run signal aborted.
|
|
87
|
-
|
|
88
|
-
Normal schema-backed agents should import the same schema as the app and write
|
|
89
|
-
through `ablo.<model>.update(...)`. The lower-level run envelope exists for
|
|
90
|
-
platform runtimes that need capability and task management without app code.
|
|
91
|
-
|
|
92
|
-
## Capabilities and Tasks
|
|
93
|
-
|
|
94
|
-
Capabilities scope what an agent is allowed to do. Tasks group a run for audit
|
|
95
|
-
and cost attribution.
|
|
96
|
-
|
|
97
|
-
Most users do not create either one manually. The SDK and hosted API manage the
|
|
98
|
-
common case. Manual capability and task APIs are for platform builders, custom
|
|
99
|
-
agent runtimes, and internal infrastructure.
|
|
100
|
-
|
|
101
|
-
Use `lease` as a crash cleanup window. A successful agent run still closes when
|
|
102
|
-
the handler returns, fails, or is cancelled.
|
|
82
|
+
Agents should import the same schema as the app and write through
|
|
83
|
+
`ablo.<model>.claim(...)` plus `ablo.<model>.update(...)`.
|
|
103
84
|
|
|
104
85
|
## Audit Trail
|
|
105
86
|
|
|
@@ -107,9 +88,7 @@ Accepted writes can be attributed to:
|
|
|
107
88
|
|
|
108
89
|
- the actor that wrote,
|
|
109
90
|
- the human or system the actor worked on behalf of,
|
|
110
|
-
- the
|
|
111
|
-
- the task or run that caused it,
|
|
112
|
-
- the resource, operation, and state cursor.
|
|
91
|
+
- the model, operation, and state cursor.
|
|
113
92
|
|
|
114
93
|
For agent work, this is what lets an audit surface answer: "what changed, who
|
|
115
94
|
authorized it, which run did it, and what state was it based on?"
|
|
@@ -137,12 +116,8 @@ Ablo does not need a customer database URL. When your own database is canonical,
|
|
|
137
116
|
Ablo calls a signed Data Source endpoint and records the coordination result for
|
|
138
117
|
receipts, realtime fanout, and audit. See [Connect Your Database](./data-sources.md).
|
|
139
118
|
|
|
140
|
-
##
|
|
141
|
-
|
|
142
|
-
Most apps should use `ablo.<model>.create/update/delete`. Use
|
|
143
|
-
`commits.create(...)` only when you need a low-level batch or a schema-less
|
|
144
|
-
runtime.
|
|
119
|
+
## Writes
|
|
145
120
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
121
|
+
Use `ablo.<model>.create/update/delete` for state changes. The server validates
|
|
122
|
+
authorization, stale state, active claim conflicts, and idempotency before
|
|
123
|
+
accepting the write.
|