Shared state is a hard problem. Not hard in the abstract, computer-science sense (the concepts are well understood). Hard in the someone has to actually build this sense, where every team that wants a live leaderboard, a shared config panel, or a poll that updates in real time ends up reinventing the same wheels: conflict resolution, reconnection handling, state recovery.
Most teams do not want to spend their time building and maintaining that layer. They want to ship the feature that depends on it.
That is what LiveObjects is for.
From experimental to production-ready
When we first shipped LiveObjects, the API was explicitly experimental. We had the primitives (LiveMap for synchronized key-value state, LiveCounter for distributed counting) but the ergonomics needed work. Early adopters were clear: working directly with object instances felt brittle, especially when objects were replaced. Subscriptions broke. Navigating nested structures was cumbersome. The mental model didn't fit how people actually wanted to build.
So we rebuilt the API from the ground up. The result shipped in the JavaScript SDK before the end of last year, moving LiveObjects into Public Preview. It's centered on path-based operations. Instead of binding to specific object instances, you work with PathObjects that resolve at runtime against whatever exists at that location. Replace the object underneath, and your subscriptions follow automatically.
That feedback loop, from experimental signal to a redesigned API, is what today's release reflects. The API is stable and ready for production.
What changed?
A new API designed around how you think about state
The old approach required holding references to specific object instances, which meant reasoning about object identity rather than the shape of your data. The new PathObject API flips this: you describe a path, and the SDK handles the rest.
import * as Ably from 'ably';
import { LiveObjects, LiveMap, LiveCounter } from 'ably/liveobjects';
const client = new Ably.Realtime({
key: 'your-api-key',
plugins: { LiveObjects }
});
const channel = client.channels.get('game:room-42', {
modes: ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH']
});
const root = await channel.object.get();
await root.batch((ctx) => {
ctx.set('leaderboard', LiveMap.create({
alice: LiveCounter.create(0),
bob: LiveCounter.create(0),
}));
ctx.set('round', LiveCounter.create(1));
});
root.get('leaderboard').subscribe(({ object }) => {
renderLeaderboard(object.compact());
});
await root.get('leaderboard').get('alice').increment(10);
Subscriptions now observe paths rather than instances. If the underlying object is replaced, your subscription keeps working. No rewiring.
Object resets
One of the most requested features: reset an object to a clean state without tearing down and recreating the channel. Previously, the workaround was destroying the channel entirely, which forced clients to reconnect, reattach subscribers, re-establish presence, and race the teardown. Object resets remove all of that. Useful for a new game round, a cleared poll, or a reset config without losing connection state.
Reliable data expiry
State now expires reliably after 90 days by default. Previously this was best-effort. If you're building anything with ephemeral sessions or time-bounded content, you can depend on this rather than writing your own cleanup logic.
Revised object limits
The 100-object-per-channel limit has been revised to apply sensibly to top-level objects. Applications with nested structures can model data naturally without counting objects or designing workarounds to stay under the limit.
Easier map handling
.compact() and .compactJson() convert any LiveMap tree to a plain JavaScript object in one call, useful for rendering, serialization, or passing state to code that doesn't know about LiveObjects.
const state = root.get('leaderboard').compact();
// { alice: 120, bob: 95 }
What you can build
Live polls, leaderboards, collaborative forms, shared dashboards: any feature where multiple clients write to the same state and see each other's changes immediately. LiveObjects handles these well. But the use case we're focused on most right now is AI.
State sync for AI sessions
Agents need to share context. Not respond to a single request and forget it, but maintain a live picture of what's happening: what the user is working on, what tasks are in progress, what the session looks like.
The naive approach is polling or rebuilding session context on every request. That works until it doesn't. Agents diverge, state drifts, and the coordination layer becomes the thing your team maintains instead of the product.
LiveObjects is a cleaner mechanism. Multiple clients and agents read and write shared state simultaneously, conflicts are resolved automatically, and every subscriber sees updates the moment they land.
await root.get('session').batch((ctx) => {
ctx.set('current_task', 'Summarizing document');
ctx.set('progress', LiveCounter.create(0));
ctx.set('context', LiveMap.create({
page_title: 'Q3 Report',
selected_text: 'Revenue grew 24% YoY...'
}));
});
root.get('session').subscribe(({ object }) => {
updateAgentStatusPanel(object.compact());
});
If you're building AI applications and using Ably for token streaming, LiveObjects handles the state layer: what the model is working with, what it's doing, and what users can steer in real time.
Multiple SDKs, production-ready
LiveObjects is available in JavaScript today, with Swift and Java coming in the weeks that follow. Other SDKs are available now via inband objects and the REST API for platforms without a native client yet.
We're making a stability commitment for each SDK when it reaches the bar, not flipping a global flag while only one runtime is actually ready.
Get started
The LiveObjects plugin ships as part of the standard SDK.
npm install ably
The LiveObjects docs have quick-start guides and a migration guide if you're upgrading from the experimental API. If you're building AI applications, the AI Transport docs cover how LiveObjects fits into the state sync layer.




