None

celld, a new open-source project from Deno, reimplements Cloudflare's Durable Objects model on infrastructure you own — and it's a good lens for asking what should actually own an AI agent's state.

TL;DR

  • celld gives a named entity — an agent, a workflow, a chat room — a single owning node, its own SQLite database, and durability via LTX replication to object storage.
  • Object storage does more than back up state: conditional writes and epoch fencing tokens make it the coordination layer that decides who owns what.
  • The trade-off is real: no cross-cell transactions, no single-query view across entities, and a hot cell can't scale past one thread on one node.
  • The bigger idea outlives the project: for long-lived, stateful entities like AI agents, the right question may be "where is the owner of this state?" rather than "how do workers safely coordinate over it?"

For most of the last decade, cloud application architecture has pushed us towards the same basic model:

None

Application servers are disposable.

State lives somewhere else.

If two instances need to work on the same logical entity, they coordinate through databases, queues, locks, leases, optimistic concurrency or some combination of them.

That model works.

But it creates an interesting question:

What if we moved the computation to the state instead?

That is the idea I found most interesting while reading about celld, a new open-source project from Deno.

celld is an open-source implementation of the Cloudflare Workers and Durable Objects programming model, built to run on infrastructure you own. It combines lightweight V8 isolates, SQLite, LTX replication and object storage to create something that looks like a persistent, movable actor.

The project itself is very early.

The architecture, however, is worth understanding.

One object, one owner

Imagine an application containing an agent: agent-123.

In a conventional architecture, its state might be scattered across shared infrastructure:

None

Multiple workers may interact with the same agent.

You therefore need mechanisms to prevent two workers modifying its state incorrectly at the same time.

Now invert that model.

Give agent-123 a single logical owner:

None

Requests concerning that agent are routed to the runtime currently responsible for it.

Instead of asking:

How do multiple workers safely coordinate over this state?

we ask:

Where is the owner of this state?

That is a surprisingly important change.

Compute becomes part of the stateful entity

celld uses lightweight V8 isolates rather than giving every logical entity its own container or operating-system process.

Conceptually, one machine can look like this:

None

Each cell can run application code while owning its own persistent SQLite database.

This gives us an unusual combination: a single logical entity that bundles its compute, its memory and its database.

The database is no longer necessarily a remote service.

For a warm cell, accessing durable state can be a local SQLite operation.

That removes an entire network boundary from the normal execution path. Instead of application → network → PostgreSQL, we get V8 isolate → SQLite.

This is one of the architectural ideas behind Cloudflare Durable Objects too.

The state and the computation responsible for that state are deliberately colocated.

But where does durability come from?

A SQLite database on a VM is fast.

It is not particularly useful as distributed durable storage if that VM disappears.

This is where the architecture becomes more interesting.

celld combines SQLite with LTX, the transaction format used by the Litestream ecosystem.

The rough model is:

None

The local SQLite database acts as the hot operational store.

Changes are captured as LTX and replicated onward before a write is acknowledged — but "replicated onward" doesn't always mean the same thing. With multiple nodes available, celld acknowledges a write once a peer node has it durably on its own disk, and uploads to object storage afterward, off the critical path. Only a lone node with no peers waits on the object-store round trip itself:

None

Either way, celld won't acknowledge a write until it has that proof. This gives the system an important property, which celld's own docs describe as a recovery point objective of zero: once a durable write has been acknowledged, losing the machine holding the SQLite database should not mean losing that write.

The local database becomes reconstructible state — from a peer's copy, or from object storage if that's what stood behind the acknowledgment. The exact mechanics of that write path, and a couple of open questions the docs don't answer, are worth a post of their own.

Object storage becomes more than backup

This was probably the part of the architecture I found most interesting.

Object storage isn't being used solely for backup.

It participates in coordination.

Suppose we have three celld nodes — Node A, Node B and Node C — and one of them currently owns agent-123.

