Skip to main content
Status: idea. Nothing on this page is implemented. It is a design proposal for how vexa’s existing primitives compose into a multi-agent swarm — recorded now so implementation PRs have an invariant checklist to build against.
The whole idea in a paragraph: the agent workspace lives in object storage; redis only carries. Agents write through per file, checkpoint at turn boundaries, and refresh by manifest etag; conditional puts reject stale writes. A swarm is defined by the concierge but identified by its bundle prefix: the shared ledger is membership truth — join by writing your entry, leave by removing it; a child mounted on the bundle is a member. Fast messages ride existing unit streams; slow coordination goes through the ledger. Agents request agents via the dispatcher: standing grants auto-approve, everything else becomes a Telegram card, and approval mints the child’s token scope — silence means denied. Every unit carries its parent chain. The concierge is the single human channel and swarm manager (map, reaper, finalizer) but never a gatekeeper: it can misreport, not grant. A fixed-schedule heartbeat reports ground truth — runtime state joined with ledger checkpoints, wedged agents shown stale. No beat means the system is dead; the runtime watches the concierge itself.
A swarm is many agent units working the same problem. Everything below composes primitives that already exist — the runtime kernel (spawn + supervise), the redis unit:<id>:in / unit:<id>:out streams (streaming), the durable object-storage workspace, and the governance rule that untrusted proposals pass through a human gate. The swarm layer is protocol, not new infrastructure.

Swarm = shared ledger

A swarm is defined by the concierge (the human-comms agent, below) — it decides that a set of agents constitutes a swarm working one problem — but identified by shared state: the swarm id is the workspace bundle’s object-storage prefix, joining is writing your entry to the coordination ledger (etag puts make concurrent joins safe), leaving is removing it, and a spawned child mounted on the same bundle is in the swarm by construction. Definition lives in the manager, truth lives in the workspace — the concierge can never claim a swarm shape the ledger contradicts. There is no separate membership service or discovery protocol; the concierge owns the swarm map (live bundles, members, heartbeat schedule per swarm) and the three lifecycle rules:
  • Reaper — while assembling the heartbeat, the concierge joins ledger entries against runtime state; an entry with no live unit and no recent checkpoint is moved to a tombstone section (kept, not deleted, so a resuming agent finds its claim context).
  • Finalizer — last member out: the concierge promotes durable decisions into the knowledge base, archives the ledger, and stops that swarm’s heartbeat.
  • Cross-swarm access — touching another swarm’s bundle is a grant-shaped event through the dispatcher (scoped token for that prefix), not a casual read; otherwise the boundary is only a convention.

Shared workspace (durable plane)

The agent workspace lives in object storage, not redis — redis is the carrier, object storage the store, same split as everywhere else in the architecture.
RuleWhat it means
Write-throughevery local file write is async-put to the bundle; the local FS is just the working copy
Checkpoint at turn boundariesend of an agent turn = consistent snapshot; agents refresh (manifest etag → conditional GETs) at turn start
Conditional putswrites carry the etag they read; stale overwrites are rejected — this is the concurrency safety between siblings
Coordination ledgera HANDOVER-style file inside the bundle carries claims, holds, decisions, and open questions; agents read it before acting
The ledger doubles as each agent’s status line — the heartbeat report (below) is assembled from it, so there is no separate status API.

Swarm messaging (live plane)

Fast agent↔agent messages ride the existing unit streams — a message envelope on unit:<id>:in/:out, no new broker or protocol. Slow coordination (claims, decisions) goes through the workspace ledger and is seen at turn boundaries. Two planes, two latencies, both already deployed.

Agents requesting agents

Spawning is the runtime’s mechanism (P11); who may spawn what is policy, owned by a dispatcher:
  1. Request — parent emits spawn.request.v1 on its :out stream: child spec, task, workspace scope, budget/TTL, parent id. Parents never call the runtime directly.
  2. Policy — the dispatcher auto-approves inside a parent’s standing grant (e.g. N read-only children within budget) and escalates everything else to the human.
  3. Human gate — an approval card in Telegram: task summary, requester chain, cost, what the child can touch. Approve/deny via inline buttons carrying a signed request id.
  4. Grant — on approve, the dispatcher spawns via the runtime and mints the child a dispatch token whose scope is the approved scope. Approval is capability minting, not a yes/no — the human decision is enforced by the same fail-closed token machinery, not by the child’s good behavior.
  5. Lineage — every unit carries its parent chain: kill-tree revocation, budget inheritance, and an audit trail (“this agent exists because X approved request Y at 14:32”), recorded in the workspace ledger.
Timeouts fail closed: no answer in N minutes ⇒ denied, parent re-plans. Standing approvals are grant objects in the workspace — visible, revocable, not dispatcher special cases.

Concierge & heartbeat

One designated unit — the concierge — is the single human channel (the same Telegram bot that carries approval cards) and the swarm manager: it defines swarms, holds the swarm map, and runs the lifecycle rules above. Humans reply in-thread; the concierge routes human.message.v1 onto the target unit’s :in stream and confirms delivery. It remains manager, not gatekeeper — spawn approvals still flow through the dispatcher’s token minting, so a compromised concierge can misreport but cannot grant capabilities. The heartbeat is a dead-man’s switch: it fires on a fixed schedule, and the human contract is simply no message at the expected time ⇒ the system is down. Silence is the alarm — a dead system cannot send a failure notification, so none is designed.
  • The report is assembled from ground truth, not self-reports: runtime supervision state (alive/restarts) joined with each agent’s last workspace-ledger checkpoint. An alive-but-wedged agent shows as stale — the common failure, which liveness checks alone miss.
  • Pending approvals re-surface in every beat until resolved.
  • The runtime owns the timer and sends a bare “concierge down” if the concierge misses its slot, so the concierge is not a single point of silence.
🫀 swarm · 14:00 · all nominal
Units: 4 alive, 0 stale, 1 finished since last beat
• indexer — 3,120/8,400 files (13:58)
• doc-writer — drafting architecture page (13:55)
Approvals pending: 1 (spawn: doc-reviewer, waiting 12m)
Budget: 340k/1M tokens today

Message schemas

SchemaCarrierPurpose
spawn.request.v1unit:<id>:outparent asks for a child (spec, scope, budget, lineage)
spawn.grant.v1unit:<id>:indispatcher’s answer: granted (token scope) / denied / timeout
human.message.v1unit:<id>:inhuman → agent, routed by the concierge, logged to the ledger
Unknown message types are ignored, same forward-compatibility rule as acts.v1.