@olane/o-leader 0.6.13 → 0.7.2
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 +1130 -52
- package/dist/src/leader.node.d.ts +5 -4
- package/dist/src/leader.node.d.ts.map +1 -1
- package/dist/src/leader.node.js +18 -24
- package/dist/src/registry/registry.tool.d.ts +5 -3
- package/dist/src/registry/registry.tool.d.ts.map +1 -1
- package/dist/src/registry/registry.tool.js +4 -4
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,86 +1,1164 @@
|
|
|
1
|
-
|
|
1
|
+
# o-leader README.md Outline
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 1. Overview (30 seconds)
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### What is o-leader?
|
|
6
|
+
- **One-line definition**: The root coordinator node for Olane OS agent networks
|
|
7
|
+
- **Business value statement**: Provides centralized coordination and discovery for distributed agent networks
|
|
8
|
+
- **Key differentiator**: Self-organizing agent networks without manual infrastructure configuration
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
### When to use o-leader
|
|
11
|
+
- Building multi-agent systems requiring coordination
|
|
12
|
+
- Creating discoverable agent networks
|
|
13
|
+
- Implementing hierarchical agent architectures
|
|
14
|
+
- Scaling from single-agent to multi-agent systems
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
### Core capabilities at a glance
|
|
17
|
+
- **Network Coordination**: Entry point for agents joining the network
|
|
18
|
+
- **Agent Discovery**: Registry service for finding and connecting to agents
|
|
19
|
+
- **Network Intelligence**: Indexing and mapping of agent capabilities
|
|
20
|
+
- **Fault Tolerance**: Automatic failover and recovery coordination
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
---
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
## 2. Quick Start (5 minutes)
|
|
17
25
|
|
|
18
|
-
###
|
|
26
|
+
### Prerequisites
|
|
27
|
+
- Node.js 20.x or higher
|
|
28
|
+
- Basic understanding of Olane OS concepts
|
|
29
|
+
- An o-core installation
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
### Installation
|
|
32
|
+
```bash
|
|
33
|
+
npm install @olane/o-leader
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Basic Leader Node Setup
|
|
37
|
+
```typescript
|
|
38
|
+
import { oLeaderNode } from '@olane/o-leader';
|
|
39
|
+
import { RegistryMemoryTool } from '@olane/o-leader';
|
|
40
|
+
|
|
41
|
+
// Create a leader node
|
|
42
|
+
const leader = new oLeaderNode({
|
|
43
|
+
networkName: 'my-agent-network',
|
|
44
|
+
// configuration options
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Start the leader
|
|
48
|
+
await leader.start();
|
|
49
|
+
|
|
50
|
+
// Leader is now ready to coordinate your agent network
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Expected Output
|
|
54
|
+
- Leader node running at `o://leader`
|
|
55
|
+
- Registry service available at `o://registry`
|
|
56
|
+
- Network ready to accept agent join requests
|
|
57
|
+
|
|
58
|
+
### Next Steps
|
|
59
|
+
- [Add agents to your network](#joining-agents-to-network)
|
|
60
|
+
- [Configure network policies](#network-policies)
|
|
61
|
+
- [Implement custom validation](#custom-join-validation)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Core Concepts
|
|
66
|
+
|
|
67
|
+
### Leader Node Architecture
|
|
68
|
+
|
|
69
|
+
#### What is a Leader Node?
|
|
70
|
+
The leader node is the **root coordinator** of an Olane OS agent network. Unlike traditional orchestrators, it enables **emergent coordination** rather than explicit control.
|
|
71
|
+
|
|
72
|
+
#### Key Responsibilities
|
|
73
|
+
1. **Network Entry Point**: First contact for agents joining the network
|
|
74
|
+
2. **Registry Management**: Maintains live directory of agents and capabilities
|
|
75
|
+
3. **Discovery Coordination**: Helps agents find each other via `o://` addressing
|
|
76
|
+
4. **Network Intelligence**: Tracks and indexes agent capabilities across the network
|
|
77
|
+
|
|
78
|
+
#### Leader vs Traditional Orchestration
|
|
79
|
+
| Traditional Orchestrators | o-leader Node |
|
|
80
|
+
|--------------------------|---------------|
|
|
81
|
+
| Pre-defined workflows | Emergent workflows |
|
|
82
|
+
| Centralized control | Distributed coordination |
|
|
83
|
+
| Manual scaling | Self-organizing |
|
|
84
|
+
| Fixed topology | Dynamic hierarchy |
|
|
85
|
+
|
|
86
|
+
### The Registry Service
|
|
87
|
+
|
|
88
|
+
#### Purpose
|
|
89
|
+
The registry is the **discovery mechanism** for your agent network - a dynamic directory where agents register their capabilities and find other agents.
|
|
90
|
+
|
|
91
|
+
#### Registry Operations
|
|
92
|
+
- **`commit`**: Register an agent and its capabilities
|
|
93
|
+
- **`search`**: Find agents by address, protocol, or capability
|
|
94
|
+
- **find_all`**: List all registered agents
|
|
95
|
+
- **`remove`**: Deregister an agent from the network
|
|
96
|
+
|
|
97
|
+
#### How Agents Use the Registry
|
|
98
|
+
```typescript
|
|
99
|
+
// Agent searches for specialized capabilities
|
|
100
|
+
const agents = await leader.use(new oAddress('o://registry'), {
|
|
101
|
+
method: 'search',
|
|
102
|
+
params: {
|
|
103
|
+
protocols: ['payment-processing', 'tax-calculation']
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Returns all agents with these capabilities
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Network Joining Flow
|
|
111
|
+
|
|
112
|
+
#### The Join Process
|
|
113
|
+
1. New agent makes join request to leader (`o://leader`)
|
|
114
|
+
2. Leader validates the join request (customizable)
|
|
115
|
+
3. Leader updates parent-child relationships
|
|
116
|
+
4. Agent is registered in the registry
|
|
117
|
+
5. Agent receives network configuration
|
|
118
|
+
6. Agent can now discover and communicate with other agents
|
|
119
|
+
|
|
120
|
+
#### Join Request Structure
|
|
121
|
+
```typescript
|
|
122
|
+
{
|
|
123
|
+
caller: 'o://company/finance/analyst',
|
|
124
|
+
parent: 'o://company/finance',
|
|
125
|
+
transports: ['webrtc', 'websocket']
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Network Indexing
|
|
130
|
+
|
|
131
|
+
#### What is Network Indexing?
|
|
132
|
+
Periodic crawling of all registered agents to build a **comprehensive map** of network capabilities, enabling intelligent routing and discovery.
|
|
133
|
+
|
|
134
|
+
#### Why Indexing Matters
|
|
135
|
+
- **Capability Discovery**: Agents can find specialized tools across the network
|
|
136
|
+
- **Intelligent Routing**: Requests automatically routed to capable agents
|
|
137
|
+
- **Network Health**: Identify disconnected or unresponsive agents
|
|
138
|
+
- **Knowledge Mapping**: Build network-wide capability graph
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 4. Integration Guide
|
|
143
|
+
|
|
144
|
+
### Setting Up Your Leader Node
|
|
145
|
+
|
|
146
|
+
#### Basic Configuration
|
|
147
|
+
```typescript
|
|
148
|
+
import { oLeaderNode } from '@olane/o-leader';
|
|
149
|
+
import { RegistryMemoryTool } from '@olane/o-leader';
|
|
150
|
+
|
|
151
|
+
const leader = new oLeaderNode({
|
|
152
|
+
networkName: 'production-agents',
|
|
153
|
+
address: oAddress.leader(), // o://leader
|
|
154
|
+
type: NodeType.LEADER,
|
|
155
|
+
// Optional: custom registry implementation
|
|
156
|
+
registry: new RegistryMemoryTool(config)
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await leader.start();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Configuration Options
|
|
163
|
+
- `networkName`: Identifier for your agent network
|
|
164
|
+
- `address`: Network address (defaults to `o://leader`)
|
|
165
|
+
- `type`: Node type (always `NodeType.LEADER`)
|
|
166
|
+
- `methods`: Custom methods exposed by the leader
|
|
167
|
+
- `registry`: Registry implementation (memory or persistent)
|
|
168
|
+
|
|
169
|
+
### Joining Agents to Network
|
|
170
|
+
|
|
171
|
+
#### From an Agent Node
|
|
172
|
+
```typescript
|
|
173
|
+
import { oNode } from '@olane/o-node';
|
|
174
|
+
|
|
175
|
+
const agent = new oNode({
|
|
176
|
+
address: 'o://company/sales/analyst',
|
|
177
|
+
parent: 'o://company/sales'
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Join the network via leader
|
|
181
|
+
await agent.use(oAddress.leader(), {
|
|
182
|
+
method: 'join_network',
|
|
183
|
+
params: {
|
|
184
|
+
caller: agent.address.toString(),
|
|
185
|
+
parent: 'o://company/sales',
|
|
186
|
+
transports: ['webrtc']
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Join Request Validation
|
|
192
|
+
Customize validation logic for network access control:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
class CustomLeader extends oLeaderNode {
|
|
196
|
+
async validateJoinRequest(request: oRequest): Promise<boolean> {
|
|
197
|
+
const { caller, parent } = request.params;
|
|
198
|
+
|
|
199
|
+
// Custom validation logic
|
|
200
|
+
if (!this.isAuthorizedParent(parent)) {
|
|
201
|
+
throw new Error('Unauthorized parent address');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!this.meetsSecurityRequirements(caller)) {
|
|
205
|
+
throw new Error('Agent does not meet security requirements');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Using the Registry
|
|
214
|
+
|
|
215
|
+
#### Registering Agent Capabilities
|
|
216
|
+
```typescript
|
|
217
|
+
// Commit agent to registry
|
|
218
|
+
await leader.use(new oAddress('o://registry'), {
|
|
219
|
+
method: 'commit',
|
|
220
|
+
params: {
|
|
221
|
+
peerId: 'QmXxxx...', // libp2p peer ID
|
|
222
|
+
address: 'o://company/finance/analyst',
|
|
223
|
+
staticAddress: 'analyst-prod-01',
|
|
224
|
+
protocols: [
|
|
225
|
+
'financial-analysis',
|
|
226
|
+
'report-generation',
|
|
227
|
+
'data-visualization'
|
|
228
|
+
],
|
|
229
|
+
transports: ['webrtc', 'websocket']
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Searching for Agents
|
|
235
|
+
```typescript
|
|
236
|
+
// Find agents by capability
|
|
237
|
+
const analysts = await leader.use(new oAddress('o://registry'), {
|
|
238
|
+
method: 'search',
|
|
239
|
+
params: {
|
|
240
|
+
protocols: ['financial-analysis']
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Find agent by address
|
|
245
|
+
const agent = await leader.use(new oAddress('o://registry'), {
|
|
246
|
+
method: 'search',
|
|
247
|
+
params: {
|
|
248
|
+
address: 'o://company/finance/analyst'
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Find by static address (for stable references)
|
|
253
|
+
const stable = await leader.use(new oAddress('o://registry'), {
|
|
254
|
+
method: 'search',
|
|
255
|
+
params: {
|
|
256
|
+
staticAddress: 'analyst-prod-01'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Listing All Agents
|
|
262
|
+
```typescript
|
|
263
|
+
const allAgents = await leader.use(new oAddress('o://registry'), {
|
|
264
|
+
method: 'find_all',
|
|
265
|
+
params: {}
|
|
266
|
+
});
|
|
21
267
|
|
|
22
|
-
|
|
268
|
+
console.log(`Network has ${allAgents.result.length} active agents`);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Network Indexing
|
|
272
|
+
|
|
273
|
+
#### Triggering Network Index
|
|
274
|
+
```typescript
|
|
275
|
+
// Index entire network
|
|
276
|
+
await leader.use(oAddress.leader(), {
|
|
277
|
+
method: 'index_network',
|
|
278
|
+
params: {}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// This crawls all registered agents and indexes their capabilities
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### Custom Indexing Logic
|
|
285
|
+
Extend the leader to implement custom indexing:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
class CustomLeader extends oLeaderNode {
|
|
289
|
+
async _tool_index_network(request: oRequest): Promise<any> {
|
|
290
|
+
// Get all registered nodes
|
|
291
|
+
const nodes = await this.use(
|
|
292
|
+
new oAddress(RestrictedAddresses.REGISTRY),
|
|
293
|
+
{ method: 'find_all', params: {} }
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Custom indexing per node
|
|
297
|
+
for (const node of nodes.result.data) {
|
|
298
|
+
const capabilities = await this.indexNodeCapabilities(node);
|
|
299
|
+
await this.storeCapabilities(node.address, capabilities);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { message: 'Network indexed with custom logic!' };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 5. Advanced Topics
|
|
310
|
+
|
|
311
|
+
### Custom Registry Implementations
|
|
312
|
+
|
|
313
|
+
#### Why Custom Registries?
|
|
314
|
+
The default `RegistryMemoryTool` stores registrations in memory, which is great for development but not persistent. For production, implement a persistent registry.
|
|
315
|
+
|
|
316
|
+
#### Persistent Registry Example
|
|
317
|
+
```typescript
|
|
318
|
+
import { RegistryTool } from '@olane/o-leader';
|
|
319
|
+
import { oRequest } from '@olane/o-core';
|
|
23
320
|
|
|
24
|
-
|
|
321
|
+
class PostgresRegistryTool extends RegistryTool {
|
|
322
|
+
constructor(config: oNodeToolConfig, private db: Database) {
|
|
323
|
+
super(config);
|
|
324
|
+
}
|
|
25
325
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
326
|
+
async _tool_commit(request: oRequest): Promise<any> {
|
|
327
|
+
const params = request.params as oRegistrationParams;
|
|
328
|
+
|
|
329
|
+
await this.db.query(`
|
|
330
|
+
INSERT INTO agent_registry (peer_id, address, protocols, transports)
|
|
331
|
+
VALUES ($1, $2, $3, $4)
|
|
332
|
+
ON CONFLICT (peer_id) DO UPDATE SET
|
|
333
|
+
address = EXCLUDED.address,
|
|
334
|
+
protocols = EXCLUDED.protocols,
|
|
335
|
+
transports = EXCLUDED.transports,
|
|
336
|
+
updated_at = NOW()
|
|
337
|
+
`, [params.peerId, params.address, params.protocols, params.transports]);
|
|
338
|
+
|
|
339
|
+
return { success: true };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async _tool_search(request: oRequest): Promise<any> {
|
|
343
|
+
const params = request.params as oRegistrySearchParams;
|
|
344
|
+
// Implement database search logic
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Implement other methods...
|
|
348
|
+
}
|
|
349
|
+
```
|
|
30
350
|
|
|
31
|
-
|
|
351
|
+
#### Using Custom Registry
|
|
352
|
+
```typescript
|
|
353
|
+
const db = new Database(dbConfig);
|
|
354
|
+
const registry = new PostgresRegistryTool(config, db);
|
|
32
355
|
|
|
33
|
-
|
|
34
|
-
|
|
356
|
+
const leader = new oLeaderNode({
|
|
357
|
+
...config,
|
|
358
|
+
registry: registry
|
|
359
|
+
});
|
|
360
|
+
```
|
|
35
361
|
|
|
36
|
-
|
|
37
|
-
💡
|
|
362
|
+
### Multi-Leader Networks
|
|
38
363
|
|
|
39
|
-
|
|
364
|
+
#### When to Use Multiple Leaders
|
|
365
|
+
- **Geographic Distribution**: Leader per region for latency
|
|
366
|
+
- **Organizational Boundaries**: Leader per department or team
|
|
367
|
+
- **Fault Tolerance**: Backup leaders for high availability
|
|
368
|
+
- **Scale**: Distribute coordination load across leaders
|
|
40
369
|
|
|
41
|
-
|
|
370
|
+
#### Federation Pattern
|
|
371
|
+
```typescript
|
|
372
|
+
// Primary leader
|
|
373
|
+
const primaryLeader = new oLeaderNode({
|
|
374
|
+
networkName: 'global-network',
|
|
375
|
+
address: new oAddress('o://leader/primary')
|
|
376
|
+
});
|
|
42
377
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
378
|
+
// Regional leaders
|
|
379
|
+
const usLeader = new oLeaderNode({
|
|
380
|
+
networkName: 'us-network',
|
|
381
|
+
address: new oAddress('o://leader/us'),
|
|
382
|
+
parent: 'o://leader/primary'
|
|
383
|
+
});
|
|
47
384
|
|
|
48
|
-
|
|
385
|
+
const euLeader = new oLeaderNode({
|
|
386
|
+
networkName: 'eu-network',
|
|
387
|
+
address: new oAddress('o://leader/eu'),
|
|
388
|
+
parent: 'o://leader/primary'
|
|
389
|
+
});
|
|
49
390
|
|
|
50
|
-
|
|
391
|
+
// Each regional leader manages its own registry
|
|
392
|
+
// but can query primary leader for global discovery
|
|
393
|
+
```
|
|
51
394
|
|
|
52
|
-
###
|
|
395
|
+
### Security and Access Control
|
|
53
396
|
|
|
54
|
-
|
|
397
|
+
#### Network Access Policies
|
|
398
|
+
```typescript
|
|
399
|
+
class SecureLeader extends oLeaderNode {
|
|
400
|
+
private allowedNetworks = new Set(['o://company/*']);
|
|
401
|
+
private requiresAuth = true;
|
|
402
|
+
|
|
403
|
+
async validateJoinRequest(request: oRequest): Promise<boolean> {
|
|
404
|
+
const { caller, authToken } = request.params;
|
|
405
|
+
|
|
406
|
+
// Verify authentication
|
|
407
|
+
if (this.requiresAuth && !this.verifyToken(authToken)) {
|
|
408
|
+
throw new Error('Authentication required');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check network authorization
|
|
412
|
+
const isAllowed = Array.from(this.allowedNetworks).some(pattern =>
|
|
413
|
+
this.matchesPattern(caller, pattern)
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (!isAllowed) {
|
|
417
|
+
throw new Error(`Address ${caller} not authorized for this network`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Rate limiting
|
|
421
|
+
if (this.exceedsJoinRateLimit(request.metadata.peerId)) {
|
|
422
|
+
throw new Error('Join rate limit exceeded');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
55
429
|
|
|
56
|
-
|
|
57
|
-
|
|
430
|
+
#### Registry Access Control
|
|
431
|
+
```typescript
|
|
432
|
+
class ProtectedRegistry extends RegistryTool {
|
|
433
|
+
async _tool_search(request: oRequest): Promise<any> {
|
|
434
|
+
// Verify requesting agent has permission
|
|
435
|
+
const requester = request.metadata.caller;
|
|
436
|
+
if (!this.canSearch(requester)) {
|
|
437
|
+
throw new Error('Insufficient permissions for registry search');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Filter results based on permissions
|
|
441
|
+
const results = await this.performSearch(request.params);
|
|
442
|
+
return this.filterByPermissions(results, requester);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
58
446
|
|
|
59
|
-
|
|
447
|
+
### Network Health Monitoring
|
|
60
448
|
|
|
61
|
-
|
|
449
|
+
#### Health Check Implementation
|
|
450
|
+
```typescript
|
|
451
|
+
class MonitoredLeader extends oLeaderNode {
|
|
452
|
+
async getNetworkHealth(): Promise<NetworkHealth> {
|
|
453
|
+
const allAgents = await this.use(
|
|
454
|
+
new oAddress('o://registry'),
|
|
455
|
+
{ method: 'find_all', params: {} }
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const health = {
|
|
459
|
+
totalAgents: allAgents.result.length,
|
|
460
|
+
activeAgents: 0,
|
|
461
|
+
unhealthyAgents: [],
|
|
462
|
+
timestamp: Date.now()
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Check each agent
|
|
466
|
+
for (const agent of allAgents.result) {
|
|
467
|
+
const isHealthy = await this.checkAgentHealth(agent);
|
|
468
|
+
if (isHealthy) {
|
|
469
|
+
health.activeAgents++;
|
|
470
|
+
} else {
|
|
471
|
+
health.unhealthyAgents.push(agent.address);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return health;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
62
479
|
|
|
63
|
-
|
|
480
|
+
### Performance Optimization
|
|
64
481
|
|
|
482
|
+
#### Registry Caching
|
|
483
|
+
```typescript
|
|
484
|
+
class CachedRegistry extends RegistryTool {
|
|
485
|
+
private cache = new Map<string, CacheEntry>();
|
|
486
|
+
private cacheTTL = 60000; // 1 minute
|
|
487
|
+
|
|
488
|
+
async _tool_search(request: oRequest): Promise<any> {
|
|
489
|
+
const cacheKey = this.getCacheKey(request.params);
|
|
490
|
+
const cached = this.cache.get(cacheKey);
|
|
491
|
+
|
|
492
|
+
if (cached && !this.isExpired(cached)) {
|
|
493
|
+
return cached.data;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const results = await this.performSearch(request.params);
|
|
497
|
+
this.cache.set(cacheKey, {
|
|
498
|
+
data: results,
|
|
499
|
+
timestamp: Date.now()
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
return results;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
65
505
|
```
|
|
66
|
-
“Get my latest pull requests”
|
|
67
|
-
Results in the following steps:
|
|
68
506
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
507
|
+
#### Protocol Indexing Optimization
|
|
508
|
+
```typescript
|
|
509
|
+
class OptimizedRegistry extends RegistryMemoryTool {
|
|
510
|
+
// Maintain protocol -> agents mapping for O(1) lookups
|
|
511
|
+
private protocolIndex = new Map<string, Set<string>>();
|
|
512
|
+
|
|
513
|
+
async _tool_commit(request: oRequest): Promise<any> {
|
|
514
|
+
const result = await super._tool_commit(request);
|
|
515
|
+
|
|
516
|
+
// Update protocol index
|
|
517
|
+
const { peerId, protocols } = request.params;
|
|
518
|
+
protocols.forEach(protocol => {
|
|
519
|
+
if (!this.protocolIndex.has(protocol)) {
|
|
520
|
+
this.protocolIndex.set(protocol, new Set());
|
|
521
|
+
}
|
|
522
|
+
this.protocolIndex.get(protocol)!.add(peerId);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async _tool_search(request: oRequest): Promise<any> {
|
|
529
|
+
const { protocols } = request.params;
|
|
530
|
+
|
|
531
|
+
if (protocols) {
|
|
532
|
+
// Fast lookup using index
|
|
533
|
+
const peerIds = this.findByProtocolIndex(protocols);
|
|
534
|
+
return peerIds.map(id => this.registry.get(id));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return super._tool_search(request);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
73
540
|
```
|
|
74
541
|
|
|
75
|
-
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## 6. Best Practices
|
|
76
545
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
546
|
+
### Network Design Patterns
|
|
547
|
+
|
|
548
|
+
#### 1. Hierarchical Organization
|
|
549
|
+
Structure your network to mirror your business domains:
|
|
550
|
+
```
|
|
551
|
+
o://leader # Root coordinator
|
|
552
|
+
└── o://company # Company root
|
|
553
|
+
├── o://company/finance # Finance department
|
|
554
|
+
│ ├── o://company/finance/analyst
|
|
555
|
+
│ └── o://company/finance/reporting
|
|
556
|
+
└── o://company/engineering # Engineering department
|
|
557
|
+
├── o://company/engineering/backend
|
|
558
|
+
└── o://company/engineering/frontend
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### 2. Capability-Based Discovery
|
|
562
|
+
Register agents with clear, specific capabilities:
|
|
563
|
+
```typescript
|
|
564
|
+
// Good: Specific capabilities
|
|
565
|
+
protocols: ['payment-processing', 'stripe-api', 'refund-handling']
|
|
566
|
+
|
|
567
|
+
// Avoid: Generic capabilities
|
|
568
|
+
protocols: ['payments', 'api', 'processing']
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
#### 3. Static Addresses for Stability
|
|
572
|
+
Use static addresses for production agents that need stable references:
|
|
573
|
+
```typescript
|
|
574
|
+
{
|
|
575
|
+
address: 'o://company/finance/analyst', // Dynamic
|
|
576
|
+
staticAddress: 'analyst-prod-01' // Stable reference
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Operational Guidelines
|
|
581
|
+
|
|
582
|
+
#### Registry Maintenance
|
|
583
|
+
- **TTL Strategy**: Implement time-to-live for registry entries to auto-cleanup stale agents
|
|
584
|
+
- **Health Checks**: Periodically verify registered agents are still responsive
|
|
585
|
+
- **Cleanup**: Remove inactive agents to keep registry performant
|
|
586
|
+
|
|
587
|
+
#### Network Indexing
|
|
588
|
+
- **Scheduled Indexing**: Run network indexing on a schedule (e.g., every 5 minutes)
|
|
589
|
+
- **Event-Driven**: Trigger indexing when significant network changes occur
|
|
590
|
+
- **Incremental**: For large networks, implement incremental indexing
|
|
591
|
+
|
|
592
|
+
#### Monitoring and Observability
|
|
593
|
+
```typescript
|
|
594
|
+
// Log key network events
|
|
595
|
+
leader.on('agent:joined', (agent) => {
|
|
596
|
+
logger.info('Agent joined network', {
|
|
597
|
+
address: agent.address,
|
|
598
|
+
capabilities: agent.protocols,
|
|
599
|
+
timestamp: Date.now()
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
leader.on('agent:removed', (agent) => {
|
|
604
|
+
logger.info('Agent removed from network', {
|
|
605
|
+
address: agent.address,
|
|
606
|
+
timestamp: Date.now()
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// Metrics collection
|
|
611
|
+
const metrics = {
|
|
612
|
+
totalAgents: await registry.count(),
|
|
613
|
+
joinRate: calculateJoinRate(),
|
|
614
|
+
searchLatency: measureSearchLatency(),
|
|
615
|
+
networkHealth: await leader.getNetworkHealth()
|
|
616
|
+
};
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Security Best Practices
|
|
620
|
+
|
|
621
|
+
1. **Always validate join requests** with business logic
|
|
622
|
+
2. **Use authentication tokens** for production networks
|
|
623
|
+
3. **Implement rate limiting** on join requests
|
|
624
|
+
4. **Filter registry results** based on caller permissions
|
|
625
|
+
5. **Monitor for unusual patterns** (e.g., rapid joins, suspicious addresses)
|
|
626
|
+
6. **Use encrypted transports** (prefer webrtc over websocket for sensitive data)
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## 7. Troubleshooting
|
|
631
|
+
|
|
632
|
+
### Common Issues
|
|
633
|
+
|
|
634
|
+
#### Agents Can't Join Network
|
|
635
|
+
**Symptoms**: Join requests fail or timeout
|
|
636
|
+
**Solutions**:
|
|
637
|
+
- Verify leader is running and accessible
|
|
638
|
+
- Check network connectivity between agent and leader
|
|
639
|
+
- Review `validateJoinRequest` logic for rejections
|
|
640
|
+
- Ensure parent address exists in hierarchy
|
|
641
|
+
|
|
642
|
+
#### Registry Search Returns No Results
|
|
643
|
+
**Symptoms**: Search returns empty array when agents should exist
|
|
644
|
+
**Solutions**:
|
|
645
|
+
- Verify agents committed to registry successfully
|
|
646
|
+
- Check search parameters match registered protocols/addresses
|
|
647
|
+
- Inspect registry state: `await registry.find_all()`
|
|
648
|
+
- Look for typos in protocol names or addresses
|
|
649
|
+
|
|
650
|
+
#### Network Indexing Fails
|
|
651
|
+
**Symptoms**: Index operation throws errors or never completes
|
|
652
|
+
**Solutions**:
|
|
653
|
+
- Check for agents that are unresponsive or disconnected
|
|
654
|
+
- Implement timeout logic for indexing individual agents
|
|
655
|
+
- Add error handling to continue indexing after failures
|
|
656
|
+
- Monitor agent count vs indexed count
|
|
657
|
+
|
|
658
|
+
#### Memory Issues with Large Networks
|
|
659
|
+
**Symptoms**: Registry memory grows unbounded
|
|
660
|
+
**Solutions**:
|
|
661
|
+
- Implement persistent registry instead of in-memory
|
|
662
|
+
- Add TTL for registry entries
|
|
663
|
+
- Implement cleanup for disconnected agents
|
|
664
|
+
- Consider registry sharding for very large networks
|
|
665
|
+
|
|
666
|
+
### Debugging Tips
|
|
667
|
+
|
|
668
|
+
#### Enable Debug Logging
|
|
669
|
+
```bash
|
|
670
|
+
DEBUG=o-protocol:*,o-leader:* node your-app.js
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
#### Inspect Registry State
|
|
674
|
+
```typescript
|
|
675
|
+
const allAgents = await leader.use(
|
|
676
|
+
new oAddress('o://registry'),
|
|
677
|
+
{ method: 'find_all', params: {} }
|
|
678
|
+
);
|
|
679
|
+
console.log('Registry state:', JSON.stringify(allAgents, null, 2));
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
#### Monitor Join Requests
|
|
683
|
+
```typescript
|
|
684
|
+
class DebugLeader extends oLeaderNode {
|
|
685
|
+
async _tool_join_network(request: oRequest): Promise<any> {
|
|
686
|
+
console.log('Join request received:', {
|
|
687
|
+
caller: request.params.caller,
|
|
688
|
+
parent: request.params.parent,
|
|
689
|
+
transports: request.params.transports,
|
|
690
|
+
timestamp: Date.now()
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
return await super._tool_join_network(request);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
console.error('Join request failed:', error);
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## 8. API Reference
|
|
706
|
+
|
|
707
|
+
### oLeaderNode
|
|
708
|
+
|
|
709
|
+
#### Constructor
|
|
710
|
+
```typescript
|
|
711
|
+
constructor(config: oNodeToolConfig)
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Parameters:**
|
|
715
|
+
- `config.networkName` (string): Network identifier
|
|
716
|
+
- `config.address` (oAddress): Leader node address (default: `o://leader`)
|
|
717
|
+
- `config.type` (NodeType): Must be `NodeType.LEADER`
|
|
718
|
+
- `config.methods` (object): Custom methods to expose
|
|
719
|
+
- Other oNodeToolConfig parameters
|
|
720
|
+
|
|
721
|
+
#### Methods
|
|
722
|
+
|
|
723
|
+
##### `validateJoinRequest(request: oRequest): Promise<boolean>`
|
|
724
|
+
Override to implement custom join validation logic.
|
|
725
|
+
|
|
726
|
+
**Parameters:**
|
|
727
|
+
- `request`: Join request containing caller, parent, transports
|
|
728
|
+
|
|
729
|
+
**Returns:** Promise resolving to `true` if valid, throws error if invalid
|
|
730
|
+
|
|
731
|
+
**Example:**
|
|
732
|
+
```typescript
|
|
733
|
+
async validateJoinRequest(request: oRequest): Promise<boolean> {
|
|
734
|
+
if (!isAuthorized(request.params.caller)) {
|
|
735
|
+
throw new Error('Unauthorized');
|
|
736
|
+
}
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
##### `_tool_join_network(request: oRequest): Promise<any>`
|
|
742
|
+
Processes agent join requests.
|
|
743
|
+
|
|
744
|
+
**Request Parameters:**
|
|
745
|
+
- `caller` (string): Address of joining agent
|
|
746
|
+
- `parent` (string): Parent address in hierarchy
|
|
747
|
+
- `transports` (string[]): Available transport protocols
|
|
748
|
+
|
|
749
|
+
**Returns:** Success message
|
|
750
|
+
|
|
751
|
+
##### `_tool_index_network(request: oRequest): Promise<any>`
|
|
752
|
+
Indexes all registered agents in the network.
|
|
753
|
+
|
|
754
|
+
**Returns:** Index completion status
|
|
755
|
+
|
|
756
|
+
##### `_tool_save_plan(request: oRequest): Promise<any>`
|
|
757
|
+
Saves network coordination plans.
|
|
758
|
+
|
|
759
|
+
**Request Parameters:**
|
|
760
|
+
- `plan` (object): Plan to save
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
### RegistryTool
|
|
765
|
+
|
|
766
|
+
#### Abstract Methods
|
|
767
|
+
|
|
768
|
+
##### `_tool_commit(request: oRequest): Promise<ToolResult>`
|
|
769
|
+
Register an agent in the registry.
|
|
770
|
+
|
|
771
|
+
**Request Parameters:**
|
|
772
|
+
- `peerId` (string, required): Agent's peer ID
|
|
773
|
+
- `address` (string): Agent's o:// address
|
|
774
|
+
- `staticAddress` (string): Stable reference address
|
|
775
|
+
- `protocols` (string[]): Capabilities/protocols
|
|
776
|
+
- `transports` (string[]): Transport protocols
|
|
777
|
+
|
|
778
|
+
**Returns:** Success indicator
|
|
779
|
+
|
|
780
|
+
##### `_tool_search(request: oRequest): Promise<ToolResult>`
|
|
781
|
+
Search for agents matching criteria.
|
|
782
|
+
|
|
783
|
+
**Request Parameters:**
|
|
784
|
+
- `address` (string, optional): Search by address
|
|
785
|
+
- `staticAddress` (string, optional): Search by static address
|
|
786
|
+
- `protocols` (string[], optional): Search by protocols
|
|
787
|
+
|
|
788
|
+
**Returns:** Array of matching agents
|
|
789
|
+
|
|
790
|
+
##### `_tool_find_all(request: oRequest): Promise<ToolResult>`
|
|
791
|
+
List all registered agents.
|
|
792
|
+
|
|
793
|
+
**Returns:** Array of all registered agents
|
|
794
|
+
|
|
795
|
+
##### `_tool_remove(request: oRequest): Promise<ToolResult>`
|
|
796
|
+
Remove an agent from registry.
|
|
797
|
+
|
|
798
|
+
**Request Parameters:**
|
|
799
|
+
- `peerId` (string): Peer ID of agent to remove
|
|
800
|
+
|
|
801
|
+
**Returns:** Success indicator
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
### RegistryMemoryTool
|
|
806
|
+
|
|
807
|
+
Concrete implementation of RegistryTool using in-memory storage.
|
|
808
|
+
|
|
809
|
+
**Usage:**
|
|
810
|
+
```typescript
|
|
811
|
+
const registry = new RegistryMemoryTool(config);
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
**Storage:**
|
|
815
|
+
- Uses Map data structures for fast lookups
|
|
816
|
+
- Protocol indexing for efficient searches
|
|
817
|
+
- Not persistent across restarts
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## 9. Examples
|
|
822
|
+
|
|
823
|
+
### Example 1: Basic Multi-Agent Network
|
|
824
|
+
```typescript
|
|
825
|
+
// examples/basic-network.ts
|
|
826
|
+
import { oLeaderNode, RegistryMemoryTool } from '@olane/o-leader';
|
|
827
|
+
import { oNode } from '@olane/o-node';
|
|
828
|
+
import { oAddress } from '@olane/o-core';
|
|
829
|
+
|
|
830
|
+
async function main() {
|
|
831
|
+
// 1. Start leader
|
|
832
|
+
const leader = new oLeaderNode({
|
|
833
|
+
networkName: 'my-agents',
|
|
834
|
+
});
|
|
835
|
+
await leader.start();
|
|
836
|
+
|
|
837
|
+
// 2. Create specialized agents
|
|
838
|
+
const analyst = new oNode({
|
|
839
|
+
address: 'o://agents/financial-analyst',
|
|
840
|
+
protocols: ['financial-analysis', 'report-generation']
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const dataCollector = new oNode({
|
|
844
|
+
address: 'o://agents/data-collector',
|
|
845
|
+
protocols: ['data-fetching', 'api-integration']
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// 3. Join agents to network
|
|
849
|
+
await analyst.use(oAddress.leader(), {
|
|
850
|
+
method: 'join_network',
|
|
851
|
+
params: {
|
|
852
|
+
caller: 'o://agents/financial-analyst',
|
|
853
|
+
parent: 'o://agents',
|
|
854
|
+
transports: ['webrtc']
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
await dataCollector.use(oAddress.leader(), {
|
|
859
|
+
method: 'join_network',
|
|
860
|
+
params: {
|
|
861
|
+
caller: 'o://agents/data-collector',
|
|
862
|
+
parent: 'o://agents',
|
|
863
|
+
transports: ['webrtc']
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// 4. Use registry to find agents
|
|
868
|
+
const agents = await leader.use(new oAddress('o://registry'), {
|
|
869
|
+
method: 'find_all',
|
|
870
|
+
params: {}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
console.log(`Network has ${agents.result.length} agents`);
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### Example 2: Custom Validation
|
|
878
|
+
```typescript
|
|
879
|
+
// examples/secure-network.ts
|
|
880
|
+
import { oLeaderNode } from '@olane/o-leader';
|
|
881
|
+
import { oRequest } from '@olane/o-core';
|
|
882
|
+
|
|
883
|
+
class SecureLeader extends oLeaderNode {
|
|
884
|
+
private authorizedDomains = ['o://company', 'o://partners'];
|
|
885
|
+
|
|
886
|
+
async validateJoinRequest(request: oRequest): Promise<boolean> {
|
|
887
|
+
const { caller, authToken } = request.params;
|
|
888
|
+
|
|
889
|
+
// Verify auth token
|
|
890
|
+
if (!this.verifyAuthToken(authToken)) {
|
|
891
|
+
throw new Error('Invalid authentication token');
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Check domain authorization
|
|
895
|
+
const isAuthorized = this.authorizedDomains.some(domain =>
|
|
896
|
+
caller.startsWith(domain)
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
if (!isAuthorized) {
|
|
900
|
+
throw new Error(`Domain not authorized: ${caller}`);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
this.logger.info(`Agent ${caller} authorized to join`);
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private verifyAuthToken(token: string): boolean {
|
|
908
|
+
// Your token verification logic
|
|
909
|
+
return token === 'valid-secret';
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const leader = new SecureLeader({
|
|
914
|
+
networkName: 'secure-network'
|
|
915
|
+
});
|
|
916
|
+
await leader.start();
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Example 3: Capability Discovery
|
|
920
|
+
```typescript
|
|
921
|
+
// examples/capability-discovery.ts
|
|
922
|
+
import { oAddress } from '@olane/o-core';
|
|
923
|
+
|
|
924
|
+
async function findAnalysisAgents(leader: oLeaderNode) {
|
|
925
|
+
// Search for agents with specific capabilities
|
|
926
|
+
const result = await leader.use(new oAddress('o://registry'), {
|
|
927
|
+
method: 'search',
|
|
928
|
+
params: {
|
|
929
|
+
protocols: ['financial-analysis', 'data-visualization']
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
const agents = result.result;
|
|
934
|
+
console.log(`Found ${agents.length} agents with analysis capabilities`);
|
|
935
|
+
|
|
936
|
+
// Connect to first available agent
|
|
937
|
+
if (agents.length > 0) {
|
|
938
|
+
const targetAgent = new oAddress(agents[0].address);
|
|
939
|
+
const analysis = await leader.use(targetAgent, {
|
|
940
|
+
method: 'analyze_portfolio',
|
|
941
|
+
params: { portfolio: 'tech-stocks' }
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
return analysis;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Example 4: Network Health Dashboard
|
|
950
|
+
```typescript
|
|
951
|
+
// examples/health-dashboard.ts
|
|
952
|
+
import { oLeaderNode, RegistryMemoryTool } from '@olane/o-leader';
|
|
953
|
+
|
|
954
|
+
class HealthMonitorLeader extends oLeaderNode {
|
|
955
|
+
async getNetworkDashboard() {
|
|
956
|
+
const allAgents = await this.use(
|
|
957
|
+
new oAddress('o://registry'),
|
|
958
|
+
{ method: 'find_all', params: {} }
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
const dashboard = {
|
|
962
|
+
totalAgents: allAgents.result.length,
|
|
963
|
+
byCapability: this.groupByCapability(allAgents.result),
|
|
964
|
+
byDomain: this.groupByDomain(allAgents.result),
|
|
965
|
+
health: await this.checkAllAgentsHealth(allAgents.result)
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
return dashboard;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private groupByCapability(agents: any[]) {
|
|
972
|
+
const groups = new Map<string, number>();
|
|
973
|
+
agents.forEach(agent => {
|
|
974
|
+
agent.protocols.forEach(protocol => {
|
|
975
|
+
groups.set(protocol, (groups.get(protocol) || 0) + 1);
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
return Object.fromEntries(groups);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
private groupByDomain(agents: any[]) {
|
|
982
|
+
const groups = new Map<string, number>();
|
|
983
|
+
agents.forEach(agent => {
|
|
984
|
+
const domain = agent.address.split('/').slice(0, 3).join('/');
|
|
985
|
+
groups.set(domain, (groups.get(domain) || 0) + 1);
|
|
986
|
+
});
|
|
987
|
+
return Object.fromEntries(groups);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private async checkAllAgentsHealth(agents: any[]) {
|
|
991
|
+
// Implementation for health checks
|
|
992
|
+
return { healthy: agents.length, unhealthy: 0 };
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## 10. Migration Guide
|
|
1000
|
+
|
|
1001
|
+
### From Manual Coordination to o-leader
|
|
1002
|
+
|
|
1003
|
+
#### Before: Manual Agent Management
|
|
1004
|
+
```typescript
|
|
1005
|
+
// Manually tracking agents
|
|
1006
|
+
const agents = {
|
|
1007
|
+
'analyst-1': { address: '...', capabilities: [...] },
|
|
1008
|
+
'collector-1': { address: '...', capabilities: [...] }
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// Manual discovery
|
|
1012
|
+
function findAgent(capability) {
|
|
1013
|
+
for (const [id, agent] of Object.entries(agents)) {
|
|
1014
|
+
if (agent.capabilities.includes(capability)) {
|
|
1015
|
+
return agent;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
#### After: With o-leader
|
|
1022
|
+
```typescript
|
|
1023
|
+
// Automatic registration and discovery
|
|
1024
|
+
const leader = new oLeaderNode({ networkName: 'agents' });
|
|
1025
|
+
|
|
1026
|
+
// Agents self-register
|
|
1027
|
+
await agent.use(oAddress.leader(), {
|
|
1028
|
+
method: 'join_network',
|
|
1029
|
+
params: { caller: agent.address, parent, transports }
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
// Automatic discovery via registry
|
|
1033
|
+
const agents = await leader.use(new oAddress('o://registry'), {
|
|
1034
|
+
method: 'search',
|
|
1035
|
+
params: { protocols: ['analysis'] }
|
|
1036
|
+
});
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
### From Other Frameworks
|
|
1040
|
+
|
|
1041
|
+
#### From LangGraph
|
|
1042
|
+
LangGraph requires explicit graph definitions. With o-leader, agents discover each other:
|
|
1043
|
+
|
|
1044
|
+
```typescript
|
|
1045
|
+
// LangGraph: Pre-defined graph
|
|
1046
|
+
const graph = new StateGraph({
|
|
1047
|
+
nodes: ['analyst', 'collector', 'reporter'],
|
|
1048
|
+
edges: [['analyst', 'reporter'], ['collector', 'reporter']]
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// o-leader: Emergent connections
|
|
1052
|
+
const leader = new oLeaderNode({ networkName: 'agents' });
|
|
1053
|
+
// Agents discover each other via registry
|
|
1054
|
+
// No pre-defined connections needed
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
#### From CrewAI
|
|
1058
|
+
CrewAI uses explicit crew definitions. o-leader enables dynamic crews:
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
// CrewAI: Static crew
|
|
1062
|
+
const crew = new Crew({
|
|
1063
|
+
agents: [analyst, researcher, writer],
|
|
1064
|
+
tasks: [research_task, analysis_task, writing_task]
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// o-leader: Dynamic discovery
|
|
1068
|
+
const agents = await leader.use(new oAddress('o://registry'), {
|
|
1069
|
+
method: 'search',
|
|
1070
|
+
params: { protocols: ['research', 'analysis', 'writing'] }
|
|
1071
|
+
});
|
|
1072
|
+
// Agents can form dynamic collaborations
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
---
|
|
1076
|
+
|
|
1077
|
+
## 11. FAQ
|
|
1078
|
+
|
|
1079
|
+
### General Questions
|
|
1080
|
+
|
|
1081
|
+
**Q: Do I need a leader node for every Olane OS network?**
|
|
1082
|
+
A: Yes, every network needs at least one leader node. It serves as the entry point and coordination hub.
|
|
1083
|
+
|
|
1084
|
+
**Q: Can I have multiple leader nodes?**
|
|
1085
|
+
A: Yes, for large or distributed networks, you can implement a federation pattern with regional leaders.
|
|
1086
|
+
|
|
1087
|
+
**Q: Is the registry required?**
|
|
1088
|
+
A: The registry is built into the leader node and is essential for agent discovery in the network.
|
|
1089
|
+
|
|
1090
|
+
**Q: What happens if the leader node goes down?**
|
|
1091
|
+
A: Existing agent connections remain active, but new agents cannot join. Implement leader failover for high availability.
|
|
1092
|
+
|
|
1093
|
+
### Technical Questions
|
|
1094
|
+
|
|
1095
|
+
**Q: How does registry search performance scale?**
|
|
1096
|
+
A: In-memory registry is O(n) for searches. For large networks, implement indexed or database-backed registries.
|
|
1097
|
+
|
|
1098
|
+
**Q: Can I customize the registry implementation?**
|
|
1099
|
+
A: Yes, extend `RegistryTool` to implement custom storage backends (database, cache, etc.).
|
|
1100
|
+
|
|
1101
|
+
**Q: What transports does the leader support?**
|
|
1102
|
+
A: The leader supports all libp2p transports: WebRTC, WebSocket, TCP, QUIC, etc.
|
|
1103
|
+
|
|
1104
|
+
**Q: How do I handle registry cleanup?**
|
|
1105
|
+
A: Implement TTL logic in your registry and periodic cleanup of disconnected agents.
|
|
1106
|
+
|
|
1107
|
+
### Operational Questions
|
|
1108
|
+
|
|
1109
|
+
**Q: How often should I run network indexing?**
|
|
1110
|
+
A: Depends on network dynamics. For stable networks, every 5-10 minutes. For dynamic networks, event-driven indexing.
|
|
1111
|
+
|
|
1112
|
+
**Q: What's the recommended registry backend for production?**
|
|
1113
|
+
A: For production, use a persistent registry (PostgreSQL, Redis, etc.) instead of in-memory.
|
|
1114
|
+
|
|
1115
|
+
**Q: How do I monitor leader health?**
|
|
1116
|
+
A: Implement health check endpoints and monitoring using your observability tools.
|
|
1117
|
+
|
|
1118
|
+
**Q: Can agents join from different networks?**
|
|
1119
|
+
A: Yes, as long as they can reach the leader node and pass validation.
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
## 12. Support and Resources
|
|
1124
|
+
|
|
1125
|
+
### Documentation
|
|
1126
|
+
- [Olane OS Overview](/README.md)
|
|
1127
|
+
- [o-core Documentation](/packages/o-core/README.md)
|
|
1128
|
+
- [o-node Documentation](/packages/o-node/README.md)
|
|
1129
|
+
- [Agent Specialization Guide](/docs/specialization.md)
|
|
81
1130
|
|
|
82
1131
|
### Examples
|
|
1132
|
+
- [Basic Network Setup](/examples/basic-network)
|
|
1133
|
+
- [Secure Network](/examples/secure-network)
|
|
1134
|
+
- [Multi-Region Federation](/examples/multi-region)
|
|
1135
|
+
- [Health Monitoring](/examples/health-monitoring)
|
|
1136
|
+
|
|
1137
|
+
### Community
|
|
1138
|
+
- GitHub Issues: [Report bugs or request features](https://github.com/olane-labs/olane/issues)
|
|
1139
|
+
- Discussions: [Ask questions and share ideas](https://github.com/olane-labs/olane/discussions)
|
|
1140
|
+
- Discord: [Join our community](https://discord.gg/olane)
|
|
1141
|
+
|
|
1142
|
+
### Commercial Support
|
|
1143
|
+
For enterprise support, custom implementations, or consulting:
|
|
1144
|
+
- Email: support@olane.io
|
|
1145
|
+
- Website: https://olane.io/enterprise
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
## 13. Contributing
|
|
1150
|
+
|
|
1151
|
+
We welcome contributions! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
|
|
1152
|
+
|
|
1153
|
+
### Areas for Contribution
|
|
1154
|
+
- Registry backend implementations (PostgreSQL, Redis, MongoDB)
|
|
1155
|
+
- Performance optimizations
|
|
1156
|
+
- Security enhancements
|
|
1157
|
+
- Documentation improvements
|
|
1158
|
+
- Example projects
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
## License
|
|
83
1163
|
|
|
84
|
-
|
|
85
|
-
2. Figma example → same as above ^^
|
|
86
|
-
3. Slack / more complex examples → same as above ^
|
|
1164
|
+
ISC License - see [LICENSE](../../LICENSE) for details.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { oRequest } from '@olane/o-core';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
3
|
+
import { oNodeToolConfig } from '@olane/o-node';
|
|
4
|
+
export declare class oLeaderNode extends oLaneTool {
|
|
5
|
+
constructor(config: oNodeToolConfig);
|
|
6
|
+
initialize(): Promise<void>;
|
|
5
7
|
validateJoinRequest(request: oRequest): Promise<any>;
|
|
6
8
|
_tool_join_network(request: oRequest): Promise<any>;
|
|
7
9
|
_tool_save_plan(request: oRequest): Promise<any>;
|
|
8
|
-
_tool_index_network(request: oRequest): Promise<any>;
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=leader.node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leader.node.d.ts","sourceRoot":"","sources":["../../src/leader.node.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"leader.node.d.ts","sourceRoot":"","sources":["../../src/leader.node.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,QAAQ,EAGT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAe,eAAe,EAAmB,MAAM,eAAe,CAAC;AAG9E,qBAAa,WAAY,SAAQ,SAAS;gBAC5B,MAAM,EAAE,eAAe;IAW7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAIpD,kBAAkB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAuBnD,eAAe,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CA0CvD"}
|
package/dist/src/leader.node.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { oServerTool } from '@olane/o-tool';
|
|
1
|
+
import { NodeType, oAddress, } from '@olane/o-core';
|
|
3
2
|
import { START_METHOD } from './methods/start.method.js';
|
|
4
|
-
|
|
3
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
4
|
+
import { oSearchResolver } from '@olane/o-node';
|
|
5
|
+
import { RegistryMemoryTool } from './registry/registry-memory.tool.js';
|
|
6
|
+
export class oLeaderNode extends oLaneTool {
|
|
5
7
|
constructor(config) {
|
|
6
8
|
super({
|
|
7
9
|
...config,
|
|
8
|
-
address:
|
|
10
|
+
address: oAddress.leader(),
|
|
9
11
|
type: NodeType.LEADER,
|
|
10
12
|
methods: {
|
|
11
13
|
start: START_METHOD,
|
|
12
14
|
},
|
|
13
15
|
});
|
|
14
16
|
}
|
|
17
|
+
async initialize() {
|
|
18
|
+
await super.initialize();
|
|
19
|
+
this.router.addResolver(new oSearchResolver(this.address));
|
|
20
|
+
const registryTool = new RegistryMemoryTool({
|
|
21
|
+
name: 'registry',
|
|
22
|
+
parent: this.address,
|
|
23
|
+
leader: this.address,
|
|
24
|
+
});
|
|
25
|
+
await registryTool.start();
|
|
26
|
+
this.addChildNode(registryTool);
|
|
27
|
+
}
|
|
15
28
|
async validateJoinRequest(request) {
|
|
16
29
|
return true;
|
|
17
30
|
}
|
|
@@ -36,28 +49,9 @@ export class oLeaderNode extends oServerTool {
|
|
|
36
49
|
async _tool_save_plan(request) {
|
|
37
50
|
const { plan } = request.params;
|
|
38
51
|
this.logger.debug('Adding plan to network: ' + plan);
|
|
39
|
-
if (!this.config.
|
|
52
|
+
if (!this.config.systemName) {
|
|
40
53
|
this.logger.warn('No network name provided, cannot update config');
|
|
41
54
|
return;
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
|
-
async _tool_index_network(request) {
|
|
45
|
-
// paginate through all the registered nodes and index them
|
|
46
|
-
const nodes = await this.use(new oAddress('o://leader/register'), {
|
|
47
|
-
method: 'find_all',
|
|
48
|
-
params: {},
|
|
49
|
-
});
|
|
50
|
-
const nodesArray = nodes.result.data;
|
|
51
|
-
for (let i = 0; i < nodesArray.length; i++) {
|
|
52
|
-
// first let's get the node's tools
|
|
53
|
-
const node = nodesArray[i];
|
|
54
|
-
const { result } = await this.use(new oAddress(node.address), {
|
|
55
|
-
method: 'index_network',
|
|
56
|
-
params: {},
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
message: 'Network indexed!',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
57
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ToolResult } from '@olane/o-tool';
|
|
2
2
|
import { oRegistrationParams } from '@olane/o-protocol';
|
|
3
3
|
import { oRequest } from '@olane/o-core';
|
|
4
|
-
|
|
4
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
5
|
+
import { oNodeToolConfig } from '@olane/o-node';
|
|
6
|
+
export declare abstract class RegistryTool extends oLaneTool {
|
|
5
7
|
protected readonly registry: Map<string, oRegistrationParams>;
|
|
6
8
|
protected readonly protocolMapping: Map<string, string[]>;
|
|
7
|
-
constructor(config:
|
|
9
|
+
constructor(config: oNodeToolConfig);
|
|
8
10
|
abstract _tool_commit(request: oRequest): Promise<ToolResult>;
|
|
9
11
|
abstract _tool_search(request: oRequest): Promise<ToolResult>;
|
|
10
12
|
abstract _tool_find_all(request: oRequest): Promise<ToolResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.tool.d.ts","sourceRoot":"","sources":["../../../src/registry/registry.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"registry.tool.d.ts","sourceRoot":"","sources":["../../../src/registry/registry.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAY,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAExE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAe,eAAe,EAAE,MAAM,eAAe,CAAC;AAE7D,8BAAsB,YAAa,SAAQ,SAAS;IAClD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAa;IAC1E,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAa;gBAE1D,MAAM,EAAE,eAAe;IASnC,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAC7D,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAC7D,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAC/D,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;CAC9D"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { oAddress } from '@olane/o-core';
|
|
1
|
+
import { oAddress, RestrictedAddresses } from '@olane/o-core';
|
|
3
2
|
import { REGISTRY_PARAMS } from './methods/registry.methods.js';
|
|
4
|
-
|
|
3
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
4
|
+
export class RegistryTool extends oLaneTool {
|
|
5
5
|
constructor(config) {
|
|
6
6
|
super({
|
|
7
7
|
...config,
|
|
8
|
-
address: new oAddress(
|
|
8
|
+
address: new oAddress(RestrictedAddresses.REGISTRY),
|
|
9
9
|
methods: REGISTRY_PARAMS,
|
|
10
10
|
description: 'Network registry of tools and their respective addresses',
|
|
11
11
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-leader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -55,10 +55,11 @@
|
|
|
55
55
|
"typescript": "5.4.5"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@olane/o-config": "^0.
|
|
59
|
-
"@olane/o-core": "^0.
|
|
60
|
-
"@olane/o-
|
|
61
|
-
"@olane/o-
|
|
58
|
+
"@olane/o-config": "^0.7.1",
|
|
59
|
+
"@olane/o-core": "^0.7.1",
|
|
60
|
+
"@olane/o-lane": "^0.7.1",
|
|
61
|
+
"@olane/o-protocol": "^0.7.1",
|
|
62
|
+
"@olane/o-tool": "^0.7.1"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"debug": "^4.4.1",
|