Somewhere the system needs an authoritative answer to: who owns agent-123?

Distributed systems traditionally introduce infrastructure such as etcd, Consul, ZooKeeper, or Raft-based metadata services to answer that question.

celld instead makes use of conditional object-store operations.

Conceptually, object storage might contain something equivalent to:

None

Node B can process the agent.

Now suppose Node B disappears.

Another node can eventually obtain ownership:

None

Node C can restore the SQLite state from the durable transaction history and restart the cell.

The epoch acts as a fencing token.

If Node B unexpectedly comes back and still believes it owns the cell, its older epoch can prevent it from continuing to make authoritative writes.

This is important because distributed consensus hasn't disappeared.

The system is effectively relying on the consistency guarantees provided by the object store.

The coordination problem has moved down a layer.

Follow one request

The design becomes easier to understand if we follow a single request.

Suppose an HTTP request arrives for agent-123.

It reaches Node A.

Node A first determines where agent-123 currently lives.

There are three interesting cases.

Case 1: Node A already owns the cell

The request can execute locally:

None

This is the ideal path.

Case 2: another node owns the cell

Perhaps Node B is authoritative.

Then Node A forwards the request:

None

The important point is that the system does not load agent-123 simultaneously on both nodes and then attempt to reconcile concurrent changes.

It routes the work to the owner.

Case 3: nobody currently owns it

Perhaps agent-123 has been idle long enough to be hibernated.

A node can claim ownership, restore its database and restart its runtime:

None

This leads to another important property of the architecture.

Logical entities don't have to remain resident in memory simply because they exist.

Hibernation changes the economics

Imagine a system containing one million agents.

That does not necessarily mean one million agents are active.

Perhaps only 2,000 are doing anything right now.

A container-per-agent architecture would be absurdly expensive.

A virtual actor architecture doesn't need to work that way.

Inactive cells can exist primarily as durable state:

None

while only the active working set consumes significant compute resources:

None

When activity arrives, the cell wakes.

When activity stops, it can eventually hibernate again.

So capacity is driven more by concurrently active entities than by total entities stored in the system.

That distinction becomes particularly interesting for agents.

This looks a lot like the actor model

None of this appears from nowhere.

The obvious family resemblance is to systems such as:

  • Microsoft Orleans
  • Akka
  • Dapr Actors
  • Cloudflare Durable Objects

A cell can be understood roughly as a persistent virtual actor.

The core actor idea is simple.

Instead of arbitrary pieces of code concurrently modifying shared state, give an actor exclusive ownership over its state and communicate with it through messages: message → actor → state.

This can remove entire categories of concurrency bugs.

celld adds an interesting emphasis, however.

The persistent database is very close to the actor itself:

None

The actor doesn't merely have a pointer to some rows in a shared database.

It has its own database.

This maps surprisingly well to AI agents

One reason I find this architecture particularly interesting is that an AI agent isn't naturally stateless.

Even a relatively simple production agent quickly accumulates things such as conversation history, tasks, tool calls, tool results, memory, checkpoints, approvals, timers, events, and connections.

We can pretend that an agent is a stateless request handler — POST /agent/run — but the runtime underneath often becomes increasingly stateful.

We then reconstruct the agent from shared infrastructure on every iteration:

None

A stateful cell suggests a different model:

None

Then external events are routed to that agent:

None

The agent becomes the serialization boundary.

Rather than different workers fighting over the same state, events concerning an agent are processed by the runtime that owns it.

This doesn't solve every problem in agent engineering.

But it removes some surprisingly awkward ones.

The same argument applies to long-running workflows — the Enterprise Integration Patterns Process Manager is really the same idea under a different name. A WorkflowInstance, with its id, state, current step, pending actions, timers and history, is just another named entity that many workers would otherwise fight over. Make the workflow itself the stateful unit and events for workflow-712 go to workflow-712, not to "some available worker." The partition key and the logical runtime become the same thing, which turns the question from "which worker can safely modify this workflow?" into "where is this workflow currently running?" — the same inversion, a second time.

