---
title: Agent Examples
description: "Canonical recipes for common agent workflows: agentic chatbot, autonomous workflow, copilot, compliance, tool-augmented agent, and monitoring."
---

# Examples

Canonical recipes for common agent workflows. Each example shows a complete pattern you can adapt.

Unless an example shows its own setup, it assumes a connected wrapper and room:

```typescript [Setup]
import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents } from "@nolag/agents";

const client = NoLag(ACTOR_TOKEN);
const agents = new NoLagAgents({
  client,
  appName: APP_SLUG, // the slug returned when you created the app
  agentId: "orchestrator",
  presence: { name: "orchestrator", role: "orchestrator" },
});
await client.connect();
await agents.ready();

const room = agents.room("default-workflow");
```

## Agentic Chatbot

A customer support chatbot that uses `@nolag/chat` for the user interface and `@nolag/agents` for AI-powered responses. Both wrappers share one client. User messages are dispatched as tasks to a support agent and the answer is sent back to the chat room.

```typescript [TypeScript]
import { NoLag } from "@nolag/js-sdk";
import { NoLagChat } from "@nolag/chat";
import { NoLagAgents, Handoff } from "@nolag/agents";

const client = NoLag(BOT_TOKEN);
const chat = new NoLagChat({ client, appName: CHAT_APP_SLUG, username: "support-bot" });
const agents = new NoLagAgents({
  client,
  appName: AGENTS_APP_SLUG,
  agentId: "support-bridge",
  presence: { name: "support-bridge", role: "orchestrator" },
});
await client.connect();
await Promise.all([chat.ready(), agents.ready()]);

const chatRoom = chat.joinRoom("general");
const handoff = new Handoff(agents.room("default-workflow"));

// Fires for other users' messages only; publishers never receive their own
chatRoom.on("message", async (msg) => {
  const result = await handoff.dispatch(
    "customer-support",
    { userId: msg.userId, text: msg.text, history: chatRoom.getMessages().map((m) => m.text) },
    { waitForResult: true, timeout: 30000 },
  );
  if (result && result.status === "success") {
    chatRoom.sendMessage(String(result.payload.response));
  }
});
```

**Patterns used:** Handoff (task dispatch), composition (one actor on two apps)

## Autonomous Workflow

A multi-step pipeline where the orchestrator advances through a plan. Each step's output feeds the next step, and progress is recorded on the Blackboard so every agent (and the portal dashboard) can see where the pipeline is.

```typescript [TypeScript]
import { Handoff, Blackboard } from "@nolag/agents";

const handoff = new Handoff(room);
const blackboard = new Blackboard(room, agents.agentId);

const steps = ["research", "analyze", "draft", "review"];
const results: Record<string, unknown> = {};
blackboard.set("plan", { steps, current: 0, results });

let input: Record<string, unknown> = { topic: "AI agent coordination" };

for (let i = 0; i < steps.length; i++) {
  const step = steps[i];

  // Each step is a capability; dispatch() throws if no worker advertises it
  const result = await handoff.dispatch(step, input, { waitForResult: true, timeout: 120000 });

  if (!result || result.status === "error") {
    blackboard.set("plan", { steps, current: i, results, failed: step, error: result ? result.error : undefined });
    break;
  }

  results[step] = result.payload;
  blackboard.set("plan", { steps, current: i + 1, results });
  input = result.payload; // this step's output is the next step's input
}
```

**Patterns used:** Handoff (task dispatch), Blackboard (workflow state)

## Copilot Pattern

An AI copilot that watches editor state via the Blackboard and returns suggestions the same way. The editor writes its state to one key, a copilot process reacts to changes by dispatching a task to a `code` worker, and writes the suggestion to another key the editor watches.

```typescript [Editor]
import { Blackboard } from "@nolag/agents";

const blackboard = new Blackboard(room, agents.agentId);

// Publish editor state as the user types
editor.on("change", (content: string) => {
  blackboard.set("editor-state", {
    content,
    cursor: editor.getCursorPosition(),
    language: editor.getLanguage(),
  });
});

// Show suggestions as they arrive
blackboard.onChange("suggestion", (envelope) => {
  editor.showSuggestion(envelope.value);
});
```
```typescript [Copilot]
import { Handoff, Blackboard } from "@nolag/agents";

const handoff = new Handoff(room);
const blackboard = new Blackboard(room, agents.agentId);

// React to editor changes with a suggestion task
blackboard.onChange("editor-state", async (envelope) => {
  const result = await handoff.dispatch(
    "code",
    envelope.value as Record<string, unknown>,
    { waitForResult: true, timeout: 10000 },
  );
  if (result && result.status === "success") {
    blackboard.set("suggestion", result.payload.suggestion);
  }
});
```

