outcome-cli 1.0.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 (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Health Check System - Monitors agent availability
3
+ *
4
+ * Periodically checks registered agents to verify they are online
5
+ * and responsive. Updates agent deployment status accordingly.
6
+ *
7
+ * @module league/health-check
8
+ */
9
+
10
+ import type {
11
+ RegisteredAgent,
12
+ HealthCheckResponse,
13
+ DeploymentInfo,
14
+ } from '../agents/registered-agent.schema.js';
15
+
16
+ /**
17
+ * Health check result for a single agent.
18
+ */
19
+ export interface HealthCheckResult {
20
+ agentId: string;
21
+ agentName: string;
22
+ status: 'healthy' | 'degraded' | 'unhealthy';
23
+ latencyMs: number;
24
+ checkedAt: Date;
25
+ error?: string;
26
+ response?: HealthCheckResponse;
27
+ }
28
+
29
+ /**
30
+ * Configuration for the health check service.
31
+ */
32
+ export interface HealthCheckConfig {
33
+ /** Timeout for health check requests in milliseconds */
34
+ timeoutMs: number;
35
+ /** Number of consecutive failures before marking offline */
36
+ failureThreshold: number;
37
+ /** Interval between health checks in milliseconds */
38
+ checkIntervalMs: number;
39
+ }
40
+
41
+ /**
42
+ * Default health check configuration.
43
+ */
44
+ export const DEFAULT_HEALTH_CHECK_CONFIG: HealthCheckConfig = {
45
+ timeoutMs: 5000,
46
+ failureThreshold: 3,
47
+ checkIntervalMs: 60000, // 1 minute
48
+ };
49
+
50
+ /**
51
+ * Tracks consecutive failures per agent.
52
+ */
53
+ const failureCounters: Map<string, number> = new Map();
54
+
55
+ /**
56
+ * Perform a health check on a single agent.
57
+ *
58
+ * @param agent - The agent to check
59
+ * @param config - Health check configuration
60
+ * @returns Health check result
61
+ */
62
+ export async function checkAgentHealth(
63
+ agent: RegisteredAgent,
64
+ config: HealthCheckConfig = DEFAULT_HEALTH_CHECK_CONFIG
65
+ ): Promise<HealthCheckResult> {
66
+ const startTime = Date.now();
67
+ const controller = new AbortController();
68
+ const timeout = setTimeout(() => controller.abort(), config.timeoutMs);
69
+
70
+ try {
71
+ // Determine health endpoint URL
72
+ const healthUrl = getHealthEndpoint(agent.endpoint.url);
73
+
74
+ const response = await fetch(healthUrl, {
75
+ method: 'GET',
76
+ headers: buildHeaders(agent),
77
+ signal: controller.signal,
78
+ });
79
+
80
+ const latencyMs = Date.now() - startTime;
81
+
82
+ if (!response.ok) {
83
+ return createUnhealthyResult(agent, latencyMs, `HTTP ${response.status}`);
84
+ }
85
+
86
+ const data: HealthCheckResponse = await response.json();
87
+
88
+ // Reset failure counter on success
89
+ failureCounters.set(agent.id, 0);
90
+
91
+ return {
92
+ agentId: agent.id,
93
+ agentName: agent.name,
94
+ status: data.status,
95
+ latencyMs,
96
+ checkedAt: new Date(),
97
+ response: data,
98
+ };
99
+ } catch (error) {
100
+ const latencyMs = Date.now() - startTime;
101
+ const errorMessage =
102
+ error instanceof Error ? error.message : 'Unknown error';
103
+
104
+ // Increment failure counter
105
+ const currentFailures = (failureCounters.get(agent.id) ?? 0) + 1;
106
+ failureCounters.set(agent.id, currentFailures);
107
+
108
+ return createUnhealthyResult(agent, latencyMs, errorMessage);
109
+ } finally {
110
+ clearTimeout(timeout);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Check health of multiple agents in parallel.
116
+ *
117
+ * @param agents - Array of agents to check
118
+ * @param config - Health check configuration
119
+ * @returns Array of health check results
120
+ */
121
+ export async function checkMultipleAgents(
122
+ agents: RegisteredAgent[],
123
+ config: HealthCheckConfig = DEFAULT_HEALTH_CHECK_CONFIG
124
+ ): Promise<HealthCheckResult[]> {
125
+ const results = await Promise.all(
126
+ agents.map((agent) => checkAgentHealth(agent, config))
127
+ );
128
+ return results;
129
+ }
130
+
131
+ /**
132
+ * Determine if an agent should be marked offline based on failure count.
133
+ *
134
+ * @param agentId - The agent ID
135
+ * @param config - Health check configuration
136
+ * @returns True if agent should be marked offline
137
+ */
138
+ export function shouldMarkOffline(
139
+ agentId: string,
140
+ config: HealthCheckConfig = DEFAULT_HEALTH_CHECK_CONFIG
141
+ ): boolean {
142
+ const failures = failureCounters.get(agentId) ?? 0;
143
+ return failures >= config.failureThreshold;
144
+ }
145
+
146
+ /**
147
+ * Get current failure count for an agent.
148
+ *
149
+ * @param agentId - The agent ID
150
+ * @returns Number of consecutive failures
151
+ */
152
+ export function getFailureCount(agentId: string): number {
153
+ return failureCounters.get(agentId) ?? 0;
154
+ }
155
+
156
+ /**
157
+ * Reset failure counter for an agent.
158
+ *
159
+ * @param agentId - The agent ID
160
+ */
161
+ export function resetFailureCount(agentId: string): void {
162
+ failureCounters.set(agentId, 0);
163
+ }
164
+
165
+ /**
166
+ * Update deployment info based on health check result.
167
+ *
168
+ * @param currentInfo - Current deployment info
169
+ * @param result - Health check result
170
+ * @param config - Health check configuration
171
+ * @returns Updated deployment info
172
+ */
173
+ export function updateDeploymentInfo(
174
+ currentInfo: DeploymentInfo,
175
+ result: HealthCheckResult,
176
+ config: HealthCheckConfig = DEFAULT_HEALTH_CHECK_CONFIG
177
+ ): DeploymentInfo {
178
+ const shouldBeOffline = shouldMarkOffline(result.agentId, config);
179
+
180
+ let newStatus: DeploymentInfo['status'];
181
+ if (shouldBeOffline) {
182
+ newStatus = 'offline';
183
+ } else if (result.status === 'healthy') {
184
+ newStatus = 'online';
185
+ } else if (result.status === 'degraded') {
186
+ newStatus = 'degraded';
187
+ } else {
188
+ newStatus = currentInfo.status; // Keep current if single failure
189
+ }
190
+
191
+ // Calculate rolling average latency
192
+ const avgLatency =
193
+ currentInfo.averageLatencyMs === 0
194
+ ? result.latencyMs
195
+ : Math.round((currentInfo.averageLatencyMs + result.latencyMs) / 2);
196
+
197
+ // Calculate uptime percentage (simplified)
198
+ const wasOnline = currentInfo.status === 'online';
199
+ const isNowOnline = newStatus === 'online';
200
+ const uptimeChange = isNowOnline ? 1 : wasOnline ? -5 : 0;
201
+ const newUptime = Math.max(
202
+ 0,
203
+ Math.min(100, currentInfo.uptimePercent + uptimeChange)
204
+ );
205
+
206
+ return {
207
+ ...currentInfo,
208
+ status: newStatus,
209
+ lastHealthCheck: result.checkedAt,
210
+ averageLatencyMs: avgLatency,
211
+ uptimePercent: newUptime,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Get the health endpoint URL from a chat endpoint URL.
217
+ */
218
+ function getHealthEndpoint(chatUrl: string): string {
219
+ const url = new URL(chatUrl);
220
+
221
+ // Try common health check paths
222
+ if (url.pathname.endsWith('/chat')) {
223
+ url.pathname = url.pathname.replace(/\/chat$/, '/health');
224
+ } else if (url.pathname.endsWith('/v1/chat/completions')) {
225
+ url.pathname = url.pathname.replace(/\/v1\/chat\/completions$/, '/health');
226
+ } else {
227
+ // Append /health to the base
228
+ url.pathname = url.pathname.replace(/\/$/, '') + '/health';
229
+ }
230
+
231
+ return url.toString();
232
+ }
233
+
234
+ /**
235
+ * Build authentication headers for an agent.
236
+ */
237
+ function buildHeaders(agent: RegisteredAgent): Record<string, string> {
238
+ if (agent.endpoint.authType === 'none') {
239
+ return {};
240
+ }
241
+
242
+ // Note: In production, this would decrypt the token
243
+ const token = agent.endpoint.encryptedAuthToken ?? '';
244
+ const headerName = agent.endpoint.authHeader ?? 'Authorization';
245
+ const headerValue =
246
+ agent.endpoint.authType === 'bearer' ? `Bearer ${token}` : token;
247
+
248
+ return { [headerName]: headerValue };
249
+ }
250
+
251
+ /**
252
+ * Create an unhealthy result.
253
+ */
254
+ function createUnhealthyResult(
255
+ agent: RegisteredAgent,
256
+ latencyMs: number,
257
+ error: string
258
+ ): HealthCheckResult {
259
+ return {
260
+ agentId: agent.id,
261
+ agentName: agent.name,
262
+ status: 'unhealthy',
263
+ latencyMs,
264
+ checkedAt: new Date(),
265
+ error,
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Health Check Service class for managing periodic checks.
271
+ */
272
+ export class HealthCheckService {
273
+ private config: HealthCheckConfig;
274
+ private intervalId: NodeJS.Timeout | null = null;
275
+ private onStatusChange?: (
276
+ agentId: string,
277
+ oldStatus: string,
278
+ newStatus: string
279
+ ) => void;
280
+
281
+ constructor(
282
+ config: Partial<HealthCheckConfig> = {},
283
+ onStatusChange?: (
284
+ agentId: string,
285
+ oldStatus: string,
286
+ newStatus: string
287
+ ) => void
288
+ ) {
289
+ this.config = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };
290
+ this.onStatusChange = onStatusChange;
291
+ }
292
+
293
+ /**
294
+ * Start periodic health checks.
295
+ *
296
+ * @param getAgents - Function to get current list of agents
297
+ * @param updateAgent - Function to update agent deployment info
298
+ */
299
+ start(
300
+ getAgents: () => RegisteredAgent[],
301
+ updateAgent: (agentId: string, deployment: DeploymentInfo) => void
302
+ ): void {
303
+ if (this.intervalId) {
304
+ this.stop();
305
+ }
306
+
307
+ const runChecks = async () => {
308
+ const agents = getAgents();
309
+ const results = await checkMultipleAgents(agents, this.config);
310
+
311
+ for (const result of results) {
312
+ const agent = agents.find((a) => a.id === result.agentId);
313
+ if (!agent) continue;
314
+
315
+ const oldStatus = agent.deployment.status;
316
+ const newDeployment = updateDeploymentInfo(
317
+ agent.deployment,
318
+ result,
319
+ this.config
320
+ );
321
+
322
+ if (oldStatus !== newDeployment.status && this.onStatusChange) {
323
+ this.onStatusChange(agent.id, oldStatus, newDeployment.status);
324
+ }
325
+
326
+ updateAgent(agent.id, newDeployment);
327
+ }
328
+ };
329
+
330
+ // Run immediately
331
+ runChecks();
332
+
333
+ // Then run periodically
334
+ this.intervalId = setInterval(runChecks, this.config.checkIntervalMs);
335
+ }
336
+
337
+ /**
338
+ * Stop periodic health checks.
339
+ */
340
+ stop(): void {
341
+ if (this.intervalId) {
342
+ clearInterval(this.intervalId);
343
+ this.intervalId = null;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Check if the service is running.
349
+ */
350
+ isRunning(): boolean {
351
+ return this.intervalId !== null;
352
+ }
353
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * League Module - Parallel agent competition system
3
+ *
4
+ * @module league
5
+ */
6
+
7
+ export {
8
+ type LeagueConfig,
9
+ type AgentResult,
10
+ type LeagueResult,
11
+ runLeague,
12
+ runLeagueMock,
13
+ } from './runLeague.js';
14
+
15
+ export {
16
+ type KillReason,
17
+ type AgentLimits,
18
+ type RunningAgent,
19
+ shouldKillAgent,
20
+ killAgent,
21
+ checkAllAgents,
22
+ } from './killAgent.js';
23
+
24
+ export {
25
+ type AgentMetrics,
26
+ type AgentScore,
27
+ scoreAgent,
28
+ determineWinner,
29
+ rankAgents,
30
+ calculateLeagueStats,
31
+ } from './scoreAgent.js';
32
+
33
+ // Battle Orchestrator - Production battle management
34
+ export {
35
+ type BattleConfig,
36
+ type BattleTask,
37
+ type BattleResult,
38
+ type AgentBattleRequest,
39
+ type AgentBattleResponse,
40
+ type AgentBattleResult,
41
+ BattleOrchestrator,
42
+ AgentOfflineError,
43
+ createBattleOrchestrator,
44
+ } from './battle-orchestrator.js';
45
+
46
+ // Health Check System - Agent availability monitoring
47
+ export {
48
+ type HealthCheckResult,
49
+ type HealthCheckConfig,
50
+ DEFAULT_HEALTH_CHECK_CONFIG,
51
+ checkAgentHealth,
52
+ checkMultipleAgents,
53
+ shouldMarkOffline,
54
+ getFailureCount,
55
+ resetFailureCount,
56
+ updateDeploymentInfo,
57
+ HealthCheckService,
58
+ } from './health-check.js';
59
+
60
+ // Team Coordinator - Team battle management
61
+ export {
62
+ type TeamConfig,
63
+ type TeamState,
64
+ type StateUpdateResult,
65
+ type MemberContribution,
66
+ type TeamPayoutDistribution,
67
+ type TeamStateChangeEvent,
68
+ type TeamStateChangeListener,
69
+ StateConflictError,
70
+ TeamNotFoundError,
71
+ NotTeamMemberError,
72
+ TeamCoordinator,
73
+ createTeamCoordinator,
74
+ } from './team-coordinator.js';
75
+
76
+ // Multi-Step Orchestrator - Multi-step bounty execution
77
+ export {
78
+ type TaskNode,
79
+ type MultiStepBounty,
80
+ type TaskExecutionResult,
81
+ type MultiStepResult,
82
+ type TaskContext,
83
+ type MultiStepAgent,
84
+ CyclicDependencyError,
85
+ InvalidDependencyError,
86
+ InvalidFinalTaskError,
87
+ TaskExecutionError,
88
+ validateTaskGraph,
89
+ validateMultiStepBounty,
90
+ getTopologicalOrder,
91
+ MultiStepOrchestrator,
92
+ createMultiStepOrchestrator,
93
+ } from './multi-step-orchestrator.js';
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Kill Agent - Agent termination logic
3
+ *
4
+ * Handles agent termination conditions and cleanup.
5
+ *
6
+ * @module league/killAgent
7
+ * @see Requirements 4.3, 4.4, 10.2
8
+ */
9
+
10
+ import type { CostTracker } from '../runtime/costTracker.js';
11
+
12
+ /**
13
+ * Reason for agent termination.
14
+ */
15
+ export interface KillReason {
16
+ /** Type of termination */
17
+ type: 'cost_exceeded' | 'attempts_exceeded' | 'timeout' | 'competitor_won';
18
+ /** Human-readable details */
19
+ details: string;
20
+ }
21
+
22
+ /**
23
+ * Limits that trigger agent termination.
24
+ */
25
+ export interface AgentLimits {
26
+ /** Maximum tokens allowed */
27
+ maxTokens: number;
28
+ /** Maximum attempts allowed */
29
+ maxAttempts: number;
30
+ /** Maximum runtime in milliseconds */
31
+ maxRuntimeMs: number;
32
+ }
33
+
34
+ /**
35
+ * State of a running agent.
36
+ */
37
+ export interface RunningAgent {
38
+ /** Agent ID */
39
+ agentId: string;
40
+ /** Current attempt count */
41
+ attempts: number;
42
+ /** Cost tracker for the agent */
43
+ costTracker: CostTracker;
44
+ /** Start time of the agent run */
45
+ startTime: number;
46
+ /** Whether a competitor has already won */
47
+ competitorWon: boolean;
48
+ }
49
+
50
+ /**
51
+ * Checks if an agent should be terminated based on current state.
52
+ *
53
+ * @param agent - Current agent state
54
+ * @param limits - Termination limits
55
+ * @returns KillReason if agent should be killed, null otherwise
56
+ *
57
+ * @example
58
+ * const reason = shouldKillAgent(agent, {
59
+ * maxTokens: 10000,
60
+ * maxAttempts: 5,
61
+ * maxRuntimeMs: 300000
62
+ * });
63
+ * if (reason) {
64
+ * await killAgent(agent.agentId, reason);
65
+ * }
66
+ *
67
+ * @see Requirements 4.3, 4.4, 10.2
68
+ */
69
+ export function shouldKillAgent(
70
+ agent: RunningAgent,
71
+ limits: AgentLimits
72
+ ): KillReason | null {
73
+ // Check if competitor already won (Requirement 4.5)
74
+ if (agent.competitorWon) {
75
+ return {
76
+ type: 'competitor_won',
77
+ details: `Another agent achieved success first`,
78
+ };
79
+ }
80
+
81
+ // Check cost ceiling (Requirement 4.4, 10.1)
82
+ if (agent.costTracker.tokensSpent > limits.maxTokens) {
83
+ return {
84
+ type: 'cost_exceeded',
85
+ details: `Token usage ${agent.costTracker.tokensSpent} exceeded ceiling ${limits.maxTokens}`,
86
+ };
87
+ }
88
+
89
+ // Check attempt limit (Requirement 4.3)
90
+ if (agent.attempts >= limits.maxAttempts) {
91
+ return {
92
+ type: 'attempts_exceeded',
93
+ details: `Attempt count ${agent.attempts} reached limit ${limits.maxAttempts}`,
94
+ };
95
+ }
96
+
97
+ // Check runtime limit (Requirement 10.2)
98
+ const elapsed = Date.now() - agent.startTime;
99
+ if (elapsed >= limits.maxRuntimeMs) {
100
+ return {
101
+ type: 'timeout',
102
+ details: `Runtime ${elapsed}ms exceeded limit ${limits.maxRuntimeMs}ms`,
103
+ };
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Terminates an agent with the given reason.
111
+ *
112
+ * @param agentId - ID of the agent to terminate
113
+ * @param reason - Reason for termination
114
+ *
115
+ * @see Requirements 4.3, 4.4
116
+ */
117
+ export async function killAgent(
118
+ agentId: string,
119
+ reason: KillReason
120
+ ): Promise<void> {
121
+ // Log termination
122
+ console.log(`🔴 Agent ${agentId} terminated: ${reason.type} - ${reason.details}`);
123
+
124
+ // In production, this would:
125
+ // 1. Send termination signal to the agent
126
+ // 2. Clean up any resources
127
+ // 3. Record termination in Durable Objects
128
+ }
129
+
130
+ /**
131
+ * Checks multiple agents and returns which ones should be killed.
132
+ *
133
+ * @param agents - Array of running agents
134
+ * @param limits - Termination limits
135
+ * @returns Map of agentId to KillReason for agents that should be killed
136
+ */
137
+ export function checkAllAgents(
138
+ agents: RunningAgent[],
139
+ limits: AgentLimits
140
+ ): Map<string, KillReason> {
141
+ const toKill = new Map<string, KillReason>();
142
+
143
+ for (const agent of agents) {
144
+ const reason = shouldKillAgent(agent, limits);
145
+ if (reason) {
146
+ toKill.set(agent.agentId, reason);
147
+ }
148
+ }
149
+
150
+ return toKill;
151
+ }