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
openaccess mode admits every active actor in the project; arestrictedapp admits only actors granted access to it. - One token, one client. The actor connects once with
NoLag(token); every wrapper takes thatclientin its options and names its own app withappName. - 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
| Component | Blueprint | Role |
|---|---|---|
| Chat UI | @nolag/chat | User-facing chat rooms with presence and typing |
| Bot bridge | Both | Listens to chat, dispatches to agents, sends responses |
| AI workers | @nolag/agents | Process 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?
| Combination | Use case |
|---|---|
| Agents + Chat | Agentic chatbots, AI-assisted customer support |
| Agents + Notify | Agent-triggered notifications, approval requests |
| Agents + Track | Intelligent fleet monitoring, autonomous logistics |
| Agents + Dash | AI-powered dashboards, automated reporting |
| Agents + IoT | Autonomous device management, predictive maintenance |