**Patterns used:** Blackboard (shared state), Handoff (suggestion generation)

## Compliance / Approval Workflow

An agent generates a report and requests human approval before publishing. The human can approve, reject, or defer, with an optional reason. Each outcome is emitted with Observe so the flow is auditable in the Event Stream.

```typescript [Agent]
import { Approve, Observe } from "@nolag/agents";

const approve = new Approve(room, agents.agentId);
const observe = new Observe(room, agents.agentId);

const draft = await generateReport(data);

try {
  const approval = await approve.request(
    "publish-report",
    { title: "Q3 earnings report", author: "finance-agent", content: draft },
    { urgency: "high", timeout: 3600000 }, // 1 hour, then the promise rejects
  );

  if (approval.decision === "approved") {
    await publishReport(draft);
    observe.emit("report:published", { approvedBy: approval.respondedBy });
  } else {
    observe.emit("report:rejected", { decision: approval.decision, reason: approval.reason }, "warning");
  }
} catch (err) {
  observe.emit("report:unanswered", { message: String(err) }, "error");
}
```
```typescript [Approver]
import { Approve } from "@nolag/agents";

const approve = new Approve(room, agents.agentId);

// A human-facing app renders the request and forwards the decision
approve.onRequest((request, respond) => {
  showApprovalDialog(request.action, request.context, request.urgency, (approved: boolean, reason?: string) => {
    respond(approved ? "approved" : "rejected", reason);
  });
});
```

**Patterns used:** Approve (human-in-the-loop), Observe (audit trail)

## Tool-Augmented Agent

A tool server registers handlers for web search and database queries. A worker invokes them by name while handling a task. Handlers are local to the server that registered them; callers need to know the tool names.

```typescript [Tool server]
import { Tools } from "@nolag/agents";

const tools = new Tools(room, agents.agentId);

tools.register("web-search", async (args) => {
  return { results: await searchWeb(args.query as string) };
});

tools.register("database-query", async (args) => {
  return { rows: await db.query(args.sql as string, args.params as unknown[]) };
});
```
```typescript [Worker]
import { Handoff, Tools } from "@nolag/agents";

const handoff = new Handoff(room);
const tools = new Tools(room, agents.agentId);

handoff.onTask(["answer"], async (task, respond) => {
  const search = await tools.invoke("web-search", { query: task.payload.question });
  const metrics = await tools.invoke("database-query", {
    sql: "SELECT * FROM metrics WHERE topic = $1",
    params: [task.payload.topic],
  });

  if (search.status === "error" || metrics.status === "error") {
    respond("error", {}, { code: "tool_failed", message: search.error?.message ?? metrics.error?.message ?? "" });
    return;
  }

  const answer = await llm.complete({
    prompt: task.payload.question,
    context: { search: search.result, metrics: metrics.result },
  });

  respond("success", { answer });
});
```

**Patterns used:** Tools (remote invocation), Handoff (task dispatch)

## Monitoring Dashboard

A monitoring agent that counts tasks, collects the Observe event stream, and alerts operations when a worker reports a failure or an agent leaves the room. Handoff emits no events of its own, so the worker emits the timing events the monitor aggregates.

```typescript [Worker]
import { Handoff, Observe } from "@nolag/agents";

const handoff = new Handoff(room);
const observe = new Observe(room, agents.agentId);

handoff.onTask(["summarize"], async (task, respond) => {
  const started = Date.now();
  try {
    const summary = await summarize(task.payload);
    respond("success", { summary });
    observe.emit("task:completed", { taskId: task.taskId, duration: Date.now() - started });
  } catch (err) {
    respond("error", {}, { code: "summarize_failed", message: String(err) });
    observe.emit("task:failed", { taskId: task.taskId, message: String(err) }, "error");
  }
});
```
```typescript [Monitor]
import { Observe } from "@nolag/agents";

const observe = new Observe(room, agents.agentId);
const metrics = { tasks: 0, completed: 0, failed: 0, avgDuration: 0 };
const durations: number[] = [];

// Every task envelope on the room reaches the monitor
room.on("task", () => metrics.tasks++);

observe.on((event) => {
  metrics.completed++;
  durations.push(Number(event.payload.duration));
  metrics.avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
}, { category: "task:completed" });

observe.on((event) => {
  metrics.failed++;
  alertOps("Task failed", event.payload);
}, { category: "task:failed" });

// Agent health from room presence
room.on("presenceJoin", (actorId, presence) => {
  console.log("Agent online:", presence.name, presence.capabilities);
});
room.on("presenceLeave", (actorId) => {
  alertOps("Agent disconnected", { actorId, remaining: room.getConnectedAgents().length });
});
```

**Patterns used:** Observe (event stream), Presence (agent health)
