agent-inbox 0.1.8 → 0.1.9

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.
Files changed (70) hide show
  1. package/CLAUDE.md +44 -7
  2. package/README.md +67 -24
  3. package/dist/federation/connection-manager.d.ts +13 -2
  4. package/dist/federation/connection-manager.d.ts.map +1 -1
  5. package/dist/federation/connection-manager.js +109 -10
  6. package/dist/federation/connection-manager.js.map +1 -1
  7. package/dist/index.d.mts +2 -0
  8. package/dist/index.d.ts +25 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +58 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +1 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/ipc/ipc-server.d.ts +2 -0
  15. package/dist/ipc/ipc-server.d.ts.map +1 -1
  16. package/dist/ipc/ipc-server.js +48 -0
  17. package/dist/ipc/ipc-server.js.map +1 -1
  18. package/dist/map/map-client.d.ts +100 -0
  19. package/dist/map/map-client.d.ts.map +1 -1
  20. package/dist/map/map-client.js +61 -0
  21. package/dist/map/map-client.js.map +1 -1
  22. package/dist/mcp/mcp-proxy.d.ts +28 -0
  23. package/dist/mcp/mcp-proxy.d.ts.map +1 -0
  24. package/dist/mcp/mcp-proxy.js +280 -0
  25. package/dist/mcp/mcp-proxy.js.map +1 -0
  26. package/dist/mesh/delivery-bridge.d.ts +47 -0
  27. package/dist/mesh/delivery-bridge.d.ts.map +1 -0
  28. package/dist/mesh/delivery-bridge.js +73 -0
  29. package/dist/mesh/delivery-bridge.js.map +1 -0
  30. package/dist/mesh/mesh-connector.d.ts +29 -0
  31. package/dist/mesh/mesh-connector.d.ts.map +1 -0
  32. package/dist/mesh/mesh-connector.js +36 -0
  33. package/dist/mesh/mesh-connector.js.map +1 -0
  34. package/dist/mesh/mesh-transport.d.ts +70 -0
  35. package/dist/mesh/mesh-transport.d.ts.map +1 -0
  36. package/dist/mesh/mesh-transport.js +92 -0
  37. package/dist/mesh/mesh-transport.js.map +1 -0
  38. package/dist/mesh/type-mapper.d.ts +67 -0
  39. package/dist/mesh/type-mapper.d.ts.map +1 -0
  40. package/dist/mesh/type-mapper.js +165 -0
  41. package/dist/mesh/type-mapper.js.map +1 -0
  42. package/dist/types.d.ts +29 -2
  43. package/dist/types.d.ts.map +1 -1
  44. package/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  45. package/package.json +7 -2
  46. package/src/federation/connection-manager.ts +125 -10
  47. package/src/index.ts +96 -5
  48. package/src/ipc/ipc-server.ts +58 -0
  49. package/src/map/map-client.ts +152 -0
  50. package/src/mcp/mcp-proxy.ts +326 -0
  51. package/src/mesh/delivery-bridge.ts +110 -0
  52. package/src/mesh/mesh-connector.ts +41 -0
  53. package/src/mesh/mesh-transport.ts +157 -0
  54. package/src/mesh/type-mapper.ts +239 -0
  55. package/src/types.ts +33 -1
  56. package/test/federation/integration.test.ts +37 -3
  57. package/test/federation/sdk-integration.test.ts +4 -8
  58. package/test/ipc-new-commands.test.ts +200 -0
  59. package/test/mcp-proxy.test.ts +191 -0
  60. package/test/mesh/delivery-bridge.test.ts +178 -0
  61. package/test/mesh/e2e-mesh.test.ts +527 -0
  62. package/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  63. package/test/mesh/federation-mesh.test.ts +269 -0
  64. package/test/mesh/mesh-connector.test.ts +66 -0
  65. package/test/mesh/mesh-transport.test.ts +191 -0
  66. package/test/mesh/meshpeer-integration.test.ts +442 -0
  67. package/test/mesh/mock-mesh.ts +125 -0
  68. package/test/mesh/mock-meshpeer.ts +266 -0
  69. package/test/mesh/type-mapper.test.ts +226 -0
  70. package/docs/PLAN.md +0 -545
