# Electric Agents vs arbe

Electric Agents is a durable agent runtime built on Electric SQL streams. Their unit of work is an **entity** — `<type>/<instance-id>` (e.g. `/horton/onboarding`) with a handler, typed state, 17 built-in collections (`runs`, `texts`, `textDeltas`, `toolCalls`, `reasoning`, `errors`, `inbox`, `wakes`, `manifests`, …), and a re-entrant `handler(ctx, wake)`. Their entity collapses three things arbe keeps separate (an actor, a transcript, a runtime) — buying URL-shaped uniformity at the cost of multi-writer threads not being a first-class primitive.

```
wakes      = server persists the trigger + grants an epoch to ONE claimer (serial per entity);
             writes flow through an idempotent producer keyed (entity, epoch); ack only after flush,
             so a crash before ack redelivers (at-least-once). Coalescing = an in-process pending-batch
             dequeue between handler runs (the eventCount/fromOffset/toOffset fields exist but stay 0);
             timing phases claimMs/preloadMs/handlerMs/httpMs/totalMs
observe    = replay-then-live; promise resolves AFTER backfill is reflected; replayWatermarks tracks resume
spawn      = create-only entity (throws if exists); .run/.text()/.send()/.status()
mkdb       = parent-owned shared collection; auto-generated CRUD tools — their multi-party transcript
```

Even adopting all their primitives, three things stay arbe's problem: **thread as a first-class addressable primitive** (Electric's nearest equivalent is `(parent, db_id)` you can't `send` to); **membership** ("who is in this thread" is meaningful — gates RLS, drives renderers, decides activation; Electric has no analogue); **turn-taking across writers** (Electric's `mkdb` punts this to the writers — we should pick a model and bake it in). These are upstream of any client-side observe API.

Three borrows ranked by leverage-to-cost. **Borrow B (small, near-term):** wrap the existing pi stream + GET endpoints in a typed `client.observe(thread(id))` whose promise resolves after backfill, paired with `signal.dispatch.{started,completed,skipped}` lifecycle parity. **Borrow C (medium, design-first):** adopt the claim/ack wake contract as the dispatch primitive. Today `createThreadDispatcher()` fires through `waitUntil` with no ack, so a worker evicted mid-turn drops the reaction silently and nothing redelivers (`reconcileStuckThread` only relabels status on a later read — it never re-runs the turn). The contract fixes exactly that: the chat entry already persisted on the stream *is* the trigger; one consumer claims it per thread (serial), runs the turn writing through an idempotent producer keyed (thread, trigger-offset), and acks the offset only after the writes flush — a dead worker redelivers instead of dropping. Coalescing is an in-process pending-batch dequeue between turns, not an offset range. The same wake→write-projection shape subsumes agent-to-agent RPC, human-in-the-loop, and swarm orchestration; its first concrete consumer is the detached-coding completion re-entry (child terminal = trigger, the brain's summary = handler — see [dispatch](../system/dispatch.md)). Lands on env-bound threads once membership/turn-taking are settled. Reference implementation: Electric's `process-wake.ts`. **Borrow A (large, defer):** unified `HandlerContext` for agent authoring (`ctx.events`, `ctx.firstWake`, `ctx.db.actions`, `ctx.spawn`, `ctx.observe`, `ctx.send`, `ctx.useAgent`) — comes along with C; not worth standalone unless agent-authoring friction becomes a felt pain.

Three CLI improvements worth stealing without adopting their substrate: broaden `arbe thread entries create <ref>` to accept room/agent refs (landed: room → primary thread; agent → get-or-create DM thread); add `--follow` / `-f` to `arbe thread entries list` (subsumes ad-hoc raw-stream tailing for non-debug consumers, pairs with `--from <offset>`); add `arbe ps` as the one cross-cutting "what's running right now" view (landed as `arbe house ps`: running/dispatching threads + recent failed/skipped dispatches, house-scoped). **Don't borrow:** type-uniform verbs across all nouns (loses the distinctions our nouns encode); human-pickable id slugs (threads are mostly auto-created, slugs would be aliasing overhead); a separate `arbe send` sugar verb (no characters saved if `entries create` accepts any conversational ref).

Verdict by use case: **do-dispatch-rpc daemon** = no-fit (the wake substrate is the right *inspiration* — adopt the contract on top of arbe's dispatch, don't migrate); **multi-turn coordination** = partial (parent-orchestrates-children fits; peer-collaborates-in-room is what we have); **shared scratchpad across rooms** = could borrow `mkdb` semantics on our existing stream substrate without adopting Electric whole; **room-as-entity** = no-fit (their model can't express it; ours already does). Hosting wholesale would mean rewriting authz/RLS/sync against Electric SQL and depending on their coordinator + runtime hosts on their release cadence — these borrows take none of that on.

**See:** [thinking/anthropic-managed-agents](./anthropic-managed-agents.md) (sister comparison; both systems converge on the same brain/hands/log decoupling), [system/dispatch](../system/dispatch.md), [threads](../system/threads.md).
