From 9573e31e74e1f3ac48710b6bebfa000aa5fcf8a6 Mon Sep 17 00:00:00 2001 From: owenpearson Date: Thu, 18 Jun 2026 16:41:25 +0100 Subject: [PATCH] wip: liveobjects docs --- src/data/nav/aitransport.ts | 4 + .../api/javascript/core/agent-session.mdx | 2 + .../api/javascript/core/client-session.mdx | 2 + .../ai-transport/concepts/authentication.mdx | 1 + .../docs/ai-transport/concepts/sessions.mdx | 2 + .../ai-transport/features/live-objects.mdx | 117 ++++++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 src/pages/docs/ai-transport/features/live-objects.mdx diff --git a/src/data/nav/aitransport.ts b/src/data/nav/aitransport.ts index 28dc6b6fe0..6012b4c834 100644 --- a/src/data/nav/aitransport.ts +++ b/src/data/nav/aitransport.ts @@ -159,6 +159,10 @@ export default { name: 'Agent presence', link: '/docs/ai-transport/features/agent-presence', }, + { + name: 'LiveObjects State', + link: '/docs/ai-transport/features/live-objects', + }, { name: 'Push notifications', link: '/docs/ai-transport/features/push-notifications', diff --git a/src/pages/docs/ai-transport/api/javascript/core/agent-session.mdx b/src/pages/docs/ai-transport/api/javascript/core/agent-session.mdx index 070a99cd74..86636601b4 100644 --- a/src/pages/docs/ai-transport/api/javascript/core/agent-session.mdx +++ b/src/pages/docs/ai-transport/api/javascript/core/agent-session.mdx @@ -40,6 +40,7 @@ await run.start(); | Property | Description | Type | | --- | --- | --- | | presence | The Ably presence object for the session's channel. Use it to see which clients are connected — for example, to detect whether the requesting user is still online (`enter`, `leave`, `get`, `subscribe`). The session adds no semantics — it is the same instance the channel exposes — and presence operations implicitly attach, so they work without first awaiting [`connect()`](#connect). | `Ably.RealtimePresence` | +| object | The Ably LiveObjects root for the session's channel. Use it to read and write shared `LiveMap` / `LiveCounter` state on the channel the session already uses. The session adds no semantics; it is the same instance the channel exposes. Operating on it requires the client to be constructed with the `LiveObjects` plugin from `ably/liveobjects` and the object modes to be requested via [`channelModes`](#constructor-params); without both, the underlying SDK throws. See [LiveObjects State](/docs/ai-transport/features/live-objects). | `RealtimeObject` | @@ -74,6 +75,7 @@ const session = createAgentSession({ | client | required | The Ably Realtime client. The caller owns its lifecycle; `session.close()` does not close the client. | `Ably.Realtime` | | channelName | required | The channel to publish to. The session owns this channel; do not also resolve it elsewhere with conflicting options. | String | | codec | required | The codec used to encode events and messages. | `Codec` | +| channelModes | optional | Extra channel modes to request on top of the modes AI Transport always needs. Pass `OBJECT_MODES` to use Ably LiveObjects via [`object`](#properties). Omit to attach with the default mode set. The session requests the union, so extra modes never drop the modes AI Transport relies on. See [LiveObjects State](/docs/ai-transport/features/live-objects). | `Ably.ChannelMode[]` | | logger | optional | Logger instance for diagnostic output. | `Logger` | | onError | optional | Called with non-fatal session-level errors not scoped to any run (cancel listener failures, channel continuity loss). | `(error: Ably.ErrorInfo) => void` | | inputEventLookupTimeoutMs | optional | How long `Run.start()` waits for the run's input event(s) to arrive on the channel before rejecting with `InputEventNotFound`. Defaults to 30000. | Number | diff --git a/src/pages/docs/ai-transport/api/javascript/core/client-session.mdx b/src/pages/docs/ai-transport/api/javascript/core/client-session.mdx index b4108ad6bc..511057442b 100644 --- a/src/pages/docs/ai-transport/api/javascript/core/client-session.mdx +++ b/src/pages/docs/ai-transport/api/javascript/core/client-session.mdx @@ -38,6 +38,7 @@ await session.connect(); | tree | The complete conversation tree. Holds every known Run node and emits events on any change. Use `view` in most cases; reach for `tree` for low-level inspection. | | | view | The default paginated, branch-aware view for rendering. Events scope to the visible messages. |
| | presence | The Ably presence object for the session's channel. Use it to see which clients are connected (`enter`, `leave`, `get`, `subscribe`). The session adds no semantics — it is the same instance the channel exposes — and presence operations implicitly attach, so they work without first awaiting [`connect()`](#connect). | `Ably.RealtimePresence` | +| object | The Ably LiveObjects root for the session's channel. Use it to read and write shared `LiveMap` / `LiveCounter` state on the channel the session already uses. The session adds no semantics; it is the same instance the channel exposes. Operating on it requires the client to be constructed with the `LiveObjects` plugin from `ably/liveobjects` and the object modes to be requested via [`channelModes`](#constructor-params); without both, the underlying SDK throws. See [LiveObjects State](/docs/ai-transport/features/live-objects). | `RealtimeObject` |
@@ -104,6 +105,7 @@ const session = createClientSession({ | channelName | required | The channel to subscribe to and publish cancel signals on. The session owns this channel; do not also resolve it elsewhere with conflicting options. | String | | codec | required | The codec used to encode and decode events and messages. | `Codec` | | messages | optional | Initial messages to seed the conversation tree with. Forms a linear chain. | `TMessage[]` | +| channelModes | optional | Extra channel modes to request on top of the modes AI Transport always needs. Pass `OBJECT_MODES` to use Ably LiveObjects via [`object`](#properties). Omit to attach with the default mode set. The session requests the union, so extra modes never drop the modes AI Transport relies on. See [LiveObjects State](/docs/ai-transport/features/live-objects). | `Ably.ChannelMode[]` | | logger | optional | Logger instance for diagnostic output. | `Logger` | diff --git a/src/pages/docs/ai-transport/concepts/authentication.mdx b/src/pages/docs/ai-transport/concepts/authentication.mdx index f636df4848..0c5026b578 100644 --- a/src/pages/docs/ai-transport/concepts/authentication.mdx +++ b/src/pages/docs/ai-transport/concepts/authentication.mdx @@ -43,6 +43,7 @@ Capabilities are permissions on the Ably channel. When you issue a token for an | Receive streamed tokens | `subscribe` | | Replay history on reconnect | `subscribe`, `history` | | Cancel a Run | `publish` | +| Read and write shared objects | `object-subscribe`, `object-publish` | | All AI Transport features | `publish`, `subscribe`, `history` | A token that's missing a capability fails silently at the operation site, not at construction. Capability mismatches are one of the most common setup mistakes; see [troubleshooting: capability or token scope mismatch](/docs/ai-transport/troubleshooting#capability-mismatch). diff --git a/src/pages/docs/ai-transport/concepts/sessions.mdx b/src/pages/docs/ai-transport/concepts/sessions.mdx index a76ba707f0..e4eff34523 100644 --- a/src/pages/docs/ai-transport/concepts/sessions.mdx +++ b/src/pages/docs/ai-transport/concepts/sessions.mdx @@ -83,6 +83,8 @@ New participants join at any time. A second client attaching to the channel hydr To see which participants are currently connected, use presence: the session channel carries Ably Presence, exposed directly as `session.presence`. See [Agent presence](/docs/ai-transport/features/agent-presence). +The session channel also carries Ably LiveObjects, exposed directly as `session.object`, for shared mutable state, such as a document, a scoreboard, or a roster, that every participant reads and writes alongside the conversation. See [LiveObjects State](/docs/ai-transport/features/live-objects). + ## Support persistence models The channel serves two distinct roles: live delivery and historical persistence. It always serves as the live delivery layer. Whether it also serves as the historical persistence layer depends on the configuration. diff --git a/src/pages/docs/ai-transport/features/live-objects.mdx b/src/pages/docs/ai-transport/features/live-objects.mdx new file mode 100644 index 0000000000..cabfb1b128 --- /dev/null +++ b/src/pages/docs/ai-transport/features/live-objects.mdx @@ -0,0 +1,117 @@ +--- +title: "LiveObjects State" +meta_description: "Keep shared, mutable state alongside an AI Transport conversation with Ably LiveObjects, exposed directly on the session." +meta_keywords: "LiveObjects, shared state, LiveMap, LiveCounter, session object, AI Transport, Ably" +intro: "Keep shared application state next to the conversation. AI Transport session channels carry Ably LiveObjects, exposed directly as session.object, so agents and clients read and write the same LiveMap and LiveCounter state on the channel they already share." +redirect_from: + - /docs/ai-transport/sessions-identity/shared-state +--- + +An AI Transport session channel is an ordinary Ably channel, so it carries Ably [LiveObjects](/docs/liveobjects) like any other channel. Use LiveObjects to hold shared, mutable state, such as a document the agent edits, a scoreboard, or a roster of participants, that every client reads and writes in real time without threading it through the conversation. Both [`ClientSession`](/docs/ai-transport/api/javascript/core/client-session) and [`AgentSession`](/docs/ai-transport/api/javascript/core/agent-session) expose the channel's LiveObjects root directly as `session.object`, so you don't need to resolve the channel separately. + + + +## Objects on a session + +`session.object` returns the same `RealtimeObject` instance the session's channel exposes. The session adds no semantics of its own: `get`, `subscribe`, and the `LiveMap` / `LiveCounter` operations behave exactly as they do on a raw Ably channel. Resolve the channel's root object, read it as plain JSON, and re-render whenever it changes: + + +```javascript +// session.object is the channel's LiveObjects root, the same instance +// channel.object exposes. +const root = await session.object.get(); + +// Read the current state as plain JSON. +render(root.compactJson()); + +// Re-render on every change, nested objects included. +root.subscribe(() => render(root.compactJson())); +``` + + +Object state syncs when the channel attaches, so a client that joins late, or reloads, sees the current state immediately, before any conversation history has loaded. + +## Enable LiveObjects + +LiveObjects is not part of the default channel mode set, so it needs explicit opt-in. Three things must line up, or `session.object` operations throw: + +- Construct the Ably Realtime client with the `LiveObjects` plugin from `ably/liveobjects`. +- Request the object channel modes on the session, by passing `OBJECT_MODES` as the session's `channelModes` option. +- Grant the connection's token the `object-subscribe` and `object-publish` capabilities. See [authentication](/docs/ai-transport/concepts/authentication#capabilities). + +Pass the plugin when you create the client, and the modes when you create the session: + + +```javascript +import * as Ably from 'ably'; +import { LiveObjects } from 'ably/liveobjects'; +import { createClientSession, OBJECT_MODES } from '@ably/ai-transport'; +import { UIMessageCodec } from '@ably/ai-transport/vercel'; + +// Without the LiveObjects plugin, session.object throws. +const ably = new Ably.Realtime({ authUrl: '/api/auth/token', plugins: { LiveObjects } }); + +const session = createClientSession({ + client: ably, + channelName: 'conversation-42', + codec: UIMessageCodec, + // Opt the session's channel into LiveObjects. The session requests these + // modes on top of the ones AI Transport always needs, so the transport + // itself is unaffected. + channelModes: OBJECT_MODES, +}); +``` + + +`OBJECT_MODES` is `['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH']`. Setting channel modes is a full replacement of the default set, not an addition, so the session resolves the union of `OBJECT_MODES` with the modes it always needs. Requesting object modes never drops the modes AI Transport relies on. + +## Read and write objects + +An agent reads and writes the same root object. A per-request agent attaches the channel and resolves the root. Object state has already synced, so the agent mutates the current state through the same `LiveMap` and `LiveCounter` API: + + +```javascript +const session = createAgentSession({ + client: ably, + channelName: invocation.sessionName, + channelModes: OBJECT_MODES, +}); +await session.connect(); + +const root = await session.object.get(); + +// Read the current state to ground the model. +const snapshot = root.compactJson(); + +// Write back through the object; every subscribed client sees it. +await root.set('title', 'Trip to Lisbon'); +``` + + +Writes propagate to every subscribed client over the same channel. Concurrent writers are safe where the operation is commutative: `LiveCounter.increment` from two clients merges, while a `LiveMap.set` on the same key is last-write-wins. Partition writes by key so no two writers race on the same `set`. + +## Edge cases and unhappy paths + +- A client constructed without the `LiveObjects` plugin throws when you call `session.object`. The session does not suppress the error; construct the client with `plugins: { LiveObjects }`. +- A session created without `channelModes: OBJECT_MODES` attaches without object modes, and object operations fail. Pass the modes when you create the session. +- A token missing the `object-subscribe` or `object-publish` capability fails at the operation site, not at construction. The server grants only the permitted subset of requested modes. Capability scoping is part of [authentication](/docs/ai-transport/concepts/authentication#capabilities). +- A `LiveMap.set` on a key two clients write concurrently is last-write-wins, so one write is lost. Partition writes by key, or use a `LiveCounter` where the operation must merge. +- The channel namespace must have LiveObjects enabled in its rules. Object operations fail on a namespace that does not permit them. + +## FAQ + +### Why does session.object throw? + +One of the three requirements is missing: the `LiveObjects` plugin on the client, `channelModes: OBJECT_MODES` on the session, or the `object-subscribe` / `object-publish` capabilities on the token. See [Enable LiveObjects](#enable). + +### Can both the agent and the client write to the same object? + +Yes. Both call the same `LiveMap` and `LiveCounter` API on `session.object`. Concurrent writes merge where the operation is commutative (`LiveCounter.increment`); a `LiveMap.set` on the same key is last-write-wins. Partition writes by key to avoid races. + +## Related features + +- [LiveObjects](/docs/liveobjects): the Ably LiveObjects API, including `LiveMap` and `LiveCounter`. +- [Sessions](/docs/ai-transport/concepts/sessions): `session.object` on the client and agent sessions. +- [Agent presence](/docs/ai-transport/features/agent-presence): the same pass-through pattern for Ably Presence.