Quickstart
This sends work from a parent agent to a child agent, settles it, and delivers
the reply. The outbox is a Map, and post prints the reply instead of sending
it anywhere.
import { BirdDog, type BirdDogMail } from "@fungi.computer/bird-dog";
const parent = { kind: "agent", agentId: "parent", sessionId: "parent-session",} as const;const child = { kind: "agent", agentId: "child", sessionId: "child-session",} as const;
const rows = new Map<string, string>();const room = await BirdDog.make({ outbox: { put: async (key, value) => { rows.set(key, value); }, read: async (key) => rows.get(key), list: async (limit, afterKey) => [...rows.entries()] .sort(([a], [b]) => a.localeCompare(b)) .filter(([key]) => afterKey === undefined || key > afterKey) .slice(0, limit) .map(([key, value]) => ({ key, value })), delete: async (key) => { rows.delete(key); }, }, post: async (mail: BirdDogMail) => { console.log(`to ${mail.to.agentId}:\n${mail.content}`); return true; // the recipient accepted it }, scheduleWake: async (at) => { console.log(`wake me at ${at}`); }, now: () => Date.now(),});
// 1. Turn accepted work into a Watchdog job. Hand this to Watchdog's ingest.const job = BirdDog.agentJob({ work: { jobId: "job-1", sessionId: child.sessionId, commandFactId: "command-1", }, mail: { key: "delegation-1", to: child, from: parent, replyTo: [parent], content: "Find out why the build is red", createdAt: Date.now(), },});
// 2. Your Watchdog executor decodes the payload and runs the child's work.const payload = BirdDog.decodeAgentJob(job);console.log(payload.work.commandFactId); // "command-1"
// 3. When Watchdog settles the job, queue the replies, then deliver them.await room.fanOut({ job: { ...job, state: "settled", outcome: "completed" }, content: "A test fixture was missing. Fixed it.",});await room.drainOutbox();In a real host, the settled job comes from Watchdog’s tick() result and
drainOutbox() runs from the wake you scheduled.