overmind-mcp 3.5.2 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,892 +0,0 @@
1
- # 🌉 Overmind Bridge — Agents Persistants & A2A
2
-
3
- > **Version**: Overmind v3.5.0 — `src/bridge/` (1928 lignes OverBridgeServer + 443 OverBridgeService + 259 AgentRegistry)
4
- > **Date**: 2026-07-09
5
- > **Source**: `src/bridge/OverBridgeServer.ts`, `src/bridge/OverBridgeService.ts`, `src/bridge/AgentRegistry.ts`, `src/bin/overmind-bridge.ts`, `discord_llm/src/overmind-bridge.ts`
6
-
7
- ---
8
-
9
- ## Table des Matières
10
-
11
- 1. [Vue d'Ensemble](#vue-densemble)
12
- 2. [Architecture du Bridge](#architecture-du-bridge)
13
- 3. [Les 3 Couches de Persistance](#les-3-couches-de-persistance)
14
- 4. [AgentRegistry — État Live en Mémoire](#agentregistry--état-live-en-mémoire)
15
- 5. [SessionStore — Sessions Multi-Tenant](#sessionstore--sessions-multi-tenant)
16
- 6. [MessageLog — Persistence Postgres](#messagelog--persistence-postgres)
17
- 7. [JSON-RPC 2.0 — Toutes les Méthodes](#json-rpc-20--toutes-les-méthodes)
18
- 8. [A2A — Agent-to-Agent](#a2a--agent-to-agent)
19
- 9. [A2A Extended — Broadcast, Pipeline, Fanout, Delegate, Query](#a2a-extended--broadcast-pipeline-fanout-delegate-query)
20
- 10. [Comment Construire un Agent Persistant (comme discord_llm)](#comment-construire-un-agent-persistant-comme-discord_llm)
21
- 11. [CLI overmind-bridge](#cli-overmind-bridge)
22
- 12. [Démarrer le Serveur Bridge](#démarrer-le-serveur-bridge)
23
-
24
- ---
25
-
26
- ## Vue d'Ensemble
27
-
28
- L'Overmind Bridge est un **serveur HTTP JSON-RPC 2.0** qui transforme les appels MCP one-shot en conversations persistantes et multi-tenant. C'est la couche qui rend les agents "always-on" — même entre deux requêtes Discord, l'état de l'agent (session, contexte, mémoire) est préservé.
29
-
30
- ### Le Problème Résolu
31
-
32
- ```
33
- Sans Bridge:
34
- Discord !sniper → spawn hermes CLI → répond → meurt
35
- Discord !sniper → spawn hermes CLI → répond → meurt (perte de contexte!)
36
-
37
- Avec Bridge:
38
- Discord !sniper → POST /rpc agent.run → agent (session persistée)
39
- Discord !sniper → POST /rpc agent.run → agent (MÊME session, contexte conservé!)
40
- ```
41
-
42
- ---
43
-
44
- ## Architecture du Bridge
45
-
46
- ```
47
- ┌──────────────────────────────────────────────────────────────────┐
48
- │ CLIENTS (Discord, curl, Python) │
49
- │ │
50
- │ discord_llm/overmind-bridge.ts overmind-bridge CLI │
51
- │ (Express, port 3001) (call, scenario, status) │
52
- └──────────────────────┬───────────────────────┬───────────────────┘
53
- │ POST /rpc │
54
- │ JSON-RPC 2.0 │
55
- ▼ ▼
56
- ┌──────────────────────────────────────────────────────────────────┐
57
- │ OverBridgeServer (port 3100) │
58
- │ src/bridge/OverBridgeServer.ts │
59
- │ │
60
- │ ┌─────────────────────────────────────────────────────────────┐ │
61
- │ │ dispatchRpc() — route les méthodes JSON-RPC │ │
62
- │ │ agent.run | agent.a2a | agent.broadcast | agent.pipeline │ │
63
- │ │ agent.fanout | agent.delegate | agent.query │ │
64
- │ │ agent.status | agent.list | agent.kill │ │
65
- │ │ message.history | message.get | message.replay | stats │ │
66
- │ │ session.get | session.list | session.delete | session.stats│ │
67
- │ │ webhook.sms | health.ping │ │
68
- │ └─────────────────────────────────────────────────────────────┘ │
69
- │ │
70
- │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
71
- │ │ AgentRegistry│ │ SessionStore │ │ MessageLog (Postgres) │ │
72
- │ │ (in-memory) │ │ (JSON file) │ │ (bridge_messages table) │ │
73
- │ │ busy/idle │ │ externalKey │ │ from/to/prompt/response │ │
74
- │ │ mutex/agent │ │ → sessionId │ │ status: pending→done │ │
75
- │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
76
- │ │
77
- │ ┌─────────────────────────────────────────────────────────────┐ │
78
- │ │ OverBridgeService (SDK wrapper) │ │
79
- │ │ → BridgeProxy → HTTP POST http://[::1]:3099/mcp │ │
80
- │ │ → Overmind MCP Server → HermesGatewayRunner → Gateway :8642 │ │
81
- │ └─────────────────────────────────────────────────────────────┘ │
82
- └──────────────────────────────────────────────────────────────────┘
83
- ```
84
-
85
- ---
86
-
87
- ## Les 3 Couches de Persistance
88
-
89
- | Couche | Stockage | TTL | Rôle |
90
- |--------|----------|-----|------|
91
- | **AgentRegistry** | In-memory (Map) | Session live | Mutex par agent (1 run à la fois), statut busy/idle/online |
92
- | **SessionStore** | JSON file (`~/.overmind/bridge/sessions.json`) | 4h | Map `externalKey` → `sessionId` (multi-tenant: 1 session par utilisateur Discord) |
93
- | **MessageLog** | PostgreSQL (`bridge_messages` table) | Permanent | Historique complet: from, to, prompt, response, status, timestamps |
94
-
95
- ---
96
-
97
- ## AgentRegistry — État Live en Mémoire
98
-
99
- **Source**: `src/bridge/AgentRegistry.ts`
100
-
101
- Tracke l'état live de chaque agent avec un **mutex par agent** pour sérialiser les appels concurrents :
102
-
103
- ```typescript
104
- interface AgentLiveState {
105
- name: string;
106
- runner: RunnerType;
107
- status: 'online' | 'offline' | 'busy' | 'idle';
108
- pid?: number;
109
- currentSessionId?: string; // Session en cours (si busy)
110
- lastActivityAt: number;
111
- totalRuns: number;
112
- totalErrors: number;
113
- a2aReceived: number; // Compteur A2A reçu
114
- a2aSent: number; // Compteur A2A envoyé
115
- }
116
- ```
117
-
118
- ### Mutex — 1 Run à la Fois par Agent
119
-
120
- ```typescript
121
- // Si agent "trader_btc" est déjà en train de tourner,
122
- // le 2e appel attend que le 1er termine.
123
- await this.registry.withLock('trader_btc', async () => {
124
- this.registry.markBusy('trader_btc', sessionId);
125
- const result = await this.service.runAgent({ ... });
126
- this.registry.markIdle('trader_btc', !result.isError);
127
- return result;
128
- });
129
- ```
130
-
131
- ### Méthodes Disponibles
132
-
133
- | Méthode | Description |
134
- |---------|-------------|
135
- | `register(name, runner)` | Enregistre l'agent dans le registry |
136
- | `markBusy(name, sessionId)` | Marque comme occupé (run en cours) |
137
- | `markIdle(name, success)` | Marque comme libre + incrémente compteurs |
138
- | `markOnline(name)` | Marque comme en ligne |
139
- | `markOffline(name)` | Marque comme hors ligne |
140
- | `get(name)` | Retourne l'état live complet |
141
- | `list({ status, runner })` | Liste filtrée des agents |
142
- | `stats()` | Stats globales (total, busy, idle, online) |
143
- | `incrementA2aSent(name)` | Incrémente le compteur A2A envoyé |
144
- | `incrementA2aReceived(name)` | Incrémente le compteur A2A reçu |
145
- | `prune(maxAgeMs)` | Nettoie les agents offline > 24h (auto toutes les 6h) |
146
-
147
- ---
148
-
149
- ## SessionStore — Sessions Multi-Tenant
150
-
151
- **Source**: `src/bridge/SessionStore.ts`
152
-
153
- Le **multi-tenant** : chaque utilisateur externe a sa propre session persistante avec l'agent.
154
-
155
- ### Comment ça Marche
156
-
157
- ```
158
- Utilisateur Discord A (!sniper analyse BTC)
159
- → externalKey = "discord_user_123"
160
- → SessionStore.get("discord_user_123", "sniperbot_analyst")
161
- → sessionId = "abc-123" (session restaurée!)
162
- → runAgent avec sessionId="abc-123"
163
- → SessionStore.set("discord_user_123", "sniperbot_analyst", "abc-123")
164
-
165
- Utilisateur Discord B (!sniper analyse ETH)
166
- → externalKey = "discord_user_456"
167
- → SessionStore.get("discord_user_456", "sniperbot_analyst")
168
- → pas de session → nouvelle session
169
- → sessionId = "def-456"
170
- ```
171
-
172
- ### Persistence
173
-
174
- ```json
175
- // ~/.overmind/bridge/sessions.json
176
- {
177
- "discord_user_123:sniperbot_analyst": {
178
- "externalKey": "discord_user_123",
179
- "agentName": "sniperbot_analyst",
180
- "runner": "hermes",
181
- "sessionId": "abc-123",
182
- "context": { "lastTopic": "BTC", "language": "fr" },
183
- "createdAt": "2026-07-09T18:00:00Z",
184
- "updatedAt": "2026-07-09T18:05:00Z"
185
- }
186
- }
187
- ```
188
-
189
- ### TTL et Cleanup
190
-
191
- - TTL par défaut: **4h** (`sessionTtlMs: 4 * 60 * 60 * 1000`)
192
- - Cleanup automatique toutes les **5min** (`cleanupIntervalMs`)
193
- - Sessions expirées → supprimées du fichier
194
-
195
- ### Directives — L'Agent Peut Mettre à Jour Sa Session
196
-
197
- Quand `enableDirectives: true`, l'agent peut émettre des directives dans sa réponse :
198
-
199
- ```
200
- SESSION_ID: new-session-xyz → met à jour le sessionId
201
- CONTEXT_UPDATE: {"lastTopic": "ETH"} → patch le contexte
202
- BRIDGE_HINT: "Analyse terminée" → log seulement
203
- ```
204
-
205
- Le `DirectiveParser` (`src/bridge/DirectiveParser.ts`) parse ces directives, les applique au SessionStore, et les retire du texte visible par l'utilisateur.
206
-
207
- ---
208
-
209
- ## MessageLog — Persistence Postgres
210
-
211
- **Source**: `src/bridge/MessageLog.ts`
212
-
213
- Historique permanent de tous les messages A2A et agent.run dans PostgreSQL.
214
-
215
- ### Schéma de Table
216
-
217
- ```sql
218
- CREATE TABLE bridge_messages (
219
- id UUID PRIMARY KEY,
220
- from_agent VARCHAR(255), -- NULL si client externe
221
- to_agent VARCHAR(255) NOT NULL,
222
- runner VARCHAR(50) NOT NULL,
223
- prompt TEXT NOT NULL,
224
- response TEXT,
225
- session_id VARCHAR(255),
226
- status VARCHAR(20) DEFAULT 'pending', -- pending → running → done/failed/timeout
227
- metadata JSONB,
228
- created_at TIMESTAMPTZ DEFAULT NOW(),
229
- updated_at TIMESTAMPTZ DEFAULT NOW()
230
- );
231
-
232
- CREATE INDEX idx_bridge_messages_to_agent ON bridge_messages(to_agent, created_at DESC);
233
- CREATE INDEX idx_bridge_messages_from_agent ON bridge_messages(from_agent, created_at DESC);
234
- CREATE INDEX idx_bridge_messages_status ON bridge_messages(status);
235
- ```
236
-
237
- ### Cycle de Vie d'un Message
238
-
239
- ```
240
- agent.run appelé
241
- → messageLog.create() → status: "pending"
242
- → messageLog.markRunning() → status: "running"
243
- → agent termine...
244
- → succès: messageLog.markDone() → status: "done", response sauvegardée
245
- → erreur: messageLog.markFailed() → status: "failed", error sauvegardé
246
- → timeout: messageLog.markTimeout() → status: "timeout"
247
- ```
248
-
249
- ### Replay
250
-
251
- ```bash
252
- # Rejouer un message (re-run avec le même prompt)
253
- curl -X POST http://localhost:3100/rpc \
254
- -d '{"jsonrpc":"2.0","id":1,"method":"message.replay","params":{"id":"7f3e8a1b-..."}}'
255
- ```
256
-
257
- ---
258
-
259
- ## JSON-RPC 2.0 — Toutes les Méthodes
260
-
261
- ### Endpoint
262
-
263
- ```
264
- POST /rpc
265
- Content-Type: application/json
266
- Authorization: Bearer <token> (optionnel si authToken configuré)
267
-
268
- # Single request
269
- {"jsonrpc":"2.0","id":1,"method":"agent.run","params":{...}}
270
-
271
- # Batch (parallèle)
272
- [
273
- {"jsonrpc":"2.0","id":1,"method":"agent.run","params":{...}},
274
- {"jsonrpc":"2.0","id":2,"method":"agent.list","params":{}}
275
- ]
276
- ```
277
-
278
- ### Méthodes Disponibles (24 méthodes)
279
-
280
- #### Agents (10 méthodes)
281
-
282
- | Méthode | Params Requis | Params Optionnels | Description |
283
- |---------|---------------|-------------------|-------------|
284
- | `agent.run` | `agentName`, `runner`, `prompt` | `sessionId`, `path`, `model`, `mode`, `silent`, `externalKey`, `parseDirectives`, `metadata` | Lance un agent |
285
- | `agent.a2a` | `fromAgent`, `toAgent`, `runner`, `prompt` | `model`, `path`, `metadata` | Agent A parle à Agent B |
286
- | `agent.broadcast` | `fromAgent`, `runner`, `prompt` | `targets[]`, `race`, `agentTimeoutMs`, `model` | Fan-out global |
287
- | `agent.pipeline` | `initiator`, `runner`, `prompt`, `steps[]` | `accumulateContext`, `totalTimeoutMs` | Chaîne A→B→C |
288
- | `agent.fanout` | `fromAgent`, `runner`, `prompt`, `targets[]` | `mergeStrategy`, `agentTimeoutMs`, `model` | 1→N + merge |
289
- | `agent.delegate` | `fromAgent`, `toAgent`, `runner`, `prompt` | `async`, `callbackUrl`, `model` | Fire-and-forget |
290
- | `agent.query` | `fromAgent`, `runner`, `prompt`, `targets[]` | `agentTimeoutMs`, `model` | Query multi-agents |
291
- | `agent.status` | `agentName` | `runner`, `action`, `sinceTimestamp`, `timeoutMs` | Status live |
292
- | `agent.list` | — | `status`, `runner` | Liste des agents |
293
- | `agent.kill` | `agentName` | `runner` | Kill un agent |
294
-
295
- #### Messages (4 méthodes)
296
-
297
- | Méthode | Params | Description |
298
- |---------|--------|-------------|
299
- | `message.history` | `toAgent?`, `fromAgent?`, `status?`, `limit?`, `offset?`, `sinceHours?` | Historique paginé |
300
- | `message.get` | `id` (UUID) | Récupère un message |
301
- | `message.replay` | `id` (UUID) | Rejoue un message |
302
- | `message.stats` | — | Stats globales |
303
-
304
- #### Sessions (4 méthodes)
305
-
306
- | Méthode | Params | Description |
307
- |---------|--------|-------------|
308
- | `session.get` | `externalKey`, `agentName` | Session d'un utilisateur |
309
- | `session.list` | — | Toutes les sessions |
310
- | `session.delete` | `externalKey`, `agentName` | Supprime une session |
311
- | `session.stats` | — | Stats des sessions |
312
-
313
- #### Autres (6 méthodes)
314
-
315
- | Méthode | Description |
316
- |---------|-------------|
317
- | `health.ping` | Liveness check |
318
- | `webhook.sms` | Adapt + auto-dispatch webhook SMS |
319
- | `GET /health` | Healthcheck enrichi (hors RPC) |
320
- | `POST /webhook/:provider` | Webhook HTTP (voipms, twilio, discord) |
321
- | `GET /f/:filename` | Static file serve |
322
- | `OPTIONS *` | CORS preflight |
323
-
324
- ---
325
-
326
- ## A2A — Agent-to-Agent
327
-
328
- ### Principe
329
-
330
- Agent A envoie un message à Agent B. Le bridge :
331
- 1. Enrichit le prompt avec un header A2A standardisé
332
- 2. Persiste le message dans MessageLog (from=A, to=B)
333
- 3. Incrémente les compteurs A2A (sent pour A, received pour B)
334
- 4. Exécute B sous mutex (sérialise les appels concurrents vers B)
335
- 5. Retourne la réponse de B à A
336
-
337
- ### Format du Prompt A2A
338
-
339
- ```
340
- [A2A — Agent-to-Agent Message]
341
- FROM: trader_btc
342
- TO: analyst_eth
343
- TIMESTAMP: 2026-07-09T18:30:00.000Z
344
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
345
- Valide mon analyse : le BTC va casser les 100k dans 48h
346
- ```
347
-
348
- ### Exemple curl
349
-
350
- ```bash
351
- curl -X POST http://localhost:3100/rpc \
352
- -H "Content-Type: application/json" \
353
- -d '{
354
- "jsonrpc": "2.0",
355
- "id": 1,
356
- "method": "agent.a2a",
357
- "params": {
358
- "fromAgent": "trader_btc",
359
- "toAgent": "analyst_eth",
360
- "runner": "hermes",
361
- "prompt": "Valide mon analyse : le BTC va casser les 100k dans 48h"
362
- }
363
- }'
364
- ```
365
-
366
- ### Réponse
367
-
368
- ```json
369
- {
370
- "jsonrpc": "2.0",
371
- "id": 1,
372
- "result": {
373
- "messageId": "7f3e8a1b-...",
374
- "from": "trader_btc",
375
- "to": "analyst_eth",
376
- "sessionId": "api-abc123",
377
- "content": [{ "type": "text", "text": "Je valide avec conviction 75..." }],
378
- "isError": false
379
- }
380
- }
381
- ```
382
-
383
- ---
384
-
385
- ## A2A Extended — Broadcast, Pipeline, Fanout, Delegate, Query
386
-
387
- ### 1. Broadcast — 1 → Tous (Fan-out Global)
388
-
389
- Un agent envoie un message à tous les agents online (ou à une liste cible).
390
-
391
- ```bash
392
- curl -X POST http://localhost:3100/rpc \
393
- -d '{
394
- "jsonrpc": "2.0", "id": 1,
395
- "method": "agent.broadcast",
396
- "params": {
397
- "fromAgent": "commander",
398
- "runner": "hermes",
399
- "prompt": "Alerte marché : BTC drop 5%",
400
- "race": false,
401
- "agentTimeoutMs": 60000
402
- }
403
- }'
404
- ```
405
-
406
- - `race: true` → retourne dès qu'un agent répond (premier qui gagne)
407
- - `race: false` → attend tous les agents
408
- - Si `targets` absent → tous les agents `online` (excluant l'émetteur)
409
-
410
- ### 2. Pipeline — Chaîne Séquentielle A→B→C
411
-
412
- L'output de chaque step devient l'input du suivant.
413
-
414
- ```bash
415
- curl -X POST http://localhost:3100/rpc \
416
- -d '{
417
- "jsonrpc": "2.0", "id": 1,
418
- "method": "agent.pipeline",
419
- "params": {
420
- "initiator": "commander",
421
- "runner": "hermes",
422
- "prompt": "Analyse le marché BTC actuel",
423
- "steps": [
424
- { "agentName": "data_collector", "promptPrefix": "Récupère les données:" },
425
- { "agentName": "analyst", "promptPrefix": "Analyse ces données:" },
426
- { "agentName": "strategist", "promptPrefix": "Propose une stratégie:" }
427
- ],
428
- "accumulateContext": true,
429
- "totalTimeoutMs": 3600000
430
- }
431
- }'
432
- ```
433
-
434
- - `accumulateContext: true` → chaque step reçoit TOUS les outputs précédents
435
- - `accumulateContext: false` → chaque step ne reçoit que le dernier output
436
- - Timeout global avec deadline check à chaque step
437
-
438
- ### 3. Fanout — 1 → N Parallèle + Merge
439
-
440
- Un agent demande à plusieurs agents en parallèle, puis merge les résultats.
441
-
442
- ```bash
443
- curl -X POST http://localhost:3100/rpc \
444
- -d '{
445
- "jsonrpc": "2.0", "id": 1,
446
- "method": "agent.fanout",
447
- "params": {
448
- "fromAgent": "commander",
449
- "runner": "hermes",
450
- "prompt": "Quelle stratégie pour le BTC?",
451
- "targets": ["analyst_1", "analyst_2", "analyst_3"],
452
- "mergeStrategy": "best"
453
- }
454
- }'
455
- ```
456
-
457
- Stratégies de merge :
458
-
459
- | Stratégie | Description |
460
- |-----------|-------------|
461
- | `concat` | Concatène toutes les réponses (défaut) |
462
- | `best` | Sélectionne la plus longue réponse |
463
- | `vote` | Vote par bucket de taille (100 chars) |
464
- | `first_success` | Premier qui réussit |
465
-
466
- ### 4. Delegate — Fire-and-Forget Async
467
-
468
- Délègue une tâche à un agent de façon asynchrone. Retourne immédiatement avec un `taskId`.
469
-
470
- ```bash
471
- curl -X POST http://localhost:3100/rpc \
472
- -d '{
473
- "jsonrpc": "2.0", "id": 1,
474
- "method": "agent.delegate",
475
- "params": {
476
- "fromAgent": "commander",
477
- "toAgent": "researcher",
478
- "runner": "hermes",
479
- "prompt": "Recherche l'historique des crashes BTC 2024",
480
- "async": true,
481
- "callbackUrl": "https://myapp.com/webhook/agent-done"
482
- }
483
- }'
484
- ```
485
-
486
- - `async: true` → retourne immédiatement avec `taskId`
487
- - `callbackUrl` → POST le résultat quand l'agent termine
488
- - L'agent peut être suivi via `agent.status(agentName: "researcher")`
489
-
490
- ### 5. Query — Multi-Agent Read-Only
491
-
492
- Comme fanout mais optimisé pour des réponses courtes et rapides.
493
-
494
- ```bash
495
- curl -X POST http://localhost:3100/rpc \
496
- -d '{
497
- "jsonrpc": "2.0", "id": 1,
498
- "method": "agent.query",
499
- "params": {
500
- "fromAgent": "commander",
501
- "runner": "hermes",
502
- "prompt": "Prix actuel du BTC?",
503
- "targets": ["price_bot_1", "price_bot_2"],
504
- "agentTimeoutMs": 30000
505
- }
506
- }'
507
- ```
508
-
509
- ---
510
-
511
- ## Comment Construire un Agent Persistant (comme discord_llm)
512
-
513
- `discord_llm` est l'exemple canonique d'un client persistant du bridge. Voici le pattern complet :
514
-
515
- ### Architecture discord_llm
516
-
517
- ```
518
- ┌─────────────────────────────────────────────────────────┐
519
- │ discord_llm/ (package: discord-claude-bridge v3.5.0) │
520
- │ │
521
- │ startpipeline.ts → PipelineManager │
522
- │ ├── spawn overmind-bridge.ts (Express :3001) │
523
- │ │ ├── OverBridgeService (SDK wrapper) │
524
- │ │ ├── BridgeProxy → HTTP [::1]:3099/mcp │
525
- │ │ ├── SessionStore (multi-tenant, JSON file) │
526
- │ │ └── WebhookAdapter (voipms, twilio, discord) │
527
- │ │ │
528
- │ └── spawn discord-bot.ts (Discord.js) │
529
- │ ├── reçoit !sniper <message> │
530
- │ └── POST localhost:3001/send │
531
- │ → OverBridgeService.runAgentForDiscord() │
532
- │ → BridgeProxy → MCP :3099 │
533
- │ → HermesGatewayRunner → Gateway :8642 │
534
- │ → réponse → Discord │
535
- └─────────────────────────────────────────────────────────┘
536
- ```
537
-
538
- ### Étape 1 : Importer le SDK Overmind
539
-
540
- ```typescript
541
- // Import depuis overmind-mcp (package npm ou file:../Workflow)
542
- import {
543
- OverBridgeService, // SDK haut niveau (runAgent, sessions, heartbeat)
544
- SessionStore, // Multi-tenant (externalKey → sessionId)
545
- WebhookAdapter, // Adaptation webhooks (SMS, Discord, Twilio)
546
- BridgeProxy, // Low-level MCP transport (HTTP JSON-RPC)
547
- } from 'overmind-mcp/bridge';
548
- ```
549
-
550
- ### Étape 2 : Configurer le Bridge
551
-
552
- ```typescript
553
- const OVERMIND_MCP_URL = process.env.OVERMIND_MCP_URL || 'http://localhost:3099/mcp';
554
- const AGENT_NAME = process.env.BRIDGE_AGENT || 'sniperbot_analyst';
555
- const RUNNER = 'hermes';
556
-
557
- // Low-level proxy → parle au MCP server
558
- const proxy = new BridgeProxy({
559
- mcpUrl: OVERMIND_MCP_URL,
560
- defaultTimeoutMs: 60_000,
561
- agentTimeoutMs: 2_700_000, // 45 min
562
- maxRetries: 2,
563
- retryDelayMs: 2_000,
564
- }, undefined, logger);
565
-
566
- // High-level service → session continuity + retry + circuit breaker
567
- const service = new OverBridgeService({
568
- mcpUrl: OVERMIND_MCP_URL,
569
- defaultTimeoutMs: 60_000,
570
- agentTimeoutMs: 2_700_000,
571
- maxRetries: 2,
572
- retryDelayMs: 2_000,
573
- }, logger);
574
-
575
- // Multi-tenant sessions
576
- const sessionStore = new SessionStore({
577
- persistPath: '~/.overmind/bridge/sessions.json',
578
- ttlMs: 4 * 60 * 60 * 1000, // 4h
579
- cleanupIntervalMs: 5 * 60 * 1000, // 5min
580
- }, logger);
581
-
582
- await sessionStore.init();
583
- ```
584
-
585
- ### Étape 3 : Exposer les Endpoints HTTP
586
-
587
- ```typescript
588
- import express from 'express';
589
- const app = express();
590
- app.use(express.json({ limit: '10mb' }));
591
-
592
- // ─── Route legacy (Discord bot) ─────────────────────────
593
- app.post('/send', async (req, res) => {
594
- const { message, userId, username, channelId } = req.body;
595
-
596
- // Lance l'agent avec contexte Discord
597
- const result = await service.runAgentForDiscord(
598
- AGENT_NAME, RUNNER, message,
599
- { channelId, userId, username }
600
- );
601
-
602
- if (result.isError) {
603
- return res.status(500).json({ error: result.content.map(c => c.text).join('\n') });
604
- }
605
-
606
- res.json({
607
- result: result.content.map(c => c.text).join('\n'),
608
- session_id: result.sessionId || service.sessionId,
609
- });
610
- });
611
-
612
- // ─── JSON-RPC 2.0 endpoint ──────────────────────────────
613
- app.post('/rpc', async (req, res) => {
614
- const { method, params, id } = req.body;
615
-
616
- switch (method) {
617
- case 'agent.run':
618
- // Résolution sessionId via SessionStore
619
- let sessionId = params.sessionId;
620
- if (!sessionId && params.externalKey) {
621
- const stored = sessionStore.get(params.externalKey, params.agentName);
622
- if (stored) sessionId = stored.sessionId;
623
- }
624
-
625
- const result = await service.runAgent({
626
- agentName: params.agentName,
627
- runner: params.runner,
628
- prompt: params.prompt,
629
- sessionId,
630
- model: params.model,
631
- });
632
-
633
- // Sauvegarde sessionId pour cet utilisateur
634
- if (params.externalKey && result.sessionId) {
635
- sessionStore.set({
636
- externalKey: params.externalKey,
637
- agentName: params.agentName,
638
- runner: params.runner,
639
- sessionId: result.sessionId,
640
- });
641
- }
642
-
643
- return res.json({ jsonrpc: '2.0', id, result: {
644
- sessionId: result.sessionId,
645
- content: result.content,
646
- isError: result.isError,
647
- }});
648
-
649
- case 'agent.a2a':
650
- const enrichedPrompt = [
651
- `[A2A — Agent-to-Agent Message]`,
652
- `FROM: ${params.fromAgent}`,
653
- `TO: ${params.toAgent}`,
654
- `TIMESTAMP: ${new Date().toISOString()}`,
655
- `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
656
- params.prompt,
657
- ].join('\n');
658
-
659
- const a2aResult = await service.runAgent({
660
- agentName: params.toAgent,
661
- runner: params.runner,
662
- prompt: enrichedPrompt,
663
- model: params.model,
664
- });
665
-
666
- return res.json({ jsonrpc: '2.0', id, result: {
667
- from: params.fromAgent,
668
- to: params.toAgent,
669
- sessionId: a2aResult.sessionId,
670
- content: a2aResult.content,
671
- isError: a2aResult.isError,
672
- }});
673
-
674
- case 'agent.list':
675
- const agents = await proxy.call('list_agents', { details: params.details });
676
- return res.json({ jsonrpc: '2.0', id, result: agents.result });
677
-
678
- case 'session.get':
679
- return res.json({ jsonrpc: '2.0', id, result: {
680
- session: sessionStore.get(params.externalKey, params.agentName) ?? null,
681
- }});
682
-
683
- case 'session.list':
684
- return res.json({ jsonrpc: '2.0', id, result: {
685
- sessions: sessionStore.list(),
686
- stats: sessionStore.stats(),
687
- }});
688
-
689
- case 'health.ping':
690
- return res.json({ jsonrpc: '2.0', id, result: { pong: true, ts: Date.now() }});
691
- }
692
- });
693
-
694
- // ─── Health ─────────────────────────────────────────────
695
- app.get('/health', async (_req, res) => {
696
- let mcpStatus = 'unknown';
697
- try {
698
- const health = await proxy.healthCheck();
699
- mcpStatus = health.status;
700
- } catch { mcpStatus = 'offline'; }
701
- res.json({
702
- status: 'ok',
703
- version: '5.1.0',
704
- mcp: { url: OVERMIND_MCP_URL, status: mcpStatus },
705
- sessions: sessionStore.stats(),
706
- });
707
- });
708
-
709
- app.listen(3001, () => logger.info('Bridge on :3001'));
710
- ```
711
-
712
- ### Étape 4 : Démarrer le Pipeline
713
-
714
- ```typescript
715
- // startpipeline.ts — orchestre bridge + bot
716
- class PipelineManager {
717
- async start() {
718
- // 1. Démarrer le bridge (HTTP server)
719
- this.bridgeProcess = spawn('node', ['dist/overmind-bridge.js']);
720
-
721
- // 2. Attendre qu'il soit prêt
722
- await this.waitForBridge(); // GET /health jusqu'à 200
723
-
724
- // 3. Démarrer le bot Discord
725
- this.discordProcess = spawn('node', ['dist/discord-bot.js']);
726
-
727
- // 4. Auto-restart du bot si crash (max 3 tentatives)
728
- this.discordProcess.on('exit', (code) => {
729
- if (!this.isShuttingDown) this.restartDiscordBot();
730
- });
731
- }
732
- }
733
- ```
734
-
735
- ### Étape 5 : Configurer le .env
736
-
737
- ```bash
738
- # discord_llm/.env
739
- CLAUDE_SERVER_PORT=3001 # Port du bridge
740
- RUNNER=hermes # Runner Overmind
741
- BRIDGE_AGENT=sniperbot_analyst # Agent par défaut
742
- OVERMIND_MCP_URL=http://[::1]:3099/mcp # MCP server
743
- DISCORD_BOT_TOKEN=*** # Token Discord
744
- BOT_PREFIX=!sniper # Préfixe commandes
745
- ```
746
-
747
- ---
748
-
749
- ## CLI overmind-bridge
750
-
751
- Le CLI `overmind-bridge` (`src/bin/overmind-bridge.ts`) permet de piloter le bridge sans écrire de code.
752
-
753
- ### Démarrer le serveur
754
-
755
- ```bash
756
- overmind-bridge server --port 3100
757
- ```
758
-
759
- ### Appels one-shot (8 sources de prompt)
760
-
761
- ```bash
762
- # 1. Flag direct
763
- overmind-bridge call agent.run --agent scout --runner hermes --prompt "Analyse BTC"
764
-
765
- # 2. Stdin
766
- echo "Analyse BTC" | overmind-bridge call agent.run --agent scout --runner hermes --prompt-stdin
767
-
768
- # 3. Fichier
769
- overmind-bridge call agent.run --agent scout --runner hermes --prompt-file ./brief.txt
770
-
771
- # 4. Fichier + variables
772
- overmind-bridge call agent.run --agent scout --runner hermes \
773
- --prompt-file ./brief.txt --var ticker=BTC --var timeframe=4h
774
-
775
- # 5-8. env-var, scenario, pipe, multi-file (voir --help)
776
- ```
777
-
778
- ### A2A via CLI
779
-
780
- ```bash
781
- overmind-bridge call agent.a2a \
782
- --from scout --to analyst --runner hermes \
783
- --prompt "Valide mon analyse"
784
- ```
785
-
786
- ### Scénarios multi-agents
787
-
788
- ```bash
789
- # JSON scenario file
790
- overmind-bridge scenario ./workflow.json --var ticker=BTC
791
- ```
792
-
793
- ### Status et Health
794
-
795
- ```bash
796
- # Status de tous les agents
797
- overmind-bridge status
798
-
799
- # Health du serveur
800
- overmind-bridge health
801
-
802
- # Replay un message
803
- overmind-bridge replay --id 7f3e8a1b-...
804
-
805
- # Sessions
806
- overmind-bridge sessions list
807
- overmind-bridge sessions get --key "+141****7735" --agent pdf_bon_travail
808
- overmind-bridge sessions rm --key "+141****7735" --agent pdf_bon_travail
809
- ```
810
-
811
- ---
812
-
813
- ## Démarrer le Serveur Bridge
814
-
815
- ### Option A : OverBridgeServer natif (port 3100)
816
-
817
- ```typescript
818
- import { OverBridgeServer, OverBridgeService, loadMessageLogConfigFromEnv } from 'overmind-mcp/bridge';
819
-
820
- const service = new OverBridgeService({
821
- mcpUrl: 'http://[::1]:3099/mcp',
822
- defaultTimeoutMs: 60_000,
823
- agentTimeoutMs: 2_700_000,
824
- }, logger);
825
-
826
- const server = new OverBridgeServer(service, {
827
- port: 3100,
828
- host: '127.0.0.1',
829
- postgres: loadMessageLogConfigFromEnv(),
830
- enableMessageLog: true, // Persistence Postgres
831
- enableSessionStore: true, // Multi-tenant sessions
832
- enableDirectives: true, // SESSION_ID, CONTEXT_UPDATE directives
833
- enableWebhooks: true, // /webhook/:provider
834
- sessionTtlMs: 4 * 60 * 60 * 1000, // 4h
835
- rateLimitMax: 100, // 100 req/min par IP
836
- allowedOrigins: ['*'],
837
- sanitizeJson: true, // Windows path repair
838
- });
839
-
840
- await server.start();
841
- // POST http://127.0.0.1:3100/rpc (JSON-RPC 2.0)
842
- // GET http://127.0.0.1:3100/health
843
- // POST http://127.0.0.1:3100/webhook/:provider
844
- ```
845
-
846
- ### Option B : Bridge léger Express (comme discord_llm)
847
-
848
- Pour un client qui n'a pas besoin de Postgres MessageLog mais veut les sessions multi-tenant :
849
-
850
- ```typescript
851
- import express from 'express';
852
- import { OverBridgeService, SessionStore, BridgeProxy } from 'overmind-mcp/bridge';
853
-
854
- const app = express();
855
- app.use(express.json());
856
-
857
- const service = new OverBridgeService({ mcpUrl: 'http://[::1]:3099/mcp' });
858
- const sessions = new SessionStore({ persistPath: './sessions.json' });
859
- await sessions.init();
860
-
861
- // POST /send, POST /rpc, GET /health...
862
- app.listen(3001);
863
- ```
864
-
865
- ### Option C : CLI direct
866
-
867
- ```bash
868
- # Démarrer le serveur OverBridgeServer complet
869
- overmind-bridge server --port 3100 --enable-message-log --enable-sessions --enable-webhooks
870
- ```
871
-
872
- ---
873
-
874
- ## Configurations du OverBridgeServer
875
-
876
- | Config | Default | Description |
877
- |--------|---------|-------------|
878
- | `port` | 3100 | Port HTTP |
879
- | `host` | 127.0.0.1 | Host d'écoute |
880
- | `postgres` | — | Config Postgres pour MessageLog |
881
- | `enableMessageLog` | false | Persistence des messages en DB |
882
- | `enableSessionStore` | false | Sessions multi-tenant |
883
- | `enableDirectives` | false | Parse SESSION_ID, CONTEXT_UPDATE |
884
- | `enableWebhooks` | false | Auto-mount /webhook/:provider |
885
- | `authToken` | — | Bearer token auth |
886
- | `healthCheckIntervalMs` | 30000 | Heartbeat vers MCP |
887
- | `sessionTtlMs` | 14400000 (4h) | TTL sessions |
888
- | `sessionStorePath` | ~/.overmind/bridge/sessions.json | Fichier persistence |
889
- | `jsonBodyLimit` | 10mb | Limite body |
890
- | `sanitizeJson` | false | Repair JSON Windows paths |
891
- | `allowedOrigins` | localhost:3000,5173 | CORS |
892
- | `rateLimitMax` | 100 | Max req/min par IP |