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/totalMsobserve = replay-then-live; promise resolves AFTER backfill is reflected; replayWatermarks tracks resumespawn = create-only entity (throws if exists); .run/.text()/.send()/.status()mkdb = parent-owned shared collection; auto-generated CRUD tools — their multi-party transcriptEven 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). 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 (sister comparison; both systems converge on the same brain/hands/log decoupling), system/dispatch, threads.