elsabro 2.3.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +668 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/package.json +3 -2
  6. package/references/SYSTEM_INDEX.md +379 -5
  7. package/references/agent-marketplace.md +2274 -0
  8. package/references/agent-protocol.md +1126 -0
  9. package/references/ai-code-suggestions.md +2413 -0
  10. package/references/checkpointing.md +595 -0
  11. package/references/collaboration-patterns.md +851 -0
  12. package/references/collaborative-sessions.md +1081 -0
  13. package/references/configuration-management.md +1810 -0
  14. package/references/cost-tracking.md +1095 -0
  15. package/references/enterprise-sso.md +2001 -0
  16. package/references/error-contracts-v2.md +968 -0
  17. package/references/event-driven.md +1031 -0
  18. package/references/flow-orchestration.md +940 -0
  19. package/references/flow-visualization.md +1557 -0
  20. package/references/ide-integrations.md +3513 -0
  21. package/references/interrupt-system.md +681 -0
  22. package/references/kubernetes-deployment.md +3099 -0
  23. package/references/memory-system.md +683 -0
  24. package/references/mobile-companion.md +3236 -0
  25. package/references/multi-llm-providers.md +2494 -0
  26. package/references/multi-project-memory.md +1182 -0
  27. package/references/observability.md +793 -0
  28. package/references/output-schemas.md +858 -0
  29. package/references/performance-profiler.md +955 -0
  30. package/references/plugin-system.md +1526 -0
  31. package/references/prompt-management.md +292 -0
  32. package/references/sandbox-execution.md +303 -0
  33. package/references/security-system.md +1253 -0
  34. package/references/streaming.md +696 -0
  35. package/references/testing-framework.md +1151 -0
  36. package/references/time-travel.md +802 -0
  37. package/references/tool-registry.md +886 -0
  38. package/references/voice-commands.md +3296 -0
  39. package/templates/agent-marketplace-config.json +220 -0
  40. package/templates/agent-protocol-config.json +136 -0
  41. package/templates/ai-suggestions-config.json +100 -0
  42. package/templates/checkpoint-state.json +61 -0
  43. package/templates/collaboration-config.json +157 -0
  44. package/templates/collaborative-sessions-config.json +153 -0
  45. package/templates/configuration-config.json +245 -0
  46. package/templates/cost-tracking-config.json +148 -0
  47. package/templates/enterprise-sso-config.json +438 -0
  48. package/templates/events-config.json +148 -0
  49. package/templates/flow-visualization-config.json +196 -0
  50. package/templates/ide-integrations-config.json +442 -0
  51. package/templates/kubernetes-config.json +764 -0
  52. package/templates/memory-state.json +84 -0
  53. package/templates/mobile-companion-config.json +600 -0
  54. package/templates/multi-llm-config.json +544 -0
  55. package/templates/multi-project-memory-config.json +145 -0
  56. package/templates/observability-config.json +109 -0
  57. package/templates/performance-profiler-config.json +125 -0
  58. package/templates/plugin-config.json +170 -0
  59. package/templates/prompt-management-config.json +86 -0
  60. package/templates/sandbox-config.json +185 -0
  61. package/templates/schemas-config.json +65 -0
  62. package/templates/security-config.json +120 -0
  63. package/templates/streaming-config.json +72 -0
  64. package/templates/testing-config.json +81 -0
  65. package/templates/timetravel-config.json +62 -0
  66. package/templates/tool-registry-config.json +109 -0
  67. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,968 @@
