Blueprint Composition

Combine the agents Blueprint with other Blueprints to build powerful real-time applications.

How Composition Works

NoLag Blueprints are independent apps within the same project. Each Blueprint manages its own rooms and topics. Composition happens through cross-app actor access: one actor, with one token and one connection, can use several apps at the same time.

For example, an actor representing a chatbot can attach a @nolag/chat wrapper (for user-facing messaging) and a @nolag/agents wrapper (for AI coordination) to the same client. The actor bridges the two apps, forwarding user messages to the agent workflow and sending agent responses back to the chat.

Key concepts

  • Actors are created at the project level. An app in its default open access mode admits every active actor in the project; a restricted app admits only actors granted access to it.
  • One token, one client. The actor connects once with NoLag(token); every wrapper takes that client in its options and names its own app with appName.
  • Each wrapper keeps its own rooms and topics on the shared connection.
  • The bridge code listens on one wrapper and publishes on the other.

Example: Agentic Chatbot

A customer support chatbot that uses @nolag/chat for the user-facing interface and @nolag/agents for the AI backend. When a user sends a message, the bot dispatches it as a task to a customer-support agent, waits for the result, and posts the response back to the chat room.

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

// One actor, one connection, two apps
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()]);

// User-facing chat room (seeded by the chat blueprint)
const chatRoom = chat.joinRoom("general");

// Agent coordination room (seeded by the agents blueprint)
const agentRoom = agents.room("default-workflow");
const handoff = new Handoff(agentRoom);

// Publishers never receive their own messages, so this fires only for other users
chatRoom.on("message", async (msg) => {
  const result = await handoff.dispatch(
    "customer-support",
    {
      userId: msg.userId,
      text: msg.text,
      history: chatRoom.getMessages().map((m) => ({ from: m.username, text: m.text })),
    },
    { waitForResult: true, timeout: 30000 },
  );

  if (result && result.status === "success") {
    chatRoom.sendMessage(String(result.payload.response));
  }
});

Architecture

ComponentBlueprintRole
Chat UI@nolag/chatUser-facing chat rooms with presence and typing
Bot bridgeBothListens to chat, dispatches to agents, sends responses
AI workers@nolag/agentsProcess user queries with an LLM, return responses

Example: Monitored Delivery Fleet

A delivery fleet that uses @nolag/track for real-time GPS tracking and @nolag/agents for intelligent monitoring. @nolag/track evaluates geofences on the client for every location update it receives, so when a vehicle enters one, the monitor dispatches an analysis task and records the outcome on the Blackboard.

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

const client = NoLag(MONITOR_TOKEN);
const track = new NoLagTrack({ client, appName: TRACK_APP_SLUG, assetId: "fleet-monitor" });
const agents = new NoLagAgents({
  client,
  appName: AGENTS_APP_SLUG,
  agentId: "fleet-monitor",
  presence: { name: "fleet-monitor", role: "orchestrator" },
});

await client.connect();
await Promise.all([track.ready(), agents.ready()]);

// Tracking zone (seeded by the track blueprint) with a client-side geofence
const fleet = track.joinZone("fleet-zone");
fleet.addGeofence({
  id: "depot",
  shape: "circle",
  center: { lat: -37.8136, lng: 144.9631 },
  radiusMeters: 250,
});

const agentRoom = agents.room("default-workflow");
const handoff = new Handoff(agentRoom);
const blackboard = new Blackboard(agentRoom, agents.agentId);

// When a vehicle enters the geofence, trigger agent analysis
fleet.on("geofenceTriggered", async (event) => {
  if (event.type !== "enter") return;

  const result = await handoff.dispatch(
    "logistics",
    { assetId: event.assetId, geofenceId: event.geofenceId, point: event.point },
    { waitForResult: true, timeout: 30000 },
  );

  // Record the outcome on shared state (read-modify-write, last writer wins)
  if (result && result.status === "success") {
    const status = (blackboard.get("fleet-status") as Record<string, unknown> | undefined) ?? {};
    blackboard.set("fleet-status", { ...status, [event.assetId]: result.payload.status });
  }
});

Composition Patterns

Event bridge

The simplest pattern: listen for events on one Blueprint and trigger actions on another. The chatbot example above uses this: chat messages trigger agent tasks.

Shared state sync

Use the Blackboard pattern to maintain state that is derived from events in other Blueprints. The fleet example writes fleet status from tracking events.

Approval pipeline

Combine @nolag/agents (Approve pattern) with @nolag/notify. A bridge handles approve.onRequest() by sending the request as a notification; when a human answers it, the bridge calls respond("approved") or respond("rejected", reason) and the agent workflow continues.

Which Blueprints compose well?

CombinationUse case
Agents + ChatAgentic chatbots, AI-assisted customer support
Agents + NotifyAgent-triggered notifications, approval requests
Agents + TrackIntelligent fleet monitoring, autonomous logistics
Agents + DashAI-powered dashboards, automated reporting
Agents + IoTAutonomous device management, predictive maintenance