package/docs/PLAN.md DELETED
@@ -1,545 +0,0 @@
1
- # Agent Inbox — Implementation Plan
2
-
3
- ## Project Structure
4
-
5
- ```
6
- agent-inbox/
7
- ├── package.json
8
- ├── tsconfig.json
9
- ├── vitest.config.ts
10
- ├── DESIGN.md
11
- ├── src/
12
- │ ├── index.ts # Entry point — starts IPC server + MAP connection + MCP server
13
- │ ├── types.ts # Core types: Agent, Message, Recipient, Conversation, Turn, Thread, MessageContent
14
- │ │
15
- │ ├── storage/
16
- │ │ ├── interface.ts # Storage interface
17
- │ │ └── memory.ts # InMemoryStorage implementation
18
- │ │
19
- │ ├── router/
20
- │ │ └── message-router.ts # Message routing logic: resolve recipients, local vs remote delivery
21
- │ │
22
- │ ├── traceability/
23
- │ │ └── traceability.ts # Auto-create conversations/turns/threads from messaging events
24
- │ │
25
- │ ├── ipc/
26
- │ │ └── ipc-server.ts # UNIX socket server (NDJSON protocol, same pattern as sidecar)
27
- │ │
28
- │ ├── map/
29
- │ │ └── map-client.ts # MAP SDK connection wrapper (connect, send, receive, lifecycle)
30
- │ │
31
- │ └── mcp/
32
- │ └── mcp-server.ts # MCP tools: send_message, check_inbox, read_thread, list_agents
33
-
34
- └── test/
35
- ├── storage.test.ts
36
- ├── message-router.test.ts
37
- ├── traceability.test.ts
38
- ├── ipc-server.test.ts
39
- └── mcp-server.test.ts
40
- ```
41
-
42
- ## Implementation Steps
43
-
44
- ### Step 1: Project Scaffolding
45
-
46
- Set up the TypeScript project with dependencies.
47
-
48
- **package.json**:
49
- - `typescript`, `vitest` (dev)
50
- - `@multi-agent-protocol/sdk` (peer, optional — same as claude-code-swarm)
51
- - `@modelcontextprotocol/sdk` (MCP server)
52
- - `ulid` (ID generation)
53
-
54
- **tsconfig.json**: ESM output, strict mode, Node 18+ target.
55
-
56
- ### Step 2: Core Types (`src/types.ts`)
57
-
58
- Define all data model types from the design doc:
59
- - `Agent` — agent_id, display_name, program, model, scope, status, metadata, timestamps
60
- - `Message` — id, scope, sender_id, recipients, subject, content, thread_tag, in_reply_to, importance, metadata, created_at
61
- - `Recipient` — agent_id, kind (to/cc/bcc), delivered_at, read_at, ack_at
62
- - `MessageContent` — union type: text, data, event, reference, x-*
63
- - `Conversation`, `Turn`, `Thread` — traceability entities
64
- - `IpcCommand` / `IpcResponse` — UNIX socket protocol shapes (matching sidecar conventions)
65
-
66
- ### Step 3: Storage Interface + InMemoryStorage
67
-
68
- **`src/storage/interface.ts`**:
69
- ```typescript
70
- interface Storage {
71
- getAgent(agentId: string): Agent | undefined;
72
- putAgent(agent: Agent): void;
73
- listAgents(scope: string): Agent[];
74
-
75
- getMessage(id: string): Message | undefined;
76
- putMessage(message: Message): Message;
77
- getInbox(agentId: string, opts?: { unreadOnly?: boolean }): Message[];
78
- getThread(threadTag: string, scope: string): Message[];
79
- searchMessages(query: string, scope: string): Message[];
80
-
81
- getConversation(id: string): Conversation | undefined;
82
- putConversation(conversation: Conversation): Conversation;
83
- listConversations(scope: string): Conversation[];
84
- addTurn(turn: Turn): void;
85
- getTurns(conversationId: string): Turn[];
86
- }
87
- ```
88
-
89
- **`src/storage/memory.ts`**: Maps and arrays. `getInbox` filters by recipient agent_id + unread. `getThread` filters by thread_tag + scope. `searchMessages` does simple substring matching.
90
-
91
- **Tests**: CRUD operations, inbox filtering, thread retrieval.
92
-
93
- ### Step 4: Message Router (`src/router/message-router.ts`)
94
-
95
- Core routing logic:
96
- - `resolveRecipients(to)` — resolve target(s) from agent ID, scope, or qualified address
97
- - `normalizeContent(payload)` — wrap plain strings as `{ type: "text", text: ... }`, pass structured content through
98
- - `routeMessage(command)` — create Message record, resolve recipients, determine local vs remote delivery
99
- - `isLocal(agentId)` — check if agent is in local registry
100
-
101
- For Phase 1, all delivery is local (single MAP server). Federation routing deferred to Phase 3.
102
-
103
- **Tests**: Recipient resolution, content normalization, local routing.
104
-
105
- ### Step 5: Traceability Layer (`src/traceability/traceability.ts`)
106
-
107
- Auto-creates traceability records from messaging events:
108
- - Message with `conversationId` → add Turn to that Conversation
109
- - Message with `thread_tag`, no `conversationId` → find or create Conversation for `thread_tag + scope`, add Turn
110
- - Message with neither → add Turn to catch-all Conversation for scope
111
- - Reply (`in_reply_to`) → create Thread if not exists, link Turn
112
-
113
- Uses an internal EventEmitter — the message router emits `message.created` events, the traceability layer subscribes.
114
-
115
- **Tests**: Auto-conversation creation, thread creation from replies, catch-all grouping.
116
-
117
- ### Step 6: IPC Server (`src/ipc/ipc-server.ts`)
118
-
119
- UNIX socket server using the same NDJSON protocol as the sidecar. This is how the claude-code-swarm plugin communicates with Agent Inbox.
120
-
121
- **Socket path**: Configurable, default `~/.claude/agent-inbox/inbox.sock` (or per-session).
122
-
123
- **Commands handled**:
124
-
125
- | Command | Action |
126
- |---------|--------|
127
- | `{ action: "send", from, to, payload, threadTag?, inReplyTo?, meta? }` | Route message via message-router |
128
- | `{ action: "emit", event, meta? }` | Broadcast to scope via MAP connection |
129
- | `{ action: "notify", event: { type: "agent.spawn", agent: {...} } }` | Update agent registry |
130
- | `{ action: "notify", event: { type: "agent.done", agentId: "..." } }` | Mark agent offline |
131
- | `{ action: "ping" }` | Health check → `{ ok: true, pid }` |
132
-
133
- **Response**: `{ ok: true, messageId?: string }` or `{ ok: false, error: string }`
134
-
135
- Same NDJSON-over-UNIX-socket pattern as sidecar-server.mjs: parse newline-delimited JSON, dispatch command, respond with JSON + newline.
136
-
137
- **Tests**: Socket connection, command dispatch, response format, concurrent clients.
138
-
139
- ### Step 7: MAP Client (`src/map/map-client.ts`)
140
-
141
- Wrapper around `@multi-agent-protocol/sdk` for Agent Inbox's MAP connection.
142
-
143
- - `connect(serverUrl, opts)` — connect to MAP server as `agent-inbox` role
144
- - `send(target, message)` — deliver message to specific agent via MAP
145
- - `onMessage(handler)` — receive incoming messages, store in inbox, write to inbox.jsonl
146
- - `disconnect()` — clean shutdown
147
-
148
- The MAP client is **optional** — Agent Inbox can run without a MAP server in standalone/testing mode (IPC + MCP only, no MAP transport).
149
-
150
- When connected, incoming MAP messages are:
151
- 1. Stored via `storage.putMessage()`
152
- 2. Processed by traceability layer
153
- 3. Written to `inbox.jsonl` for Claude context injection (same format as sidecar)
154
-
155
- **Tests**: Mock MAP connection, message receive flow, standalone mode.
156
-
157
- ### Step 8: MCP Tools Server (`src/mcp/mcp-server.ts`)
158
-
159
- MCP server exposing agent-facing tools via `@modelcontextprotocol/sdk`.
160
-
161
- **Phase 1 tools**:
162
-
163
- | Tool | Parameters | Returns |
164
- |------|-----------|---------|
165
- | `send_message` | `to, body?, content?, from?, threadTag?, inReplyTo?, importance?, subject?` | `{ ok, messageId }` |
166
- | `check_inbox` | `agentId, unreadOnly?, limit?` | `{ count, messages: [...] }` |
167
- | `read_thread` | `threadTag, scope?` | `{ threadTag, count, messages: [...] }` |
168
- | `list_agents` | `scope?, includeFederated?` | `{ count, agents: [...] }` |
169
-
170
- Registration is handled automatically (auto-register on first `check_inbox`). Replies use `send_message` with `inReplyTo`. Acknowledgement is implicit on read. Each tool delegates to the message router and storage. The MCP server runs as a stdio transport (standard for Claude Code MCP tools).
171
-
172
- **Tests**: Tool invocation, parameter validation, response format.
173
-
174
- ### Step 9: Entry Point (`src/index.ts`)
175
-
176
- Wire everything together:
177
-
178
- ```typescript
179
- // 1. Create storage
180
- const storage = new InMemoryStorage();
181
-
182
- // 2. Create internal event emitter
183
- const events = new EventEmitter();
184
-
185
- // 3. Create traceability layer (subscribes to events)
186
- const traceability = new TraceabilityLayer(storage, events);
187
-
188
- // 4. Create message router
189
- const router = new MessageRouter(storage, events);
190
-
191
- // 5. Optionally connect to MAP server
192
- const mapClient = config.map?.server
193
- ? await MapClient.connect(config.map.server, config.map)
194
- : null;
195
-
196
- // 6. Start IPC server (UNIX socket)
197
- const ipcServer = new IpcServer(socketPath, router, storage, mapClient);
198
- await ipcServer.start();
199
-
200
- // 7. Start MCP server (stdio)
201
- const mcpServer = new McpServer(router, storage);
202
- await mcpServer.start();
203
- ```
204
-
205
- ### Step 10: Integration Testing
206
-
207
- End-to-end tests:
208
- 1. **IPC flow**: Send command via UNIX socket → message stored → appears in inbox
209
- 2. **MCP flow**: Call `send_message` tool → message routed → check_inbox returns it
210
- 3. **MAP flow** (with mock server): Receive MAP message → stored in inbox → written to inbox.jsonl
211
- 4. **Traceability**: Send messages with thread_tags → conversations auto-created
212
- 5. **Lifecycle notifications**: Plugin sends spawn/done notify → agent registry updated
213
- 6. **Graceful degradation**: MAP unavailable → IPC still works, MCP still works
214
-
215
- ## Dependency Graph
216
-
217
- ```
218
- types.ts ← storage/interface.ts ← storage/memory.ts
219
-
220
- types.ts ← router/message-router.ts (depends on storage, events)
221
-
222
- types.ts ← traceability/traceability.ts (depends on storage, events)
223
-
224
- ipc/ipc-server.ts (depends on router, storage, map-client)
225
- map/map-client.ts (depends on storage, events, MAP SDK)
226
- mcp/mcp-server.ts (depends on router, storage)
227
-
228
- index.ts (wires everything)
229
- ```
230
-
231
- Build order: types → storage → router → traceability → ipc/map/mcp → index
232
-
233
- ## Phase 2: Traceability + MAP Mail
234
-
235
- - SQLiteStorage implementation (persistent storage with FTS5)
236
- - MAP JSON-RPC endpoint (`mail/*` methods)
237
- - Full-text search via SQLite
238
- - `map/subscribe` real-time event streaming
239
- - Full traceability: conversations, turns, threads with auto-grouping
240
-
241
- ## Phase 3: Federation + Multi-Server
242
-
243
- Federation uses MAP's native `federation/connect` and `federation/route` protocol. Agent Inbox acts as a **gateway agent** in MAP's federation model.
244
-
245
- ### Project Structure Additions
246
-
247
- ```
248
- agent-inbox/
249
- ├── src/
250
- │ ├── federation/
251
- │ │ ├── connection-manager.ts # Manage MAP + peer connections, federation/connect handshake
252
- │ │ ├── routing-engine.ts # Configurable routing: table / broadcast / hierarchical
253
- │ │ ├── delivery-queue.ts # Queue messages for offline peers (memory or SQLite)
254
- │ │ ├── address.ts # Parse/resolve agent-id@system-id addresses
255
- │ │ └── trust.ts # Trust policies: allow-list, scope perms, auth (stubs)
256
- │ │
257
- │ ├── registry/
258
- │ │ └── warm-registry.ts # Agent registry with active/away/expired lifecycle + TTL
259
- │ │
260
- │ ├── map/
261
- │ │ └── map-client.ts # Updated: system ID resolution from MAP systemInfo
262
- │ │
263
- │ └── types.ts # Updated: federation types
264
-
265
- └── test/
266
- ├── federation/
267
- │ ├── connection-manager.test.ts
268
- │ ├── routing-engine.test.ts
269
- │ ├── delivery-queue.test.ts
270
- │ ├── address.test.ts
271
- │ └── trust.test.ts
272
- └── registry/
273
- └── warm-registry.test.ts
274
- ```
275
-
276
- ### Step 11: Core Types — Federation Extensions (`src/types.ts`)
277
-
278
- New types for federation:
279
-
280
- ```typescript
281
- // System identity
282
- interface SystemId {
283
- id: string; // resolved system ID
284
- source: 'config' | 'map' | 'auto'; // how it was resolved
285
- }
286
-
287
- // Federated addressing
288
- interface FederatedAddress {
289
- agent?: string; // target agent ID
290
- system?: string; // target system ID
291
- scope?: string; // optional scope qualifier
292
- }
293
-
294
- // Agent registry with warm model
295
- type AgentStatus = 'active' | 'away' | 'expired';
296
-
297
- interface AgentRegistryConfig {
298
- gracePeriodMs: number; // disconnect → expired (default: 60000)
299
- retainExpiredMs: number; // keep expired entries (default: 3600000)
300
- requeueOnReconnect: boolean; // flush queued messages on reconnect (default: true)
301
- }
302
-
303
- // Federation peer configuration
304
- interface FederationPeerConfig {
305
- systemId: string; // human-friendly alias
306
- url: string; // MAP server URL
307
- auth?: FederationAuth; // auth for federation/connect
308
- exposure?: ExposurePolicy; // what we expose to this peer
309
- }
310
-
311
- // Federation link state
312
- interface FederationLink {
313
- peerId: string;
314
- sessionId: string;
315
- status: 'connected' | 'disconnected' | 'authenticating';
316
- exposure: ExposurePolicy;
317
- }
318
-
319
- // Exposure control (MAP model)
320
- type ExposureLevel = 'none' | 'gateway' | 'tagged' | 'all';
321
-
322
- interface ExposurePolicy {
323
- agents: ExposureLevel;
324
- scopes?: string[]; // for 'tagged' level
325
- events?: ExposureLevel;
326
- }
327
-
328
- // Routing configuration
329
- interface FederationRoutingConfig {
330
- strategy: 'table' | 'broadcast' | 'hierarchical';
331
- tableTTL?: number; // routing entry TTL (default: 300000)
332
- refreshOnMiss?: boolean; // query peers on cache miss (default: true)
333
- broadcastTimeout?: number; // ms for broadcast strategy (default: 5000)
334
- upstream?: string[]; // for hierarchical strategy
335
- }
336
-
337
- // Delivery queue configuration
338
- interface DeliveryQueueConfig {
339
- persistence: 'memory' | 'sqlite';
340
- maxTTL: number; // default: 86400000 (24h)
341
- maxQueueSize: number; // per destination (default: 10000)
342
- retryStrategy: 'exponential' | 'fixed';
343
- retryBaseInterval: number; // ms (default: 1000)
344
- retryMaxAttempts: number; // 0 = unlimited until TTL
345
- flushOnReconnect: boolean; // default: true
346
- overflow: 'drop-oldest' | 'drop-newest' | 'reject-new';
347
- }
348
-
349
- // Trust policies
350
- interface FederationTrustPolicy {
351
- allowedServers: string[]; // implemented first
352
- scopePermissions: Record<string, string[]>; // stub
353
- requireAuth: boolean; // stub
354
- authMethod?: 'bearer' | 'api-key' | 'mtls' | 'did:wba';
355
- authTokens?: Record<string, string>; // stub
356
- }
357
- ```
358
-
359
- ### Step 12: Address Resolution (`src/federation/address.ts`)
360
-
361
- Parse and resolve federated addresses:
362
-
363
- - `parseAddress("agent-beta")` → `{ agent: "agent-beta" }` (local)
364
- - `parseAddress("agent-beta@backend-team")` → `{ agent: "agent-beta", system: "backend-team" }` (federated)
365
- - `parseAddress("@backend-team")` → `{ system: "backend-team" }` (broadcast to system)
366
-
367
- Resolution order:
368
- 1. No `@` → local registry lookup (same scope first, then all scopes)
369
- 2. With `@` → connection manager resolves system ID to peer connection
370
- 3. With `to_scope` → scope-qualified lookup (local or remote)
371
-
372
- **Tests**: Parse formats, resolution order, unknown agent/system handling.
373
-
374
- ### Step 13: Warm Agent Registry (`src/registry/warm-registry.ts`)
375
-
376
- Wraps the storage layer with TTL-based lifecycle:
377
-
378
- - `register(agentId, ...)` — set status `active`. Reject if ID taken by active/away agent (first-wins).
379
- - `disconnect(agentId)` — set status `away`, start grace period timer
380
- - `reconnect(agentId)` — set status back to `active`, cancel grace timer, flush queued messages
381
- - `expire(agentId)` — set status `expired` (called by timer after grace period)
382
- - `cleanup()` — remove entries past `retainExpiredMs`
383
- - `isRoutable(agentId)` → true for `active` or `away`
384
- - `getStatus(agentId)` → `active` | `away` | `expired` | `unknown`
385
-
386
- **Tests**: Lifecycle transitions, grace period expiry, reconnect flush, ID conflict rejection.
387
-
388
- ### Step 14: Connection Manager (`src/federation/connection-manager.ts`)
389
-
390
- Manages MAP connections and federation peer links:
391
-
392
- - `connect(serverUrl, opts)` → connect to a MAP server (local agent traffic)
393
- - `federate(peer: FederationPeerConfig)` → establish `federation/connect` with auth + exposure exchange
394
- - `resolve(address: FederatedAddress)` → `{ connection, peerId }` or undefined
395
- - `route(message)` → determine correct connection + deliver via `federation/route`
396
- - `peers()` → list all federation links and their status
397
- - `disconnect(peerId)` → tear down a specific peer link
398
-
399
- System ID tiered resolution on startup:
400
- 1. Explicit config (`INBOX_SYSTEM_ID`)
401
- 2. Derived from MAP server `systemInfo.name` (from `map/connect` response)
402
- 3. Auto-generated `inbox-{random}` (persisted to file for restart stability)
403
-
404
- **Tests**: Multi-connection management, federation handshake (mocked), peer lifecycle.
405
-
406
- ### Step 15: Routing Engine (`src/federation/routing-engine.ts`)
407
-
408
- Configurable routing strategy:
409
-
410
- **Table strategy** (default):
411
- - Populate routing table from `federation/connect` exposure data
412
- - Entries: `{ agentId → peerId, lastSeen, status }`
413
- - TTL-based expiry
414
- - On cache miss + `refreshOnMiss`: query peers before failing
415
-
416
- **Broadcast strategy**:
417
- - Forward to all connected peers
418
- - First responder wins (within `broadcastTimeout`)
419
-
420
- **Hierarchical strategy**:
421
- - Check local routing table first
422
- - On miss, delegate to configured `upstream` hub(s)
423
-
424
- All strategies share the same interface: `resolveRoute(address) → peerId | null`
425
-
426
- **Tests**: Table population/expiry, broadcast fanout, hierarchical delegation, strategy switching.
427
-
428
- ### Step 16: Delivery Queue (`src/federation/delivery-queue.ts`)
429
-
430
- Queue messages for offline/unreachable peers:
431
-
432
- - `enqueue(peerId, message)` — add to queue (check overflow policy)
433
- - `flush(peerId)` — drain queue on reconnect (if `flushOnReconnect`)
434
- - `tick()` — process retries (exponential or fixed backoff), expire past TTL
435
- - `size(peerId)` → current queue depth
436
-
437
- Supports `memory` or `sqlite` persistence backends.
438
-
439
- **Tests**: Enqueue/flush, TTL expiry, overflow policies, retry backoff timing.
440
-
441
- ### Step 17: Trust Policies (`src/federation/trust.ts`)
442
-
443
- Layered trust enforcement:
444
-
445
- - **Layer 1 (implemented)**: `allowedServers` — reject `federation/connect` from unknown systems
446
- - **Layer 2 (stub)**: `scopePermissions` — always allow (returns true)
447
- - **Layer 3 (stub)**: `requireAuth` — always pass (returns true)
448
-
449
- Interface:
450
- - `canConnect(systemId)` → boolean
451
- - `canRoute(fromSystem, toScope)` → boolean
452
- - `validateAuth(systemId, credentials)` → boolean
453
-
454
- **Tests**: Allow-list enforcement, stub passthrough behavior.
455
-
456
- ### Step 18: Integration — Wire Federation into Existing Code
457
-
458
- Update existing components:
459
-
460
- **`src/router/message-router.ts`**:
461
- - `resolveRecipients()` now calls `parseAddress()` for `@system` format
462
- - `routeMessage()` checks `isLocal()` first, then delegates to connection manager for remote
463
- - Remote delivery goes through routing engine → connection manager → `federation/route`
464
-
465
- **`src/map/map-client.ts`**:
466
- - Extract `systemInfo.name` from `map/connect` response for system ID resolution
467
- - Support multiple connections (local MAP + federation peers)
468
-
469
- **`src/index.ts`**:
470
- - Load federation config from env/config file
471
- - Initialize warm registry, connection manager, routing engine, delivery queue, trust policies
472
- - Wire into existing message router
473
-
474
- **`src/mcp/mcp-server.ts`**:
475
- - `send_message` `to` parameter accepts `agent@system` format
476
- - `list_agents` can optionally include federated agents (from routing table)
477
-
478
- ### Step 19: Federation Integration Testing
479
-
480
- End-to-end tests:
481
-
482
- 1. **Federated send**: Agent A on system 1 → `send_message(to="AgentC@system2")` → routed via federation/route → delivered
483
- 2. **Hub relay**: System A → Hub → System B routing
484
- 3. **Warm registry**: Agent disconnects → messages queue → agent reconnects → messages flush
485
- 4. **Offline peer**: Peer disconnects → messages queue → peer reconnects → queue flushes
486
- 5. **Trust enforcement**: Unknown system tries to federate → rejected by allow-list
487
- 6. **Routing strategies**: Table miss + refresh, broadcast fanout, hierarchical delegation
488
- 7. **Address parsing**: All address formats resolve correctly
489
- 8. **System ID resolution**: Config > MAP systemInfo > auto-generated, persisted across restarts
490
-
491
- ### Step 20: ConnectionManager Transport Layer
492
-
493
- Wire real MAP SDK connections into ConnectionManager so federation routing
494
- actually sends/receives messages over the wire.
495
-
496
- **Design decisions:**
497
- - **SDK injection (option B)**: `index.ts` imports the MAP SDK once (via MapClient's
498
- existing dynamic import path), then passes the `AgentConnection` class to
499
- ConnectionManager. No duplicate dynamic imports.
500
- - **Incoming messages delegate to router**: When a federated peer sends a message,
501
- ConnectionManager receives it via `conn.onMessage()` and delegates to
502
- `MessageRouter.routeMessage()` to store and distribute locally. Clean boundary.
503
- - **MapClient stays single-connection**: Only handles the local MAP server.
504
- ConnectionManager owns all federation peer connections.
505
-
506
- **Changes:**
507
-
508
- **`src/federation/connection-manager.ts`**:
509
- - New constructor param: `sdkConnectFn` — `(url, opts) => Promise<MapConnection>`
510
- (the bound `AgentConnection.connect` from the SDK). Optional — when null,
511
- federation stays event-based (no transport, for testing).
512
- - New private field: `connections: Map<string, MapConnection>` — real SDK connections
513
- per federated peer.
514
- - `federate()` updated: after trust check + creating FederationLink, calls
515
- `sdkConnectFn(peer.url, { name: systemId, role: "gateway", ... })` to open
516
- a real connection. Registers `conn.onMessage()` handler that delegates to
517
- the injected message handler callback.
518
- - `route()` updated: instead of emitting `federation.route` event, calls
519
- `this.connections.get(peerId).send(target, payload)`.
520
- - `disconnect()` updated: calls `conn.disconnect()` on the real connection.
521
- - New constructor param: `onIncomingMessage` callback — called when a peer
522
- connection receives a message. `index.ts` wires this to `router.routeMessage()`.
523
-
524
- **`src/index.ts`**:
525
- - After MapClient successfully imports the SDK, extract `AgentConnection.connect`
526
- and pass it to ConnectionManager.
527
- - Pass `onIncomingMessage` callback that calls `router.routeMessage()` with the
528
- incoming message data, setting the sender to `sender@peerId` format.
529
-
530
- **`src/map/map-client.ts`**:
531
- - Expose `getAgentConnectionClass()` — returns the dynamically imported
532
- `AgentConnection` class (or undefined if SDK not available). Called by
533
- `index.ts` to inject into ConnectionManager.
534
-
535
- **Testing:**
536
- - Unit tests: mock the `sdkConnectFn` to return a fake connection object.
537
- Verify `federate()` opens connections, `route()` calls `conn.send()`,
538
- incoming messages are forwarded via callback.
539
- - Integration tests: extend existing suite with transport-level scenarios.
540
-
541
- ## Deferred (Future)
542
-
543
- - **agentic-mesh integration**: Optional transport layer alternative to direct MAP federation. Kept in plans but not in Phase 3 scope.
544
- - **Federation discovery**: Automatic peer discovery via `.well-known` or `did:wba`. Phase 3 uses explicit config only.
545
- - **Transitive routing**: System A → Hub → System B (hub relays without explicit peer link between A and B). Partially supported by hierarchical routing strategy but trust implications need design.