@nolag/agents 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# @nolag/agents
|
|
2
|
+
|
|
3
|
+
Multi-agent coordination SDK for [NoLag](https://nolag.app) — dispatch tasks, share state, observe decisions, gate actions with human approval, and invoke remote tools across connected agents.
|
|
4
|
+
|
|
5
|
+
## How It Works with NoLag
|
|
6
|
+
|
|
7
|
+
NoLag is a real-time messaging platform that handles WebSocket connections, message routing, persistence, and scaling. This SDK wraps the low-level [@nolag/js-sdk](https://www.npmjs.com/package/@nolag/js-sdk) and gives you a purpose-built multi-agent coordination API — task handoff, shared blackboard state, tool invocation, approval gates, and observability — without needing to manage topics or subscriptions yourself.
|
|
8
|
+
|
|
9
|
+
### Getting Your Token
|
|
10
|
+
|
|
11
|
+
1. Sign up at [nolag.app](https://nolag.app)
|
|
12
|
+
2. Create a new **project** in the portal
|
|
13
|
+
3. Choose the **Agents** blueprint when creating an app — this pre-configures the topics (`tasks`, `results`, `state`, `events`, `inbox`, `tools`, `approval`), rooms, and lobbies your agent workflow needs
|
|
14
|
+
4. Go to the app's **Tokens** page and generate an **actor token** for each agent
|
|
15
|
+
5. Use that token when connecting with this SDK
|
|
16
|
+
|
|
17
|
+
Each token identifies a unique agent (actor) in NoLag. The blueprint handles all the infrastructure setup — you just write your agent logic.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @nolag/js-sdk @nolag/agents
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { NoLagAgents, Handoff } from "@nolag/agents";
|
|
29
|
+
|
|
30
|
+
// --- Orchestrator ---
|
|
31
|
+
const orchestrator = new NoLagAgents("ORCHESTRATOR_TOKEN", {
|
|
32
|
+
agentId: "orchestrator-1",
|
|
33
|
+
presence: { name: "orchestrator-1", role: "orchestrator" },
|
|
34
|
+
});
|
|
35
|
+
await orchestrator.connect();
|
|
36
|
+
|
|
37
|
+
const room = orchestrator.room("default-workflow");
|
|
38
|
+
const handoff = new Handoff(room);
|
|
39
|
+
|
|
40
|
+
// Dispatch a task and wait for the result
|
|
41
|
+
const result = await handoff.dispatch("summarize", { text: "..." }, {
|
|
42
|
+
waitForResult: true,
|
|
43
|
+
timeout: 30_000,
|
|
44
|
+
});
|
|
45
|
+
console.log("Result:", result?.payload);
|
|
46
|
+
|
|
47
|
+
// --- Worker ---
|
|
48
|
+
const worker = new NoLagAgents("WORKER_TOKEN", {
|
|
49
|
+
agentId: "worker-1",
|
|
50
|
+
presence: {
|
|
51
|
+
name: "worker-1",
|
|
52
|
+
role: "agent",
|
|
53
|
+
capabilities: ["summarize"],
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
await worker.connect();
|
|
57
|
+
|
|
58
|
+
const workerRoom = worker.room("default-workflow");
|
|
59
|
+
const workerHandoff = new Handoff(workerRoom);
|
|
60
|
+
|
|
61
|
+
workerHandoff.onTask(["summarize"], async (task, respond) => {
|
|
62
|
+
const summary = await summarize(task.payload.text);
|
|
63
|
+
respond("success", { summary });
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Coordination Patterns
|
|
68
|
+
|
|
69
|
+
### Handoff — Task Dispatch & Results
|
|
70
|
+
|
|
71
|
+
Dispatch tasks to agents by capability. The SDK uses presence-based service discovery to verify a capable agent is connected before dispatching.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { Handoff } from "@nolag/agents";
|
|
75
|
+
|
|
76
|
+
const handoff = new Handoff(room);
|
|
77
|
+
|
|
78
|
+
// Orchestrator: dispatch work
|
|
79
|
+
const result = await handoff.dispatch("translate", { text, lang: "es" }, {
|
|
80
|
+
waitForResult: true,
|
|
81
|
+
priority: "high",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Worker: handle tasks
|
|
85
|
+
handoff.onTask(["translate"], async (task, respond) => {
|
|
86
|
+
const translated = await translate(task.payload.text, task.payload.lang);
|
|
87
|
+
respond("success", { translated });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Check who can handle a capability
|
|
91
|
+
const agents = handoff.getCapableAgents("translate");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Blackboard — Shared State
|
|
95
|
+
|
|
96
|
+
Read and write key-value pairs visible to all agents in the room. State is retained so new agents receive current values on join.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Blackboard } from "@nolag/agents";
|
|
100
|
+
|
|
101
|
+
const board = new Blackboard(room, agentId);
|
|
102
|
+
|
|
103
|
+
// Write state
|
|
104
|
+
board.set("progress", { completed: 3, total: 10 });
|
|
105
|
+
|
|
106
|
+
// Read state
|
|
107
|
+
const progress = board.get("progress");
|
|
108
|
+
|
|
109
|
+
// React to changes
|
|
110
|
+
board.onChange("progress", (envelope) => {
|
|
111
|
+
console.log(`Progress updated by ${envelope.updatedBy}:`, envelope.value);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Tools — Remote Tool Invocation
|
|
116
|
+
|
|
117
|
+
Register tool handlers on one agent, invoke them from another. Uses correlated request/response over pub/sub.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { Tools } from "@nolag/agents";
|
|
121
|
+
|
|
122
|
+
const tools = new Tools(room, agentId);
|
|
123
|
+
|
|
124
|
+
// Tool server: register handlers
|
|
125
|
+
tools.register("web_search", async (args) => {
|
|
126
|
+
return await search(args.query as string);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Caller: invoke a remote tool
|
|
130
|
+
const response = await tools.invoke("web_search", { query: "NoLag docs" });
|
|
131
|
+
console.log(response.result);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Approve — Human-in-the-Loop Gates
|
|
135
|
+
|
|
136
|
+
Request approval before taking actions. Humans or supervisor agents approve, reject, or defer.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { Approve } from "@nolag/agents";
|
|
140
|
+
|
|
141
|
+
const approve = new Approve(room, agentId);
|
|
142
|
+
|
|
143
|
+
// Agent: request approval
|
|
144
|
+
const response = await approve.request("delete_record", { recordId: 42 }, {
|
|
145
|
+
urgency: "high",
|
|
146
|
+
timeout: 60_000,
|
|
147
|
+
});
|
|
148
|
+
if (response.decision === "approved") {
|
|
149
|
+
await deleteRecord(42);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Human/supervisor: handle approval requests
|
|
153
|
+
approve.onRequest((request, respond) => {
|
|
154
|
+
console.log(`Action: ${request.action}`, request.context);
|
|
155
|
+
respond("approved", "Looks good");
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Inbox — Direct Agent Messaging
|
|
160
|
+
|
|
161
|
+
Send messages directly to a specific agent. Messages are addressed by agent ID.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { Inbox } from "@nolag/agents";
|
|
165
|
+
|
|
166
|
+
const inbox = new Inbox(room, agentId);
|
|
167
|
+
|
|
168
|
+
// Send a direct message
|
|
169
|
+
inbox.send("worker-2", { instruction: "re-process item 7" });
|
|
170
|
+
|
|
171
|
+
// Receive messages
|
|
172
|
+
inbox.onMessage((msg) => {
|
|
173
|
+
console.log(`From ${msg.from}:`, msg.payload);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Observe — Observability Events
|
|
178
|
+
|
|
179
|
+
Emit structured events for monitoring dashboards. Events have severity, category, and agent attribution.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { Observe } from "@nolag/agents";
|
|
183
|
+
|
|
184
|
+
const observe = new Observe(room, agentId);
|
|
185
|
+
|
|
186
|
+
// Emit events
|
|
187
|
+
observe.emit("task.completed", { taskId: "t-1", duration: 1200 }, "info");
|
|
188
|
+
observe.emit("rate_limit", { service: "openai" }, "warning");
|
|
189
|
+
|
|
190
|
+
// Listen for events (with optional filters)
|
|
191
|
+
observe.on((event) => {
|
|
192
|
+
console.log(`[${event.severity}] ${event.category}:`, event.payload);
|
|
193
|
+
}, { severity: "warning" });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## API Reference
|
|
197
|
+
|
|
198
|
+
### `NoLagAgents`
|
|
199
|
+
|
|
200
|
+
#### Constructor
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const agents = new NoLagAgents(token: string, options?: NoLagAgentsOptions);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Options:**
|
|
207
|
+
|
|
208
|
+
| Option | Type | Default | Description |
|
|
209
|
+
|--------|------|---------|-------------|
|
|
210
|
+
| `appName` | `string` | `"agents"` | App slug for the agents workflow |
|
|
211
|
+
| `agentId` | `string` | auto-generated | Unique agent ID |
|
|
212
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
213
|
+
| `rooms` | `string[]` | `["default-workflow"]` | Rooms to auto-join on connect |
|
|
214
|
+
| `lobby` | `string` | — | Lobby slug for cross-room presence observation |
|
|
215
|
+
| `presence` | `AgentPresenceData` | — | Presence data advertised to other agents |
|
|
216
|
+
| `clientOptions` | `Partial<NoLagOptions>` | — | Additional options passed to `@nolag/js-sdk` |
|
|
217
|
+
|
|
218
|
+
#### Methods
|
|
219
|
+
|
|
220
|
+
| Method | Returns | Description |
|
|
221
|
+
|--------|---------|-------------|
|
|
222
|
+
| `connect()` | `Promise<void>` | Connect to NoLag and join configured rooms |
|
|
223
|
+
| `disconnect()` | `void` | Disconnect and clean up |
|
|
224
|
+
| `room(name)` | `AgentRoom` | Get or create a room (auto-joins if not already joined) |
|
|
225
|
+
| `subscribeLobby(slug)` | `Promise<Record>` | Subscribe to a lobby for cross-room presence |
|
|
226
|
+
|
|
227
|
+
#### Properties
|
|
228
|
+
|
|
229
|
+
| Property | Type | Description |
|
|
230
|
+
|----------|------|-------------|
|
|
231
|
+
| `agentId` | `string` | The agent's unique ID |
|
|
232
|
+
| `connected` | `boolean` | Whether currently connected |
|
|
233
|
+
| `rooms` | `ReadonlyMap<string, AgentRoom>` | All joined rooms |
|
|
234
|
+
|
|
235
|
+
#### Events
|
|
236
|
+
|
|
237
|
+
| Event | Payload | Description |
|
|
238
|
+
|-------|---------|-------------|
|
|
239
|
+
| `connected` | — | Connected to NoLag |
|
|
240
|
+
| `disconnected` | `reason: string` | Disconnected |
|
|
241
|
+
| `reconnected` | — | Reconnected after disconnect |
|
|
242
|
+
| `error` | `Error` | Connection or protocol error |
|
|
243
|
+
|
|
244
|
+
### `AgentRoom`
|
|
245
|
+
|
|
246
|
+
#### Methods
|
|
247
|
+
|
|
248
|
+
| Method | Returns | Description |
|
|
249
|
+
|--------|---------|-------------|
|
|
250
|
+
| `getConnectedAgents()` | `ConnectedAgent[]` | Get all connected agents |
|
|
251
|
+
| `findAgents(capability)` | `ConnectedAgent[]` | Find agents with a capability |
|
|
252
|
+
| `hasCapability(capability)` | `boolean` | Check if any agent has a capability |
|
|
253
|
+
| `getAvailableCapabilities()` | `string[]` | Get all capabilities across connected agents |
|
|
254
|
+
| `setPresence(data)` | `void` | Update this agent's presence |
|
|
255
|
+
| `fetchPresence()` | `Promise<ConnectedAgent[]>` | Fetch current presence snapshot |
|
|
256
|
+
| `publishTask(envelope)` | `void` | Publish to tasks topic |
|
|
257
|
+
| `publishResult(envelope)` | `void` | Publish to results topic |
|
|
258
|
+
| `publishState(data)` | `void` | Publish to state topic (retained) |
|
|
259
|
+
| `publishEvent(data)` | `void` | Publish to events topic |
|
|
260
|
+
| `publishInbox(data)` | `void` | Publish to inbox topic |
|
|
261
|
+
| `publishTools(data)` | `void` | Publish to tools topic |
|
|
262
|
+
| `publishApproval(data)` | `void` | Publish to approval topic (retained) |
|
|
263
|
+
|
|
264
|
+
#### Properties
|
|
265
|
+
|
|
266
|
+
| Property | Type | Description |
|
|
267
|
+
|----------|------|-------------|
|
|
268
|
+
| `name` | `string` | Room name |
|
|
269
|
+
| `agentId` | `string` | This agent's ID |
|
|
270
|
+
| `context` | `RoomContext` | Underlying `@nolag/js-sdk` room context |
|
|
271
|
+
|
|
272
|
+
#### Events
|
|
273
|
+
|
|
274
|
+
| Event | Payload | Description |
|
|
275
|
+
|-------|---------|-------------|
|
|
276
|
+
| `task` | `TaskEnvelope` | Task dispatched |
|
|
277
|
+
| `result` | `ResultEnvelope` | Task result received |
|
|
278
|
+
| `stateChange` | `StateEnvelope` | Shared state updated |
|
|
279
|
+
| `event` | `EventEnvelope` | Observability event |
|
|
280
|
+
| `inbox` | `Record<string, unknown>` | Inbox message received |
|
|
281
|
+
| `approvalRequest` | `ApprovalRequestEnvelope` | Approval requested |
|
|
282
|
+
| `approvalResponse` | `ApprovalResponseEnvelope` | Approval decision received |
|
|
283
|
+
| `toolRequest` | `ToolRequestEnvelope` | Tool invocation requested |
|
|
284
|
+
| `toolResponse` | `ToolResponseEnvelope` | Tool result received |
|
|
285
|
+
| `presenceJoin` | `actorId, AgentPresenceData` | Agent joined |
|
|
286
|
+
| `presenceLeave` | `actorId` | Agent left |
|
|
287
|
+
| `presenceUpdate` | `actorId, AgentPresenceData` | Agent presence updated |
|
|
288
|
+
|
|
289
|
+
## Types
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
interface AgentPresenceData {
|
|
293
|
+
name: string;
|
|
294
|
+
role: string; // "orchestrator" | "agent" | "observer" | "human" | "tool-server"
|
|
295
|
+
capabilities?: string[];
|
|
296
|
+
metadata?: Record<string, unknown>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
interface ConnectedAgent {
|
|
300
|
+
actorId: string;
|
|
301
|
+
name: string;
|
|
302
|
+
role: string;
|
|
303
|
+
capabilities: string[];
|
|
304
|
+
metadata?: Record<string, unknown>;
|
|
305
|
+
connectedAt: number;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface TaskEnvelope {
|
|
309
|
+
type: "task";
|
|
310
|
+
taskId: string;
|
|
311
|
+
correlationId: string;
|
|
312
|
+
capability: string;
|
|
313
|
+
priority: "low" | "medium" | "high" | "critical";
|
|
314
|
+
payload: Record<string, unknown>;
|
|
315
|
+
tags?: string[];
|
|
316
|
+
metadata?: Record<string, unknown>;
|
|
317
|
+
createdAt: number;
|
|
318
|
+
createdBy?: string;
|
|
319
|
+
timeout?: number;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface ResultEnvelope {
|
|
323
|
+
type: "result";
|
|
324
|
+
taskId: string;
|
|
325
|
+
correlationId: string;
|
|
326
|
+
status: "success" | "error" | "partial";
|
|
327
|
+
payload: Record<string, unknown>;
|
|
328
|
+
error?: { code: string; message: string };
|
|
329
|
+
completedAt: number;
|
|
330
|
+
completedBy?: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
interface StateEnvelope {
|
|
334
|
+
type: "state";
|
|
335
|
+
key: string;
|
|
336
|
+
value: unknown;
|
|
337
|
+
version: number;
|
|
338
|
+
updatedAt: number;
|
|
339
|
+
updatedBy: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
interface EventEnvelope {
|
|
343
|
+
type: "event";
|
|
344
|
+
eventId: string;
|
|
345
|
+
severity: "debug" | "info" | "warning" | "error" | "critical";
|
|
346
|
+
category: string;
|
|
347
|
+
payload: Record<string, unknown>;
|
|
348
|
+
timestamp: number;
|
|
349
|
+
emittedBy: string;
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## License
|
|
354
|
+
|
|
355
|
+
MIT
|