UNPKG

overmind-mcp

Version:

Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour

665 lines (518 loc) • 22.5 kB
# 🌉 Overmind Bridge — Agents Persistants & A2A (Guide Unifié) > **Version**: Overmind v3.5.2 — Pattern générique, 2 à 100 agents > **Date**: 2026-07-10 > **Statut**: Remplace `overmind-bridge-persistent-agents.md`, `overmind-bridge-commands-reference.md`, `doc_guide_agent_hermes_permanent.md`, `guide_agent_hermes_overmind.md`, `agent-http-tutorial.md` --- ## 1. Concepts ### Qu'est-ce qu'un agent persistant ? Un agent persistant = **un process `overmind-bridge` autonome** qui : - Écoute sur son propre port HTTP - A son propre agent Hermes dédié (profil) - Garde sa session entre les appels (mémoire conversationnelle) - Communique avec les autres agents via HTTP A2A (peer-to-peer) - Forward les appels LLM vers le MCP Overmind → Hermes Gateway ### Pourquoi des bridges isolés ? | Sans bridge | Avec bridge isolé | |-------------|-------------------| | `spawn('hermes', ['-p', agent])` à chaque call | Process persistant, session conservée | | 5-10s startup Python par call | <500ms (HTTP direct au Gateway) | | Pas de communication inter-agent | A2A HTTP peer-to-peer | | Crash d'un agent = crash global | Crash d'un agent ≠ impact sur les autres | | Pas de monitoring par agent | `GET /health` par agent | ### L'échelle : 2 à 100 agents Le pattern est **identique** que tu aies 2 ou 100 agents. Chaque agent = : - 1 profil Hermes (`~/.hermes/profiles/<name>/`) - 1 process bridge (`node dist/bridges/<name>/src/bridge.js`) - 1 port dédié (séquence : 3101, 3102, ..., 31XX) --- ## 2. Architecture ``` ┌──────────────────────────────────────────────────────────────┐ │ N BRIDGES ISOLÉS — PEER-TO-PEER │ │ │ │ :3101 agent_alpha → RPC locaux + A2A vers peers │ │ :3102 agent_beta → RPC locaux + A2A vers peers │ │ :3103 agent_gamma → RPC locaux + A2A vers peers │ │ ... │ │ :31XX agent_omega → RPC locaux + A2A vers peers │ │ │ │ A2A = HTTP POST inter-bridges (JSON-RPC 2.0) │ │ Chaque bridge a ses propres clients vers les autres │ │ │ │ SERVICES PARTAGÉS (single instance): │ │ :3099 Overmind MCP Server (routing run_agent, memory, ...) │ │ :8642 Hermes Gateway API (X-Hermes-Profile routing) │ │ :5432 PostgreSQL (MessageLog + pgvector Memory) │ └──────────────────────────────────────────────────────────────┘ ``` ### Ce que ce n'est PAS - ❌ Un seul `OverBridgeServer` partagé qui dispatche vers N agents - ❌ Un bridge "central" qui connaît tous les agents - ❌ N instances Hermes Gateway (1 seule suffit, le header `X-Hermes-Profile` route) ### Services partagés (NE PAS dupliquer) | Service | Port | URL | Rôle | |---------|------|-----|------| | **Overmind MCP** | :3099 | `http://[::1]:3099/mcp` | 14 tools : run_agent, a2a_hub, agent_control, memory_*, create_agent | | **Hermes Gateway** | :8642 | `http://127.0.0.1:8642` | API Server HTTP+SSE, OpenAI-compatible, profile routing | | **PostgreSQL** | :5432 | `localhost` | MessageLog (`bridge_messages`) + Memory (pgvector) | ### Allocation des ports Les ports suivent une séquence simple : **3101 + offset**. | Agent # | Port | Exemple nom | |---------|------|-------------| | #1 | 3101 | `agent_alpha` | | #2 | 3102 | `agent_beta` | | #3 | 3103 | `agent_gamma` | | ... | ... | ... | | #N | 3100+N | `agent_<name>` | --- ## 3. Créer un Agent Persistant ### Étape 1 : Créer le profil Hermes ```python # Via MCP tool (recommandé) create_agent( name: "agent_alpha", runner: "hermes", prompt: "Tu es l'agent Alpha. Tes missions: ...", model: "glm-5.2" ) ``` Ou via CLI : ```bash hermes profile create agent_alpha --no-alias --description "Agent Alpha" hermes -p agent_alpha config set model.provider z-ai hermes -p agent_alpha config set model.model glm-5.2 ``` ### Structure créée sur disque ``` ~/.hermes/profiles/agent_alpha/ ├── config.yaml # provider, model, mcp_servers ├── .env # clés API (GLM_API_KEY, MINIMAX_CN_API_KEY, ...) ├── SOUL.md # system prompt + instructions mémoire ├── profile.yaml # metadata kanban routing ├── workspace.yaml # kind: persistent, gc_eligible: false ├── memories/ # state.db (mémoire SQLite isolée) ├── sessions/ # historique conversations └── skills/ # mémoire procédurale ``` ### Auto-détection du provider | Modèle contient | Provider | Clé API | |-----------------|----------|---------| | `minimax`, `m3` | `minimax-cn` | `MINIMAX_CN_API_KEY` | | `glm`, `zai` | `z-ai` | `GLM_API_KEY` | | `claude`, `sonnet` | `anthropic` | `ANTHROPIC_API_KEY` | | `gpt` | `openai` | `OPENAI_API_KEY` | | `deepseek` | `deepseek` | `DEEPSEEK_API_KEY` | | *(autre)* | `openrouter` | `OPENROUTER_API_KEY` | ### Étape 2 : Créer le bridge ```bash # Créer la structure mkdir -p bridges/agent_alpha/{src/clients,logs} ``` **`bridges/agent_alpha/.env`** : ```bash AGENT_NAME=agent_alpha RUNNER=hermes BRIDGE_PORT=3101 BRIDGE_HOST=127.0.0.1 MCP_URL=http://[::1]:3099/mcp HERMES_GATEWAY_URL=http://127.0.0.1:8642 HERMES_GATEWAY_KEY=<key> SESSION_TTL_MS=14400000 MESSAGE_LOG_ENABLED=true MCP_TIMEOUT_MS=120000 # Peers (autres bridges — vide si seul, ou liste tous les autres) PEER_BETA_URL=http://127.0.0.1:3102 PEER_GAMMA_URL=http://127.0.0.1:3103 ``` **`bridges/agent_alpha/src/config.ts`** : ```typescript import { existsSync, readFileSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { PeerMap } from '../../../common/src/types.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); function loadEnv(): Record<string, string> { const candidatePaths = [ resolve(__dirname, '..', '.env'), resolve(__dirname, '..', '..', '..', '.env'), ]; for (const p of candidatePaths) { if (existsSync(p)) return parseEnv(readFileSync(p, 'utf-8')); } throw new Error(`Missing .env. Looked in:\n ${candidatePaths.join('\n ')}`); } function parseEnv(raw: string): Record<string, string> { const env: Record<string, string> = {}; for (const line of raw.split(/\r?\n/)) { const t = line.trim(); if (!t || t.startsWith('#')) continue; const idx = t.indexOf('='); if (idx === -1) continue; env[t.slice(0, idx).trim()] = t.slice(idx + 1).trim().replace(/^["']|["']$/g, ''); } return env; } const env = loadEnv(); export const config = { agentName: env.AGENT_NAME ?? 'agent_alpha', runner: env.RUNNER ?? 'hermes', port: parseInt(env.BRIDGE_PORT ?? '3101', 10), host: env.BRIDGE_HOST ?? '127.0.0.1', mcpUrl: env.MCP_URL ?? 'http://[::1]:3099/mcp', hermesGatewayUrl: env.HERMES_GATEWAY_URL ?? 'http://127.0.0.1:8642', gatewayKey: env.HERMES_GATEWAY_KEY ?? '', peers: { beta: env.PEER_BETA_URL, gamma: env.PEER_GAMMA_URL, // ... ajouter autant de peers que nécessaire } as PeerMap, sessionTtlMs: parseInt(env.SESSION_TTL_MS ?? '14400000', 10), messageLogEnabled: env.MESSAGE_LOG_ENABLED === 'true', mcpTimeoutMs: parseInt(env.MCP_TIMEOUT_MS ?? '120000', 10), }; ``` **`bridges/agent_alpha/src/bridge.ts`** : ```typescript import { BaseBridge } from '../../../common/src/BaseBridge.js'; import { Logger } from '../../../common/src/logger.js'; import { config } from './config.js'; const logger = new Logger(config.agentName); class AgentAlphaBridge extends BaseBridge { constructor() { super({ agentName: config.agentName, port: config.port, host: config.host, mcpUrl: config.mcpUrl, gatewayUrl: config.hermesGatewayUrl, peers: config.peers, mcpTimeoutMs: config.mcpTimeoutMs, sessionTtlMs: config.sessionTtlMs, messageLogEnabled: config.messageLogEnabled, }); // Enregistrer les méthodes RPC locales spécifiques à cet agent this.registerRpcMethod('alpha.execute', async (params) => { logger.info('Execute requested', params); return { status: 'done', ts: Date.now() }; }); } } const bridge = new AgentAlphaBridge(); bridge.start().catch((err) => { logger.error('Failed to start', { error: (err as Error).message }); process.exit(1); }); const shutdown = async (signal: string) => { logger.info(`Received ${signal} — shutting down`); await bridge.stop(); process.exit(0); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); ``` ### Étape 3 : Build et démarrage ```bash # Build (depuis la racine du projet) npm run build # Démarrer ce bridge node dist/bridges/agent_alpha/src/bridge.js # Vérifier curl http://127.0.0.1:3101/health ``` --- ## 4. Module Common (partagé entre tous les bridges) ### `common/src/BaseBridge.ts` Classe abstraite — chaque bridge l'étend. Fournit : - Serveur HTTP (`POST /rpc`, `GET /health`, `POST /shutdown`) - `registerRpcMethod(name, handler)` — méthodes RPC locales - `forwardToMcp(request)` — forward vers MCP Overmind (`:3099/mcp`) - SSE parsing (FastMCP répond en Server-Sent Events) - Keep-alive HTTP agent (perf: connection reuse) - Graceful shutdown (SIGINT/SIGTERM) **Routage RPC interne** : ``` POST /rpc → dispatchRpc(method) 1. health.ping → local (toujours) 2. Méthode locale (registerRpcMethod) → handler local 3. Sinon → forwardToMcp() → POST http://[::1]:3099/mcp ``` ### `common/src/BridgeClient.ts` Client HTTP pour appels inter-bridges (A2A). Fournit : - `call(method, params)` — JSON-RPC 2.0 avec retry + backoff - `healthCheck()` — GET /health sur un peer - `sendToAgent(agentName, prompt)` — wrapper `agent.run` - UUID uniques (pas de collision) - Keep-alive agent partagé ```typescript const client = new BridgeClient('http://127.0.0.1:3102'); const result = await client.call('beta.analyze', { symbol: 'BTC' }); ``` ### `common/src/types.ts` Interfaces partagées : `BridgeConfig`, `PeerMap`, `HealthStatus`, `JsonRpcRequest/Response`, `RunnerType`. ### `common/src/logger.ts` Logger zero-dep : console colorisée + fichier async (non bloquant), rotation 50MB, safe stringify (circular refs). --- ## 5. Commandes RPC (par bridge) ### Format de requête ```bash curl -X POST http://127.0.0.1:<PORT>/rpc \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"<id>","method":"<method>","params":{...}}' ``` ### Endpoints communs (tous les bridges) | Endpoint | HTTP | Description | |----------|------|-------------| | `GET /health` | GET | `{ agent, status, uptime, rpcMethods[], peerCount }` | | `POST /rpc` | POST | JSON-RPC 2.0 dispatcher | | `POST /shutdown` | POST | Arrêt propre | ### Méthodes RPC standards (tous les bridges) | Méthode | Params | Description | |---------|--------|-------------| | `health.ping` | — | Ping local (toujours OK) | ### Méthodes RPC customs (spécifiques par agent) Chaque agent enregistre ses propres méthodes via `registerRpcMethod()`. Exemple avec NEXUS : #### Exemple : nexus_master (orchestrateur) | Méthode | Params | Description | |---------|--------|-------------| | `master.status` | — | Agrège la santé de tous les peers | | `master.broadcast` | `{ message }` | Fan-out à tous les peers | | `master.pipeline` | `{ steps: [{ peer, prompt }] }` | Chaîne A→B→C | | `master.fanout` | `{ peers[], prompt, merge? }` | Parallèle N + merge | #### Exemple : nexus_trader | Méthode | Params | Description | |---------|--------|-------------| | `trade.request` | `{ symbol, side, quantity }` | Valide via risk, notifie publisher | | `trade.analysis` | `{ symbol, timeframe? }` | Délègue au researcher | #### Exemple : nexus_risk_manager | Méthode | Params | Description | |---------|--------|-------------| | `risk.validate` | `{ symbol, side, quantity }` | Valide un trade | | `risk.drawdown` | — | Check drawdown | | `risk.feedback` | `{ message, type }` | Feedback au trader | #### Pattern pour créer tes propres méthodes ```typescript // Dans ton bridge.ts this.registerRpcMethod('myagent.dosomething', async (params) => { const p = params as { input: string }; // Logique métier ici return { result: 'done', ts: Date.now() }; }); ``` --- ## 6. A2A — Communication Inter-Bridges ### A2A direct (bridge → bridge) ```typescript // Dans un bridge, appeler un autre bridge import { BridgeClient } from '../../../common/src/BridgeClient.js'; const peerClient = new BridgeClient('http://127.0.0.1:3102'); const result = await peerClient.call('trade.request', { symbol: 'BTC', side: 'BUY', quantity: 0.1, }); ``` ### A2A via MCP Hub (tool `a2a_hub`) Le MCP Overmind expose le tool `a2a_hub` avec 8 actions : | Action | Params | Description | |--------|--------|-------------| | `discover` | — | Liste tous les agents + statut | | `status` | `target` | État détaillé d'un agent | | `send` | `target, message` | Message synchrone A→B | | `delegate` | `target, message, callbackUrl?` | Async (retourne taskId) | | `pipeline` | `message, steps[]` | Chaîne A→B→C | | `fanout` | `targets[], message, mergeStrategy?` | 1→N + merge | | `query` | `targets[], message` | Question multi-agents | | `broadcast` | `message, race?` | Global à tous | ```python a2a_hub(action="send", target="nexus_trader", message="Analyse BTC") a2a_hub(action="fanout", targets=["agent_a","agent_b"], message="BTC?", mergeStrategy="best") a2a_hub(action="pipeline", message="Analyse", steps=[{"agentName":"researcher"},{"agentName":"trader"}]) ``` ### A2A via OverBridgeServer (JSON-RPC 24 méthodes) Le `OverBridgeServer` expose des méthodes A2A avancées avec persistence Postgres : | Méthode | Description | |---------|-------------| | `agent.run` | Lance un agent (avec SessionStore multi-tenant) | | `agent.a2a` | A→B avec header A2A standardisé | | `agent.broadcast` | 1→N (race ou attend tous) | | `agent.pipeline` | Chaîne A→B→C (accumulateContext?) | | `agent.fanout` | 1→N + merge (concat/best/vote/first_success) | | `agent.delegate` | Fire-and-forget + callback URL | | `agent.query` | Multi-agent read-only rapide | | `agent.status` | Status live (busy/idle/online) | | `agent.list` | Liste filtrée | | `agent.kill` | Kill un agent | | `message.history` | Historique paginé (Postgres) | | `message.get` | Récupère un message par UUID | | `message.replay` | Rejoue un message | | `message.stats` | Stats globales | | `session.get` | Session par externalKey + agentName | | `session.list` | Toutes les sessions | | `session.delete` | Supprime une session | | `session.stats` | Stats sessions | | `webhook.sms` | Adapt + auto-dispatch SMS | | `health.ping` | Liveness | --- ## 7. Lancer un Agent ### Via Gateway HTTP (recommandé — zero subprocess) ```python run_agent(runner: "hermes", agentName: "agent_alpha", prompt: "Analyse le marché") ``` Sous le capot : `HermesGatewayRunner` → `POST :8642/v1/chat/completions` avec `X-Hermes-Profile: agent_alpha` + SSE streaming. Pas de spawn CLI. ### Via curl direct sur le Gateway ```bash curl -X POST http://127.0.0.1:8642/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${API_SERVER_KEY}" \ -H "X-Hermes-Profile: agent_alpha" \ -d '{"model":"glm-5.2","messages":[{"role":"user","content":"Analyse le BTC"}],"stream":true}' ``` ### Via bridge RPC ```bash curl -X POST http://127.0.0.1:3101/rpc \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"agent.run","params":{"agentName":"agent_alpha","runner":"hermes","prompt":"Analyse le BTC"}}' ``` ### Via Hermes CLI ```bash hermes -p agent_alpha chat -q "Analyse le BTC" -Q --yolo ``` --- ## 8. Gestion du Cycle de Vie ### Profils Hermes | Action | Commande | |--------|----------| | Lister | `hermes profile list` | | Voir | `hermes profile show agent_alpha` | | Supprimer | `hermes profile delete agent_alpha --yes` | | Changer modèle | `hermes -p agent_alpha config set model.model "MiniMax-M3"` | | Update via Overmind | `update_agent_config(name: "agent_alpha", model: "new-model")` | ### Runtime | Action | Commande | |--------|----------| | Status | `agent_control(agentName: "agent_alpha", action: "status")` | | Stream output | `agent_control(agentName: "agent_alpha", action: "stream")` | | Kill | `agent_control(agentName: "agent_alpha", action: "kill")` | | Health bridge | `curl http://127.0.0.1:3101/health` | | Shutdown bridge | `curl -X POST http://127.0.0.1:3101/shutdown` | --- ## 9. Scripts Helper ### Démarrage N bridges ```bash # Start all (NEXUS example with 6) node scripts/start-all-bridges.cjs # Stop all node scripts/stop-all-bridges.cjs # Status node scripts/status-bridges.cjs # Test A2A network node scripts/test-a2a.cjs ``` ### Seed (créer la structure pour N agents) ```bash node scripts/seed-bridges.cjs # Crée bridges/<name>/{src/clients,logs} + .env pour chaque agent ``` ### Build ```bash # Build complet (tsc + postbuild copie .env et config) npm run build # Le postbuild copie automatiquement: # config/*.json → dist/config/ # bridges/*/.env → dist/bridges/*/.env ``` ### Activation Hermes Gateway ```bash # Dans config.yaml gateway: platforms: api_server: enabled: true # Dans .env API_SERVER_KEY=<key> API_SERVER_ENABLED=1 # Redémarrer hermes gateway restart ``` --- ## 10. Ce que l'Agent Hérite Automatiquement | Héritage | Source | |----------|--------| | **Mémoire isolée** | `state.db` SQLite dans `memories/` — `OVERMIND_AGENT_NAME` injecté | | **Instructions mémoire** | Bloc "## Mémoire Overmind" injecté dans `SOUL.md` | | **MCP servers** | `memory` (3 tools: search/store/runs) par défaut | | **Clés API** | Toutes les clés du `.env` parent forwardées (GLM, MiniMax, OpenAI, ...) | | **Sessions persistantes** | `sessions/` garde l'historique, `workspace.yaml: persistent` | | **Kanban routing** | `profile.yaml` pour découverte par `a2a_hub` | | **Gateway HTTP** | `HermesGatewayRunner` → `POST :8642` avec `X-Hermes-Profile` | | **Circuit breaker** | `BridgeProxy` : 5 failures → open, 30s → half-open, 3 success → closed | | **Retry automatique** | `ETIMEDOUT`, `EBODYREAD`, `ECONNRESET` → retry avec backoff | --- ## 11. Bugs Source Corrigés (2026-07-10) ### Overmind (`Workflow/src/bridge/`) | Bug | Fichier | Fix | |-----|---------|-----| | `localhost` au lieu de `[::1]` (IPv4/IPv6 mismatch) | `types.ts` | → `http://[::1]:3099/mcp` | | `ping()` sans SSE Accept (FastMCP rejette) | `BridgeProxy.ts` | → `Accept: text/event-stream` + fallback `GET /health` | | `parseSseText` non importé | `BridgeProxy.ts` | → Import ajouté | ### NEXUS (`Nexus/common/` + `bridges/`) | Bug | Fichier | Fix | |-----|---------|-----| | MCP URL `/rpc` au lieu de `/mcp` (404) | `BaseBridge.ts` | → `/mcp` + SSE parsing | | Timeout MCP hardcodé 60s | `BaseBridge.ts` | → `config.mcpTimeoutMs` (120s) | | `Date.now()` ID collision | `BridgeClient.ts` | → `crypto.randomUUID()` | | `appendFileSync` (sync I/O) | `logger.ts` | → `appendFile` async + rotation 50MB | | RPC custom forwardée au MCP (404) | `BaseBridge.ts` | → `registerRpcMethod()` dispatch local | | `.env` introuvable après build | `config.ts` | → `candidatePaths[]` multi-niveaux | | `postbuild` ne copiait pas `.env` | `postbuild.cjs` | → Section 2: copy bridges/.env | | `*/` dans JSDoc cassait CJS | `postbuild.cjs` | → Comment reworded | | Pas de graceful shutdown | `bridge.ts` ×N | → `process.on('SIGINT'/'SIGTERM')` | | Pas de keep-alive HTTP | `BridgeClient.ts` | → `HttpAgent({ keepAlive: true })` | ### discord_llm | Bug | Fichier | Fix | |-----|---------|-----| | `localhost:3099` au lieu de `[::1]` | `overmind-bridge.ts` | → `http://[::1]:3099/mcp` | | `no-useless-assignment` lint | `overmind-bridge.ts` | → `let mcpStatus: string` | --- ## 12. Exemple Complet — 3 Agents qui Communiquent ### Créer 3 agents ```python create_agent(name: "coordinator", runner: "hermes", prompt: "Tu coordonnes...", model: "glm-5.2") create_agent(name: "worker_a", runner: "hermes", prompt: "Tu exécutes...", model: "glm-5.2") create_agent(name: "worker_b", runner: "hermes", prompt: "Tu valides...", model: "glm-5.2") ``` ### Structure ``` bridges/ ├── coordinator/ # :3101 │ ├── .env # PEER_WORKER_A_URL=http://127.0.0.1:3102 │ │ # PEER_WORKER_B_URL=http://127.0.0.1:3103 │ ├── src/bridge.ts # registerRpcMethod('coord.dispatch', ...) │ └── src/clients/CoordinatorToWorkerA.ts │ ├── worker_a/ # :3102 │ ├── .env # PEER_COORDINATOR_URL=http://127.0.0.1:3101 │ └── src/bridge.ts # registerRpcMethod('worker.execute', ...) │ └── worker_b/ # :3103 ├── .env # PEER_COORDINATOR_URL=http://127.0.0.1:3101 └── src/bridge.ts # registerRpcMethod('worker.validate', ...) ``` ### Communication ```bash # Coordinator → Worker A (via bridge HTTP) curl -X POST http://127.0.0.1:3101/rpc \ -d '{"jsonrpc":"2.0","id":"1","method":"coord.dispatch","params":{"task":"analyze BTC"}}' # Worker A → Worker B (via BridgeClient dans le code) const client = new BridgeClient('http://127.0.0.1:3103'); await client.call('worker.validate', { result: 'BTC bullish' }); # Coordinator → tous (broadcast) curl -X POST http://127.0.0.1:3101/rpc \ -d '{"jsonrpc":"2.0","id":"2","method":"master.broadcast","params":{"message":"Standby"}}' ``` ### Démarrage ```bash node scripts/start-all-bridges.cjs # Lance les 3 en parallèle curl http://127.0.0.1:3101/health # Verify coordinator curl http://127.0.0.1:3102/health # Verify worker_a curl http://127.0.0.1:3103/health # Verify worker_b ``` Le pattern est **identique** pour 2, 6, 10, ou 100 agents. Ajoute un bridge, un port, un .env, et il communique avec les autres via HTTP.