But there is a major trade-off: global queries

There is a reason we like relational databases.

Suppose all agent state lives in PostgreSQL.

This is easy:

None

Now imagine 100,000 agents owning 100,000 SQLite databases.

There is no longer one database against which we can execute that query.

This is not a small implementation detail.

It changes the data architecture.

A state-per-cell design naturally pushes us towards something resembling CQRS:

None

The local cell database becomes authoritative transactional state.

Global views are projections.

For example, an agent could emit AgentWaitingForApproval, and a PostgreSQL projection could maintain agents_waiting_for_approval.

This gives us local consistency plus global eventual consistency.

For some systems that is elegant.

For others it would be unnecessary complexity.

Two more trade-offs worth naming

The query problem isn't the only cost of this model.

A cell runs on a single thread, owned by one node at a time. That's the whole point — it's what removes the concurrency bugs — but it also means a single very active entity can become a throughput ceiling that a conventional sharded database wouldn't have. A viral chat room, or an agent under heavy concurrent tool traffic, doesn't get more capacity just because the cluster has more nodes. It gets whatever one cell, on one node, can do.

The other cost is cross-cell transactions. If a workflow needs to atomically update two agents — or an agent and a workflow — at once, there's no shared transaction to reach for. Each cell is only consistent with itself. Coordinating across cells means falling back to sagas, compensating actions, or eventual consistency between them: the same techniques distributed systems have always used when a single ACID transaction isn't available.

Neither of these is a flaw exactly. They're the price of the inversion — you trade a shared, globally queryable, globally transactional database for isolated, cheaply hibernatable, lock-free entities. Which side of that trade you want depends entirely on your workload.

This is not self-hosted Cloudflare

It is also important not to overstate what celld currently represents.

Cloudflare Durable Objects come as part of a much larger managed platform.

Cloudflare handles enormous amounts of machinery around global routing, TLS, DDoS protection, deployment, networking, capacity, observability, and regional infrastructure.

A self-hosted runtime does not magically provide those properties.

You still need to operate the surrounding system.

So I would distinguish between Durable Objects — programming model + runtime + global infrastructure + managed operations — and something like celld, which is programming model + runtime.

That difference matters.

And celld itself is currently extremely early software.

This isn't a recommendation to redesign a production system around it tomorrow.

The interesting part is the architecture

None of the individual ingredients are particularly new.

Actors are old.

SQLite is old.

Object storage is established infrastructure.

Fencing tokens are established distributed-systems practice.

V8 isolates are well understood.

Hibernating inactive actors is not new either.

What makes the architecture interesting is the combination:

None

It creates a very small conceptual distributed system.

Instead of always reaching immediately for Kubernetes, PostgreSQL, Redis, Kafka, distributed locks, worker pools, and coordination services, there may be classes of applications where the core runtime starts to look closer to load balancer + stateful cells + object storage.

That is a compelling idea even if celld itself ultimately isn't the implementation we use.

Perhaps the better question is: what should own the state?

The most useful takeaway for me isn't:

Should I deploy celld?

It is:

What is the correct unit of state ownership in a distributed application?

For a traditional CRUD application, a central relational database may still be exactly the right answer.

But for systems dominated by long-lived autonomous entities — agents, workflows, games, collaborative rooms, devices, sessions — the answer may increasingly be: the entity itself.

An agent can own its state.

A workflow can own its state.

A conversation can own its state.

A project can own its state.

Then infrastructure becomes responsible for locating that entity, activating it, moving it and keeping its state durable.

That is a subtly different way of thinking about distributed applications.

We normally move data to compute:

None

Architectures such as Durable Objects and celld move us closer to:

None

For the increasingly stateful systems we are building around agents and long-running workflows, that inversion may turn out to be the most important idea in celld.

Related posts

Originally published at mickdelaney.com.