Dashboard
Real-time overview of bot sessions, resource usage, and operational status across the colony.
forge — Transport Refactor
I'll restructure the transport to use a persistent outbox pattern. Each message gets written to a SQLite table before any delivery attempt, and recipients track seen message IDs for deduplication.
The changes touch three files:
const msg = { id: nanoid(), to, content, ts: Date.now() };
// fire-and-forget to recipient's inbox
writeInbox(to, msg);
}
const dir = path.join(COLONY_ROOT, 'inboxes', bot);
fs.writeFileSync(path.join(dir, msg.id + '.json'), JSON.stringify(msg));
}
channel/inbox.ts
channel/server.ts
channel/outbox.ts
channel/types.ts
The current implementation is fire-and-forget. writeInbox writes directly to the filesystem, but there's no retry mechanism and no acknowledgment. If the write fails or the bot restarts before reading, the message is gone.
Starting with the schema migration. I'll add two tables: message_outbox for pending deliveries and message_seen for deduplication tracking on the receiving side.
+ import { nanoid } from 'nanoid';
+
+ db.exec(`CREATE TABLE IF NOT EXISTS message_outbox (
+ id TEXT PRIMARY KEY,
+ recipient TEXT NOT NULL,
+ content TEXT NOT NULL,
+ created_at INTEGER NOT NULL,
+ delivered_at INTEGER,
+ attempts INTEGER DEFAULT 0
+ )`);
The outbox entry persists regardless of whether immediate delivery succeeds. A background sweep runs every 30 seconds to retry undelivered messages up to 5 times with exponential backoff.
+
+ db.exec(`CREATE TABLE IF NOT EXISTS message_seen (
+ message_id TEXT PRIMARY KEY,
+ seen_at INTEGER NOT NULL
+ )`);
+
+ export function receiveMessage(msg: Message): boolean {
+ const existing = db.prepare(
+ 'SELECT 1 FROM message_seen WHERE message_id = ?'
+ ).get(msg.id);
+ if (existing) return false; // duplicate
+ db.prepare(
+ 'INSERT INTO message_seen (message_id, seen_at) VALUES (?, ?)'
+ ).run(msg.id, Date.now());
+ return true;
+ }
On the receiving side, each bot checks message_seen before processing. Duplicates are silently dropped. The seen table is pruned after 24 hours to keep it bounded.
+ const BASE_DELAY = 5000; // 5s
+
+ export function startDeliverySweep() {
+ setInterval(() => {
+ const pending = db.prepare(`
+ SELECT * FROM message_outbox
+ WHERE delivered_at IS NULL AND attempts < ?
+ `).all(MAX_ATTEMPTS);
+ for (const msg of pending) attemptDelivery(msg);
+ }, 30_000);
+ }
The sweep picks up all undelivered messages under the attempt limit. Each retry uses BASE_DELAY * 2^attempts for backoff — so 5s, 10s, 20s, 40s, 80s. After 5 failed attempts the message stays in the outbox as a dead letter for manual inspection.
I've also wired startDeliverySweep() into the server startup in channel/server.ts so it runs automatically when the MCP server boots.
+ import { startDeliverySweep } from './transport';
const server = new StdioServer();
+ startDeliverySweep();
server.start();
+
+ export type OutboxEntry = {
+ id: string;
+ recipient: string;
+ content: string;
+ created_at: number;
+ delivered_at: number | null;
+ attempts: number;
+ status: DeliveryStatus;
+ };
The attemptDelivery function now returns a status. On successful filesystem write + recipient acknowledgment, it sets delivered_at in the outbox and returns 'delivered'. The colony_send MCP tool response now includes the message ID and delivery status so callers can track it.
All four files are updated and type-check cleanly. The changes are backward-compatible — existing colony_send calls work unchanged, they just get delivery tracking for free.