@abloatai/ablo 0.37.0 → 0.38.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/AGENTS.md +3 -3
- package/CHANGELOG.md +162 -92
- package/README.md +86 -21
- package/dist/ai-sdk.d.ts +2 -0
- package/dist/ai-sdk.d.ts.map +1 -0
- package/dist/ai-sdk.js +2 -0
- package/dist/ai-sdk.js.map +1 -0
- package/docs/agents.md +59 -9
- package/docs/api-keys.md +51 -27
- package/docs/branch-development.md +392 -0
- package/docs/cli.md +50 -42
- package/docs/data-sources.md +11 -0
- package/docs/deployment.md +20 -19
- package/docs/examples/ai-sdk-tool.md +25 -52
- package/docs/how-it-works.md +3 -2
- package/docs/index.md +7 -3
- package/docs/integration-guide.md +6 -13
- package/docs/integrations/inngest.md +258 -0
- package/docs/integrations/temporal.md +187 -0
- package/docs/integrations.md +54 -0
- package/docs/migration.md +4 -4
- package/docs/projects.md +1 -1
- package/docs/quickstart.md +30 -22
- package/docs/webhooks.md +4 -4
- package/llms.txt +20 -18
- package/package.json +15 -3
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Inngest for long-running tasks
|
|
2
|
+
|
|
3
|
+
> Run event-driven, retryable agent tasks with Inngest while Ablo makes each
|
|
4
|
+
> shared-state effect typed, idempotent, and authoritative.
|
|
5
|
+
|
|
6
|
+
Inngest and Ablo solve different parts of a durable agent system:
|
|
7
|
+
|
|
8
|
+
| Concern | Owner |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Events, step checkpoints, retries, sleeps, flow control, cancellation | Inngest |
|
|
11
|
+
| Provider calls, prompts, and model output | AI SDK |
|
|
12
|
+
| Typed reads and writes, mutation idempotency, claims, confirmation | Ablo |
|
|
13
|
+
| Event names, function definitions, effect identity, business behavior | Your application |
|
|
14
|
+
|
|
15
|
+
The short version is:
|
|
16
|
+
|
|
17
|
+
> Inngest makes sure the job resumes. Ablo makes sure its changes are safe.
|
|
18
|
+
|
|
19
|
+
## Keep Ablo calls inside steps
|
|
20
|
+
|
|
21
|
+
An Inngest function is ordinary application code, but external I/O belongs in
|
|
22
|
+
a retriable `step.run()`. A successful step result is checkpointed and reused;
|
|
23
|
+
a failed step is independently retried.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import Ablo from '@abloatai/ablo';
|
|
27
|
+
import { Inngest, eventType } from 'inngest';
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import { schema } from './schema.js';
|
|
30
|
+
|
|
31
|
+
const inngest = new Inngest({ id: 'orders' });
|
|
32
|
+
const ablo = Ablo({
|
|
33
|
+
schema,
|
|
34
|
+
apiKey: process.env.ABLO_API_KEY,
|
|
35
|
+
transport: 'http',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const approvalRequested = eventType('orders/approval.requested', {
|
|
39
|
+
schema: z.object({
|
|
40
|
+
operationId: z.string(),
|
|
41
|
+
orderId: z.string(),
|
|
42
|
+
approvalNote: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const approveOrder = inngest.createFunction(
|
|
47
|
+
{
|
|
48
|
+
id: 'approve-order',
|
|
49
|
+
triggers: [approvalRequested],
|
|
50
|
+
retries: 4,
|
|
51
|
+
idempotency: 'event.data.operationId',
|
|
52
|
+
},
|
|
53
|
+
async ({ event, step }) => {
|
|
54
|
+
const { operationId, orderId, approvalNote } = event.data;
|
|
55
|
+
|
|
56
|
+
return step.run('write-approved-order', () =>
|
|
57
|
+
ablo.orders.update({
|
|
58
|
+
id: orderId,
|
|
59
|
+
data: { status: 'approved', approvalNote },
|
|
60
|
+
idempotencyKey: `${operationId}:approve-order:${orderId}`,
|
|
61
|
+
wait: 'confirmed',
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
There is no Inngest-specific Ablo transport. The step calls the same branded
|
|
69
|
+
model API as a route handler, worker, or command-line process.
|
|
70
|
+
|
|
71
|
+
## Use both idempotency layers
|
|
72
|
+
|
|
73
|
+
Send a globally scoped event ID and carry a stable business operation ID in the
|
|
74
|
+
event:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await inngest.send(
|
|
78
|
+
approvalRequested.create(
|
|
79
|
+
{
|
|
80
|
+
operationId,
|
|
81
|
+
orderId,
|
|
82
|
+
approvalNote: 'Approved by the durable function.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: `orders-approval-requested-${operationId}`,
|
|
86
|
+
},
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Inngest event IDs and function-level `idempotency` prevent duplicate function
|
|
92
|
+
runs for Inngest's documented 24-hour window. They do not replace the Ablo
|
|
93
|
+
mutation key. Ablo can confirm the write and the process can lose the response
|
|
94
|
+
before Inngest checkpoints the step, causing the step callback to execute
|
|
95
|
+
again.
|
|
96
|
+
|
|
97
|
+
For every mutating step:
|
|
98
|
+
|
|
99
|
+
1. Accept a stable business operation ID in the triggering event.
|
|
100
|
+
2. Derive a separate key for each logical effect.
|
|
101
|
+
3. Reuse the key and mutation body for every retry.
|
|
102
|
+
4. Use `wait: 'confirmed'` when later steps depend on the authoritative result.
|
|
103
|
+
5. Treat reuse of a key with another body as an application bug.
|
|
104
|
+
6. Keep the complete retry horizon within Ablo's documented
|
|
105
|
+
[idempotency retention window](../idempotency.md).
|
|
106
|
+
|
|
107
|
+
Never put Inngest's `attempt`, a timestamp, or a random value in the key.
|
|
108
|
+
`runId` is suitable only when a newly created function run should represent a
|
|
109
|
+
new effect.
|
|
110
|
+
|
|
111
|
+
## Compose AI SDK as durable steps
|
|
112
|
+
|
|
113
|
+
Inngest can checkpoint AI SDK calls with `step.ai.wrap()`. Keep the model call
|
|
114
|
+
and the Ablo mutation as separate steps:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { openai } from '@ai-sdk/openai';
|
|
118
|
+
import { generateText } from 'ai';
|
|
119
|
+
|
|
120
|
+
const { text: approvalNote } = await step.ai.wrap(
|
|
121
|
+
'review-order',
|
|
122
|
+
generateText,
|
|
123
|
+
{
|
|
124
|
+
model: openai('gpt-4o-mini'),
|
|
125
|
+
system:
|
|
126
|
+
'Return only a concise approval note suitable for an audit trail.',
|
|
127
|
+
prompt,
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
await step.run('apply-order-review', () =>
|
|
132
|
+
ablo.orders.update({
|
|
133
|
+
id: orderId,
|
|
134
|
+
data: { status: 'approved', approvalNote },
|
|
135
|
+
idempotencyKey: `${operationId}:apply-order-review:${orderId}`,
|
|
136
|
+
wait: 'confirmed',
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This separation means retrying a write does not spend model tokens again, and
|
|
142
|
+
retrying a model call cannot partially mutate shared state.
|
|
143
|
+
|
|
144
|
+
For a multi-turn agent loop, checkpoint every model invocation and wrap every
|
|
145
|
+
tool execution in its own `step.run()`. The generic
|
|
146
|
+
`@abloatai/ablo/ai-sdk` helpers may execute inside a step because Inngest does
|
|
147
|
+
not impose Temporal's deterministic Workflow sandbox. Explicit model calls are
|
|
148
|
+
still preferable in the first integration because they make the effect key and
|
|
149
|
+
step boundary visible.
|
|
150
|
+
|
|
151
|
+
## Claims and cancellation
|
|
152
|
+
|
|
153
|
+
A claim protects a short shared-state critical section, not an entire Inngest
|
|
154
|
+
function. Acquire, fresh-read, checked-write, and release inside one
|
|
155
|
+
`step.run()`:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
await step.run('apply-review-with-claim', async () => {
|
|
159
|
+
const claim = await ablo.orders.claim({
|
|
160
|
+
id: orderId,
|
|
161
|
+
description: 'applying an Inngest review',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
return await ablo.orders.update({
|
|
166
|
+
id: claim.data.id,
|
|
167
|
+
data: { status: 'approved', approvalNote },
|
|
168
|
+
claim,
|
|
169
|
+
idempotencyKey: `${operationId}:apply-review:${orderId}`,
|
|
170
|
+
wait: 'confirmed',
|
|
171
|
+
});
|
|
172
|
+
} finally {
|
|
173
|
+
await claim.release();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Do not hold a claim across `step.sleep()`, `step.waitForEvent()`, or an AI
|
|
179
|
+
inference step. Configure `cancelOn` for the function, but do not assume that
|
|
180
|
+
function cancellation aborts an already running Ablo network request. Keep
|
|
181
|
+
claims short, release in `finally`, and rely on bounded claim leases for
|
|
182
|
+
process-loss recovery.
|
|
183
|
+
|
|
184
|
+
## Expose the application endpoint
|
|
185
|
+
|
|
186
|
+
Mount Inngest's handler at the conventional `/api/inngest` path. For a plain
|
|
187
|
+
Node application:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { createServer } from 'node:http';
|
|
191
|
+
import { serve } from 'inngest/node';
|
|
192
|
+
|
|
193
|
+
const handler = serve({
|
|
194
|
+
client: inngest,
|
|
195
|
+
functions: [approveOrder],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
createServer((request, response) => {
|
|
199
|
+
const path = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
200
|
+
if (path === '/api/inngest') return handler(request, response);
|
|
201
|
+
response.writeHead(404).end();
|
|
202
|
+
}).listen(3000);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Use the framework-specific `serve` adapter in an existing Next.js, Express,
|
|
206
|
+
Hono, or other supported application.
|
|
207
|
+
|
|
208
|
+
Start the local app and Inngest Dev Server:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm run dev
|
|
212
|
+
npx --ignore-scripts=false inngest-cli@latest dev \
|
|
213
|
+
--no-discovery \
|
|
214
|
+
-u http://localhost:3000/api/inngest
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Set `INNGEST_EVENT_KEY` and `INNGEST_SIGNING_KEY` in production.
|
|
218
|
+
|
|
219
|
+
## Test the failure boundary
|
|
220
|
+
|
|
221
|
+
Use `@inngest/test`'s `InngestTestEngine` for function and step tests. A useful
|
|
222
|
+
integration suite proves:
|
|
223
|
+
|
|
224
|
+
- the Ablo write happens inside the expected named step;
|
|
225
|
+
- a confirmed write followed by simulated response loss retries with the same
|
|
226
|
+
key and body and commits once;
|
|
227
|
+
- conflicting reuse of a key fails;
|
|
228
|
+
- checkpointed step state bypasses the Ablo callback;
|
|
229
|
+
- claims release after write failure; and
|
|
230
|
+
- the `/api/inngest` endpoint exposes function metadata without eagerly
|
|
231
|
+
creating an Ablo connection.
|
|
232
|
+
|
|
233
|
+
The complete runnable suite lives in `examples/inngest-agent`.
|
|
234
|
+
|
|
235
|
+
## Package boundary
|
|
236
|
+
|
|
237
|
+
Inngest remains an application dependency:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
packages/
|
|
241
|
+
transaction/src/ai-sdk/ reusable model-backed AI SDK tools
|
|
242
|
+
ablo/src/ai-sdk.ts @abloatai/ablo/ai-sdk
|
|
243
|
+
|
|
244
|
+
examples/
|
|
245
|
+
inngest-agent/ events, functions, steps, endpoint, AI composition
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Do not add Inngest to `packages/agent`. A dedicated `@abloatai/inngest` package
|
|
249
|
+
is justified only after multiple real applications reveal substantial
|
|
250
|
+
reusable behavior beyond a small function or step wrapper.
|
|
251
|
+
|
|
252
|
+
## References
|
|
253
|
+
|
|
254
|
+
- [Inngest TypeScript SDK v4](https://www.inngest.com/docs/reference/typescript/v4/intro)
|
|
255
|
+
- [Inngest steps](https://www.inngest.com/docs/reference/typescript/functions/step-run)
|
|
256
|
+
- [Inngest idempotency](https://www.inngest.com/docs/guides/handling-idempotency)
|
|
257
|
+
- [Inngest AI inference steps](https://www.inngest.com/docs/features/inngest-functions/steps-workflows/step-ai-orchestration)
|
|
258
|
+
- [Testing Inngest functions](https://www.inngest.com/docs/reference/typescript/v4/testing)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Temporal for long-running tasks
|
|
2
|
+
|
|
3
|
+
> Run long-lived, retryable agent tasks with Temporal while Ablo makes each
|
|
4
|
+
> shared-state effect typed, idempotent, and authoritative.
|
|
5
|
+
|
|
6
|
+
Temporal and Ablo solve different parts of a durable agent system:
|
|
7
|
+
|
|
8
|
+
| Concern | Owner |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Workflow history, replay, timers, retries, cancellation | Temporal |
|
|
11
|
+
| Provider, messages, tools, approval, model loop | AI SDK |
|
|
12
|
+
| Typed reads and writes, idempotency, claims, confirmation | Ablo |
|
|
13
|
+
| Workflow names, task queues, retry policy, business behavior | Your application |
|
|
14
|
+
|
|
15
|
+
The short version is:
|
|
16
|
+
|
|
17
|
+
> Temporal makes sure the agent finishes. Ablo makes sure its changes are safe.
|
|
18
|
+
|
|
19
|
+
## Keep Ablo calls in Activities
|
|
20
|
+
|
|
21
|
+
Temporal Workflow code must be deterministic. An Ablo model operation performs
|
|
22
|
+
network I/O, so instantiate `Ablo()` and call model resources in an Activity:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// activities/orders.ts
|
|
26
|
+
import Ablo from '@abloatai/ablo';
|
|
27
|
+
import { schema } from '../schema.js';
|
|
28
|
+
|
|
29
|
+
const ablo = Ablo({
|
|
30
|
+
schema,
|
|
31
|
+
apiKey: process.env.ABLO_API_KEY,
|
|
32
|
+
transport: 'http',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export interface ApproveOrderInput {
|
|
36
|
+
orderId: string;
|
|
37
|
+
approvalNote: string;
|
|
38
|
+
idempotencyKey: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function approveOrder(input: ApproveOrderInput) {
|
|
42
|
+
return ablo.orders.update({
|
|
43
|
+
id: input.orderId,
|
|
44
|
+
data: {
|
|
45
|
+
status: 'approved',
|
|
46
|
+
approvalNote: input.approvalNote,
|
|
47
|
+
},
|
|
48
|
+
idempotencyKey: input.idempotencyKey,
|
|
49
|
+
wait: 'confirmed',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The Activity uses the same branded model method as a route handler, job, or
|
|
55
|
+
command-line process. There is no Temporal-specific Ablo transport.
|
|
56
|
+
|
|
57
|
+
Workflow code creates the stable logical effect identity and schedules the
|
|
58
|
+
Activity:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// workflows/approve-order.ts
|
|
62
|
+
import { proxyActivities, workflowInfo } from '@temporalio/workflow';
|
|
63
|
+
import type * as activities from '../activities/orders.js';
|
|
64
|
+
|
|
65
|
+
const { approveOrder } = proxyActivities<typeof activities>({
|
|
66
|
+
startToCloseTimeout: '1 minute',
|
|
67
|
+
retry: { maximumAttempts: 5 },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export async function approveOrderWorkflow(orderId: string) {
|
|
71
|
+
const { workflowId, runId } = workflowInfo();
|
|
72
|
+
|
|
73
|
+
return approveOrder({
|
|
74
|
+
orderId,
|
|
75
|
+
approvalNote: 'Approved by the durable workflow.',
|
|
76
|
+
idempotencyKey: `${workflowId}:${runId}:approve-order:${orderId}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Temporal may retry an Activity after the Ablo write succeeded but before the
|
|
82
|
+
result reached Temporal. Every attempt must therefore send the same
|
|
83
|
+
`idempotencyKey` and the same mutation body. Do not put an Activity attempt
|
|
84
|
+
number, timestamp, or random value in the key.
|
|
85
|
+
|
|
86
|
+
Using the Workflow Run ID is appropriate when a new run should represent a new
|
|
87
|
+
effect. If an effect must survive Continue-As-New or a newly started Workflow
|
|
88
|
+
run, accept a stable business operation ID as Workflow input instead.
|
|
89
|
+
|
|
90
|
+
## Compose AI SDK tools through Activities
|
|
91
|
+
|
|
92
|
+
Temporal's AI SDK integration can make the model interaction durable inside a
|
|
93
|
+
Workflow. Any tool that touches Ablo still delegates to an Activity:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { temporalProvider } from '@temporalio/ai-sdk/workflow';
|
|
97
|
+
import { generateText, stepCountIs, tool } from 'ai';
|
|
98
|
+
import { z } from 'zod';
|
|
99
|
+
|
|
100
|
+
const result = await generateText({
|
|
101
|
+
model: temporalProvider.languageModel('gpt-4o-mini'),
|
|
102
|
+
prompt,
|
|
103
|
+
tools: {
|
|
104
|
+
approveOrder: tool({
|
|
105
|
+
description: 'Approve the order after reviewing it.',
|
|
106
|
+
inputSchema: z.object({ approvalNote: z.string() }),
|
|
107
|
+
execute: ({ approvalNote }) =>
|
|
108
|
+
approveOrder({
|
|
109
|
+
orderId,
|
|
110
|
+
approvalNote,
|
|
111
|
+
idempotencyKey: `${effectPrefix}:approve-order:${orderId}`,
|
|
112
|
+
}),
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
stopWhen: stepCountIs(5),
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Register Temporal's `AiSdkPlugin` on the Worker and pin all
|
|
120
|
+
`@temporalio/*` packages to one compatible version set. The integration is
|
|
121
|
+
experimental, so replay-test Workflow histories before upgrading it.
|
|
122
|
+
The import above matches the example's pinned `1.21.1` release. Follow the
|
|
123
|
+
matching Temporal upgrade guide when changing that version set.
|
|
124
|
+
|
|
125
|
+
Do not import `@abloatai/ablo/ai-sdk` into Workflow code. Its `readTool`,
|
|
126
|
+
`createTool`, `updateTool`, and `deleteTool` helpers execute Ablo model
|
|
127
|
+
operations directly and are intended for ordinary Node.js AI SDK loops. In a
|
|
128
|
+
Temporal Workflow, define the tool at the application edge and make its
|
|
129
|
+
`execute` function call a proxied Activity.
|
|
130
|
+
|
|
131
|
+
## Idempotency rules
|
|
132
|
+
|
|
133
|
+
For every mutating Activity:
|
|
134
|
+
|
|
135
|
+
1. Create the key in deterministic Workflow code or accept a stable operation
|
|
136
|
+
ID in the Workflow input.
|
|
137
|
+
2. Reuse the key and mutation body for every retry of the same logical effect.
|
|
138
|
+
3. Use `wait: 'confirmed'` when later Workflow steps depend on the
|
|
139
|
+
authoritative database result.
|
|
140
|
+
4. Treat reuse of a key with a different body as an application bug.
|
|
141
|
+
5. Keep the Temporal retry horizon within Ablo's documented
|
|
142
|
+
[idempotency retention window](../idempotency.md).
|
|
143
|
+
|
|
144
|
+
This gives effective-once composition for the advertised retention window. It
|
|
145
|
+
does not make an external side effect physically execute only once; Temporal
|
|
146
|
+
Activities are intentionally retryable.
|
|
147
|
+
|
|
148
|
+
## Claims and cancellation
|
|
149
|
+
|
|
150
|
+
A claim protects a short shared-state operation, not an entire Workflow
|
|
151
|
+
history. Keep claim acquisition, the fresh read, the checked write, and release
|
|
152
|
+
inside one Activity:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { Context } from '@temporalio/activity';
|
|
156
|
+
|
|
157
|
+
export async function applyReview(orderId: string, note: string) {
|
|
158
|
+
await using claim = await ablo.orders.claim({
|
|
159
|
+
id: orderId,
|
|
160
|
+
description: 'applying review',
|
|
161
|
+
signal: Context.current().cancellationSignal,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return ablo.orders.update({
|
|
165
|
+
id: claim.data.id,
|
|
166
|
+
data: { approvalNote: note },
|
|
167
|
+
claim,
|
|
168
|
+
wait: 'confirmed',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
For long model reasoning, reason in the Workflow and use a short Activity to
|
|
174
|
+
re-read and apply the result. Do not return from an Activity while assuming a
|
|
175
|
+
held lease will remain valid.
|
|
176
|
+
|
|
177
|
+
## Complete example
|
|
178
|
+
|
|
179
|
+
The repository's
|
|
180
|
+
[`examples/temporal-agent`](../../../../examples/temporal-agent/README.md)
|
|
181
|
+
contains a Workflow, Activities, Worker, client, durable AI SDK tool, and a
|
|
182
|
+
simulated lost-response retry. It is a standalone application on purpose:
|
|
183
|
+
Temporal stays out of Ablo's core packages and out of `packages/agent`.
|
|
184
|
+
|
|
185
|
+
A dedicated `@abloatai/temporal` package should be introduced only after
|
|
186
|
+
multiple production integrations reveal substantial, stable behavior that
|
|
187
|
+
cannot be expressed clearly at this application boundary.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Integrations
|
|
2
|
+
|
|
3
|
+
> Compose Ablo's authoritative shared-state operations with the runtimes that
|
|
4
|
+
> execute, schedule, or ingest work in your application.
|
|
5
|
+
|
|
6
|
+
Integrations live at the application edge. Ablo continues to own typed reads
|
|
7
|
+
and writes, idempotency, claims, and authoritative confirmation; the external
|
|
8
|
+
runtime keeps owning the job it was designed for.
|
|
9
|
+
|
|
10
|
+
| Category | Integration | Status | Use it for |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| Long-running tasks | [Temporal](./integrations/temporal.md) | Available | Durable Workflows, Activity retries, timers, cancellation, and durable AI SDK calls |
|
|
13
|
+
| Long-running tasks | [Inngest](./integrations/inngest.md) | Available | Event-driven durable functions, retriable steps, flow control, and checkpointed AI SDK calls |
|
|
14
|
+
| Data ingestion | Connector runtimes | Planned | Bringing external data into Ablo-backed models without creating a second write authority |
|
|
15
|
+
|
|
16
|
+
An integration gets its own guide when there is runnable application code and
|
|
17
|
+
the boundary has been tested. A dedicated package comes later still: only
|
|
18
|
+
repeated production integrations that reveal substantial reusable behavior
|
|
19
|
+
justify adding another public runtime dependency.
|
|
20
|
+
|
|
21
|
+
## Long-running tasks
|
|
22
|
+
|
|
23
|
+
Use [Temporal](./integrations/temporal.md) when work must survive process
|
|
24
|
+
failure, retry Activities, wait on timers, or preserve a durable model loop.
|
|
25
|
+
Temporal owns execution history. Each tool or Activity that touches shared
|
|
26
|
+
state calls the branded Ablo model API at the application edge.
|
|
27
|
+
|
|
28
|
+
The complete example lives in `examples/temporal-agent` and includes retry,
|
|
29
|
+
replay, cancellation, claim release, and Workflow-bundle tests.
|
|
30
|
+
|
|
31
|
+
Use [Inngest](./integrations/inngest.md) when durable work starts from events
|
|
32
|
+
and should be expressed as independently retriable steps behind an application
|
|
33
|
+
HTTP endpoint. Inngest owns event delivery, step checkpoints, and flow control.
|
|
34
|
+
Each `step.run()` that touches shared state calls the same branded Ablo model
|
|
35
|
+
API used elsewhere in the application.
|
|
36
|
+
|
|
37
|
+
The complete example lives in `examples/inngest-agent` and includes
|
|
38
|
+
confirmed-write response-loss replay, key/body conflict, checkpoint reuse,
|
|
39
|
+
claim cleanup, and endpoint-discovery tests.
|
|
40
|
+
|
|
41
|
+
## Data ingestion
|
|
42
|
+
|
|
43
|
+
Data ingestion is separate from the Inngest durable-execution integration. A
|
|
44
|
+
connector or ingestion runtime belongs in this section when its public contract
|
|
45
|
+
exists. The same ownership rule will apply:
|
|
46
|
+
|
|
47
|
+
- the ingestion runtime owns connectors, polling, checkpoints, parsing, and
|
|
48
|
+
backpressure;
|
|
49
|
+
- Ablo owns validated model writes, idempotency, coordination, and confirmed
|
|
50
|
+
outcomes;
|
|
51
|
+
- the consuming application owns field mapping and business policy.
|
|
52
|
+
|
|
53
|
+
Until that contract and a runnable example exist, this page records the
|
|
54
|
+
category without publishing placeholder imports or configuration.
|
package/docs/migration.md
CHANGED
|
@@ -214,10 +214,10 @@ commonly a Data Source handler keyed on `mode`. The mapping is exactly
|
|
|
214
214
|
source resolvers, so per-project and per-environment traffic can be routed to
|
|
215
215
|
distinct stores.
|
|
216
216
|
|
|
217
|
-
> **CLI note:**
|
|
218
|
-
>
|
|
219
|
-
>
|
|
220
|
-
>
|
|
217
|
+
> **CLI note:** current login stores one mode-free `mk_` project management
|
|
218
|
+
> credential. Legacy runtime key slots remain readable during the cutover, but
|
|
219
|
+
> re-run `ablo login` before using branch/project commands so the CLI can store
|
|
220
|
+
> the new credential.
|
|
221
221
|
|
|
222
222
|
### New (non-breaking): `transport: 'http'`
|
|
223
223
|
|
package/docs/projects.md
CHANGED
|
@@ -30,7 +30,7 @@ projects existed are default-project keys automatically.
|
|
|
30
30
|
## Keys belong to exactly one project
|
|
31
31
|
|
|
32
32
|
A key's project is fixed at mint and can never be changed or overridden —
|
|
33
|
-
the same discipline as its
|
|
33
|
+
the same discipline as its immutable branch binding. Everything a runtime key mints
|
|
34
34
|
inherits its project: the short-lived session credentials (`ek_`), agent
|
|
35
35
|
keys (`rk_`), everything. There is no way to "switch projects" with an
|
|
36
36
|
existing key; you use a key minted for the project you mean.
|
package/docs/quickstart.md
CHANGED
|
@@ -12,7 +12,7 @@ schema** — your migration tool stays in charge of the shape of your database.
|
|
|
12
12
|
|
|
13
13
|
> No database yet? Pass an `apiKey` only and Ablo keeps your rows in its own log,
|
|
14
14
|
> so you can build the whole app today — like Stripe test mode. Point it at a
|
|
15
|
-
> separate or local Postgres for a
|
|
15
|
+
> separate or local Postgres for a development branch, or at your production
|
|
16
16
|
> database when you're ready.
|
|
17
17
|
|
|
18
18
|
## 1. Install and initialize
|
|
@@ -23,22 +23,23 @@ npx ablo init
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
`ablo init` scaffolds your project (next step shows what it creates) and ends
|
|
26
|
-
by signing you in — one browser click, and a `
|
|
27
|
-
|
|
28
|
-
`.env.local
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
by signing you in — one browser click, and a project-scoped `mk_` management
|
|
27
|
+
credential is saved locally. Later, `npx ablo dev` (step 4) prepares an isolated branch and writes
|
|
28
|
+
its temporary `ABLO_API_KEY` into `.env.local`, so the SDK follows your Git
|
|
29
|
+
branch with no manual copy-paste. `npx ablo login` also exists standalone. In
|
|
30
|
+
CI, set project management access explicitly:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
export
|
|
33
|
+
export ABLO_MANAGEMENT_KEY=mk_...
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Every
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
Every runtime call needs a branch-bound API key. `ablo dev` exchanges the
|
|
37
|
+
stored management credential for
|
|
38
|
+
an expiring `sk_test_*` key bound to the current development branch.
|
|
39
|
+
Production runtimes use `sk_live_*`. In production a key points at the database
|
|
40
|
+
*you* own; on a development branch you can skip the database entirely and let
|
|
41
|
+
Ablo host the rows (apiKey only). There is no keyless mode — a key is always
|
|
42
|
+
required. (The public `/sandbox` page is a separate hosted demo, not your app.)
|
|
42
43
|
|
|
43
44
|
## 2. Your Ablo schema (init scaffolded it)
|
|
44
45
|
|
|
@@ -166,17 +167,24 @@ The full setup, the honest footprint (publication + slot + the `REPLICATION` and
|
|
|
166
167
|
writer roles + the `wal_level` restart + slot/WAL retention Ablo monitors), and the
|
|
167
168
|
Preview status are in [Connect Your Database](./data-sources.md).
|
|
168
169
|
|
|
169
|
-
## 4.
|
|
170
|
+
## 4. Start the branch development loop
|
|
170
171
|
|
|
171
172
|
```bash
|
|
172
|
-
npx ablo
|
|
173
|
-
# .env.local. Add --watch to re-push on every save.
|
|
173
|
+
npx ablo dev # prepare this Git branch, push, and watch ablo/schema.ts
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
`ablo
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
`ablo dev` discovers your Git branch, ensures a matching isolated Ablo branch,
|
|
177
|
+
mints an eight-hour branch credential, writes it to gitignored `.env.local`,
|
|
178
|
+
uploads the schema *definition*, and watches for schema edits. Model names,
|
|
179
|
+
fields, and types tell Ablo which models to coordinate. Skipping the push makes
|
|
180
|
+
every write to a new or changed model fail with
|
|
181
|
+
`server_execute_unknown_model`.
|
|
182
|
+
|
|
183
|
+
Use `npx ablo dev --no-watch` when you only need to prepare and push once. Use
|
|
184
|
+
`npx ablo push` as the lower-level one-shot command when you deliberately want
|
|
185
|
+
to push with the currently active key, including a reviewed production deploy.
|
|
186
|
+
The complete mental model and CI examples are in
|
|
187
|
+
[Branch-first development](./branch-development.md).
|
|
180
188
|
|
|
181
189
|
Now map those models to your real Postgres tables. **Your migration tool owns the
|
|
182
190
|
tables** — Ablo reads them, it does not create or migrate them:
|
|
@@ -191,8 +199,8 @@ tables** — Ablo reads them, it does not create or migrate them:
|
|
|
191
199
|
> your schema needs. Once they exist, your own migration tool stays in charge
|
|
192
200
|
> of them — Ablo adopts whatever shape you evolve.
|
|
193
201
|
|
|
194
|
-
|
|
195
|
-
hosted API
|
|
202
|
+
No Ablo server runs locally. The `ablo dev` process only watches your schema;
|
|
203
|
+
your app talks to Ablo's hosted API, and the rows live in your database.
|
|
196
204
|
|
|
197
205
|
## 5. Write through the model
|
|
198
206
|
|
package/docs/webhooks.md
CHANGED
|
@@ -116,10 +116,10 @@ work asynchronously after responding.
|
|
|
116
116
|
|
|
117
117
|
## 2. Test locally
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
`npx ablo dev` prepares an isolated schema branch; it does not forward webhook
|
|
120
|
+
deliveries. Register an HTTPS endpoint that Ablo can reach. For local handler
|
|
121
|
+
development, expose your app with the HTTPS tunnel your team already trusts,
|
|
122
|
+
then register that temporary URL and remove the endpoint when you finish.
|
|
123
123
|
|
|
124
124
|
## 3. Register your endpoint
|
|
125
125
|
|