@abloatai/ablo 0.37.1 → 0.39.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 +142 -13
- package/README.md +65 -28
- 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
package/AGENTS.md
CHANGED
|
@@ -13,10 +13,10 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
|
|
|
13
13
|
- **Auth:** set `ABLO_API_KEY` in the environment. Do **NOT** run `ablo login` — it opens a browser device flow and blocks an agent.
|
|
14
14
|
- **Connect your database — logical replication (the primary path):** `npx ablo connect` prints the setup SQL (`wal_level=logical`, a publication, a `REPLICATION` role); `npx ablo connect register` registers the source with Ablo in one step. Ablo **consumes your Postgres' logical-replication stream** — it never runs DDL on, writes to, owns, or migrates your database, and your application keeps the write path. Registration **is** the enable; there is no tier or flag to pick. (Ablo hosts only the transaction log + coordination, never your rows.)
|
|
15
15
|
- **Fallback — signed Data Source endpoint** (DB can't grant a `REPLICATION` role): the generated `ablo/data-source.ts` exposes one route; Ablo sends signed requests and your app touches its own DB. **Only in this mode** does `npx ablo migrate` provision the adapter's bookkeeping tables (`ablo_outbox`, `ablo_idempotency`) plus your Ablo models — it does **not** touch your other tables. Keep your own migrations (drizzle-kit / prisma migrate) for auth and anything outside the Ablo schema.
|
|
16
|
-
- **No database yet?**
|
|
16
|
+
- **No database yet?** Run `npx ablo dev --no-watch --branch <name>` to create an isolated non-root branch and obtain its expiring `sk_test_` credential. The branch uses a throwaway hosted data plane; Production remains the protected root. There is no shared Sandbox mode.
|
|
17
17
|
- **Adopt an existing DB schema:** `npx ablo pull prisma [path]` / `pull drizzle <module>` (lossless) or `pull` (live DB, lossy). Writes `ablo/schema.ts`.
|
|
18
|
-
- **
|
|
19
|
-
- **Other long-running:** `npx ablo logs --no-follow` (default tails forever). `
|
|
18
|
+
- **Prepare a branch and push your schema — REQUIRED before any write works.** The server keeps its OWN copy of the schema. After you create or edit `ablo/schema.ts`, run `npx ablo dev --no-watch --branch <git-or-ci-ref>`: it ensures an isolated branch, mints a temporary branch credential, writes it to gitignored `.env.local`, pushes once, and exits. **Skip this and every write to a new or changed model fails with `server_execute_unknown_model`.** Plain `ablo dev` watches forever — never run it bare in an agent. `ablo push` is the lower-level one-shot command for an already selected key, including reviewed production deploys.
|
|
19
|
+
- **Other long-running:** `npx ablo logs --no-follow` (default tails forever). `status`, `branch check`, `push`, `pull`, `check`, `generate`, `docs`, and `dev --no-watch` are one-shot.
|
|
20
20
|
|
|
21
21
|
When you use the signed-endpoint fallback, the generated `ablo/data-source.ts` is the whole endpoint and needs no hand-editing: `dataSourceNext({ schema, apiKey, adapter: prismaDataSource(prisma, schema) })` (or `drizzleDataSource(db, schema)`). The adapter owns commit / idempotency / outbox.
|
|
22
22
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,147 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.39.0
|
|
4
|
+
|
|
5
|
+
### Schema-relative session grants
|
|
6
|
+
|
|
7
|
+
`mintUserSessionKey` accepts `activeSchemaOperations` in place of `operations`.
|
|
8
|
+
Send the verbs a session needs, such as `['read', 'create', 'update',
|
|
9
|
+
'delete']`, and the server resolves them against the models in that session's
|
|
10
|
+
active schema. A backend that mints sessions for organizations whose schemas it
|
|
11
|
+
does not own no longer has to enumerate models it has no way to know.
|
|
12
|
+
|
|
13
|
+
Calls that pass `operations` are unchanged. A request carries one form or the
|
|
14
|
+
other, and the SDK rejects a call carrying both or neither before the round
|
|
15
|
+
trip. What the session ends up holding is the same concrete `model.verb`
|
|
16
|
+
allowlist either way, so nothing about enforcement changes. A verb is granted
|
|
17
|
+
only where the model accepts it, which leaves an immutable model with reads
|
|
18
|
+
alone. An organization that has not pushed a schema yet is told to push one
|
|
19
|
+
rather than issued a key, since a grant that resolves to nothing would widen
|
|
20
|
+
rather than narrow what the session can reach.
|
|
21
|
+
|
|
22
|
+
### A named error for models outside your schema
|
|
23
|
+
|
|
24
|
+
Reaching for a model that your schema projection leaves out now fails with a
|
|
25
|
+
named `model_not_in_schema` error at the point of access, rather than
|
|
26
|
+
surfacing later as a property that happens to be undefined.
|
|
27
|
+
|
|
28
|
+
### Declared premises serialize against concurrent writers
|
|
29
|
+
|
|
30
|
+
A commit that declares row premises now serializes those rows against
|
|
31
|
+
concurrent commits, not only the rows it writes. Two commits that read the
|
|
32
|
+
same rows and wrote different ones could previously both pass validation in a
|
|
33
|
+
narrow window. The later commit now observes the earlier one and is rejected
|
|
34
|
+
as stale, so a functional update re-reads and re-runs instead of committing on
|
|
35
|
+
outdated reads.
|
|
36
|
+
|
|
37
|
+
### Faster, atomic live updates
|
|
38
|
+
|
|
39
|
+
The client applies an incoming batch of changes as one action, so reactive
|
|
40
|
+
observers see the whole batch or none of it, never a half-applied batch. The
|
|
41
|
+
apply path does substantially less work per change, the final partial batch of
|
|
42
|
+
a burst materializes after 10 ms rather than 100 ms, and the client no longer
|
|
43
|
+
assembles debug payloads it discards.
|
|
44
|
+
|
|
45
|
+
## 0.38.0
|
|
46
|
+
|
|
47
|
+
### Branch-isolated development
|
|
48
|
+
|
|
49
|
+
Ablo branches replace the former shared Sandbox mode. Every project has a
|
|
50
|
+
protected Production root and can have multiple isolated development, preview,
|
|
51
|
+
test, or long-lived branches. Credentials are bound to immutable branch
|
|
52
|
+
identities, so changing a slug or request parameter cannot redirect a write.
|
|
53
|
+
|
|
54
|
+
`npx ablo dev` now discovers the current Git or CI branch, ensures the matching
|
|
55
|
+
Ablo branch, mints an expiring branch credential, writes it to the gitignored
|
|
56
|
+
local environment, pushes the schema, and watches for changes. Use
|
|
57
|
+
`npx ablo dev --no-watch --branch <name>` for a one-shot agent or CI setup.
|
|
58
|
+
|
|
59
|
+
The CLI also provides `ablo branch list`, `create`, `ensure`, `status`, `check`,
|
|
60
|
+
`credential`, and `delete`. The public HTTP and OpenAPI contracts expose the
|
|
61
|
+
same lifecycle for other languages and deployment systems.
|
|
62
|
+
|
|
63
|
+
This release removes the CLI mode switch and the dashboard Sandbox surface.
|
|
64
|
+
Production remains the protected root; a development credential cannot select
|
|
65
|
+
another branch or gain production authority.
|
|
66
|
+
|
|
67
|
+
Source adapters and PostgreSQL footprint helpers now select immutable branches.
|
|
68
|
+
If you construct `FootprintPlane` or `SourceRequestContext`, replace
|
|
69
|
+
`environment`, `mode`, and `sandboxId` with `branchId`.
|
|
70
|
+
|
|
71
|
+
### Temporal and Inngest integration guides
|
|
72
|
+
|
|
73
|
+
New runnable examples and documentation show how Temporal and Inngest
|
|
74
|
+
workflows use Ablo without introducing another authority path. Temporal
|
|
75
|
+
activities and Inngest steps create the Ablo client, perform authoritative
|
|
76
|
+
reads, acquire claims for long-running work, and submit idempotent writes
|
|
77
|
+
through the same transaction API as every other caller.
|
|
78
|
+
|
|
79
|
+
The integrations keep each product in its proper role: Temporal and Inngest
|
|
80
|
+
own durable execution, scheduling, retries, and workflow history; Ablo owns
|
|
81
|
+
shared-data authority, claims, conflicts, idempotency, settlement, and ordered
|
|
82
|
+
observation. Workflow code does not open WebSockets or hold live client state.
|
|
83
|
+
|
|
84
|
+
### Database adapter foundation, starting with PostgreSQL
|
|
85
|
+
|
|
86
|
+
Customer-database adapters now declare their database, ORM binding, and
|
|
87
|
+
observation strategy as separate axes. The existing Prisma, Drizzle, and Kysely
|
|
88
|
+
integrations are accurately identified as PostgreSQL bindings using either a
|
|
89
|
+
transactional outbox or PostgreSQL WAL.
|
|
90
|
+
|
|
91
|
+
All built-in adapters are constructed through one validating factory and the
|
|
92
|
+
shared conformance suite checks that the three axes are present. Impossible
|
|
93
|
+
capability combinations fail during adapter construction instead of silently
|
|
94
|
+
advertising guarantees the database path cannot provide.
|
|
95
|
+
|
|
96
|
+
PostgreSQL is the first database profile on this axis. The contract leaves room
|
|
97
|
+
for additional databases without pretending their transaction, observation,
|
|
98
|
+
and change-capture guarantees are interchangeable with PostgreSQL.
|
|
99
|
+
|
|
100
|
+
### Generated language SDK foundation
|
|
101
|
+
|
|
102
|
+
The HTTP API is now explicitly treated as Ablo's language-neutral product
|
|
103
|
+
boundary. Its OpenAPI artifact follows the current Ablo version and every
|
|
104
|
+
operation has a stable, unique name suitable for deterministic Python and Go
|
|
105
|
+
generation.
|
|
106
|
+
|
|
107
|
+
The OpenAPI generator and drift check are restored as repository and CI gates.
|
|
108
|
+
Future language clients will generate their transport, wire models, and error
|
|
109
|
+
decoding from this contract, retaining handwritten code only for thin
|
|
110
|
+
language-idiomatic resource and claim façades.
|
|
111
|
+
|
|
112
|
+
The artifact now names shared claim, receipt, cursor, page, and error schemas;
|
|
113
|
+
references the canonical error envelope from every operation; publishes typed
|
|
114
|
+
pagination parameters and explicit union discriminators; and normalizes Zod's
|
|
115
|
+
JSON Schema output to the portable subset accepted by the Python and Go
|
|
116
|
+
generator candidates. CI also validates the rendered OpenAPI 3.1 document with
|
|
117
|
+
an independent, pinned Redocly CLI.
|
|
118
|
+
|
|
119
|
+
### AI SDK tools over the transaction API
|
|
120
|
+
|
|
121
|
+
`@abloatai/ablo/ai-sdk` now exposes small tool adapters for authoritative
|
|
122
|
+
reads, idempotent creates, concurrency-safe updates, and claimed deletes. These
|
|
123
|
+
helpers use the caller's existing typed Ablo model resource; they do not add an
|
|
124
|
+
agent runtime or a second transport.
|
|
125
|
+
|
|
126
|
+
The public surface follows the model verbs directly: `readTool`, `createTool`,
|
|
127
|
+
`updateTool`, and `deleteTool`. AI SDK metadata and approval policy pass through
|
|
128
|
+
to each tool, destructive tools require approval by default, and cancellation
|
|
129
|
+
stops queued claim acquisition.
|
|
130
|
+
|
|
131
|
+
This deliberately replaces the previously published `coordinatedTool` naming:
|
|
132
|
+
use `updateTool`, `UpdateToolModel`, `UpdateToolOptions`, `UpdateToolResult`,
|
|
133
|
+
`UpdateStrategy`, and the `status` result field. The former
|
|
134
|
+
`coordinatedTool`, `CoordinatedModel`, `CoordinatedToolOptions`,
|
|
135
|
+
`CoordinatedWriteResult`, and `CoordinationStrategy` exports are removed rather
|
|
136
|
+
than retained as legacy aliases.
|
|
137
|
+
|
|
138
|
+
Queued tool writes now use Ablo's server-owned FIFO claim queue rather than
|
|
139
|
+
recreating coordination with client-side polling.
|
|
140
|
+
|
|
141
|
+
Internal job dispatch, concrete worker tools, prompts, sandboxes, and model
|
|
142
|
+
selection remain application concerns and are not part of the public Ablo
|
|
143
|
+
agent surface.
|
|
144
|
+
|
|
3
145
|
## 0.37.1
|
|
4
146
|
|
|
5
147
|
### A clearer introduction to Ablo
|
|
@@ -64,16 +206,3 @@ Use `authEndpoint` for the browser credential endpoint. The supported routes
|
|
|
64
206
|
are `/v1/ephemeral_keys` and `/v1/capabilities`; the former `apiKey` endpoint
|
|
65
207
|
option and legacy route aliases are removed. Credential callbacks now use the
|
|
66
208
|
`CredentialProvider` type.
|
|
67
|
-
|
|
68
|
-
### Verified throughput without dropping coordination guarantees
|
|
69
|
-
|
|
70
|
-
The strict AWS benchmark sustained more than 10,000 committed operations per
|
|
71
|
-
second across create, mixed-create, update, and delete workloads, with zero
|
|
72
|
-
write errors and sub-second publication drain.
|
|
73
|
-
|
|
74
|
-
The result covers the documented single-plane, 12-client, 500-operation test
|
|
75
|
-
topology. Atomic commits, authorization, idempotency, conflict handling, audit
|
|
76
|
-
delivery, ordered observation, replay, and authoritative confirmation remained
|
|
77
|
-
enabled throughout the run.
|
|
78
|
-
|
|
79
|
-
Release notes are generated from the repository changesets.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>
|
|
6
|
+
<strong>The transaction layer for AI agents.</strong>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -22,28 +22,56 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
writes, claims, safe retries, authoritative confirmation, live updates, and
|
|
28
|
-
attribution while your Postgres remains the source of truth.
|
|
25
|
+
Safely coordinate AI agents, humans, workflows, and services writing to the
|
|
26
|
+
same database.
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
Ablo is an authoritative transaction layer for shared application state. Every
|
|
29
|
+
write goes through one typed API where authority, idempotency, conflicts,
|
|
30
|
+
ordering, and confirmation can be enforced. Your Postgres remains the source
|
|
31
|
+
of truth.
|
|
33
32
|
|
|
34
33
|
## Why Ablo
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Software used to have one writer: a human clicking through an application. AI
|
|
36
|
+
applications now have humans, agents, workflows, and services acting
|
|
37
|
+
concurrently. Databases keep transactions consistent. They do not coordinate
|
|
38
|
+
autonomous work that reads now, reasons for thirty seconds, and writes later.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
Humans handle this naturally. We see that somebody is editing, agree on who
|
|
41
|
+
takes which part, wait our turn, and look again before continuing. Ablo gives
|
|
42
|
+
software actors those same capabilities: bounded authority, shared ownership,
|
|
43
|
+
fresh context, safe handoffs, and an attributed record of what happened.
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
## Start
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install @abloatai/ablo
|
|
49
|
+
npx ablo init
|
|
50
|
+
npx ablo dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`ablo dev` prepares an isolated Ablo branch for your Git branch, writes its
|
|
54
|
+
temporary credential to gitignored `.env.local`, pushes the schema, and watches
|
|
55
|
+
for changes.
|
|
56
|
+
|
|
57
|
+
Read and write through the transaction layer:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const order = await ablo.orders.get({ id: orderId });
|
|
61
|
+
|
|
62
|
+
if (!order) throw new Error('Order not found');
|
|
63
|
+
await ablo.orders.update({
|
|
64
|
+
id: order.id,
|
|
65
|
+
data: { status: 'approved' },
|
|
66
|
+
wait: 'confirmed',
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`confirmed` means the authoritative database reported the change back. The
|
|
71
|
+
same commit can be retried safely if the caller loses its connection.
|
|
72
|
+
|
|
73
|
+
When work takes thirty seconds instead of one request, coordinate before the
|
|
74
|
+
agent starts reasoning:
|
|
47
75
|
|
|
48
76
|
```ts
|
|
49
77
|
await using claim = await ablo.orders.claim({ id: orderId });
|
|
@@ -51,33 +79,42 @@ await using claim = await ablo.orders.claim({ id: orderId });
|
|
|
51
79
|
const priced = await pricingAgent(claim.data);
|
|
52
80
|
|
|
53
81
|
await ablo.orders.update({
|
|
54
|
-
id:
|
|
82
|
+
id: claim.data.id,
|
|
55
83
|
data: { total: priced.total, status: 'repriced' },
|
|
56
84
|
claim,
|
|
57
85
|
wait: 'confirmed',
|
|
58
86
|
});
|
|
59
87
|
```
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
Another actor touching the same work waits fairly and receives fresh state when
|
|
90
|
+
its turn begins. If the agent fails, the claim releases automatically. If its
|
|
91
|
+
context became stale, the write is rejected instead of silently overwriting
|
|
92
|
+
work it never saw.
|
|
64
93
|
|
|
65
|
-
|
|
94
|
+
If you use AI SDK, expose the same operation as a typed model tool:
|
|
66
95
|
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
```ts
|
|
97
|
+
import { updateTool } from '@abloatai/ablo/ai-sdk';
|
|
98
|
+
|
|
99
|
+
const approveOrder = updateTool(ablo.orders, {
|
|
100
|
+
description: 'Approve an order after reviewing it.',
|
|
101
|
+
inputSchema: z.object({ orderId: z.string() }),
|
|
102
|
+
id: ({ orderId }) => orderId,
|
|
103
|
+
apply: () => ({ status: 'approved' }),
|
|
104
|
+
});
|
|
71
105
|
```
|
|
72
106
|
|
|
107
|
+
Ablo supplies `readTool`, `createTool`, `updateTool`, and `deleteTool` over the
|
|
108
|
+
same authoritative resources. AI SDK keeps ownership of the model loop and tool
|
|
109
|
+
execution.
|
|
110
|
+
|
|
73
111
|
Use `@abloatai/ablo` for agents and backend code,
|
|
74
112
|
`@abloatai/ablo/client` for live applications, and
|
|
75
113
|
`@abloatai/ablo/react` for React. All entrypoints share the same schema,
|
|
76
114
|
authority, commits, claims, and ordered changes.
|
|
77
115
|
|
|
78
|
-
Read the [Quickstart](
|
|
116
|
+
Read the [Quickstart](https://docs.abloatai.com/quickstart), browse
|
|
79
117
|
[docs.abloatai.com](https://docs.abloatai.com), or run `npx ablo docs`.
|
|
80
|
-
Coding agents can read `node_modules/@abloatai/ablo/llms.txt`.
|
|
81
118
|
|
|
82
119
|
## Contributing
|
|
83
120
|
|
package/dist/ai-sdk.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
|
package/dist/ai-sdk.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
|
package/docs/agents.md
CHANGED
|
@@ -6,7 +6,7 @@ An agent is a **reactive** participant: it wakes on something happening, reads
|
|
|
6
6
|
what it needs, writes a result, and goes idle. That's a request/response
|
|
7
7
|
workload — so agents talk to Ablo over **plain HTTP**, holding no WebSocket. The
|
|
8
8
|
credential *is* the identity; the server resolves the org, scope, and actor from
|
|
9
|
-
the key on every request
|
|
9
|
+
the key on every request.
|
|
10
10
|
|
|
11
11
|
Agents get the stateless plane (HTTP). People — when you add the `humans()`
|
|
12
12
|
plugin — get the live plane (WebSocket: presence, optimistic, sub-100ms).
|
|
@@ -31,7 +31,7 @@ import { schema } from "./schema";
|
|
|
31
31
|
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: "http" });
|
|
32
32
|
|
|
33
33
|
// Reads + writes, fully typed off your schema.
|
|
34
|
-
// `
|
|
34
|
+
// `get` resolves to the row, or `undefined` when none matches.
|
|
35
35
|
const open = await ablo.tasks.list({ where: { status: "todo" } });
|
|
36
36
|
|
|
37
37
|
const task = await ablo.tasks.get({ id: open[0].id });
|
|
@@ -41,11 +41,57 @@ console.log(task.title);
|
|
|
41
41
|
await ablo.tasks.update({ id: task.id, data: { status: "done" } });
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
It exposes `
|
|
45
|
-
and `claim`. It does **not** expose
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
It exposes `get` / `list` / `create` / `update` / `delete`, plus `commits`
|
|
45
|
+
and `claim`. It does **not** expose stateful-only `local` reads or `onChange`
|
|
46
|
+
subscriptions. Those need a live connection, so with `transport: 'http'` they
|
|
47
|
+
are compile errors rather than runtime surprises.
|
|
48
|
+
|
|
49
|
+
## AI SDK tools
|
|
50
|
+
|
|
51
|
+
Keep AI SDK in charge of the model loop and expose only the Ablo operations the
|
|
52
|
+
model needs:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { generateText } from 'ai';
|
|
56
|
+
import {
|
|
57
|
+
createTool,
|
|
58
|
+
deleteTool,
|
|
59
|
+
readTool,
|
|
60
|
+
updateTool,
|
|
61
|
+
} from '@abloatai/ablo/ai-sdk';
|
|
62
|
+
|
|
63
|
+
const tools = {
|
|
64
|
+
getTask: readTool(ablo.tasks, {
|
|
65
|
+
description: 'Read the current task.',
|
|
66
|
+
inputSchema: z.object({ taskId: z.string() }),
|
|
67
|
+
id: ({ taskId }) => taskId,
|
|
68
|
+
}),
|
|
69
|
+
createTask: createTool(ablo.tasks, {
|
|
70
|
+
description: 'Create a task.',
|
|
71
|
+
inputSchema: z.object({ requestId: z.string(), title: z.string() }),
|
|
72
|
+
id: ({ requestId }) => requestId,
|
|
73
|
+
data: ({ title }) => ({ title, status: 'todo' }),
|
|
74
|
+
}),
|
|
75
|
+
updateTask: updateTool(ablo.tasks, {
|
|
76
|
+
description: 'Update a task without overwriting concurrent work.',
|
|
77
|
+
inputSchema: z.object({ taskId: z.string(), status: z.string() }),
|
|
78
|
+
id: ({ taskId }) => taskId,
|
|
79
|
+
apply: (_current, { status }) => ({ status }),
|
|
80
|
+
}),
|
|
81
|
+
deleteTask: deleteTool(ablo.tasks, {
|
|
82
|
+
description: 'Delete a task after taking its claim.',
|
|
83
|
+
inputSchema: z.object({ taskId: z.string() }),
|
|
84
|
+
id: ({ taskId }) => taskId,
|
|
85
|
+
// Destructive tools require AI SDK approval by default.
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await generateText({ model, messages, tools });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
These are adapters over the same typed resources used by ordinary backend
|
|
93
|
+
code. Ablo does not own the planner, prompt system, memory, provider, worker,
|
|
94
|
+
or workflow runtime.
|
|
49
95
|
|
|
50
96
|
## Coordination: claim, queue, reorder
|
|
51
97
|
|
|
@@ -59,7 +105,11 @@ clobber the same record.
|
|
|
59
105
|
await using claim = await ablo.tasks.claim({ id: taskId });
|
|
60
106
|
const task = claim.data;
|
|
61
107
|
// …no one else can hold this row while you work…
|
|
62
|
-
await ablo.tasks.update({
|
|
108
|
+
await ablo.tasks.update({
|
|
109
|
+
id: task.id,
|
|
110
|
+
data: { status: "in_review" },
|
|
111
|
+
claim,
|
|
112
|
+
});
|
|
63
113
|
|
|
64
114
|
await ablo.tasks.claim.state({ id: taskId }); // who holds it now (or null)
|
|
65
115
|
await ablo.tasks.claim.queue({ id: taskId }); // the FIFO wait-line behind the holder
|
|
@@ -111,5 +161,5 @@ agents costs nothing on the live plane — that capacity stays for humans.
|
|
|
111
161
|
`onChange` (live subscriptions) and the `local` reads (local synced-pool
|
|
112
162
|
reads) require a WebSocket and a local store — they're for interactive UIs, not
|
|
113
163
|
stateless agents. An agent reacts to an external trigger (a job/queue/webhook),
|
|
114
|
-
then reads with `list`/`
|
|
164
|
+
then reads with `list`/`get`. See [client behavior](/client-behavior) for
|
|
115
165
|
the full surface and [guarantees](/guarantees) for the coordination semantics.
|
package/docs/api-keys.md
CHANGED
|
@@ -27,6 +27,11 @@ Pick your row:
|
|
|
27
27
|
|
|
28
28
|
That's the whole story: one knob, filled by audience.
|
|
29
29
|
|
|
30
|
+
The `mk_` credential created by `ablo login` is different: it is a CLI
|
|
31
|
+
control-plane credential, not an application API key. It can manage projects
|
|
32
|
+
and branches and exchange for a branch-bound runtime key. Do not pass it to
|
|
33
|
+
`Ablo(...)` or put it in `ABLO_API_KEY`.
|
|
34
|
+
|
|
30
35
|
**Coming from Stripe? It's the same key model, same prefixes:**
|
|
31
36
|
|
|
32
37
|
| Stripe | Ablo | Where it goes |
|
|
@@ -36,7 +41,14 @@ That's the whole story: one knob, filled by audience.
|
|
|
36
41
|
| restricted `rk_` (granular) | `rk_` | scoped agents (`agents.create({ can })`) |
|
|
37
42
|
| ephemeral key (client, customer-scoped) | `ek_` | per-user browser sessions (`sessions.create({ user, can })`) |
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
Ablo also has one credential class that Stripe does not need:
|
|
45
|
+
|
|
46
|
+
| Prefix | Purpose | Mode | Stored where |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `mk_` | project and branch management | none | CLI credential store or `ABLO_MANAGEMENT_KEY` |
|
|
49
|
+
|
|
50
|
+
Trust class lives in the prefix too — `sk_test_` / `sk_live_` — exactly like Stripe.
|
|
51
|
+
It does not select a branch; the immutable server-side binding does that. The
|
|
40
52
|
`apiKey` resolver fetching an `ek_` is Ablo's ephemeral-key flow: server mints, client holds.
|
|
41
53
|
|
|
42
54
|
**Why a function for browser writes?** Anything you ship to a browser must be public, and a
|
|
@@ -91,21 +103,28 @@ cannot reach any control-plane operation. The moment the browser needs to write
|
|
|
91
103
|
on a specific user's behalf, mint a short-lived `ek_` user session from your
|
|
92
104
|
backend instead (see the Sessions guide).
|
|
93
105
|
|
|
94
|
-
##
|
|
106
|
+
## Branches and production
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
A branch is your project at full strength over its own rows: the same models,
|
|
109
|
+
the same schema, the same claims and the same rules production runs.
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
Production is the project's root branch. Development branches are isolated
|
|
112
|
+
children, and a key's immutable branch binding decides which rows, schema,
|
|
113
|
+
claims, and log it can reach:
|
|
114
|
+
|
|
115
|
+
- `sk_test_…` — a key bound to a development branch. Its reads and
|
|
116
|
+
writes are invisible to production and to other branches.
|
|
100
117
|
- `sk_live_…` — a key against your live data.
|
|
101
118
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
`npx ablo dev` derives a branch from Git, ensures the matching child, and mints
|
|
120
|
+
an expiring `sk_test_` key for it. The credential carries the immutable branch
|
|
121
|
+
id; changing a slug in a request cannot change its authority. A child receives
|
|
122
|
+
the parent's active schema when it is created and owns its artifact after that.
|
|
123
|
+
A schema change reaches production only through the reviewed live-key path in
|
|
124
|
+
[Deployment](./deployment.md).
|
|
125
|
+
|
|
126
|
+
The shared default sandbox is no longer part of the development workflow.
|
|
127
|
+
Branch identity is required for newly provisioned CLI and runtime credentials.
|
|
109
128
|
|
|
110
129
|
## Scopes
|
|
111
130
|
|
|
@@ -114,25 +133,30 @@ only what its job needs. A secret key with **no scopes** has full org authority
|
|
|
114
133
|
(the default for a `sk_live_` backend key); a key with a non-empty scope set is
|
|
115
134
|
restricted to exactly those grants:
|
|
116
135
|
|
|
117
|
-
- `schema:push` — author the
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- `
|
|
122
|
-
|
|
136
|
+
- `schema:push` — author the schema artifact on the key's bound plane
|
|
137
|
+
(`ablo push`, `ablo dev`). A production push is high-risk because it changes
|
|
138
|
+
the live contract; a child push remains inside that branch. A
|
|
139
|
+
full-authority key has it implicitly; a restricted key needs it explicitly.
|
|
140
|
+
- `project:manage` — list, create, and rename projects.
|
|
141
|
+
- `branch:manage` — list, create, and delete child branches and mint their
|
|
142
|
+
temporary credentials.
|
|
143
|
+
|
|
144
|
+
Both management scopes are explicit grants on `mk_` credentials. Runtime
|
|
145
|
+
`sk_`, `rk_`, `pk_`, and `ek_` credentials cannot become management
|
|
146
|
+
credentials through an empty scope set or a CLI fallback.
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
schema too. Hand data-only keys to embedded apps and CI agents; reserve
|
|
128
|
-
schema-authoring keys for the developer running `ablo dev`.
|
|
148
|
+
Branch binding remains an authority boundary even when a key has no granular
|
|
149
|
+
scope strings: a temporary child key can act only inside that child. It cannot
|
|
150
|
+
manage siblings or gain root authority.
|
|
129
151
|
|
|
130
152
|
### `ablo dev`
|
|
131
153
|
|
|
132
154
|
```sh
|
|
133
|
-
|
|
155
|
+
npx ablo login
|
|
156
|
+
npx ablo dev
|
|
134
157
|
```
|
|
135
158
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
159
|
+
The stored `mk_` project credential is used only to ensure the Git-derived child and
|
|
160
|
+
mint an expiring branch credential. `dev` writes that temporary key to
|
|
161
|
+
gitignored `.env.local`, pushes `ablo/schema.ts` to the child, and re-pushes on
|
|
162
|
+
every save. See [Branch-first development](./branch-development.md).
|