overmind-mcp 2.3.2 → 2.3.4

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,656 +1,656 @@
1
- # `agent_control` — Contrôle Unifié du Cycle de Vie des Agents
2
-
3
- > **Outil unifié** qui remplace les 4 outils précédents : `get_agent_status`, `stream_agent_output`, `kill_agent`, `wait_agent`.
4
- > Document technique complet : usage, patterns async, lookup par PID/timestamp, dashboard.
5
-
6
- ---
7
-
8
- ## Table des Matières
9
-
10
- 1. [Vue d'Ensemble](#1-vue-densemble)
11
- 2. [Actions Disponibles](#2-actions-disponibles)
12
- 3. [Codes d'Erreur](#3-codes-derreur)
13
- 4. [Patterns Async avec OverMind](#4-patterns-async-avec-overmind)
14
- 5. [Tracker PID ↔ Session ↔ Agent](#5-tracker-pid--session--agent)
15
- 6. [Dashboard en Temps Réel](#6-dashboard-en-temps-réel)
16
- 7. [Flux Complet de Debug](#7-flux-complet-de-debug)
17
- 8. [Référence Rapide](#8-référence-rapide)
18
-
19
- ---
20
-
21
- ## 1. Vue d'Ensemble
22
-
23
- ### Problème Résolu
24
-
25
- Quand un agent est lancé via `run_agent`, le processus fils (`claude`, `kilo`, etc.) tourne en arrière-plan. Le seul lien entre le processus parent (OverMind MCP) et le processus enfant est le `sessionId` — généré par le runner, opaque pour OverMind.
26
-
27
- **Problème** : Si OverMind restart ou crash, le `sessionId` est perdu et le child process devient **orphelin**.
28
-
29
- **Solution** : Le **Process Registry** (`sessions.json`) stocke le mapping complet :
30
-
31
- ```
32
- pid ↔ sessionId ↔ agentName ↔ runner ↔ status ↔ outputBuffer
33
- ```
34
-
35
- `agent_control` est l'unique interface pour interagir avec ce registry.
36
-
37
- ### Architecture
38
-
39
- ```
40
- Client OverMind MCP Process
41
- │ │ │
42
- │ run_agent() │ spawn(claude) │
43
- │──────────────────────────────────►│ registerProcess(pid) │
44
- │ │─────────────────────────────►│
45
- │ │ │
46
- │ agent_control(action: "status") │ │
47
- │──────────────────────────────────►│ getProcessStatus(pid) │
48
- │ { status, pid, outputBuffer } │ │
49
- │◄─────────────────────────────────│ │
50
- │ │ │
51
- │ agent_control(action: "stream") │ │
52
- │──────────────────────────────────►│ read outputBuffer │
53
- │ { output, isComplete } │ │
54
- │◄─────────────────────────────────│ │
55
- │ │ │
56
- │ agent_control(action: "wait") │ poll every 1s... │
57
- │──────────────────────────────────►│ │
58
- │ │ child.on('close') │
59
- │ { result } │◄─────────────────────────────│
60
- │◄─────────────────────────────────│ │
61
- ```
62
-
63
- ---
64
-
65
- ## 2. Actions Disponibles
66
-
67
- ### `status` — Lecture Pure, Zero Side-Effect
68
-
69
- Retourne l'état courant du process **sans modifier le registry**.
70
-
71
- ```javascript
72
- agent_control({
73
- agentName: 'sniper_analyst',
74
- runner: 'kilo',
75
- action: 'status',
76
- })
77
- ```
78
-
79
- **Réponse :**
80
-
81
- ```markdown
82
- **Agent:** sniper_analyst
83
- **Runner:** kilo
84
- **Status:** running
85
- **Started:** 2026-05-10T14:32:00.000Z
86
- **PID:** 12345
87
- **Session ID:** sess_abc123
88
-
89
- **Output Buffer (2048 chars):**
90
-
91
- ```
92
- Thinking...
93
- Fetching BTC data...
94
- Analysis complete.
95
- ```
96
- ```
97
-
98
- **États possibles :**
99
-
100
- | Status | Signification |
101
- |--------|---------------|
102
- | `running` | Process actif, PID valide |
103
- | `done` | Terminé avec code 0 |
104
- | `failed` | Terminé avec erreur (exit code != 0) |
105
- | `orphaned` | Parent mort mais child tourne encore |
106
-
107
- ---
108
-
109
- ### `stream` — Lecture + Indicateur de Complétude
110
-
111
- Retourne l'output accumulé + un flag `isComplete` pour savoir si le process est fini.
112
-
113
- ```javascript
114
- agent_control({
115
- agentName: 'sniper_analyst',
116
- runner: 'kilo',
117
- action: 'stream',
118
- sinceTimestamp: 1746892800000, // optionnel
119
- })
120
- ```
121
-
122
- **Réponse :**
123
-
124
- ```markdown
125
- **Agent:** sniper_analyst
126
- **Status:** running
127
- **isComplete:** false
128
- **PID:** 12345
129
- **Last Output At:** 2026-05-10T14:32:45.000Z
130
-
131
- **Output (2048 chars):**
132
-
133
- ```
134
- Thinking...
135
- Fetching BTC data...
136
- ```
137
- ```
138
-
139
- Quand `isComplete: true` :
140
- ```markdown
141
- **Agent:** sniper_analyst
142
- **Status:** done
143
- **isComplete:** true
144
- **PID:** 12345
145
-
146
- **Output (4096 chars):**
147
-
148
- ```
149
- Final analysis: BUY signal detected.
150
- ```
151
- ```
152
-
153
- ---
154
-
155
- ### `kill` — Destruction Irréversible
156
-
157
- Tue le process tree via `taskkill /F /T /PID` (Windows) ou `kill -9` (Unix).
158
-
159
- ```javascript
160
- agent_control({
161
- agentName: 'sniper_analyst',
162
- runner: 'kilo',
163
- action: 'kill',
164
- })
165
- ```
166
-
167
- **Réponse (succès) :**
168
-
169
- ```markdown
170
- Agent "sniper_analyst" tué avec succès (PID: 12345). Status mis à jour → 'failed' dans le registry.
171
- ```
172
-
173
- **Réponse (échec) :**
174
-
175
- ```markdown
176
- Agent "sniper_analyst" n'est pas en cours d'exécution (status: done). Impossible de tuer un agent déjà terminé.
177
- ```
178
-
179
- > **⚠️ IRRÉVERSIBLE** : Une fois tué, le process ne peut pas être récupéré. Utiliser `kill` uniquement pour un abort d'urgence ou kill-switch.
180
-
181
- ---
182
-
183
- ### `wait` — Blocage Async avec Polling
184
-
185
- Poll toutes les 1s jusqu'à ce que le status ne soit plus `running`, ou que le timeout soit atteint.
186
-
187
- ```javascript
188
- agent_control({
189
- agentName: 'sniper_analyst',
190
- runner: 'kilo',
191
- action: 'wait',
192
- timeoutMs: 300000, // 5 minutes (défaut: 900000 = 15 min)
193
- })
194
- ```
195
-
196
- **Réponse (terminé) :**
197
-
198
- ```markdown
199
- Final analysis: BUY signal detected. Portfolio balanced.
200
- ```
201
-
202
- **Réponse (timeout) :**
203
-
204
- ```markdown
205
- Timeout de 300000ms atteint. L'agent "sniper_analyst" est toujours en cours d'exécution (status: running). Utilisez action="kill" pour forcer l'arrêt ou augmentez timeoutMs.
206
- ```
207
-
208
- **Réponse (erreur) :**
209
-
210
- ```markdown
211
- Agent terminé avec erreur (failed):
212
-
213
- TypeError: Cannot read property 'price' of undefined
214
- at analyzeBTC (/app/bot.js:42)
215
- at processTicksAndCallbacks (internal/process/task_queues.js:95)
216
- ```
217
-
218
- ---
219
-
220
- ## 3. Codes d'Erreur
221
-
222
- Chaque erreur est structurée avec un **code** pour faciliter le debugging programatique.
223
-
224
- | Code | Signification | Action recommandée |
225
- |------|---------------|---------------------|
226
- | `AGENT_NOT_FOUND` | Agent absent du registry | Vérifier le nom ou le runner |
227
- | `AGENT_NOT_RUNNING` | Action `kill` sur un agent déjà terminé | Ne pas tuer un agent already done |
228
- | `KILL_FAILED` | `taskkill`/`kill` a échoué | Vérifier permissions Windows/Unix |
229
- | `WAIT_TIMEOUT` | Timeout atteint sans terminaison | Augmenter `timeoutMs` ou utiliser `kill` |
230
- | `ORPHANED_PROCESS` | Process zombie détecté | `kill` puis relancer l'agent |
231
-
232
- ---
233
-
234
- ## 4. Patterns Async avec OverMind
235
-
236
- ### Pattern 1 : Lancer et Ne Pas Bloquer (Fire & Forget)
237
-
238
- ```javascript
239
- // 1. Lancer l'agent en arrière-plan
240
- const runResult = await run_agent({
241
- runner: 'kilo',
242
- agentName: 'crypto_scanner',
243
- prompt: 'Scan BTC/USDT for trading opportunities',
244
- });
245
-
246
- // 2. Récupérer le PID depuis le output (format: "PID: 12345")
247
- // OU depuis le sessionId pour retrouver le process plus tard
248
- const sessionId = runResult.sessionId; // "sess_abc123"
249
-
250
- // 3. Polling non-bloquant pour vérifier l'état
251
- const status = await agent_control({
252
- agentName: 'crypto_scanner',
253
- runner: 'kilo',
254
- action: 'status',
255
- });
256
- ```
257
-
258
- ### Pattern 2 : Lancer et Attendre (Blocking Wait)
259
-
260
- ```javascript
261
- // Une seule ligne pour lancer et attendre
262
- const result = await agent_control({
263
- agentName: 'long_task',
264
- runner: 'claude',
265
- action: 'wait',
266
- timeoutMs: 600000, // 10 minutes
267
- });
268
-
269
- if (result.isError) {
270
- console.error('Task failed:', result.content[0].text);
271
- } else {
272
- console.log('Result:', result.content[0].text);
273
- }
274
- ```
275
-
276
- ### Pattern 3 : Orchestration Séquentielle
277
-
278
- ```javascript
279
- async function runWorkflow(steps) {
280
- const results = [];
281
-
282
- for (const step of steps) {
283
- console.log(`Starting step: ${step.name}`);
284
-
285
- const result = await agent_control({
286
- agentName: step.agentName,
287
- runner: step.runner,
288
- action: 'wait',
289
- timeoutMs: step.timeoutMs || 300000,
290
- });
291
-
292
- if (result.isError) {
293
- return {
294
- success: false,
295
- failedStep: step.name,
296
- error: result.content[0].text,
297
- partialResults: results,
298
- };
299
- }
300
-
301
- results.push({ step: step.name, output: result.content[0].text });
302
- }
303
-
304
- return { success: true, results };
305
- }
306
-
307
- // Usage
308
- const workflow = await runWorkflow([
309
- { name: 'fetch_data', agentName: 'data_fetcher', runner: 'kilo', timeoutMs: 60000 },
310
- { name: 'analyze', agentName: 'analyzer', runner: 'claude', timeoutMs: 300000 },
311
- { name: 'report', agentName: 'reporter', runner: 'kilo', timeoutMs: 120000 },
312
- ]);
313
- ```
314
-
315
- ### Pattern 4 : Exécution Parallèle (Fan-Out)
316
-
317
- ```javascript
318
- // Lancer plusieurs agents en parallèle
319
- const agents = [
320
- { agentName: 'btc_analyst', runner: 'kilo', prompt: 'Analyze BTC' },
321
- { agentName: 'eth_analyst', runner: 'kilo', prompt: 'Analyze ETH' },
322
- { agentName: 'sol_analyst', runner: 'claude', prompt: 'Analyze SOL' },
323
- ];
324
-
325
- // Lancer les 3 en parallèle
326
- const runPromises = agents.map(a =>
327
- run_agent({ runner: a.runner, agentName: a.agentName, prompt: a.prompt })
328
- );
329
-
330
- await Promise.all(runPromises);
331
-
332
- // Attendre que tous soient prêts
333
- const waitPromises = agents.map(a =>
334
- agent_control({ agentName: a.agentName, runner: a.runner, action: 'wait', timeoutMs: 300000 })
335
- );
336
-
337
- const results = await Promise.all(waitPromises);
338
-
339
- for (const [i, r] of results.entries()) {
340
- console.log(`${agents[i].agentName}:`, r.isError ? 'FAILED' : 'OK');
341
- }
342
- ```
343
-
344
- ### Pattern 5 : Resume après Crash OverMind
345
-
346
- Quand OverMind restart, on peut se rattacher aux agents en cours via le registry :
347
-
348
- ```javascript
349
- // 1. Scanner tous les agents "running" dans le registry
350
- const runningAgents = await getRunningProcesses();
351
-
352
- // 2. Pour chaque agent encore vivant mais "orphaned"
353
- for (const agent of runningAgents) {
354
- const status = await agent_control({
355
- agentName: agent.agentName,
356
- runner: agent.runner,
357
- action: 'status',
358
- });
359
-
360
- if (status.content[0].text.includes('orphaned')) {
361
- console.log(`Orphaned agent detected: ${agent.agentName} (PID: ${agent.pid})`);
362
- // Option: kill et relancer, ou laisser tel quel
363
- }
364
- }
365
- ```
366
-
367
- ---
368
-
369
- ## 5. Tracker PID ↔ Session ↔ Agent
370
-
371
- Le registry dans `sessions.json` stocke la cartographie complète :
372
-
373
- ```json
374
- {
375
- "kilo:sniper_analyst": {
376
- "id": "sess_abc123",
377
- "ts": 1746892800000,
378
- "pid": 12345,
379
- "runner": "kilo",
380
- "agentName": "sniper_analyst",
381
- "status": "running",
382
- "outputBuffer": "Thinking...\nFetching data...",
383
- "exitCode": null,
384
- "lastOutputAt": 1746892900000
385
- },
386
- "claude:reporter": {
387
- "id": "sess_def456",
388
- "ts": 1746892850000,
389
- "pid": 67890,
390
- "runner": "claude",
391
- "agentName": "reporter",
392
- "status": "done",
393
- "outputBuffer": "Report generated successfully.",
394
- "exitCode": 0,
395
- "lastOutputAt": 1746893500000
396
- }
397
- }
398
- ```
399
-
400
- ### Trouver un Agent par Timestamp
401
-
402
- ```javascript
403
- // Trouver tous les agents活跃 après une date donnée
404
- async function findAgentsAfter(timestampMs) {
405
- const allAgents = await getProcessStatus('*'); // sans runner = tous
406
-
407
- return allAgents
408
- .filter(a => a.ts >= timestampMs)
409
- .sort((a, b) => a.ts - b.ts); // plus récents d'abord
410
- }
411
-
412
- // Usage
413
- const recent = await findAgentsAfter(Date.now() - 3600000); // dernière heure
414
- console.log(recent.map(a => `${a.agentName} @ ${new Date(a.ts).toISOString()}`));
415
- ```
416
-
417
- ### Trouver un Agent par PID
418
-
419
- ```javascript
420
- // Via le registry scan
421
- async function findAgentByPid(pid) {
422
- const { store } = await readStore();
423
-
424
- for (const [key, entry] of Object.entries(store)) {
425
- if (typeof entry === 'object' && entry !== null && entry.pid === pid) {
426
- return { key, ...entry };
427
- }
428
- }
429
- return null;
430
- }
431
-
432
- // Usage
433
- const agent = await findAgentByPid(12345);
434
- console.log(`Found: ${agent.agentName} (${agent.runner})`);
435
- ```
436
-
437
- ### Trouver un Agent par Session ID
438
-
439
- ```javascript
440
- // Via le registry scan
441
- async function findAgentBySession(sessionId) {
442
- const { store } = await readStore();
443
-
444
- for (const [key, entry] of Object.entries(store)) {
445
- if (typeof entry === 'object' && entry !== null && entry.id === sessionId) {
446
- return { key, ...entry };
447
- }
448
- }
449
- return null;
450
- }
451
-
452
- // Usage
453
- const agent = await findAgentBySession('sess_abc123');
454
- if (agent) {
455
- console.log(`PID: ${agent.pid}, Status: ${agent.status}`);
456
- }
457
- ```
458
-
459
- ---
460
-
461
- ## 6. Dashboard en Temps Réel
462
-
463
- ### Exemple Complet : Dashboard CLI
464
-
465
- ```javascript
466
- import { getRunningProcesses } from './lib/processRegistry.js';
467
-
468
- async function dashboard() {
469
- console.clear();
470
- console.log('═══════════════════════════════════════════════════════');
471
- console.log(' OVERMIND AGENT DASHBOARD');
472
- console.log('═══════════════════════════════════════════════════════\n');
473
-
474
- const running = await getRunningProcesses();
475
-
476
- if (running.length === 0) {
477
- console.log('Aucun agent en cours d\'exécution.\n');
478
- return;
479
- }
480
-
481
- console.log(`📊 ${running.length} agent(s) actifs\n`);
482
- console.log('┌──────────────────┬────────┬─────────┬───────────────┬────────────────────────┐');
483
- console.log('│ Agent │ Runner │ PID │ Status │ Started │');
484
- console.log('├──────────────────┼────────┼─────────┼───────────────┼────────────────────────┤');
485
-
486
- for (const agent of running) {
487
- const elapsed = Date.now() - agent.ts;
488
- const elapsedStr = elapsed < 60000
489
- ? `${Math.floor(elapsed / 1000)}s`
490
- : `${Math.floor(elapsed / 60000)}m`;
491
-
492
- const statusColor = agent.status === 'running' ? '🟢' : '⚠️';
493
- console.log(
494
- `│ ${agent.agentName.padEnd(16)} │ ${(agent.runner || '?').padEnd(6)} │ ${String(agent.pid || 'N/A').padStart(7)} │ ${statusColor} ${agent.status.padEnd(11)} │ ${new Date(agent.ts).toLocaleTimeString().padEnd(22)} │`
495
- );
496
- }
497
-
498
- console.log('└──────────────────┴────────┴─────────┴───────────────┴────────────────────────┘\n');
499
-
500
- // Detail du premier agent
501
- if (running[0]) {
502
- const detail = await agent_control({
503
- agentName: running[0].agentName,
504
- runner: running[0].runner,
505
- action: 'stream',
506
- });
507
- console.log('═══ OUTPUT RECENT ═══');
508
- console.log(detail.content[0].text.slice(-500));
509
- }
510
- }
511
-
512
- // Rafraîchir toutes les 3 secondes
513
- setInterval(dashboard, 3000);
514
- ```
515
-
516
- ### Exemple : Tableau de Bord HTML
517
-
518
- ```html
519
- <!DOCTYPE html>
520
- <html>
521
- <head>
522
- <title>OverMind Dashboard</title>
523
- <meta http-equiv="refresh" content="5">
524
- <style>
525
- body { font-family: monospace; background: #0d1117; color: #e6edf3; padding: 20px; }
526
- .agent { border: 1px solid #30363d; padding: 10px; margin: 10px 0; border-radius: 6px; }
527
- .running { border-left: 4px solid #3fb950; }
528
- .done { border-left: 4px solid #58a6ff; }
529
- .failed { border-left: 4px solid #f85149; }
530
- .orphaned { border-left: 4px solid #d29922; }
531
- .pid { color: #8b949e; }
532
- .output { background: #161b22; padding: 8px; margin-top: 8px; max-height: 100px; overflow: auto; }
533
- </style>
534
- </head>
535
- <body>
536
- <h1>🤖 OverMind Agent Dashboard</h1>
537
- <div id="agents"></div>
538
-
539
- <script>
540
- async function refresh() {
541
- // Appeler agent_control pour chaque agent running
542
- const response = await fetch('/api/agents/running');
543
- const agents = await response.json();
544
-
545
- document.getElementById('agents').innerHTML = agents.map(agent => `
546
- <div class="agent ${agent.status}">
547
- <strong>${agent.agentName}</strong>
548
- <span class="pid">(${agent.runner} | PID: ${agent.pid})</span>
549
- <br>
550
- Status: ${agent.status} | Started: ${new Date(agent.ts).toLocaleString()}
551
- <br>
552
- Elapsed: ${Math.floor((Date.now() - agent.ts) / 1000)}s
553
- <div class="output">${escapeHtml(agent.outputBuffer || '(no output)').slice(-200)}</div>
554
- </div>
555
- `).join('');
556
- }
557
-
558
- function escapeHtml(text) {
559
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
560
- }
561
-
562
- refresh();
563
- setInterval(refresh, 5000);
564
- </script>
565
- </body>
566
- </html>
567
- ```
568
-
569
- ---
570
-
571
- ## 7. Flux Complet de Debug
572
-
573
- Quand un agent ne répond plus ou pose problème :
574
-
575
- ```
576
- STEP 1: Identifier le problème
577
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
578
- agent_control({ agentName: "sniper_analyst", action: "status" })
579
-
580
- → Si status: "running" + lastOutputAt ancien → agent peut-être bloqué
581
- → Si status: "orphaned" → parent mort, child tourne encore
582
- → Si status: "done" → agent déjà terminé, problem elsewhere
583
-
584
- STEP 2: Voir l'output
585
- ━━━━━━━━━━━━━━━━━━━━━━
586
- agent_control({ agentName: "sniper_analyst", action: "stream" })
587
-
588
- → Identifier la dernière ligne avant le blocage
589
- → Vérifier s'il y a des erreurs dans le buffer
590
-
591
- STEP 3: Décider de l'action
592
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━
593
-
594
- ┌─ still running, output OK → Attendre (action: "wait", timeoutMs: X)
595
-
596
- ├─ still running, BLOCKED → kill (action: "kill") → restart
597
-
598
- ├─ orphaned → kill (action: "kill") → restart
599
-
600
- └─ done with error → Analyser output → fix prompt → restart
601
-
602
- STEP 4: Nettoyer si nécessaire
603
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
604
- Le registry fait le cleanup automatique après 1h (TTL).
605
- Pour forcer le cleanup immédiat :
606
-
607
- unregisterProcess(pid) → supprime l'entrée du registry
608
- ```
609
-
610
- ---
611
-
612
- ## 8. Référence Rapide
613
-
614
- ```javascript
615
- // Status simple
616
- agent_control({ agentName: "...", action: "status" })
617
-
618
- // Stream avec filtre timestamp
619
- agent_control({ agentName: "...", action: "stream", sinceTimestamp: 1746892800000 })
620
-
621
- // Kill
622
- agent_control({ agentName: "...", action: "kill" })
623
-
624
- // Wait avec timeout custom
625
- agent_control({ agentName: "...", action: "wait", timeoutMs: 300000 })
626
-
627
- // Avec runner explicite (recommendé)
628
- agent_control({ agentName: "...", runner: "kilo", action: "..." })
629
-
630
- // Avec config path (si plusieurs OverMind)
631
- agent_control({ agentName: "...", config: "/path/to/overmind", action: "..." })
632
- ```
633
-
634
- ### Paramètres Communs
635
-
636
- | Param | Type | Description |
637
- |-------|------|-------------|
638
- | `agentName` | string | Nom unique de l'agent |
639
- | `runner` | enum | Type de runner (optionnel, déduit si omis) |
640
- | `config` | string | Chemin racine Overmind (optionnel) |
641
-
642
- ### Actions
643
-
644
- | Action | Description | Retourne |
645
- |--------|-------------|----------|
646
- | `status` | État courant | pid, status, sessionId, outputBuffer |
647
- | `stream` | Output + complétude | output, isComplete |
648
- | `kill` | Arrêt forcé | confirmation ou erreur |
649
- | `wait` | Attente terminaison | output final ou timeout |
650
-
651
- ---
652
-
653
- **Fichier source** : `src/tools/agent_control.ts`
654
- **Registry** : `src/lib/processRegistry.ts`
655
- **Store** : `.claude/sessions.json`
1
+ # `agent_control` — Contrôle Unifié du Cycle de Vie des Agents
2
+
3
+ > **Outil unifié** qui remplace les 4 outils précédents : `get_agent_status`, `stream_agent_output`, `kill_agent`, `wait_agent`.
4
+ > Document technique complet : usage, patterns async, lookup par PID/timestamp, dashboard.
5
+
6
+ ---
7
+
8
+ ## Table des Matières
9
+
10
+ 1. [Vue d'Ensemble](#1-vue-densemble)
11
+ 2. [Actions Disponibles](#2-actions-disponibles)
12
+ 3. [Codes d'Erreur](#3-codes-derreur)
13
+ 4. [Patterns Async avec OverMind](#4-patterns-async-avec-overmind)
14
+ 5. [Tracker PID ↔ Session ↔ Agent](#5-tracker-pid--session--agent)
15
+ 6. [Dashboard en Temps Réel](#6-dashboard-en-temps-réel)
16
+ 7. [Flux Complet de Debug](#7-flux-complet-de-debug)
17
+ 8. [Référence Rapide](#8-référence-rapide)
18
+
19
+ ---
20
+
21
+ ## 1. Vue d'Ensemble
22
+
23
+ ### Problème Résolu
24
+
25
+ Quand un agent est lancé via `run_agent`, le processus fils (`claude`, `kilo`, etc.) tourne en arrière-plan. Le seul lien entre le processus parent (OverMind MCP) et le processus enfant est le `sessionId` — généré par le runner, opaque pour OverMind.
26
+
27
+ **Problème** : Si OverMind restart ou crash, le `sessionId` est perdu et le child process devient **orphelin**.
28
+
29
+ **Solution** : Le **Process Registry** (`sessions.json`) stocke le mapping complet :
30
+
31
+ ```
32
+ pid ↔ sessionId ↔ agentName ↔ runner ↔ status ↔ outputBuffer
33
+ ```
34
+
35
+ `agent_control` est l'unique interface pour interagir avec ce registry.
36
+
37
+ ### Architecture
38
+
39
+ ```
40
+ Client OverMind MCP Process
41
+ │ │ │
42
+ │ run_agent() │ spawn(claude) │
43
+ │──────────────────────────────────►│ registerProcess(pid) │
44
+ │ │─────────────────────────────►│
45
+ │ │ │
46
+ │ agent_control(action: "status") │ │
47
+ │──────────────────────────────────►│ getProcessStatus(pid) │
48
+ │ { status, pid, outputBuffer } │ │
49
+ │◄─────────────────────────────────│ │
50
+ │ │ │
51
+ │ agent_control(action: "stream") │ │
52
+ │──────────────────────────────────►│ read outputBuffer │
53
+ │ { output, isComplete } │ │
54
+ │◄─────────────────────────────────│ │
55
+ │ │ │
56
+ │ agent_control(action: "wait") │ poll every 1s... │
57
+ │──────────────────────────────────►│ │
58
+ │ │ child.on('close') │
59
+ │ { result } │◄─────────────────────────────│
60
+ │◄─────────────────────────────────│ │
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 2. Actions Disponibles
66
+
67
+ ### `status` — Lecture Pure, Zero Side-Effect
68
+
69
+ Retourne l'état courant du process **sans modifier le registry**.
70
+
71
+ ```javascript
72
+ agent_control({
73
+ agentName: 'sniper_analyst',
74
+ runner: 'kilo',
75
+ action: 'status',
76
+ })
77
+ ```
78
+
79
+ **Réponse :**
80
+
81
+ ```markdown
82
+ **Agent:** sniper_analyst
83
+ **Runner:** kilo
84
+ **Status:** running
85
+ **Started:** 2026-05-10T14:32:00.000Z
86
+ **PID:** 12345
87
+ **Session ID:** sess_abc123
88
+
89
+ **Output Buffer (2048 chars):**
90
+
91
+ ```
92
+ Thinking...
93
+ Fetching BTC data...
94
+ Analysis complete.
95
+ ```
96
+ ```
97
+
98
+ **États possibles :**
99
+
100
+ | Status | Signification |
101
+ |--------|---------------|
102
+ | `running` | Process actif, PID valide |
103
+ | `done` | Terminé avec code 0 |
104
+ | `failed` | Terminé avec erreur (exit code != 0) |
105
+ | `orphaned` | Parent mort mais child tourne encore |
106
+
107
+ ---
108
+
109
+ ### `stream` — Lecture + Indicateur de Complétude
110
+
111
+ Retourne l'output accumulé + un flag `isComplete` pour savoir si le process est fini.
112
+
113
+ ```javascript
114
+ agent_control({
115
+ agentName: 'sniper_analyst',
116
+ runner: 'kilo',
117
+ action: 'stream',
118
+ sinceTimestamp: 1746892800000, // optionnel
119
+ })
120
+ ```
121
+
122
+ **Réponse :**
123
+
124
+ ```markdown
125
+ **Agent:** sniper_analyst
126
+ **Status:** running
127
+ **isComplete:** false
128
+ **PID:** 12345
129
+ **Last Output At:** 2026-05-10T14:32:45.000Z
130
+
131
+ **Output (2048 chars):**
132
+
133
+ ```
134
+ Thinking...
135
+ Fetching BTC data...
136
+ ```
137
+ ```
138
+
139
+ Quand `isComplete: true` :
140
+ ```markdown
141
+ **Agent:** sniper_analyst
142
+ **Status:** done
143
+ **isComplete:** true
144
+ **PID:** 12345
145
+
146
+ **Output (4096 chars):**
147
+
148
+ ```
149
+ Final analysis: BUY signal detected.
150
+ ```
151
+ ```
152
+
153
+ ---
154
+
155
+ ### `kill` — Destruction Irréversible
156
+
157
+ Tue le process tree via `taskkill /F /T /PID` (Windows) ou `kill -9` (Unix).
158
+
159
+ ```javascript
160
+ agent_control({
161
+ agentName: 'sniper_analyst',
162
+ runner: 'kilo',
163
+ action: 'kill',
164
+ })
165
+ ```
166
+
167
+ **Réponse (succès) :**
168
+
169
+ ```markdown
170
+ Agent "sniper_analyst" tué avec succès (PID: 12345). Status mis à jour → 'failed' dans le registry.
171
+ ```
172
+
173
+ **Réponse (échec) :**
174
+
175
+ ```markdown
176
+ Agent "sniper_analyst" n'est pas en cours d'exécution (status: done). Impossible de tuer un agent déjà terminé.
177
+ ```
178
+
179
+ > **⚠️ IRRÉVERSIBLE** : Une fois tué, le process ne peut pas être récupéré. Utiliser `kill` uniquement pour un abort d'urgence ou kill-switch.
180
+
181
+ ---
182
+
183
+ ### `wait` — Blocage Async avec Polling
184
+
185
+ Poll toutes les 1s jusqu'à ce que le status ne soit plus `running`, ou que le timeout soit atteint.
186
+
187
+ ```javascript
188
+ agent_control({
189
+ agentName: 'sniper_analyst',
190
+ runner: 'kilo',
191
+ action: 'wait',
192
+ timeoutMs: 300000, // 5 minutes (défaut: 900000 = 15 min)
193
+ })
194
+ ```
195
+
196
+ **Réponse (terminé) :**
197
+
198
+ ```markdown
199
+ Final analysis: BUY signal detected. Portfolio balanced.
200
+ ```
201
+
202
+ **Réponse (timeout) :**
203
+
204
+ ```markdown
205
+ Timeout de 300000ms atteint. L'agent "sniper_analyst" est toujours en cours d'exécution (status: running). Utilisez action="kill" pour forcer l'arrêt ou augmentez timeoutMs.
206
+ ```
207
+
208
+ **Réponse (erreur) :**
209
+
210
+ ```markdown
211
+ Agent terminé avec erreur (failed):
212
+
213
+ TypeError: Cannot read property 'price' of undefined
214
+ at analyzeBTC (/app/bot.js:42)
215
+ at processTicksAndCallbacks (internal/process/task_queues.js:95)
216
+ ```
217
+
218
+ ---
219
+
220
+ ## 3. Codes d'Erreur
221
+
222
+ Chaque erreur est structurée avec un **code** pour faciliter le debugging programatique.
223
+
224
+ | Code | Signification | Action recommandée |
225
+ |------|---------------|---------------------|
226
+ | `AGENT_NOT_FOUND` | Agent absent du registry | Vérifier le nom ou le runner |
227
+ | `AGENT_NOT_RUNNING` | Action `kill` sur un agent déjà terminé | Ne pas tuer un agent already done |
228
+ | `KILL_FAILED` | `taskkill`/`kill` a échoué | Vérifier permissions Windows/Unix |
229
+ | `WAIT_TIMEOUT` | Timeout atteint sans terminaison | Augmenter `timeoutMs` ou utiliser `kill` |
230
+ | `ORPHANED_PROCESS` | Process zombie détecté | `kill` puis relancer l'agent |
231
+
232
+ ---
233
+
234
+ ## 4. Patterns Async avec OverMind
235
+
236
+ ### Pattern 1 : Lancer et Ne Pas Bloquer (Fire & Forget)
237
+
238
+ ```javascript
239
+ // 1. Lancer l'agent en arrière-plan
240
+ const runResult = await run_agent({
241
+ runner: 'kilo',
242
+ agentName: 'crypto_scanner',
243
+ prompt: 'Scan BTC/USDT for trading opportunities',
244
+ });
245
+
246
+ // 2. Récupérer le PID depuis le output (format: "PID: 12345")
247
+ // OU depuis le sessionId pour retrouver le process plus tard
248
+ const sessionId = runResult.sessionId; // "sess_abc123"
249
+
250
+ // 3. Polling non-bloquant pour vérifier l'état
251
+ const status = await agent_control({
252
+ agentName: 'crypto_scanner',
253
+ runner: 'kilo',
254
+ action: 'status',
255
+ });
256
+ ```
257
+
258
+ ### Pattern 2 : Lancer et Attendre (Blocking Wait)
259
+
260
+ ```javascript
261
+ // Une seule ligne pour lancer et attendre
262
+ const result = await agent_control({
263
+ agentName: 'long_task',
264
+ runner: 'claude',
265
+ action: 'wait',
266
+ timeoutMs: 600000, // 10 minutes
267
+ });
268
+
269
+ if (result.isError) {
270
+ console.error('Task failed:', result.content[0].text);
271
+ } else {
272
+ console.log('Result:', result.content[0].text);
273
+ }
274
+ ```
275
+
276
+ ### Pattern 3 : Orchestration Séquentielle
277
+
278
+ ```javascript
279
+ async function runWorkflow(steps) {
280
+ const results = [];
281
+
282
+ for (const step of steps) {
283
+ console.log(`Starting step: ${step.name}`);
284
+
285
+ const result = await agent_control({
286
+ agentName: step.agentName,
287
+ runner: step.runner,
288
+ action: 'wait',
289
+ timeoutMs: step.timeoutMs || 300000,
290
+ });
291
+
292
+ if (result.isError) {
293
+ return {
294
+ success: false,
295
+ failedStep: step.name,
296
+ error: result.content[0].text,
297
+ partialResults: results,
298
+ };
299
+ }
300
+
301
+ results.push({ step: step.name, output: result.content[0].text });
302
+ }
303
+
304
+ return { success: true, results };
305
+ }
306
+
307
+ // Usage
308
+ const workflow = await runWorkflow([
309
+ { name: 'fetch_data', agentName: 'data_fetcher', runner: 'kilo', timeoutMs: 60000 },
310
+ { name: 'analyze', agentName: 'analyzer', runner: 'claude', timeoutMs: 300000 },
311
+ { name: 'report', agentName: 'reporter', runner: 'kilo', timeoutMs: 120000 },
312
+ ]);
313
+ ```
314
+
315
+ ### Pattern 4 : Exécution Parallèle (Fan-Out)
316
+
317
+ ```javascript
318
+ // Lancer plusieurs agents en parallèle
319
+ const agents = [
320
+ { agentName: 'btc_analyst', runner: 'kilo', prompt: 'Analyze BTC' },
321
+ { agentName: 'eth_analyst', runner: 'kilo', prompt: 'Analyze ETH' },
322
+ { agentName: 'sol_analyst', runner: 'claude', prompt: 'Analyze SOL' },
323
+ ];
324
+
325
+ // Lancer les 3 en parallèle
326
+ const runPromises = agents.map(a =>
327
+ run_agent({ runner: a.runner, agentName: a.agentName, prompt: a.prompt })
328
+ );
329
+
330
+ await Promise.all(runPromises);
331
+
332
+ // Attendre que tous soient prêts
333
+ const waitPromises = agents.map(a =>
334
+ agent_control({ agentName: a.agentName, runner: a.runner, action: 'wait', timeoutMs: 300000 })
335
+ );
336
+
337
+ const results = await Promise.all(waitPromises);
338
+
339
+ for (const [i, r] of results.entries()) {
340
+ console.log(`${agents[i].agentName}:`, r.isError ? 'FAILED' : 'OK');
341
+ }
342
+ ```
343
+
344
+ ### Pattern 5 : Resume après Crash OverMind
345
+
346
+ Quand OverMind restart, on peut se rattacher aux agents en cours via le registry :
347
+
348
+ ```javascript
349
+ // 1. Scanner tous les agents "running" dans le registry
350
+ const runningAgents = await getRunningProcesses();
351
+
352
+ // 2. Pour chaque agent encore vivant mais "orphaned"
353
+ for (const agent of runningAgents) {
354
+ const status = await agent_control({
355
+ agentName: agent.agentName,
356
+ runner: agent.runner,
357
+ action: 'status',
358
+ });
359
+
360
+ if (status.content[0].text.includes('orphaned')) {
361
+ console.log(`Orphaned agent detected: ${agent.agentName} (PID: ${agent.pid})`);
362
+ // Option: kill et relancer, ou laisser tel quel
363
+ }
364
+ }
365
+ ```
366
+
367
+ ---
368
+
369
+ ## 5. Tracker PID ↔ Session ↔ Agent
370
+
371
+ Le registry dans `sessions.json` stocke la cartographie complète :
372
+
373
+ ```json
374
+ {
375
+ "kilo:sniper_analyst": {
376
+ "id": "sess_abc123",
377
+ "ts": 1746892800000,
378
+ "pid": 12345,
379
+ "runner": "kilo",
380
+ "agentName": "sniper_analyst",
381
+ "status": "running",
382
+ "outputBuffer": "Thinking...\nFetching data...",
383
+ "exitCode": null,
384
+ "lastOutputAt": 1746892900000
385
+ },
386
+ "claude:reporter": {
387
+ "id": "sess_def456",
388
+ "ts": 1746892850000,
389
+ "pid": 67890,
390
+ "runner": "claude",
391
+ "agentName": "reporter",
392
+ "status": "done",
393
+ "outputBuffer": "Report generated successfully.",
394
+ "exitCode": 0,
395
+ "lastOutputAt": 1746893500000
396
+ }
397
+ }
398
+ ```
399
+
400
+ ### Trouver un Agent par Timestamp
401
+
402
+ ```javascript
403
+ // Trouver tous les agents活跃 après une date donnée
404
+ async function findAgentsAfter(timestampMs) {
405
+ const allAgents = await getProcessStatus('*'); // sans runner = tous
406
+
407
+ return allAgents
408
+ .filter(a => a.ts >= timestampMs)
409
+ .sort((a, b) => a.ts - b.ts); // plus récents d'abord
410
+ }
411
+
412
+ // Usage
413
+ const recent = await findAgentsAfter(Date.now() - 3600000); // dernière heure
414
+ console.log(recent.map(a => `${a.agentName} @ ${new Date(a.ts).toISOString()}`));
415
+ ```
416
+
417
+ ### Trouver un Agent par PID
418
+
419
+ ```javascript
420
+ // Via le registry scan
421
+ async function findAgentByPid(pid) {
422
+ const { store } = await readStore();
423
+
424
+ for (const [key, entry] of Object.entries(store)) {
425
+ if (typeof entry === 'object' && entry !== null && entry.pid === pid) {
426
+ return { key, ...entry };
427
+ }
428
+ }
429
+ return null;
430
+ }
431
+
432
+ // Usage
433
+ const agent = await findAgentByPid(12345);
434
+ console.log(`Found: ${agent.agentName} (${agent.runner})`);
435
+ ```
436
+
437
+ ### Trouver un Agent par Session ID
438
+
439
+ ```javascript
440
+ // Via le registry scan
441
+ async function findAgentBySession(sessionId) {
442
+ const { store } = await readStore();
443
+
444
+ for (const [key, entry] of Object.entries(store)) {
445
+ if (typeof entry === 'object' && entry !== null && entry.id === sessionId) {
446
+ return { key, ...entry };
447
+ }
448
+ }
449
+ return null;
450
+ }
451
+
452
+ // Usage
453
+ const agent = await findAgentBySession('sess_abc123');
454
+ if (agent) {
455
+ console.log(`PID: ${agent.pid}, Status: ${agent.status}`);
456
+ }
457
+ ```
458
+
459
+ ---
460
+
461
+ ## 6. Dashboard en Temps Réel
462
+
463
+ ### Exemple Complet : Dashboard CLI
464
+
465
+ ```javascript
466
+ import { getRunningProcesses } from './lib/processRegistry.js';
467
+
468
+ async function dashboard() {
469
+ console.clear();
470
+ console.log('═══════════════════════════════════════════════════════');
471
+ console.log(' OVERMIND AGENT DASHBOARD');
472
+ console.log('═══════════════════════════════════════════════════════\n');
473
+
474
+ const running = await getRunningProcesses();
475
+
476
+ if (running.length === 0) {
477
+ console.log('Aucun agent en cours d\'exécution.\n');
478
+ return;
479
+ }
480
+
481
+ console.log(`📊 ${running.length} agent(s) actifs\n`);
482
+ console.log('┌──────────────────┬────────┬─────────┬───────────────┬────────────────────────┐');
483
+ console.log('│ Agent │ Runner │ PID │ Status │ Started │');
484
+ console.log('├──────────────────┼────────┼─────────┼───────────────┼────────────────────────┤');
485
+
486
+ for (const agent of running) {
487
+ const elapsed = Date.now() - agent.ts;
488
+ const elapsedStr = elapsed < 60000
489
+ ? `${Math.floor(elapsed / 1000)}s`
490
+ : `${Math.floor(elapsed / 60000)}m`;
491
+
492
+ const statusColor = agent.status === 'running' ? '🟢' : '⚠️';
493
+ console.log(
494
+ `│ ${agent.agentName.padEnd(16)} │ ${(agent.runner || '?').padEnd(6)} │ ${String(agent.pid || 'N/A').padStart(7)} │ ${statusColor} ${agent.status.padEnd(11)} │ ${new Date(agent.ts).toLocaleTimeString().padEnd(22)} │`
495
+ );
496
+ }
497
+
498
+ console.log('└──────────────────┴────────┴─────────┴───────────────┴────────────────────────┘\n');
499
+
500
+ // Detail du premier agent
501
+ if (running[0]) {
502
+ const detail = await agent_control({
503
+ agentName: running[0].agentName,
504
+ runner: running[0].runner,
505
+ action: 'stream',
506
+ });
507
+ console.log('═══ OUTPUT RECENT ═══');
508
+ console.log(detail.content[0].text.slice(-500));
509
+ }
510
+ }
511
+
512
+ // Rafraîchir toutes les 3 secondes
513
+ setInterval(dashboard, 3000);
514
+ ```
515
+
516
+ ### Exemple : Tableau de Bord HTML
517
+
518
+ ```html
519
+ <!DOCTYPE html>
520
+ <html>
521
+ <head>
522
+ <title>OverMind Dashboard</title>
523
+ <meta http-equiv="refresh" content="5">
524
+ <style>
525
+ body { font-family: monospace; background: #0d1117; color: #e6edf3; padding: 20px; }
526
+ .agent { border: 1px solid #30363d; padding: 10px; margin: 10px 0; border-radius: 6px; }
527
+ .running { border-left: 4px solid #3fb950; }
528
+ .done { border-left: 4px solid #58a6ff; }
529
+ .failed { border-left: 4px solid #f85149; }
530
+ .orphaned { border-left: 4px solid #d29922; }
531
+ .pid { color: #8b949e; }
532
+ .output { background: #161b22; padding: 8px; margin-top: 8px; max-height: 100px; overflow: auto; }
533
+ </style>
534
+ </head>
535
+ <body>
536
+ <h1>🤖 OverMind Agent Dashboard</h1>
537
+ <div id="agents"></div>
538
+
539
+ <script>
540
+ async function refresh() {
541
+ // Appeler agent_control pour chaque agent running
542
+ const response = await fetch('/api/agents/running');
543
+ const agents = await response.json();
544
+
545
+ document.getElementById('agents').innerHTML = agents.map(agent => `
546
+ <div class="agent ${agent.status}">
547
+ <strong>${agent.agentName}</strong>
548
+ <span class="pid">(${agent.runner} | PID: ${agent.pid})</span>
549
+ <br>
550
+ Status: ${agent.status} | Started: ${new Date(agent.ts).toLocaleString()}
551
+ <br>
552
+ Elapsed: ${Math.floor((Date.now() - agent.ts) / 1000)}s
553
+ <div class="output">${escapeHtml(agent.outputBuffer || '(no output)').slice(-200)}</div>
554
+ </div>
555
+ `).join('');
556
+ }
557
+
558
+ function escapeHtml(text) {
559
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
560
+ }
561
+
562
+ refresh();
563
+ setInterval(refresh, 5000);
564
+ </script>
565
+ </body>
566
+ </html>
567
+ ```
568
+
569
+ ---
570
+
571
+ ## 7. Flux Complet de Debug
572
+
573
+ Quand un agent ne répond plus ou pose problème :
574
+
575
+ ```
576
+ STEP 1: Identifier le problème
577
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
578
+ agent_control({ agentName: "sniper_analyst", action: "status" })
579
+
580
+ → Si status: "running" + lastOutputAt ancien → agent peut-être bloqué
581
+ → Si status: "orphaned" → parent mort, child tourne encore
582
+ → Si status: "done" → agent déjà terminé, problem elsewhere
583
+
584
+ STEP 2: Voir l'output
585
+ ━━━━━━━━━━━━━━━━━━━━━━
586
+ agent_control({ agentName: "sniper_analyst", action: "stream" })
587
+
588
+ → Identifier la dernière ligne avant le blocage
589
+ → Vérifier s'il y a des erreurs dans le buffer
590
+
591
+ STEP 3: Décider de l'action
592
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━
593
+
594
+ ┌─ still running, output OK → Attendre (action: "wait", timeoutMs: X)
595
+
596
+ ├─ still running, BLOCKED → kill (action: "kill") → restart
597
+
598
+ ├─ orphaned → kill (action: "kill") → restart
599
+
600
+ └─ done with error → Analyser output → fix prompt → restart
601
+
602
+ STEP 4: Nettoyer si nécessaire
603
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
604
+ Le registry fait le cleanup automatique après 1h (TTL).
605
+ Pour forcer le cleanup immédiat :
606
+
607
+ unregisterProcess(pid) → supprime l'entrée du registry
608
+ ```
609
+
610
+ ---
611
+
612
+ ## 8. Référence Rapide
613
+
614
+ ```javascript
615
+ // Status simple
616
+ agent_control({ agentName: "...", action: "status" })
617
+
618
+ // Stream avec filtre timestamp
619
+ agent_control({ agentName: "...", action: "stream", sinceTimestamp: 1746892800000 })
620
+
621
+ // Kill
622
+ agent_control({ agentName: "...", action: "kill" })
623
+
624
+ // Wait avec timeout custom
625
+ agent_control({ agentName: "...", action: "wait", timeoutMs: 300000 })
626
+
627
+ // Avec runner explicite (recommendé)
628
+ agent_control({ agentName: "...", runner: "kilo", action: "..." })
629
+
630
+ // Avec config path (si plusieurs OverMind)
631
+ agent_control({ agentName: "...", config: "/path/to/overmind", action: "..." })
632
+ ```
633
+
634
+ ### Paramètres Communs
635
+
636
+ | Param | Type | Description |
637
+ |-------|------|-------------|
638
+ | `agentName` | string | Nom unique de l'agent |
639
+ | `runner` | enum | Type de runner (optionnel, déduit si omis) |
640
+ | `config` | string | Chemin racine Overmind (optionnel) |
641
+
642
+ ### Actions
643
+
644
+ | Action | Description | Retourne |
645
+ |--------|-------------|----------|
646
+ | `status` | État courant | pid, status, sessionId, outputBuffer |
647
+ | `stream` | Output + complétude | output, isComplete |
648
+ | `kill` | Arrêt forcé | confirmation ou erreur |
649
+ | `wait` | Attente terminaison | output final ou timeout |
650
+
651
+ ---
652
+
653
+ **Fichier source** : `src/tools/agent_control.ts`
654
+ **Registry** : `src/lib/processRegistry.ts`
655
+ **Store** : `.claude/sessions.json`
656
656
  **TTL** : 1h après terminaison (cleanup automatique)