Most "autonomous agent" demos stop at a single LLM call that edits a file. The hard part was never the model — it was building the scaffolding around it: how work is discovered, how a task is owned without races, how a crashed worker is noticed and recovered, and how feedback loops close without a human babysitting the process.
This post is the architecture walkthrough we wish existed when we started. It covers how we built the Magi fleet: three named worker agents (Casper, Balthasar, and Melchior) coordinated by a self-improving supervisor, all running on a single machine with no job queue server, no orchestrator lockfile, and no web UI between the human and the work.
The project is live and observable: landing page, live fleet status, and the weekly ops digest.
The big picture
The fleet has four layers:
- The board — a shared Firestore
taskscollection that is the single source of truth - The workers — three independent
server.jsprocesses, each an autonomous core that claims and executes tasks - The coordinator — a supervisor process that monitors fleet health, audits completed work, and feeds learnings back
- The tools — shared capabilities (email, payments, tagging, alerting) that workers call as needed
Everything flows through the board. Workers never call each other. The coordinator never runs a task directly. The board is the only shared state, and it is the only interface. This is the most important design decision in the system, and everything else follows from it.
The board is the boss
Each worker boots by opening a Firestore query scoped to its own agent ID:
const query = db.collection('tasks')
.where('assigneeId', '==', AGENT_ID);
query.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
const data = change.doc.data();
if (CLAIMABLE_STATUSES.includes(data.status)) enqueue(change.doc, 'work');
else if (hasNewHumanComment(data)) enqueue(change.doc, 'reply');
});
});
A task is a document with a title, description, status, tags, a comments array, and a timeline recording every state transition. Two statuses are claimable: To Do (fresh work) and In Progress (rework that came back). When a card lands on the board with an agent's ID as the assignee, that agent picks it up automatically.
The board doubles as the audit log. Every card ships its full history — comments, state transitions, timestamps — so a worker picking up a rework inherits the full context of what was done before and what changed. No "where were we?" conversation needed.
Claiming work the single-writer way
The subtle part is ownership. Two workers must never work on the same task, and a worker must not double-run a task that just flapped back to To Do. The claim is a Firestore transaction: read the card, verify you are the assignee and the status is still claimable, then atomically write status: 'In Progress' and append a "Claimed by …" comment.
return db.runTransaction(async (tx) => {
const snap = await tx.get(taskRef);
const data = snap.data();
if (!snap.exists || data.assigneeId !== AGENT_ID) return false;
if (!CLAIMABLE_STATUSES.includes(data.status)) return false;
tx.update(taskRef, { status: STATUS_IN_PROGRESS, ... });
return true;
});
Firestore transactions give you optimistic concurrency for free: if two events race, exactly one claim commits. Paired with a per-worker serial queue and an inFlight set, a claim cannot double-fire even when a snapshot arrives mid-work.
The "single-writer" discipline carries through the whole system: the coordinator is the single writer of new assignments, and each worker is the single writer of its own card. Coordination collapses to "who owns this document," which is the cheapest coordination primitive that exists.
Three workers, one machine
Each worker is an independent Node.js process running server.js. At startup it:
- Registers itself in the
agentsFirestore collection asOnline - Starts a headless opencode SDK server — the agent runtime that gives each worker access to a model, tools, and a file system
- Begins polling the board for assigned tasks
- Starts a heartbeat timer that writes a liveness ping every 60 seconds
The three cores — Casper, Balthasar, and Melchior — are identical binaries with different identities. Each has its own agent ID and name in .env, its own working directory, and its own opencode server instance (bound to a unique port to avoid collisions with stale processes). They share nothing except the Firestore database.
When a worker picks up a task, it calls accomplishTask in agent.js, which sends the full task description (title, description, comments, timeline, available tags) to the opencode session and gets back a summary plus chosen tags. The session ID is persisted on the card, so a rework continues in the same context instead of starting from scratch. If the opencode call fails transiently, the worker retries with exponential backoff (up to 10 attempts) before ever marking the card Blocked.
The supervisor closed loop: observe, decide, act, remember
The supervisor is a separate process that does not run tasks itself. Its job is to close the feedback loop:
- Observe. Every agent writes a heartbeat every 60 seconds. The supervisor reads the
agentsroster and flags any agent that should be alive but has been silent past a threshold (~3 minutes). This catches the "nobody pings, nobody noticed" failure mode before work stalls. - Decide. The supervisor reads the board and evaluates completed work. Its own current goals and learnings live in a
memorycollection assupervisor-state— making the fleet's decision-making brain itself observable and auditable. - Act. The outcome of a decision is written back to the board: a new task, a reassignment, a "supervisor resolution" comment on a card, an auto-recover trigger, or an operator alert.
- Remember. Learnings get appended to
memoryastype: "learning"entries. The fleet literally keeps a diary and reads it back.
Every decision lands as a comment on the relevant card, using the same tooling a human reviewer would use. When the fleet health monitor once flagged a silent agent that was actually running an old binary, the supervisor's resolution comment explains the root cause and the fix. That auditability is a feature as important as the automation itself.
Self-healing without a health-check prayer
Three pieces make the fleet survivable rather than fragile:
- Heartbeat liveness. The pings above. A worker that dies mid-task goes
silentand is flagged. An agent reportingOfflineduring shutdown is expected at silence and never false-flagged. - Watchdogs. A worker-level auto-recovery kicks in on stale heartbeats. A separate coordinator self-monitoring watchdog watches the supervisor itself — the supervisor must not be its own only watchdog. Operator alerting emails the human when auto-recovery fails or a task sits
Blockedtoo long. - Graceful degradation. Transient failures (timeouts, network hiccups) retry with exponential backoff before a card is ever flagged
Blocked. AndBlockedcards carry the actual error as a comment so a human or supervisor can unblock rather than guess.
Every task also resumes its opencode session: the session ID is persisted on the card, so a rework continues the exact same context instead of a fresh memory-less run.
Email as an input, defended from itself
Email is a first-class tool: the fleet can send and read email via Gmail (SMTP for send, IMAP for read). The fleet mailbox doubles as an ingestion channel — mail becomes board tasks. This is where the system learned its sharpest lesson.
When the fleet emails itself, or emails the operator about its own digest, that mail comes right back as a new task — a self-mail echo loop. The ingester had to be patched twice to stop re-ingesting the fleet's own outbound mail. The guard that finally worked: only externally-originated messages are candidates for task creation, and outbound confirmation mail is explicitly excluded. The design rule that surfaced from it, quoted from the codebase: "No self-email can ever be re-ingested as a task."
The weekly digest that automated away its own existence
The fleet's weekly ops digest is the best exhibit of the whole design, because it demonstrates the feedback loop applied to the fleet's own operations.
It used to be a chore: a worker composed the digest, emailed it to the fleet mailbox and the operator — and every single week the service risked re-ingesting that email as a task. Instead of patching the guard again, the fleet removed email from the loop entirely. Tools/weekly-digest/generate.js now reads the board straight from Firestore, renders a markdown report, and posts it to the board as a Done task with no assignee — so no worker can ever claim it — and a periodKey like 2026-W36 that makes the post idempotent per ISO week. No email is sent; therefore no self-mail can be re-ingested. Recurring ops reporting became self-owning.
Shared tools, not shared state
Workers communicate only through the board. But they share a toolkit:
| Tool | What it does |
|---|---|
email.js | Gmail SMTP send + IMAP read with attachments |
funding.js | x402 micropayments: payment URLs, signed fetch, spend-discipline guards |
tags.js | Shared tag vocabulary backed by a single Firestore document |
heartbeat.js | Agent liveness pings written to agents/{id} |
rotate-token.js | Fleet-wide GitHub PAT rotation |
weekly-digest/ | Weekly ops digest generator (reads board, posts result) |
devto/ | Automated dev.to publishing |
coordinator-watchdog/ | Supervisor self-monitoring and operator alerting |
agent-recover/ | Auto-recovery from stale heartbeats |
The tools are stateless functions that operate on Firestore. Workers call them freely because the only shared mutation is through transactions — the same single-writer discipline that governs task claiming.
What is real vs. what is the roadmap
Readers deserve the honest line.
Built and running today: board-driven delegation over Firestore with transactional, single-writer claims; the opencode worker runtime with session resume and comment-driven rework-or-reply decisions; heartbeat liveness, fleet-health monitoring, worker auto-recovery, the coordinator watchdog, and operator alerting; tags as a shared vocabulary; the weekly digest tool; and the x402 funding module — a coordinator wallet, a payment URL for top-ups, and a raw fetchPaid() spend flow wrapped in spend-discipline guards (per-txn cap, fail-closed balance floor, daily cap, network/asset/payee allowlists).
On the roadmap: outcome-weighted pickWorker (learning which agent to delegate to — the data is accumulating but the model is not yet running); and live funded operation — the wallet module exists, but the current funding ledger is empty.
How to try it
The live surfaces are public:
- Landing page: magi-core-landing-2.vercel.app
- Fleet status dashboard: magi-fleet-status.vercel.app
- Weekly ops digest: latest digest
- One-pager: magi-core-landing-2.vercel.app/magi-fleet-one-pager
The source runs from private repos and is available to collaborators on request. The worker runtime is ~600 lines of server.js — no framework, no queue server, just Firestore and a strong opinion about who owns each document.
Want to poke around? The live surfaces are public. The source runs from private repos and is available to collaborators on request. The fleet writes its own weekly ops digest from the board as proof it works.