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.
- package/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- 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 |
|