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.
- package/CLAUDE.md +44 -7
- package/README.md +67 -24
- package/dist/federation/connection-manager.d.ts +13 -2
- package/dist/federation/connection-manager.d.ts.map +1 -1
- package/dist/federation/connection-manager.js +109 -10
- package/dist/federation/connection-manager.js.map +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ipc/ipc-server.d.ts +2 -0
- package/dist/ipc/ipc-server.d.ts.map +1 -1
- package/dist/ipc/ipc-server.js +48 -0
- package/dist/ipc/ipc-server.js.map +1 -1
- package/dist/map/map-client.d.ts +100 -0
- package/dist/map/map-client.d.ts.map +1 -1
- package/dist/map/map-client.js +61 -0
- package/dist/map/map-client.js.map +1 -1
- package/dist/mcp/mcp-proxy.d.ts +28 -0
- package/dist/mcp/mcp-proxy.d.ts.map +1 -0
- package/dist/mcp/mcp-proxy.js +280 -0
- package/dist/mcp/mcp-proxy.js.map +1 -0
- package/dist/mesh/delivery-bridge.d.ts +47 -0
- package/dist/mesh/delivery-bridge.d.ts.map +1 -0
- package/dist/mesh/delivery-bridge.js +73 -0
- package/dist/mesh/delivery-bridge.js.map +1 -0
- package/dist/mesh/mesh-connector.d.ts +29 -0
- package/dist/mesh/mesh-connector.d.ts.map +1 -0
- package/dist/mesh/mesh-connector.js +36 -0
- package/dist/mesh/mesh-connector.js.map +1 -0
- package/dist/mesh/mesh-transport.d.ts +70 -0
- package/dist/mesh/mesh-transport.d.ts.map +1 -0
- package/dist/mesh/mesh-transport.js +92 -0
- package/dist/mesh/mesh-transport.js.map +1 -0
- package/dist/mesh/type-mapper.d.ts +67 -0
- package/dist/mesh/type-mapper.d.ts.map +1 -0
- package/dist/mesh/type-mapper.js +165 -0
- package/dist/mesh/type-mapper.js.map +1 -0
- package/dist/types.d.ts +29 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
- package/package.json +7 -2
- package/src/federation/connection-manager.ts +125 -10
- package/src/index.ts +96 -5
- package/src/ipc/ipc-server.ts +58 -0
- package/src/map/map-client.ts +152 -0
- package/src/mcp/mcp-proxy.ts +326 -0
- package/src/mesh/delivery-bridge.ts +110 -0
- package/src/mesh/mesh-connector.ts +41 -0
- package/src/mesh/mesh-transport.ts +157 -0
- package/src/mesh/type-mapper.ts +239 -0
- package/src/types.ts +33 -1
- package/test/federation/integration.test.ts +37 -3
- package/test/federation/sdk-integration.test.ts +4 -8
- package/test/ipc-new-commands.test.ts +200 -0
- package/test/mcp-proxy.test.ts +191 -0
- package/test/mesh/delivery-bridge.test.ts +178 -0
- package/test/mesh/e2e-mesh.test.ts +527 -0
- package/test/mesh/e2e-real-meshpeer.test.ts +629 -0
- package/test/mesh/federation-mesh.test.ts +269 -0
- package/test/mesh/mesh-connector.test.ts +66 -0
- package/test/mesh/mesh-transport.test.ts +191 -0
- package/test/mesh/meshpeer-integration.test.ts +442 -0
- package/test/mesh/mock-mesh.ts +125 -0
- package/test/mesh/mock-meshpeer.ts +266 -0
- package/test/mesh/type-mapper.test.ts +226 -0
- 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.
|