Skip to content

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.