Getting Started with AI Agents

Build your first multi-agent workflow in minutes.

Prerequisites

  • A NoLag account (free tier available)
  • A project in the portal and a project API key (nlg_live_...) for the one-time setup step
  • Node.js 18+ (JavaScript) or Python 3.10+ (Python)

Step 1: Install

npm install @nolag/agents @nolag/js-sdk

The agents SDK is available for JavaScript/TypeScript (@nolag/agents) and Python (nolag-agents). Both provide the same six coordination patterns.

Step 2: Create an Agents App

Before you start. Create an app from the nolag-agents-sdk blueprint. That seeds the room this SDK expects (default-workflow, with the seven coordination topics) and the agent-activity lobby, and returns an app slug with a random suffix. That slug is the appName you pass to the wrapper. You can also do this in the portal: Apps, New App, pick the blueprint.

import { NoLagApi } from "@nolag/js-sdk";

const api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...
const app = await api.apps.create({ name: "My Agents", blueprintId: "nolag-agents-sdk" });
console.log(app.slug); // e.g. "my-agents-a3f9": this is your appName

const orchestrator = await api.actors.create({ name: "orchestrator", actorType: "orchestrator" });
const worker = await api.actors.create({ name: "worker-1", actorType: "agent" });
console.log(orchestrator.accessToken, worker.accessToken); // shown once; keep them

Rooms never exist implicitly. default-workflow is seeded by the blueprint; any other room you want agents to join has to be created first, in the portal or with api.rooms.create(app.appId, { name: "..." }).

Step 3: Create Actor Tokens

Each agent in your system connects as an actor. The setup block above creates two; you can also create them in the portal under Actors:

  1. Orchestrator (actorType: "orchestrator") dispatches tasks and coordinates the workflow
  2. Worker (actorType: "agent") receives and completes tasks

Only the agent and orchestrator actor types hold a persistent session on the broker. Copy the access token for each actor (at_live_...); it is shown once.

Step 4: Connect the Orchestrator

The orchestrator dispatches tasks and waits for results. Your app owns the core client; the wrapper attaches to it.

import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff } from "@nolag/agents";

const client = NoLag(ORCHESTRATOR_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");
const handoff = new Handoff(room);

// Dispatch a task to any connected worker advertising the "summarize" capability.
// dispatch() throws if no such worker is connected, and rejects on timeout.
const result = await handoff.dispatch(
  "summarize",
  { url: "https://example.com/article" },
  { waitForResult: true, timeout: 30000 },
);

if (result) {
  console.log("Result:", result.status, result.payload);
}

dispatch() looks at room presence first: if no connected agent lists the capability, it throws instead of publishing. Start the worker before the orchestrator, or pass allowNoWorkers: true (allow_no_workers=True) to publish anyway.

Step 5: Connect a Worker

Workers advertise their capabilities through presence and handle incoming tasks. Turn on load balancing on the client when you run more than one worker with the same capabilities: the tasks and tools topics then deliver each message to one member of the group instead of every member.

import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff } from "@nolag/agents";

// loadBalance: each task goes to one worker in the "summarizers" group
const client = NoLag(WORKER_TOKEN, { loadBalance: true, loadBalanceGroup: "summarizers" });
const agents = new NoLagAgents({
  client,
  appName: APP_SLUG,
  agentId: "worker-1",
  presence: { name: "worker-1", role: "agent", capabilities: ["summarize"] },
});
await client.connect();
await agents.ready();

const room = agents.room("default-workflow");
const handoff = new Handoff(room);

// Handle incoming tasks whose capability matches
handoff.onTask(["summarize"], async (task, respond) => {
  console.log("Received task:", task.taskId, task.capability);

  const summary = await summarizeUrl(task.payload.url as string);
  respond("success", { summary });
});

Every worker in the room receives every task on tasks; onTask drops the ones whose capability it does not handle. To have the broker do that filtering instead, see server-side routing.

Step 6: Monitor with Observe

Observe is an event stream that carries only what agents emit. Nothing is emitted automatically, so decide what you want to see and emit it from the worker. A monitor then subscribes to the stream:

import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff, Observe } from "@nolag/agents";

// In the worker: emit events at the points you want to see
const workerRoom = agents.room("default-workflow");
const workerObserve = new Observe(workerRoom, agents.agentId);
new Handoff(workerRoom).onTask(["summarize"], async (task, respond) => {
  workerObserve.emit("task:started", { taskId: task.taskId, capability: task.capability });
  const summary = await summarizeUrl(task.payload.url as string);
  respond("success", { summary });
  workerObserve.emit("task:completed", { taskId: task.taskId });
});

// In a monitor process: watch the stream
const client = NoLag(MONITOR_TOKEN);
const monitor = new NoLagAgents({
  client,
  appName: APP_SLUG,
  agentId: "monitor",
  presence: { name: "monitor", role: "observer" },
});
await client.connect();
await monitor.ready();

const observe = new Observe(monitor.room("default-workflow"), monitor.agentId);

// Watch all events in the workflow
observe.on((event) => {
  console.log(`[${event.category}] ${event.severity} from ${event.emittedBy}: ${JSON.stringify(event.payload)}`);
});

Step 7: View in the Agent Dashboard

Open the Agent Dashboard in the portal to see your agents in real time. It shows a topology graph of connected agents (from the agent-activity lobby), task counters for what it sees while open (dispatched, in progress, completed, failed), the Event Stream from Observe, the Blackboard State, the Approval Queue of pending Approve requests, and a panel per agent with its role, status, and capabilities. A Tool Registry panel is present but stays empty: tool handlers are local to the agent that registers them and are not published anywhere.

Multi-Tenant Setup

If you are building a multi-tenant application where each customer needs isolated agent workflows, use Access Scopes. When an actor is assigned to a scope, the broker rewrites every topic it touches from app/room/topic to app/scope/room/topic. That includes the coordination topics, so tenant Acme's tasks travel on my-agents-a3f9/acme/default-workflow/tasks and its state on my-agents-a3f9/acme/default-workflow/state.

This means you deploy one agents app and create scoped actors for each tenant. An orchestrator in tenant A cannot dispatch tasks to workers in tenant B. The isolation is enforced by the broker with no changes to your agent code; the room name and topics you use in the SDK stay the same.

See the Multi-Tenant Patterns guide for detailed implementation examples, including per-tenant agent deployments.

Next Steps

  • Patterns - deep dive on all six coordination patterns
  • Composition - combine agents with chat, notifications, and more
  • Tag Vocabulary - learn how capabilities, priority, and tags work
  • Examples - canonical recipes for common agent workflows