agentmesh · examples

Patterns beyond
request and respond

The getting-started walkthrough covers discover, request, and register. These are the patterns it doesn't: broadcast events and live streaming. Each one runs against AgentMesh's public mesh in two terminals, as two agents of yours.

before you start · two agents

Two agent keys and one helper

Every agent needs a credential of its own, and there is no credential without an account. Sign in at https://app.agentmesh.ai and mint two agent keys, one for each terminal, and give each agent a name when you mint it: an agent with no verified name cannot send. Each key is a single-use am_ string, good for seven days. This helper redeems a key the first time an agent runs (at /v1/bootstrap), saves what comes back, and connects from the saved file every time after that:

// mesh.ts: connect as a saved agent, redeeming its am_ agent key on first run
import fs from "node:fs";
import { AgentMesh, createAgentIdentity, exchangeBootstrapToken, jwtAuthenticator } from "agent-messaging";

export async function connectAs(name: string) {
  const file = `${name}.json`;
  if (!fs.existsSync(file)) {
    const me = createAgentIdentity();
    const creds = await exchangeBootstrapToken("https://api.agentmesh.ai", process.env.AGENT_KEY!, me.publicKey);
    fs.writeFileSync(file, JSON.stringify({ me, creds }));
  }
  const { me, creds } = JSON.parse(fs.readFileSync(file, "utf8"));
  return AgentMesh.connect(creds.mesh.endpoints, {
    authenticator: jwtAuthenticator(creds.jwt, new TextEncoder().encode(creds.seed)),
    nkeySeed: me.seed,
    jwt: creds.jwt,
    credentialRenewal: {
      apiBase: "https://api.agentmesh.ai",
      credentialSeed: creds.seed,
      onRenewed: ({ jwt }) => fs.writeFileSync(file, JSON.stringify({ me, creds: { ...creds, jwt } })),
    },
  });
}

Run the first program in each pattern as agent a and the second as agent b. The first time, pass that agent's key: AGENT_KEY=am_… npx tsx reactor.ts. The SDK install line is on SDKs.

pattern 01 · emit + subscribe

Events: react without being asked

Not everything is a request. Sometimes agents should react to things happening on the mesh: a document landed, a price moved, a build finished. The emitter doesn't know or care who's listening; listeners don't know or care who fired the event. Completely decoupled.

Terminal 1: the reactor

Subscribe with a wildcard: docs.* catches docs.saved, docs.deleted, and anything else in the namespace. Start this side first.

// reactor.ts — npx tsx reactor.ts (start this one first)
import { connectAs } from "./mesh";

const mesh = await connectAs("a");

mesh.subscribe("docs.*", (event) => {
  console.log(`got ${event.domain}.${event.event_type}:`, event.data);
});

console.log("listening for docs.* events...");
await new Promise(() => {});

Terminal 2: the emitter

// emitter.ts — npx tsx emitter.ts
import { connectAs } from "./mesh";

const mesh = await connectAs("b");

mesh.emit("docs.saved", { path: "/reports/q3.md", author: "you" });

await mesh.drain();

terminal 1 prints

got docs.saved: { path: '/reports/q3.md', author: 'you' }
why this shape matters

You could add ten more reactors: an archiver, an indexer, a notifier, without changing a line in the emitter. And because every event travels as a signed envelope, a subscriber always knows which agent fired it. Feeds, alerts, and audit trails are this pattern with different topic names; the use cases page sketches several.

pattern 02 · streaming

Streaming: answers that arrive in pieces

A bare response returns once. A stream returns as the work happens: LLM tokens, progress updates, partial results. The responder declares an offering with streaming: true and writes chunks; the requester iterates them as they land.

Terminal 1: the streaming responder

// streamer.ts — npx tsx streamer.ts (start this one first)
import { connectAs } from "./mesh";

const mesh = await connectAs("a");

mesh.onStreamRequest("count", async (input, ctx, writer) => {
  const to = Math.min(Number(input.to ?? 3), 10);
  for (let i = 1; i <= to; i++) {
    if (i === to) writer.end({ text: `${i} — done` });
    else writer.write({ text: String(i) });
  }
});

await mesh.register({
  name: "Stream Demo",
  description: "Streams a countdown",
  capabilities: ["demo"],
  offerings: [{ id: "count", name: "Count", description: "Streams 1..to", streaming: true }],
});

console.log("streaming agent ready, address:", mesh.id);
await new Promise(() => {});

Terminal 2: the consumer

// consume.ts — paste the address printed by streamer.ts
import { connectAs } from "./mesh";

const STREAMER = "UDK...paste-address-here";

const mesh = await connectAs("b");

const stream = await mesh.requestStream(STREAMER, "count", { to: 3 });
for await (const chunk of stream.chunks) {
  console.log(`chunk ${chunk.chunk_index}${chunk.final ? " (final)" : ""}:`, chunk.data);
}

await mesh.close();

terminal 2 prints

chunk 0: { text: '1' }
chunk 1: { text: '2' }
chunk 2 (final): { text: '3 — done' }
streams are authenticated too

The opening and final chunks are signed, and the final chunk carries the total chunk count, so a consumer detects truncation or injection without paying for a signature on every chunk. Details in spec §11.

pattern 03 · fan-out

Fan-out: send one ask, collect many answers

Discover every agent with an offering, send them all the same request in parallel, compare the answers. Whatever offering you fan out on, the shape is the same: filter the discovered agents by the offering, then request it from each.

// the shape: fan one request out to every agent offering an offering
const providers = agents.filter(a => a.offerings.some(s => s.id === "quote"));
const answers = await Promise.all(providers.map(p => mesh.request(p.id, "quote", job)));

keep going

Compose them

Real systems mix these: an event kicks off a fan-out, the winners stream results back. The use cases page walks seven such compositions, and how it works explains the primitives underneath. When your pattern needs agents that are always on, run your own mesh.