1
+ ---
2
+ name: error-contracts-v2
3
+ description: Contratos de error expandidos v2 - Circuit Breaker, Bulkhead, Fallback
4
+ version: 2.0.0
5
+ ---
6
+
7
+ # ELSABRO Error Contracts v2
8
+
9
+ ## Nuevos Contratos (v3.0)
10
+
11
+ Este documento extiende `/references/error-contracts.md` con 3 nuevos contratos de resiliencia.
12
+
13
+ ```
14
+ ┌──────────────────────────────────────────────────────────────────────────┐
15
+ │ RESILIENCE PATTERNS │
16
+ ├──────────────────────────────────────────────────────────────────────────┤
17
+ │ │
18
+ │ ┌─────────────────┐ │
19
+ │ │ REQUEST │ │
20
+ │ └────────┬────────┘ │
21
+ │ │ │
22
+ │ ▼ │
23
+ │ ┌─────────────────┐ OPEN? ┌─────────────────┐ │
24
+ │ │ CIRCUIT BREAKER │ ──────────► │ FALLBACK │ │
25
+ │ └────────┬────────┘ └─────────────────┘ │
26
+ │ │ CLOSED │
27
+ │ ▼ │
28
+ │ ┌─────────────────┐ FULL? ┌─────────────────┐ │
29
+ │ │ BULKHEAD │ ──────────► │ FALLBACK │ │
30
+ │ └────────┬────────┘ └─────────────────┘ │
31
+ │ │ OK │
32
+ │ ▼ │
33
+ │ ┌─────────────────┐ │
34
+ │ │ EXECUTE │ │
35
+ │ └────────┬────────┘ │
36
+ │ │ │
37
+ │ ▼ │
38
+ │ SUCCESS / FAILURE │
39
+ │ │
40
+ └──────────────────────────────────────────────────────────────────────────┘
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Contrato 8: ContractCircuitBreaker
46
+
47
+ ### Propósito
48
+
49
+ Prevenir cascadas de fallos cortando temporalmente las llamadas a un servicio/agente que está fallando repetidamente.
50
+
51
+ ### Estados del Circuit Breaker
52
+
53
+ ```
54
+ ┌─────────────────────────────────────────────────────────────────────────┐
55
+ │ CIRCUIT BREAKER STATES │
56
+ ├─────────────────────────────────────────────────────────────────────────┤
57
+ │ │
58
+ │ ┌──────────┐ │
59
+ │ │ CLOSED │ ◄──────── Estado normal, requests pasan │
60
+ │ └────┬─────┘ │
61
+ │ │ │
62
+ │ │ failures >= threshold │
63
+ │ ▼ │
64
+ │ ┌──────────┐ │
65
+ │ │ OPEN │ ◄──────── Circuito abierto, requests bloqueados │
66
+ │ └────┬─────┘ │
67
+ │ │ │
68
+ │ │ timeout expires │
69
+ │ ▼ │
70
+ │ ┌──────────┐ │
71
+ │ │HALF-OPEN │ ◄──────── Probando si el servicio se recuperó │
72
+ │ └────┬─────┘ │
73
+ │ │ │
74
+ │ ┌────┴────┐ │
75
+ │ │ │ │
76
+ │ success failure │
77
+ │ │ │ │
78
+ │ ▼ ▼ │
79
+ │ CLOSED OPEN │
80
+ │ │
81
+ └─────────────────────────────────────────────────────────────────────────┘
82
+ ```
83
+
84
+ ### Código Ejecutable
85
+
86
+ ```javascript
87
+ /**
88
+ * ContractCircuitBreaker
89
+ * Previene cascadas de fallos con pattern circuit breaker
90
+ */
91
+ class ContractCircuitBreaker {
92
+ static STATES = {
93
+ CLOSED: 'CLOSED',
94
+ OPEN: 'OPEN',
95
+ HALF_OPEN: 'HALF_OPEN'
96
+ };
97
+
98
+ constructor(options = {}) {
99
+ this.config = {
100
+ failureThreshold: options.failureThreshold || 5,
101
+ successThreshold: options.successThreshold || 2,
102
+ timeout: options.timeout || 30000,
103
+ monitorInterval: options.monitorInterval || 5000
104
+ };
105
+
106
+ this.circuits = new Map();
107
+ }
108
+
109
+ /**
110
+ * Obtiene o crea circuit para un agente
111
+ */
112
+ getCircuit(agentId) {
113
+ if (!this.circuits.has(agentId)) {
114
+ this.circuits.set(agentId, {
115
+ state: ContractCircuitBreaker.STATES.CLOSED,
116
+ failures: 0,
117
+ successes: 0,
118
+ lastFailure: null,
119
+ lastStateChange: Date.now(),
120
+ stats: {
121
+ totalCalls: 0,
122
+ totalFailures: 0,
123
+ totalSuccesses: 0,
124
+ tripped: 0
125
+ }
126
+ });
127
+ }
128
+ return this.circuits.get(agentId);
129
+ }
130
+
131
+ /**
132
+ * Verifica si se puede ejecutar
133
+ */
134
+ canExecute(agentId) {
135
+ const circuit = this.getCircuit(agentId);
136
+
137
+ switch (circuit.state) {
138
+ case ContractCircuitBreaker.STATES.CLOSED:
139
+ return { allowed: true, state: 'CLOSED' };
140
+
141
+ case ContractCircuitBreaker.STATES.OPEN:
142
+ // Verificar si timeout expiró
143
+ if (Date.now() - circuit.lastStateChange >= this.config.timeout) {
144
+ this.transitionTo(agentId, ContractCircuitBreaker.STATES.HALF_OPEN);
145
+ return { allowed: true, state: 'HALF_OPEN', warning: 'Testing recovery' };
146
+ }
147
+ return {
148
+ allowed: false,
149
+ state: 'OPEN',
150
+ error: {
151
+ code: 'CIRCUIT_OPEN',
152
+ severity: 'HIGH',
153
+ message: `Circuit breaker OPEN for ${agentId}`,
154
+ retryAfter: this.config.timeout - (Date.now() - circuit.lastStateChange),
155
+ stats: circuit.stats
156
+ }
157
+ };
158
+
159
+ case ContractCircuitBreaker.STATES.HALF_OPEN:
160
+ return { allowed: true, state: 'HALF_OPEN', warning: 'Circuit testing' };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Registra éxito
166
+ */
167
+ recordSuccess(agentId) {
168
+ const circuit = this.getCircuit(agentId);
169
+ circuit.stats.totalCalls++;
170
+ circuit.stats.totalSuccesses++;
171
+
172
+ if (circuit.state === ContractCircuitBreaker.STATES.HALF_OPEN) {
173
+ circuit.successes++;
174
+
175
+ if (circuit.successes >= this.config.successThreshold) {
176
+ this.transitionTo(agentId, ContractCircuitBreaker.STATES.CLOSED);
177
+ return { recovered: true, state: 'CLOSED' };
178
+ }
179
+ }
180
+
181
+ circuit.failures = 0;
182
+ return { state: circuit.state };
183
+ }
184
+
185
+ /**
186
+ * Registra fallo
187
+ */
188
+ recordFailure(agentId, error) {
189
+ const circuit = this.getCircuit(agentId);
190
+ circuit.stats.totalCalls++;
191
+ circuit.stats.totalFailures++;
192
+ circuit.failures++;
193
+ circuit.lastFailure = { at: Date.now(), error: error?.message };
194
+
195
+ if (circuit.state === ContractCircuitBreaker.STATES.HALF_OPEN) {
196
+ // Fallo durante prueba, volver a OPEN
197
+ this.transitionTo(agentId, ContractCircuitBreaker.STATES.OPEN);
198
+ return { tripped: true, state: 'OPEN', reason: 'Failed during recovery test' };
199
+ }
200
+
201
+ if (circuit.state === ContractCircuitBreaker.STATES.CLOSED &&
202
+ circuit.failures >= this.config.failureThreshold) {
203
+ this.transitionTo(agentId, ContractCircuitBreaker.STATES.OPEN);
204
+ circuit.stats.tripped++;
205
+ return { tripped: true, state: 'OPEN', reason: 'Failure threshold exceeded' };
206
+ }
207
+
208
+ return { state: circuit.state, failures: circuit.failures };
209
+ }
210
+
211
+ /**
212
+ * Transición de estado
213
+ */
214
+ transitionTo(agentId, newState) {
215
+ const circuit = this.getCircuit(agentId);
216
+ const oldState = circuit.state;
217
+
218
+ circuit.state = newState;
219
+ circuit.lastStateChange = Date.now();
220
+ circuit.failures = 0;
221
+ circuit.successes = 0;
222
+
223
+ // Emitir evento para logging
224
+ this.emitStateChange(agentId, oldState, newState);
225
+
226
+ return { from: oldState, to: newState };
227
+ }
228
+
229
+ /**
230
+ * Reset manual
231
+ */
232
+ reset(agentId) {
233
+ if (this.circuits.has(agentId)) {
234
+ this.transitionTo(agentId, ContractCircuitBreaker.STATES.CLOSED);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Obtiene estado de todos los circuits
240
+ */
241
+ getStatus() {
242
+ const status = {};
243
+ for (const [agentId, circuit] of this.circuits) {
244
+ status[agentId] = {
245
+ state: circuit.state,
246
+ failures: circuit.failures,
247
+ stats: circuit.stats,
248
+ lastFailure: circuit.lastFailure
249
+ };
250
+ }
251
+ return status;
252
+ }
253
+
254
+ emitStateChange(agentId, from, to) {
255
+ console.log(`[CircuitBreaker] ${agentId}: ${from} → ${to}`);
256
+ }
257
+ }
258
+ ```
259
+
260
+ ### Notificación al Usuario
261
+
262
+ ```
263
+ ╔════════════════════════════════════════════════════════════╗
264
+ ║ ⚡ CIRCUIT BREAKER TRIGGERED ║
265
+ ╠════════════════════════════════════════════════════════════╣
266
+ ║ ║
267
+ ║ Agent: elsabro-executor ║
268
+ ║ State: OPEN ║
269
+ ║ Reason: 5 consecutive failures ║
270
+ ║ ║
271
+ ║ Stats: ║
272
+ ║ └─ Total calls: 12 ║
273
+ ║ └─ Total failures: 7 ║
274
+ ║ └─ Times tripped: 2 ║
275
+ ║ ║
276
+ ║ Auto-retry in: 30 seconds ║
277
+ ║ ║
278
+ ╠════════════════════════════════════════════════════════════╣
279
+ ║ [w] Wait for auto-retry ║
280
+ ║ [r] Reset manually and retry now ║
281
+ ║ [f] Use fallback agent ║
282
+ ║ [a] Abort operation ║
283
+ ╚════════════════════════════════════════════════════════════╝
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Contrato 9: ContractBulkhead
289
+
290
+ ### Propósito
291
+
292
+ Aislar fallos limitando la concurrencia por tipo de operación/agente. Evita que un agente problemático consuma todos los recursos.
293
+
294
+ ### Diagrama
295
+
296
+ ```
297
+ ┌──────────────────────────────────────────────────────────────────────────┐
298
+ │ BULKHEAD ISOLATION │
299
+ ├──────────────────────────────────────────────────────────────────────────┤
300
+ │ │
301
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
302
+ │ │ TOTAL CAPACITY: 10 │ │
303
+ │ ├─────────────────────────────────────────────────────────────────┤ │
304
+ │ │ │ │
305
+ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
306
+ │ │ │ EXPLORATION │ │IMPLEMENTATION│ │ VERIFICATION │ │ │
307
+ │ │ │ [3/4] │ │ [2/4] │ │ [1/2] │ │ │
308
+ │ │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ │
309
+ │ │ │ █ █ █ ░ │ │ █ █ ░ ░ │ │ █ ░ │ │ │
310
+ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
311
+ │ │ │ │
312
+ │ │ Queued: 2 exploration, 0 implementation, 1 verification │ │
313
+ │ │ │ │
314
+ │ └─────────────────────────────────────────────────────────────────┘ │
315
+ │ │
316
+ └──────────────────────────────────────────────────────────────────────────┘
317
+ ```
318
+
319
+ ### Código Ejecutable
320
+
321
+ ```javascript
322
+ /**
323
+ * ContractBulkhead
324
+ * Aísla fallos con semáforos por tipo de operación
325
+ */
326
+ class ContractBulkhead {
327
+ constructor(options = {}) {
328
+ this.config = {
329
+ globalLimit: options.globalLimit || 10,
330
+ queueTimeout: options.queueTimeout || 60000,
331
+ defaultLimit: options.defaultLimit || 3
332
+ };
333
+
334
+ this.partitions = new Map();
335
+ this.globalActive = 0;
336
+ this.queues = new Map();
337
+ }
338
+
339
+ /**
340
+ * Configura límites por partición
341
+ */
342
+ configurePartition(partitionId, limit) {
343
+ if (!this.partitions.has(partitionId)) {
344
+ this.partitions.set(partitionId, {
345
+ limit: limit,
346
+ active: 0,
347
+ total: 0,
348
+ rejected: 0
349
+ });
350
+ this.queues.set(partitionId, []);
351
+ } else {
352
+ this.partitions.get(partitionId).limit = limit;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Intenta adquirir slot
358
+ */
359
+ async acquire(partitionId, options = {}) {
360
+ const { timeout = this.config.queueTimeout, priority = 0 } = options;
361
+
362
+ // Asegurar partición existe
363
+ if (!this.partitions.has(partitionId)) {
364
+ this.configurePartition(partitionId, this.config.defaultLimit);
365
+ }
366
+
367
+ const partition = this.partitions.get(partitionId);
368
+
369
+ // Verificar límite global
370
+ if (this.globalActive >= this.config.globalLimit) {
371
+ return this.enqueueOrReject(partitionId, { timeout, priority, reason: 'global_limit' });
372
+ }
373
+
374
+ // Verificar límite de partición
375
+ if (partition.active >= partition.limit) {
376
+ return this.enqueueOrReject(partitionId, { timeout, priority, reason: 'partition_limit' });
377
+ }
378
+
379
+ // Adquirir slot
380
+ partition.active++;
381
+ partition.total++;
382
+ this.globalActive++;
383
+
384
+ return {
385
+ acquired: true,
386
+ slot: {
387
+ partitionId,
388
+ acquiredAt: Date.now(),
389
+ release: () => this.release(partitionId)
390
+ },
391
+ stats: this.getPartitionStats(partitionId)
392
+ };
393
+ }
394
+
395
+ /**
396
+ * Encola o rechaza si no hay capacidad
397
+ */
398
+ async enqueueOrReject(partitionId, options) {
399
+ const queue = this.queues.get(partitionId);
400
+ const partition = this.partitions.get(partitionId);
401
+
402
+ // Si la cola está muy llena, rechazar
403
+ if (queue.length >= partition.limit * 2) {
404
+ partition.rejected++;
405
+ return {
406
+ acquired: false,
407
+ error: {
408
+ code: 'BULKHEAD_REJECTED',
409
+ severity: 'MEDIUM',
410
+ message: `Partition ${partitionId} at capacity and queue full`,
411
+ queueLength: queue.length,
412
+ activeSlots: partition.active,
413
+ limit: partition.limit
414
+ }
415
+ };
416
+ }
417
+
418
+ // Encolar con timeout
419
+ return new Promise((resolve, reject) => {
420
+ const timeoutId = setTimeout(() => {
421
+ // Remover de cola
422
+ const idx = queue.findIndex(q => q.id === queueEntry.id);
423
+ if (idx !== -1) queue.splice(idx, 1);
424
+
425
+ resolve({
426
+ acquired: false,
427
+ error: {
428
+ code: 'BULKHEAD_TIMEOUT',
429
+ severity: 'MEDIUM',
430
+ message: `Timeout waiting for slot in partition ${partitionId}`,
431
+ waitedMs: options.timeout
432
+ }
433
+ });
434
+ }, options.timeout);
435
+
436
+ const queueEntry = {
437
+ id: `q_${Date.now()}_${Math.random().toString(36).substring(7)}`,
438
+ priority: options.priority,
439
+ resolve: (result) => {
440
+ clearTimeout(timeoutId);
441
+ resolve(result);
442
+ },
443
+ enqueuedAt: Date.now()
444
+ };
445
+
446
+ // Insertar ordenado por prioridad
447
+ const insertIdx = queue.findIndex(q => q.priority < options.priority);
448
+ if (insertIdx === -1) {
449
+ queue.push(queueEntry);
450
+ } else {
451
+ queue.splice(insertIdx, 0, queueEntry);
452
+ }
453
+ });
454
+ }
455
+
456
+ /**
457
+ * Libera un slot
458
+ */
459
+ release(partitionId) {
460
+ const partition = this.partitions.get(partitionId);
461
+ const queue = this.queues.get(partitionId);
462
+
463
+ if (partition && partition.active > 0) {
464
+ partition.active--;
465
+ this.globalActive--;
466
+
467
+ // Procesar cola
468
+ if (queue && queue.length > 0) {
469
+ const next = queue.shift();
470
+ partition.active++;
471
+ partition.total++;
472
+ this.globalActive++;
473
+
474
+ next.resolve({
475
+ acquired: true,
476
+ slot: {
477
+ partitionId,
478
+ acquiredAt: Date.now(),
479
+ waitedMs: Date.now() - next.enqueuedAt,
480
+ release: () => this.release(partitionId)
481
+ },
482
+ stats: this.getPartitionStats(partitionId)
483
+ });
484
+ }
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Obtiene stats de una partición
490
+ */
491
+ getPartitionStats(partitionId) {
492
+ const partition = this.partitions.get(partitionId);
493
+ const queue = this.queues.get(partitionId);
494
+
495
+ return {
496
+ partitionId,
497
+ active: partition?.active || 0,
498
+ limit: partition?.limit || 0,
499
+ queued: queue?.length || 0,
500
+ total: partition?.total || 0,
501
+ rejected: partition?.rejected || 0,
502
+ utilization: partition ? (partition.active / partition.limit * 100).toFixed(1) + '%' : '0%'
503
+ };
504
+ }
505
+
506
+ /**
507
+ * Obtiene stats globales
508
+ */
509
+ getGlobalStats() {
510
+ const stats = {
511
+ globalActive: this.globalActive,
512
+ globalLimit: this.config.globalLimit,
513
+ partitions: {}
514
+ };
515
+
516
+ for (const [id] of this.partitions) {
517
+ stats.partitions[id] = this.getPartitionStats(id);
518
+ }
519
+
520
+ return stats;
521
+ }
522
+ }
523
+
524
+ // Configuración por defecto para ELSABRO
525
+ const bulkhead = new ContractBulkhead({ globalLimit: 10 });
526
+ bulkhead.configurePartition('exploration', 4);
527
+ bulkhead.configurePartition('implementation', 4);
528
+ bulkhead.configurePartition('verification', 2);
529
+ ```
530
+
531
+ ### Notificación al Usuario
532
+
533
+ ```
534
+ ╔════════════════════════════════════════════════════════════╗
535
+ ║ 🔒 BULKHEAD LIMIT REACHED ║
536
+ ╠════════════════════════════════════════════════════════════╣
537
+ ║ ║
538
+ ║ Partition: implementation ║
539
+ ║ Status: 4/4 slots in use ║
540
+ ║ Queue: 2 waiting ║
541
+ ║ ║
542
+ ║ Current operations: ║
543
+ ║ └─ elsabro-executor: implementing auth (2m 30s) ║
544
+ ║ └─ elsabro-executor: implementing api (1m 45s) ║
545
+ ║ └─ feature-dev:code-architect: designing db (3m 10s) ║
546
+ ║ └─ elsabro-planner: planning tests (45s) ║
547
+ ║ ║
548
+ ╠════════════════════════════════════════════════════════════╣
549
+ ║ [w] Wait in queue (estimated: 2-3 min) ║
550
+ ║ [p] Priority queue (skip ahead) ║
551
+ ║ [a] Abort and try different approach ║
552
+ ╚════════════════════════════════════════════════════════════╝
553
+ ```
554
+
555
+ ---
556
+
557
+ ## Contrato 10: ContractFallback
558
+
559
+ ### Propósito
560
+
561
+ Proporcionar comportamiento alternativo cuando un agente/operación falla, permitiendo degradación graceful en lugar de fallo total.
562
+
563
+ ### Estrategias de Fallback
564
+
565
+ ```
566
+ ┌──────────────────────────────────────────────────────────────────────────┐
567
+ │ FALLBACK STRATEGIES │
568
+ ├──────────────────────────────────────────────────────────────────────────┤
569
+ │ │
570
+ │ 1. ALTERNATIVE AGENT │
571
+ │ elsabro-executor fails → use elsabro-quick-dev │
572
+ │ │
573
+ │ 2. SIMPLIFIED OPERATION │
574
+ │ Full analysis fails → quick analysis only │
575
+ │ │
576
+ │ 3. CACHED RESULT │
577
+ │ API call fails → return cached response │
578
+ │ │
579
+ │ 4. DEFAULT VALUE │
580
+ │ Config fetch fails → use default config │
581
+ │ │
582
+ │ 5. MANUAL OVERRIDE │
583
+ │ Auto-fix fails → ask user for input │
584
+ │ │
585
+ │ 6. GRACEFUL SKIP │
586
+ │ Optional step fails → skip and continue │
587
+ │ │
588
+ └──────────────────────────────────────────────────────────────────────────┘
589
+ ```
590
+
591
+ ### Código Ejecutable
592
+
593
+ ```javascript
594
+ /**
595
+ * ContractFallback
596
+ * Proporciona comportamiento alternativo cuando operaciones fallan
597
+ */
598
+ class ContractFallback {
599
+ constructor() {
600
+ this.fallbacks = new Map();
601
+ this.cache = new Map();
602
+ this.stats = {
603
+ totalFallbacks: 0,
604
+ byStrategy: {}
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Registra fallback para una operación
610
+ */
611
+ register(operationId, fallbackConfig) {
612
+ this.fallbacks.set(operationId, {
613
+ strategies: fallbackConfig.strategies || [],
614
+ maxAttempts: fallbackConfig.maxAttempts || 3,
615
+ cacheResults: fallbackConfig.cacheResults || false,
616
+ cacheTTL: fallbackConfig.cacheTTL || 300000
617
+ });
618
+ }
619
+
620
+ /**
621
+ * Ejecuta con fallback
622
+ */
623
+ async executeWithFallback(operationId, primaryFn, context = {}) {
624
+ const config = this.fallbacks.get(operationId);
625
+
626
+ if (!config) {
627
+ // Sin fallback registrado, ejecutar directamente
628
+ return primaryFn();
629
+ }
630
+
631
+ // Intentar operación principal
632
+ try {
633
+ const result = await primaryFn();
634
+
635
+ // Cache si está habilitado
636
+ if (config.cacheResults) {
637
+ this.cacheResult(operationId, result, config.cacheTTL);
638
+ }
639
+
640
+ return { success: true, result, usedFallback: false };
641
+
642
+ } catch (primaryError) {
643
+ // Intentar fallbacks en orden
644
+ for (const strategy of config.strategies) {
645
+ try {
646
+ const fallbackResult = await this.executeFallbackStrategy(
647
+ strategy,
648
+ operationId,
649
+ primaryError,
650
+ context
651
+ );
652
+
653
+ if (fallbackResult.success) {
654
+ this.recordFallback(strategy.type);
655
+ return {
656
+ success: true,
657
+ result: fallbackResult.result,
658
+ usedFallback: true,
659
+ fallbackType: strategy.type,
660
+ originalError: primaryError.message
661
+ };
662
+ }
663
+ } catch (fallbackError) {
664
+ // Continuar al siguiente fallback
665
+ continue;
666
+ }
667
+ }
668
+
669
+ // Todos los fallbacks fallaron
670
+ return {
671
+ success: false,
672
+ error: {
673
+ code: 'ALL_FALLBACKS_FAILED',
674
+ severity: 'HIGH',
675
+ message: `Operation ${operationId} and all fallbacks failed`,
676
+ primaryError: primaryError.message,
677
+ attemptedFallbacks: config.strategies.map(s => s.type)
678
+ }
679
+ };
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Ejecuta estrategia de fallback específica
685
+ */
686
+ async executeFallbackStrategy(strategy, operationId, error, context) {
687
+ switch (strategy.type) {
688
+ case 'alternative_agent':
689
+ return this.fallbackAlternativeAgent(strategy, context);
690
+
691
+ case 'simplified':
692
+ return this.fallbackSimplified(strategy, context);
693
+
694
+ case 'cached':
695
+ return this.fallbackCached(operationId);
696
+
697
+ case 'default':
698
+ return this.fallbackDefault(strategy);
699
+
700
+ case 'manual':
701
+ return this.fallbackManual(strategy, error, context);
702
+
703
+ case 'skip':
704
+ return this.fallbackSkip(strategy);
705
+
706
+ default:
707
+ throw new Error(`Unknown fallback strategy: ${strategy.type}`);
708
+ }
709
+ }
710
+
711
+ /**
712
+ * Fallback: usar agente alternativo
713
+ */
714
+ async fallbackAlternativeAgent(strategy, context) {
715
+ const alternativeAgent = strategy.agent;
716
+ const simplifiedInputs = strategy.simplifyInputs
717
+ ? this.simplifyInputs(context.inputs)
718
+ : context.inputs;
719
+
720
+ // Ejecutar agente alternativo
721
+ const result = await context.agentRegistry.execute(alternativeAgent, {
722
+ ...simplifiedInputs,
723
+ isFallback: true
724
+ });
725
+
726
+ return { success: true, result };
727
+ }
728
+
729
+ /**
730
+ * Fallback: operación simplificada
731
+ */
732
+ async fallbackSimplified(strategy, context) {
733
+ const simplifiedFn = strategy.simplifiedFn;
734
+
735
+ if (typeof simplifiedFn === 'function') {
736
+ const result = await simplifiedFn(context);
737
+ return { success: true, result };
738
+ }
739
+
740
+ throw new Error('No simplified function provided');
741
+ }
742
+
743
+ /**
744
+ * Fallback: resultado cacheado
745
+ */
746
+ async fallbackCached(operationId) {
747
+ const cached = this.cache.get(operationId);
748
+
749
+ if (cached && Date.now() < cached.expiresAt) {
750
+ return {
751
+ success: true,
752
+ result: cached.result,
753
+ fromCache: true,
754
+ cachedAt: cached.cachedAt
755
+ };
756
+ }
757
+
758
+ throw new Error('No valid cached result');
759
+ }
760
+
761
+ /**
762
+ * Fallback: valor por defecto
763
+ */
764
+ async fallbackDefault(strategy) {
765
+ return {
766
+ success: true,
767
+ result: strategy.defaultValue,
768
+ isDefault: true
769
+ };
770
+ }
771
+
772
+ /**
773
+ * Fallback: intervención manual
774
+ */
775
+ async fallbackManual(strategy, error, context) {
776
+ // Crear interrupt para el usuario
777
+ return {
778
+ success: false,
779
+ requiresManual: true,
780
+ interrupt: {
781
+ type: 'FALLBACK_MANUAL',
782
+ title: strategy.title || 'Manual intervention required',
783
+ error: error.message,
784
+ context: context,
785
+ options: strategy.options || ['retry', 'skip', 'abort']
786
+ }
787
+ };
788
+ }
789
+
790
+ /**
791
+ * Fallback: skip graceful
792
+ */
793
+ async fallbackSkip(strategy) {
794
+ return {
795
+ success: true,
796
+ result: null,
797
+ skipped: true,
798
+ reason: strategy.reason || 'Optional step skipped due to error'
799
+ };
800
+ }
801
+
802
+ /**
803
+ * Cache de resultados
804
+ */
805
+ cacheResult(operationId, result, ttl) {
806
+ this.cache.set(operationId, {
807
+ result,
808
+ cachedAt: Date.now(),
809
+ expiresAt: Date.now() + ttl
810
+ });
811
+ }
812
+
813
+ /**
814
+ * Simplifica inputs para fallback
815
+ */
816
+ simplifyInputs(inputs) {
817
+ // Remover campos opcionales/complejos
818
+ const simplified = { ...inputs };
819
+ delete simplified.advanced;
820
+ delete simplified.extra;
821
+ return simplified;
822
+ }
823
+
824
+ /**
825
+ * Registra uso de fallback
826
+ */
827
+ recordFallback(strategyType) {
828
+ this.stats.totalFallbacks++;
829
+ this.stats.byStrategy[strategyType] = (this.stats.byStrategy[strategyType] || 0) + 1;
830
+ }
831
+
832
+ /**
833
+ * Obtiene estadísticas
834
+ */
835
+ getStats() {
836
+ return this.stats;
837
+ }
838
+ }
839
+
840
+ // Configuración por defecto para ELSABRO
841
+ const fallback = new ContractFallback();
842
+
843
+ // Registro de fallbacks para agentes principales
844
+ fallback.register('elsabro-executor', {
845
+ strategies: [
846
+ { type: 'alternative_agent', agent: 'elsabro-quick-dev', simplifyInputs: true },
847
+ { type: 'cached' },
848
+ { type: 'manual', title: 'Executor failed, manual intervention needed' }
849
+ ],
850
+ cacheResults: true,
851
+ cacheTTL: 600000
852
+ });
853
+
854
+ fallback.register('exploration', {
855
+ strategies: [
856
+ { type: 'simplified', simplifiedFn: async (ctx) => ({ basic: true, files: [] }) },
857
+ { type: 'default', defaultValue: { files: [], analysis: 'Unable to explore' } }
858
+ ]
859
+ });
860
+
861
+ fallback.register('verification', {
862
+ strategies: [
863
+ { type: 'alternative_agent', agent: 'elsabro-verifier' },
864
+ { type: 'skip', reason: 'Verification skipped, proceed with caution' }
865
+ ]
866
+ });
867
+ ```
868
+
869
+ ### Notificación al Usuario
870
+
871
+ ```
872
+ ╔════════════════════════════════════════════════════════════╗
873
+ ║ 🔄 FALLBACK ACTIVATED ║
874
+ ╠════════════════════════════════════════════════════════════╣
875
+ ║ ║
876
+ ║ Primary operation: elsabro-executor ║
877
+ ║ Error: Timeout after 5 minutes ║
878
+ ║ ║
879
+ ║ Fallback used: alternative_agent ║
880
+ ║ Alternative: elsabro-quick-dev ║
881
+ ║ ║
882
+ ║ Note: Using simplified approach. Some features may be ║
883
+ ║ implemented with less optimization. ║
884
+ ║ ║
885
+ ╠════════════════════════════════════════════════════════════╣
886
+ ║ Status: Continuing with fallback... ║
887
+ ╚════════════════════════════════════════════════════════════╝
888
+ ```
889
+
890
+ ---
891
+
892
+ ## Integración de los 3 Contratos
893
+
894
+ ### Orden de Evaluación
895
+
896
+ ```javascript
897
+ async function executeWithResilience(operationId, agent, inputs, context) {
898
+ const circuitBreaker = context.circuitBreaker;
899
+ const bulkhead = context.bulkhead;
900
+ const fallbackManager = context.fallbackManager;
901
+
902
+ // 1. Verificar Circuit Breaker
903
+ const circuitCheck = circuitBreaker.canExecute(agent);
904
+ if (!circuitCheck.allowed) {
905
+ // Usar fallback si circuit está abierto
906
+ return fallbackManager.executeWithFallback(
907
+ operationId,
908
+ async () => { throw new Error('Circuit open'); },
909
+ { agent, inputs, reason: 'circuit_open' }
910
+ );
911
+ }
912
+
913
+ // 2. Adquirir slot de Bulkhead
914
+ const partition = getPartitionForAgent(agent);
915
+ const slot = await bulkhead.acquire(partition, { timeout: 60000 });
916
+
917
+ if (!slot.acquired) {
918
+ // Usar fallback si no hay capacidad
919
+ return fallbackManager.executeWithFallback(
920
+ operationId,
921
+ async () => { throw new Error('No capacity'); },
922
+ { agent, inputs, reason: 'bulkhead_full' }
923
+ );
924
+ }
925
+
926
+ try {
927
+ // 3. Ejecutar con fallback support
928
+ const result = await fallbackManager.executeWithFallback(
929
+ operationId,
930
+ async () => executeAgent(agent, inputs),
931
+ { agent, inputs, agentRegistry: context.agentRegistry }
932
+ );
933
+
934
+ // Registrar éxito en circuit breaker
935
+ if (result.success && !result.usedFallback) {
936
+ circuitBreaker.recordSuccess(agent);
937
+ }
938
+
939
+ return result;
940
+
941
+ } catch (error) {
942
+ // Registrar fallo en circuit breaker
943
+ circuitBreaker.recordFailure(agent, error);
944
+ throw error;
945
+
946
+ } finally {
947
+ // Siempre liberar slot de bulkhead
948
+ slot.slot.release();
949
+ }
950
+ }
951
+ ```
952
+
953
+ ---
954
+
955
+ ## Resumen de Contratos v2
956
+
957
+ | # | Contrato | Propósito | Trigger |
958
+ |---|----------|-----------|---------|
959
+ | 1 | RegistryValidator | Validar existencia de agentes | Antes de Task() |
960
+ | 2 | TaskLifecycle | Estados explícitos de tareas | Transiciones |
961
+ | 3 | TimeoutHandler | Timeouts con escalación | Tiempo excedido |
962
+ | 4 | RetryPolicy | Retry con backoff | Errores transitorios |
963
+ | 5 | ErrorAggregator | Agregación por políticas | Ejecución paralela |
964
+ | 6 | SeverityClassifier | Clasificar errores | Cada error |
965
+ | 7 | SessionValidator | Validar integridad | Inicio/fin sesión |
966
+ | 8 | **CircuitBreaker** | Prevenir cascadas | Fallos repetidos |
967
+ | 9 | **Bulkhead** | Aislar recursos | Límite de concurrencia |
968
+ | 10 | **Fallback** | Degradación graceful | Cualquier fallo |