@amitdeshmukh/ax-crew 8.7.3 → 9.0.1
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/CHANGELOG.md +40 -0
- package/README.md +9 -9
- package/dist/agents/agentConfig.d.ts +3 -0
- package/dist/agents/agentConfig.js +12 -4
- package/dist/agents/crew.d.ts +59 -0
- package/dist/agents/crew.js +356 -0
- package/dist/agents/deferredTools.d.ts +49 -0
- package/dist/agents/deferredTools.js +237 -0
- package/dist/agents/index.d.ts +4 -266
- package/dist/agents/index.js +3 -1014
- package/dist/agents/lazyAgent.d.ts +33 -0
- package/dist/agents/lazyAgent.js +78 -0
- package/dist/agents/statefulAgent.d.ts +97 -0
- package/dist/agents/statefulAgent.js +478 -0
- package/dist/index.d.ts +2 -2
- package/dist/types.d.ts +18 -1
- package/examples/graphjin-database-agent.ts +85 -64
- package/examples/write-post-and-publish-to-wordpress.ts +1 -1
- package/package.json +1 -1
- package/src/agents/agentConfig.ts +15 -8
- package/src/agents/crew.ts +444 -0
- package/src/agents/deferredTools.ts +275 -0
- package/src/agents/index.ts +4 -1281
- package/src/agents/lazyAgent.ts +95 -0
- package/src/agents/statefulAgent.ts +668 -0
- package/src/index.ts +7 -4
- package/src/skills/axcrew-functions.md +2 -2
- package/src/skills/axcrew-mcp.md +41 -1
- package/src/skills/axcrew-patterns.md +48 -4
- package/src/skills/axcrew-state.md +16 -16
- package/src/skills/axcrew.md +14 -1
- package/src/types.ts +19 -0
- package/.claude/settings.local.json +0 -13
- package/.claude/skills/ax-crew/SKILL.md +0 -466
package/src/agents/index.ts
CHANGED
|
@@ -1,1281 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
AxSignature,
|
|
6
|
-
AxAgentic,
|
|
7
|
-
AxFunction,
|
|
8
|
-
AxProgramForwardOptions,
|
|
9
|
-
AxProgramStreamingForwardOptions,
|
|
10
|
-
AxGenStreamingOut,
|
|
11
|
-
} from "@ax-llm/ax";
|
|
12
|
-
|
|
13
|
-
import type {
|
|
14
|
-
StateInstance,
|
|
15
|
-
FunctionRegistryType,
|
|
16
|
-
UsageCost,
|
|
17
|
-
AxCrewConfig,
|
|
18
|
-
AxCrewOptions,
|
|
19
|
-
MCPTransportConfig,
|
|
20
|
-
ACEConfig,
|
|
21
|
-
AgentExecutionMode,
|
|
22
|
-
AxCrewAxAgentOptions,
|
|
23
|
-
} from "../types.js";
|
|
24
|
-
|
|
25
|
-
import { createState } from "../state/index.js";
|
|
26
|
-
import { parseCrewConfig, parseAgentConfig } from "./agentConfig.js";
|
|
27
|
-
import { MetricsRegistry } from "../metrics/index.js";
|
|
28
|
-
|
|
29
|
-
// Define the interface for the agent configuration
|
|
30
|
-
interface ParsedAgentConfig {
|
|
31
|
-
ai: AxAI;
|
|
32
|
-
name: string;
|
|
33
|
-
executionMode: AgentExecutionMode;
|
|
34
|
-
axAgentOptions?: AxCrewAxAgentOptions;
|
|
35
|
-
description: string;
|
|
36
|
-
definition?: string;
|
|
37
|
-
signature: string | AxSignature;
|
|
38
|
-
functions: (
|
|
39
|
-
| AxFunction
|
|
40
|
-
| (new (state: Record<string, any>) => { toFunction: () => AxFunction })
|
|
41
|
-
| undefined
|
|
42
|
-
)[];
|
|
43
|
-
mcpServers?: Record<string, MCPTransportConfig>;
|
|
44
|
-
subAgentNames: string[];
|
|
45
|
-
examples?: Array<Record<string, any>>;
|
|
46
|
-
tracker?: any;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Extend the AxAgent class from ax-llm
|
|
50
|
-
class StatefulAxAgent extends AxAgent<any, any> {
|
|
51
|
-
state: StateInstance;
|
|
52
|
-
axai: any;
|
|
53
|
-
private agentName: string;
|
|
54
|
-
private agentDefinition: string;
|
|
55
|
-
private executionMode: AgentExecutionMode;
|
|
56
|
-
private axGenProgram: AxGen<any, any>;
|
|
57
|
-
private costTracker?: any;
|
|
58
|
-
private debugEnabled: boolean = false;
|
|
59
|
-
private static readonly modernAxAgentRuntime =
|
|
60
|
-
typeof (AxAgent as any)?.prototype?.getFunction === "function" &&
|
|
61
|
-
typeof (AxAgent as any)?.prototype?.setExamples !== "function";
|
|
62
|
-
// ACE-related optional state
|
|
63
|
-
private aceConfig?: ACEConfig;
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
-
private aceOptimizer?: any;
|
|
66
|
-
private acePlaybook?: any;
|
|
67
|
-
private aceBaseInstruction?: string; // Original description before playbook injection
|
|
68
|
-
private isAxAIService(obj: any): obj is AxAI {
|
|
69
|
-
return !!obj && typeof obj.getName === 'function' && typeof obj.chat === 'function';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
constructor(
|
|
73
|
-
ai: AxAI,
|
|
74
|
-
options: Readonly<{
|
|
75
|
-
name: string;
|
|
76
|
-
description: string;
|
|
77
|
-
executionMode?: AgentExecutionMode;
|
|
78
|
-
axAgentOptions?: AxCrewAxAgentOptions;
|
|
79
|
-
definition?: string;
|
|
80
|
-
signature: string | AxSignature;
|
|
81
|
-
agents?: AxAgentic<any, any>[] | undefined;
|
|
82
|
-
functions?: (AxFunction | (() => AxFunction))[] | undefined;
|
|
83
|
-
examples?: Array<Record<string, any>> | undefined;
|
|
84
|
-
mcpServers?: Record<string, MCPTransportConfig> | undefined;
|
|
85
|
-
debug?: boolean;
|
|
86
|
-
}>,
|
|
87
|
-
state: StateInstance
|
|
88
|
-
) {
|
|
89
|
-
const { examples, debug } = options;
|
|
90
|
-
const resolvedFunctions = (options.functions ?? []).map((fn) =>
|
|
91
|
-
typeof fn === "function" ? fn() : fn
|
|
92
|
-
) as AxFunction[];
|
|
93
|
-
const resolvedAgents = (options.agents ?? []) as AxAgentic<any, any>[];
|
|
94
|
-
const effectiveDefinition = (options.definition ?? options.description).trim();
|
|
95
|
-
|
|
96
|
-
if (StatefulAxAgent.modernAxAgentRuntime) {
|
|
97
|
-
const configuredAgentOptions = (options.axAgentOptions ?? {}) as Record<string, any>;
|
|
98
|
-
const configuredAgentGraph = (configuredAgentOptions.agents ?? {}) as Record<string, any>;
|
|
99
|
-
const configuredFunctionGraph = (configuredAgentOptions.functions ?? {}) as Record<string, any>;
|
|
100
|
-
const configuredActorOptions = (configuredAgentOptions.actorOptions ?? {}) as Record<string, any>;
|
|
101
|
-
const configuredResponderOptions = (configuredAgentOptions.responderOptions ?? {}) as Record<string, any>;
|
|
102
|
-
|
|
103
|
-
const modernOptions: Record<string, unknown> = {
|
|
104
|
-
...configuredAgentOptions,
|
|
105
|
-
debug: debug ?? configuredAgentOptions.debug ?? false,
|
|
106
|
-
contextFields: Array.isArray(configuredAgentOptions.contextFields)
|
|
107
|
-
? configuredAgentOptions.contextFields
|
|
108
|
-
: [],
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
modernOptions.agents = {
|
|
112
|
-
...configuredAgentGraph,
|
|
113
|
-
local: resolvedAgents,
|
|
114
|
-
};
|
|
115
|
-
modernOptions.functions = {
|
|
116
|
-
...configuredFunctionGraph,
|
|
117
|
-
local: resolvedFunctions,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
if (effectiveDefinition.length > 0) {
|
|
121
|
-
modernOptions.actorOptions = {
|
|
122
|
-
...configuredActorOptions,
|
|
123
|
-
description: configuredActorOptions.description ?? effectiveDefinition,
|
|
124
|
-
};
|
|
125
|
-
modernOptions.responderOptions = {
|
|
126
|
-
...configuredResponderOptions,
|
|
127
|
-
description: configuredResponderOptions.description ?? effectiveDefinition,
|
|
128
|
-
};
|
|
129
|
-
} else {
|
|
130
|
-
modernOptions.actorOptions = configuredActorOptions;
|
|
131
|
-
modernOptions.responderOptions = configuredResponderOptions;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
super(
|
|
135
|
-
{
|
|
136
|
-
ai,
|
|
137
|
-
agentIdentity: {
|
|
138
|
-
name: options.name,
|
|
139
|
-
description: options.description,
|
|
140
|
-
},
|
|
141
|
-
signature: options.signature as any,
|
|
142
|
-
} as any,
|
|
143
|
-
modernOptions as any
|
|
144
|
-
);
|
|
145
|
-
} else {
|
|
146
|
-
super(
|
|
147
|
-
{
|
|
148
|
-
name: options.name,
|
|
149
|
-
description: options.description,
|
|
150
|
-
definition: options.definition,
|
|
151
|
-
signature: options.signature,
|
|
152
|
-
agents: resolvedAgents,
|
|
153
|
-
functions: resolvedFunctions,
|
|
154
|
-
debug: debug ?? false,
|
|
155
|
-
} as any,
|
|
156
|
-
{} as any
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this.state = state;
|
|
161
|
-
this.axai = ai;
|
|
162
|
-
this.agentName = options.name;
|
|
163
|
-
this.agentDefinition = effectiveDefinition;
|
|
164
|
-
this.executionMode = options.executionMode ?? "axgen";
|
|
165
|
-
this.debugEnabled = debug ?? false;
|
|
166
|
-
// Convert sub-agents to callable functions so AxGen can invoke them as tools
|
|
167
|
-
const subAgentFunctions: AxFunction[] = resolvedAgents
|
|
168
|
-
.map(agent => {
|
|
169
|
-
try { return agent.getFunction() as AxFunction; }
|
|
170
|
-
catch { return undefined; }
|
|
171
|
-
})
|
|
172
|
-
.filter((fn): fn is AxFunction => fn !== undefined);
|
|
173
|
-
|
|
174
|
-
this.axGenProgram = new AxGen(options.signature as any, {
|
|
175
|
-
description: effectiveDefinition,
|
|
176
|
-
functions: [...resolvedFunctions, ...subAgentFunctions],
|
|
177
|
-
} as any);
|
|
178
|
-
|
|
179
|
-
for (const agent of resolvedAgents) {
|
|
180
|
-
try {
|
|
181
|
-
const childName = agent.getFunction().name;
|
|
182
|
-
this.axGenProgram.register(agent as any, childName);
|
|
183
|
-
} catch {
|
|
184
|
-
// Best-effort registration for optimizer/introspection support.
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Apply examples to compatibility layer if provided
|
|
189
|
-
if (examples && examples.length > 0) {
|
|
190
|
-
this.setExamplesCompat(examples);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* @deprecated Use setExamplesCompat() to avoid Ax runtime version coupling.
|
|
196
|
-
*/
|
|
197
|
-
setExamples(examples: Readonly<Array<Record<string, any>>>): void {
|
|
198
|
-
this.setExamplesCompat(examples);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
setExamplesCompat(examples: Readonly<Array<Record<string, any>>>): void {
|
|
202
|
-
this.axGenProgram.setExamples(examples as any);
|
|
203
|
-
|
|
204
|
-
const baseSetExamples = (AxAgent.prototype as any).setExamples;
|
|
205
|
-
if (typeof baseSetExamples === "function") {
|
|
206
|
-
baseSetExamples.call(this, examples);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const internalProgram = (this as any).program;
|
|
211
|
-
if (typeof internalProgram?.setExamples === "function") {
|
|
212
|
-
internalProgram.setExamples(examples as any);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @deprecated Use setDescriptionCompat() to avoid Ax runtime version coupling.
|
|
218
|
-
*/
|
|
219
|
-
setDescription(description: string): void {
|
|
220
|
-
this.setDescriptionCompat(description);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
setDescriptionCompat(description: string): void {
|
|
224
|
-
this.agentDefinition = description;
|
|
225
|
-
this.axGenProgram.setDescription(description);
|
|
226
|
-
|
|
227
|
-
const baseSetDescription = (AxAgent.prototype as any).setDescription;
|
|
228
|
-
if (typeof baseSetDescription === "function") {
|
|
229
|
-
baseSetDescription.call(this, description);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const agentRuntime = this as any;
|
|
234
|
-
if (typeof agentRuntime.program?.setDescription === "function") {
|
|
235
|
-
agentRuntime.program.setDescription(description);
|
|
236
|
-
}
|
|
237
|
-
agentRuntime.actorDescription = description;
|
|
238
|
-
agentRuntime.responderDescription = description;
|
|
239
|
-
if (typeof agentRuntime._buildSplitPrograms === "function") {
|
|
240
|
-
agentRuntime._buildSplitPrograms();
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
override getUsage() {
|
|
245
|
-
if (this.executionMode === "axgen") {
|
|
246
|
-
return this.axGenProgram.getUsage();
|
|
247
|
-
}
|
|
248
|
-
return super.getUsage();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
override resetUsage() {
|
|
252
|
-
this.axGenProgram.resetUsage();
|
|
253
|
-
super.resetUsage();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private resolveInvocationArgs<TOptions>(
|
|
257
|
-
first: Record<string, any> | AxAI,
|
|
258
|
-
second?: Record<string, any> | Readonly<TOptions>,
|
|
259
|
-
third?: Readonly<TOptions>
|
|
260
|
-
): {
|
|
261
|
-
ai: AxAI;
|
|
262
|
-
values: Record<string, any>;
|
|
263
|
-
options?: Readonly<TOptions>;
|
|
264
|
-
calledWithAI: boolean;
|
|
265
|
-
} {
|
|
266
|
-
const calledWithAI = this.isAxAIService(first);
|
|
267
|
-
const ai = (calledWithAI ? first : this.axai) as AxAI;
|
|
268
|
-
if (!ai) {
|
|
269
|
-
throw new Error(`No AI instance is configured for agent "${this.agentName}"`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const values = (calledWithAI ? second : first) as Record<string, any>;
|
|
273
|
-
const options = (calledWithAI ? third : second) as Readonly<TOptions> | undefined;
|
|
274
|
-
|
|
275
|
-
return { ai, values, options, calledWithAI };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private async executeForwardByMode(
|
|
279
|
-
mode: AgentExecutionMode,
|
|
280
|
-
ai: AxAI,
|
|
281
|
-
values: Record<string, any>,
|
|
282
|
-
options?: Readonly<AxProgramForwardOptions<any>>
|
|
283
|
-
): Promise<Record<string, any>> {
|
|
284
|
-
if (mode === "axgen") {
|
|
285
|
-
return this.axGenProgram.forward(ai, values, options as any);
|
|
286
|
-
}
|
|
287
|
-
return super.forward(ai, values, options as any);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private recordUsageMetrics(
|
|
291
|
-
labels: { crewId: string; agent: string },
|
|
292
|
-
mode: AgentExecutionMode
|
|
293
|
-
): void {
|
|
294
|
-
const builtIn =
|
|
295
|
-
mode === "axgen" ? this.axGenProgram.getUsage?.() : super.getUsage?.();
|
|
296
|
-
if (!Array.isArray(builtIn)) return;
|
|
297
|
-
|
|
298
|
-
const totals = builtIn.reduce(
|
|
299
|
-
(acc: any, u: any) => {
|
|
300
|
-
const pt = u.tokens?.promptTokens ?? u.promptTokens ?? 0;
|
|
301
|
-
const ct = u.tokens?.completionTokens ?? u.completionTokens ?? 0;
|
|
302
|
-
acc.promptTokens += typeof pt === "number" ? pt : 0;
|
|
303
|
-
acc.completionTokens += typeof ct === "number" ? ct : 0;
|
|
304
|
-
const model =
|
|
305
|
-
u.model ||
|
|
306
|
-
(this.axai as any)?.getLastUsedChatModel?.() ||
|
|
307
|
-
(this.axai as any)?.defaults?.model;
|
|
308
|
-
if (model) {
|
|
309
|
-
acc.byModel[model] = (acc.byModel[model] || 0) + (pt + ct);
|
|
310
|
-
}
|
|
311
|
-
return acc;
|
|
312
|
-
},
|
|
313
|
-
{ promptTokens: 0, completionTokens: 0, byModel: {} as Record<string, number> }
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
MetricsRegistry.recordTokens(labels, {
|
|
317
|
-
promptTokens: totals.promptTokens,
|
|
318
|
-
completionTokens: totals.completionTokens,
|
|
319
|
-
totalTokens: totals.promptTokens + totals.completionTokens,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const costTracker = (this as any).costTracker;
|
|
323
|
-
try {
|
|
324
|
-
for (const [m, count] of Object.entries(totals.byModel)) {
|
|
325
|
-
costTracker?.trackTokens?.(count, m);
|
|
326
|
-
}
|
|
327
|
-
const totalUSD = Number(costTracker?.getCurrentCost?.() ?? 0);
|
|
328
|
-
if (!Number.isNaN(totalUSD) && totalUSD > 0) {
|
|
329
|
-
MetricsRegistry.recordEstimatedCost(labels, totalUSD);
|
|
330
|
-
}
|
|
331
|
-
} catch {}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private async runForwardInvocation(
|
|
335
|
-
mode: AgentExecutionMode,
|
|
336
|
-
first: Record<string, any> | AxAI,
|
|
337
|
-
second?: Record<string, any> | Readonly<AxProgramForwardOptions<any>>,
|
|
338
|
-
third?: Readonly<AxProgramForwardOptions<any>>
|
|
339
|
-
): Promise<Record<string, any>> {
|
|
340
|
-
const { ai, values, options, calledWithAI } = this.resolveInvocationArgs(
|
|
341
|
-
first,
|
|
342
|
-
second,
|
|
343
|
-
third
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
const start = performance.now();
|
|
347
|
-
const crewId = (this.state as any)?.crewId || this.state.get?.("crewId") || "default";
|
|
348
|
-
const labels = { crewId, agent: this.agentName };
|
|
349
|
-
const taskId = `task_${crewId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
350
|
-
|
|
351
|
-
// Track execution context in crew for ACE feedback routing
|
|
352
|
-
const crewInstance = (this.state as any)?.crew as AxCrew;
|
|
353
|
-
if (crewInstance) {
|
|
354
|
-
if (calledWithAI) {
|
|
355
|
-
const parentTaskId = (this.state as any)?.currentTaskId;
|
|
356
|
-
if (parentTaskId) {
|
|
357
|
-
crewInstance.trackAgentExecution(parentTaskId, this.agentName, values);
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
crewInstance.trackAgentExecution(taskId, this.agentName, values);
|
|
361
|
-
(this.state as any).currentTaskId = taskId;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this.debugEnabled) {
|
|
366
|
-
console.log(`[ACE Debug] forward() called, mode=${mode}, aceConfig=${!!this.aceConfig}`);
|
|
367
|
-
}
|
|
368
|
-
if (this.aceConfig) {
|
|
369
|
-
await this.composeInstructionWithPlaybook();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const result = await this.executeForwardByMode(mode, ai, values, options);
|
|
373
|
-
|
|
374
|
-
const durationMs = performance.now() - start;
|
|
375
|
-
MetricsRegistry.recordRequest(labels, false, durationMs);
|
|
376
|
-
this.recordUsageMetrics(labels, mode);
|
|
377
|
-
|
|
378
|
-
if (crewInstance) {
|
|
379
|
-
if (calledWithAI) {
|
|
380
|
-
const parentTaskId = (this.state as any)?.currentTaskId;
|
|
381
|
-
if (parentTaskId) {
|
|
382
|
-
crewInstance.recordAgentResult(parentTaskId, this.agentName, result);
|
|
383
|
-
}
|
|
384
|
-
} else {
|
|
385
|
-
crewInstance.recordAgentResult(taskId, this.agentName, result);
|
|
386
|
-
delete (this.state as any).currentTaskId;
|
|
387
|
-
(result as any)._taskId = taskId;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
return result;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Function overloads for forward method
|
|
395
|
-
async forward(values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
396
|
-
async forward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramForwardOptions<any>>): Promise<Record<string, any>>;
|
|
397
|
-
|
|
398
|
-
// Implementation
|
|
399
|
-
async forward(
|
|
400
|
-
first: Record<string, any> | AxAI,
|
|
401
|
-
second?: Record<string, any> | Readonly<AxProgramForwardOptions<any>>,
|
|
402
|
-
third?: Readonly<AxProgramForwardOptions<any>>
|
|
403
|
-
): Promise<Record<string, any>> {
|
|
404
|
-
return this.runForwardInvocation(this.executionMode, first, second, third);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
private runStreamingInvocation(
|
|
408
|
-
mode: AgentExecutionMode,
|
|
409
|
-
first: Record<string, any> | AxAI,
|
|
410
|
-
second?: Record<string, any> | Readonly<AxProgramStreamingForwardOptions<any>>,
|
|
411
|
-
third?: Readonly<AxProgramStreamingForwardOptions<any>>
|
|
412
|
-
): AxGenStreamingOut<any> {
|
|
413
|
-
const { ai, values, options } = this.resolveInvocationArgs(
|
|
414
|
-
first,
|
|
415
|
-
second,
|
|
416
|
-
third
|
|
417
|
-
);
|
|
418
|
-
const start = performance.now();
|
|
419
|
-
const crewId = (this.state as any)?.crewId || this.state.get?.("crewId") || "default";
|
|
420
|
-
const labels = { crewId, agent: this.agentName };
|
|
421
|
-
|
|
422
|
-
const createStream = () =>
|
|
423
|
-
mode === "axgen"
|
|
424
|
-
? this.axGenProgram.streamingForward(ai, values, options as any)
|
|
425
|
-
: super.streamingForward(ai, values, options as any);
|
|
426
|
-
|
|
427
|
-
const wrappedGenerator = (async function* (this: StatefulAxAgent) {
|
|
428
|
-
if (this.aceConfig) {
|
|
429
|
-
await this.composeInstructionWithPlaybook();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const streamingResult = createStream();
|
|
433
|
-
try {
|
|
434
|
-
for await (const chunk of streamingResult) {
|
|
435
|
-
yield chunk;
|
|
436
|
-
}
|
|
437
|
-
} finally {
|
|
438
|
-
const durationMs = performance.now() - start;
|
|
439
|
-
MetricsRegistry.recordRequest(labels, true, durationMs);
|
|
440
|
-
this.recordUsageMetrics(labels, mode);
|
|
441
|
-
}
|
|
442
|
-
}).bind(this)();
|
|
443
|
-
|
|
444
|
-
return wrappedGenerator as AxGenStreamingOut<any>;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Add streaming forward method overloads
|
|
448
|
-
streamingForward(values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
449
|
-
streamingForward(ai: AxAI, values: Record<string, any>, options?: Readonly<AxProgramStreamingForwardOptions<any>>): AxGenStreamingOut<any>;
|
|
450
|
-
|
|
451
|
-
// Implementation
|
|
452
|
-
streamingForward(
|
|
453
|
-
first: Record<string, any> | AxAI,
|
|
454
|
-
second?: Record<string, any> | Readonly<AxProgramStreamingForwardOptions<any>>,
|
|
455
|
-
third?: Readonly<AxProgramStreamingForwardOptions<any>>
|
|
456
|
-
): AxGenStreamingOut<any> {
|
|
457
|
-
return this.runStreamingInvocation(this.executionMode, first, second, third);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Legacy cost API removed: rely on Ax trackers for cost reporting
|
|
461
|
-
getLastUsageCost(): UsageCost | null { return null; }
|
|
462
|
-
|
|
463
|
-
// Get the accumulated costs for all runs of this agent
|
|
464
|
-
getAccumulatedCosts(): UsageCost | null { return null; }
|
|
465
|
-
|
|
466
|
-
// Metrics API for this agent
|
|
467
|
-
/**
|
|
468
|
-
* Get the current metrics snapshot for this agent.
|
|
469
|
-
* Includes request counts, error rates, token usage, estimated USD cost, and function call stats.
|
|
470
|
-
*
|
|
471
|
-
* @returns A metrics snapshot scoped to this agent within its crew.
|
|
472
|
-
*/
|
|
473
|
-
getMetrics() {
|
|
474
|
-
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
475
|
-
return MetricsRegistry.snapshot({ crewId, agent: this.agentName } as any);
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Reset all tracked metrics for this agent (does not affect other agents).
|
|
479
|
-
* Call this to start fresh measurement windows for the agent.
|
|
480
|
-
*/
|
|
481
|
-
resetMetrics(): void {
|
|
482
|
-
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
483
|
-
MetricsRegistry.reset({ crewId, agent: this.agentName } as any);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// =============
|
|
487
|
-
// ACE API - Agentic Context Engineering for online learning
|
|
488
|
-
// Reference: https://axllm.dev/ace/
|
|
489
|
-
// =============
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Initialize ACE (Agentic Context Engineering) for this agent.
|
|
493
|
-
* Builds the optimizer and loads any initial playbook from persistence.
|
|
494
|
-
* Sets up the optimizer for online-only mode if compileOnStart is false.
|
|
495
|
-
*/
|
|
496
|
-
async initACE(ace?: ACEConfig): Promise<void> {
|
|
497
|
-
this.aceConfig = ace;
|
|
498
|
-
if (!ace) return;
|
|
499
|
-
try {
|
|
500
|
-
// Capture base instruction BEFORE any playbook injection (mirrors AxACE.extractProgramInstruction)
|
|
501
|
-
this.aceBaseInstruction =
|
|
502
|
-
this.agentDefinition || this.getSignature().getDescription() || '';
|
|
503
|
-
|
|
504
|
-
const { buildACEOptimizer, loadInitialPlaybook, createEmptyPlaybook } = await import('./ace.js');
|
|
505
|
-
// Build optimizer with agent's AI as student
|
|
506
|
-
this.aceOptimizer = buildACEOptimizer(this.axai, ace);
|
|
507
|
-
|
|
508
|
-
// For online-only mode (no offline compile), we need to set the program
|
|
509
|
-
// reference so applyOnlineUpdate can work. AxACE requires compile() to
|
|
510
|
-
// set the program, but we can set it directly for online-only use.
|
|
511
|
-
if (!ace.compileOnStart) {
|
|
512
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
513
|
-
(this.aceOptimizer as any).program = this;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Load initial playbook or create empty one
|
|
517
|
-
const initial = await loadInitialPlaybook(ace.persistence);
|
|
518
|
-
this.applyPlaybook(initial ?? createEmptyPlaybook());
|
|
519
|
-
|
|
520
|
-
if (this.debugEnabled) {
|
|
521
|
-
console.log(`[ACE Debug] Initialized for ${this.agentName}, base instruction: ${this.aceBaseInstruction?.slice(0, 50)}...`);
|
|
522
|
-
}
|
|
523
|
-
} catch (error) {
|
|
524
|
-
console.warn(`Failed to initialize ACE for agent ${this.agentName}:`, error);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Run offline ACE compilation with examples and metric.
|
|
530
|
-
* Compiles the playbook based on training examples.
|
|
531
|
-
*/
|
|
532
|
-
async optimizeOffline(params?: { metric?: any; examples?: any[] }): Promise<void> {
|
|
533
|
-
if (!this.aceConfig || !this.aceOptimizer) return;
|
|
534
|
-
try {
|
|
535
|
-
const { runOfflineCompile, resolveMetric } = await import('./ace.js');
|
|
536
|
-
const registry = (this as any).__functionsRegistry as FunctionRegistryType | undefined;
|
|
537
|
-
const metric = params?.metric || resolveMetric(this.aceConfig.metric, registry || {} as any);
|
|
538
|
-
const examples = params?.examples || [];
|
|
539
|
-
|
|
540
|
-
if (!metric || examples.length === 0) {
|
|
541
|
-
console.warn(`ACE offline compile skipped for ${this.agentName}: missing metric or examples`);
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const result = await runOfflineCompile({
|
|
546
|
-
program: this,
|
|
547
|
-
optimizer: this.aceOptimizer,
|
|
548
|
-
metric,
|
|
549
|
-
examples,
|
|
550
|
-
persistence: this.aceConfig.persistence
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Apply optimized playbook if compilation succeeded
|
|
554
|
-
if (result?.artifact?.playbook) {
|
|
555
|
-
await this.applyPlaybook(result.artifact.playbook);
|
|
556
|
-
}
|
|
557
|
-
} catch (error) {
|
|
558
|
-
console.warn(`ACE offline compile failed for ${this.agentName}:`, error);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Apply online ACE update based on user feedback.
|
|
564
|
-
*
|
|
565
|
-
* For preference-based feedback (e.g., "only show flights between 9am-12pm"),
|
|
566
|
-
* we use our own feedback analyzer that preserves specificity.
|
|
567
|
-
*
|
|
568
|
-
* Note: AxACE's built-in curator is designed for error correction (severity mismatches)
|
|
569
|
-
* and tends to over-abstract preference feedback into generic guidelines.
|
|
570
|
-
* We bypass it and directly use our feedback analyzer for better results.
|
|
571
|
-
*/
|
|
572
|
-
async applyOnlineUpdate(params: { example: any; prediction: any; feedback?: string }): Promise<void> {
|
|
573
|
-
if (!this.aceConfig) return;
|
|
574
|
-
if (!params.feedback?.trim()) return; // Nothing to do without feedback
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
const { persistPlaybook, addFeedbackToPlaybook, createEmptyPlaybook } = await import('./ace.js');
|
|
578
|
-
|
|
579
|
-
// Get or create playbook
|
|
580
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
581
|
-
let playbook = this.acePlaybook ?? (this.aceOptimizer as any)?.playbook;
|
|
582
|
-
if (!playbook) {
|
|
583
|
-
playbook = createEmptyPlaybook();
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (this.debugEnabled) {
|
|
587
|
-
console.log(`[ACE Debug] Adding feedback to playbook: "${params.feedback}"`);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Use teacher AI (or student AI as fallback) for smart categorization
|
|
591
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
592
|
-
const teacherAI = (this.aceOptimizer as any)?.teacherAI;
|
|
593
|
-
const aiForAnalysis = teacherAI ?? this.axai;
|
|
594
|
-
|
|
595
|
-
// Directly add feedback to playbook using our analyzer (preserves specificity)
|
|
596
|
-
await addFeedbackToPlaybook(playbook, params.feedback, aiForAnalysis, this.debugEnabled);
|
|
597
|
-
|
|
598
|
-
// Store updated playbook (injection happens in next forward() call)
|
|
599
|
-
this.applyPlaybook(playbook);
|
|
600
|
-
|
|
601
|
-
// Sync with optimizer if available
|
|
602
|
-
if (this.aceOptimizer) {
|
|
603
|
-
(this.aceOptimizer as any).playbook = playbook;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Persist if auto-persist enabled
|
|
607
|
-
if (this.aceConfig.persistence?.autoPersist) {
|
|
608
|
-
await persistPlaybook(playbook, this.aceConfig.persistence);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (this.debugEnabled) {
|
|
612
|
-
console.log(`[ACE Debug] Playbook updated, sections: ${Object.keys(playbook.sections || {}).join(', ')}`);
|
|
613
|
-
}
|
|
614
|
-
} catch (error) {
|
|
615
|
-
console.warn(`ACE online update failed for ${this.agentName}:`, error);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Get the current ACE playbook for this agent.
|
|
621
|
-
*/
|
|
622
|
-
getPlaybook(): any | undefined {
|
|
623
|
-
return this.acePlaybook;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Apply an ACE playbook to this agent.
|
|
628
|
-
* Stores the playbook for use in next forward() call.
|
|
629
|
-
* Note: Playbook is composed into instruction BEFORE each forward(), mirroring AxACE.compile behavior.
|
|
630
|
-
*/
|
|
631
|
-
applyPlaybook(pb: any): void {
|
|
632
|
-
this.acePlaybook = pb;
|
|
633
|
-
|
|
634
|
-
// Also update optimizer's internal playbook if possible
|
|
635
|
-
try {
|
|
636
|
-
(this.aceOptimizer as any).playbook = pb;
|
|
637
|
-
} catch {
|
|
638
|
-
// Ignore - optimizer may not be initialized yet
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Compose instruction with current playbook and set on agent.
|
|
644
|
-
* This mirrors what AxACE does internally before each forward() during compile().
|
|
645
|
-
* Should be called BEFORE forward() to ensure playbook is in the prompt.
|
|
646
|
-
*/
|
|
647
|
-
private async composeInstructionWithPlaybook(): Promise<void> {
|
|
648
|
-
const playbook = this.acePlaybook ?? (this.aceOptimizer as any)?.playbook;
|
|
649
|
-
|
|
650
|
-
if (this.debugEnabled) {
|
|
651
|
-
console.log(`[ACE Debug] composeInstructionWithPlaybook called`);
|
|
652
|
-
console.log(`[ACE Debug] playbook exists: ${!!playbook}, sections: ${playbook ? Object.keys(playbook.sections || {}).length : 0}`);
|
|
653
|
-
console.log(`[ACE Debug] baseInstruction: "${this.aceBaseInstruction?.slice(0, 50)}..."`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (!playbook) return;
|
|
657
|
-
|
|
658
|
-
try {
|
|
659
|
-
const { renderPlaybook } = await import('./ace.js');
|
|
660
|
-
const rendered = renderPlaybook(playbook);
|
|
661
|
-
|
|
662
|
-
if (this.debugEnabled) {
|
|
663
|
-
console.log(`[ACE Debug] rendered playbook (${rendered.length} chars): ${rendered.slice(0, 100)}...`);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (!rendered) return;
|
|
667
|
-
|
|
668
|
-
// Compose: base instruction + playbook (just like AxACE.composeInstruction)
|
|
669
|
-
const baseInstruction = this.aceBaseInstruction || '';
|
|
670
|
-
const combinedInstruction = [baseInstruction.trim(), '', rendered]
|
|
671
|
-
.filter((part) => part.trim().length > 0)
|
|
672
|
-
.join('\n\n');
|
|
673
|
-
|
|
674
|
-
if (this.debugEnabled) {
|
|
675
|
-
console.log(`[ACE Debug] combinedInstruction (${combinedInstruction.length} chars)`);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
if (combinedInstruction.length >= 20) {
|
|
679
|
-
// Call setDescription on the internal program (like AxACE does)
|
|
680
|
-
// AxAgent.setDescription() only updates the signature, but we need
|
|
681
|
-
// to update the program's description which is used for the system prompt
|
|
682
|
-
const program = (this as any).program;
|
|
683
|
-
if (program?.setDescription) {
|
|
684
|
-
program.setDescription(combinedInstruction);
|
|
685
|
-
}
|
|
686
|
-
// Also update via AxAgent's setDescription for consistency
|
|
687
|
-
this.setDescription(combinedInstruction);
|
|
688
|
-
|
|
689
|
-
if (this.debugEnabled) {
|
|
690
|
-
console.log(`[ACE Debug] setDescription called successfully`);
|
|
691
|
-
console.log(`[ACE Debug] Verifying - signature desc length: ${this.getSignature().getDescription()?.length}`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
} catch (error) {
|
|
695
|
-
console.warn('[ACE Debug] Failed to compose instruction:', error);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* Lightweight proxy that stands in for a real agent in the crew's agent map.
|
|
702
|
-
* It exposes the same `getFunction()` interface (built from the crew config)
|
|
703
|
-
* but defers the expensive `createAgent()` call — and therefore MCP server
|
|
704
|
-
* startup — until the Manager actually delegates to it.
|
|
705
|
-
*
|
|
706
|
-
* Usage: `crew.addLazyAgent("CreateChart")` instead of `crew.addAgent("CreateChart")`
|
|
707
|
-
*/
|
|
708
|
-
class LazyStatefulAxAgent {
|
|
709
|
-
private realAgent: StatefulAxAgent | null = null;
|
|
710
|
-
private crewRef: any; // AxCrew — forward-declared to avoid circular ref
|
|
711
|
-
private agentName: string;
|
|
712
|
-
private description: string;
|
|
713
|
-
private signatureStr: string;
|
|
714
|
-
private func: AxFunction;
|
|
715
|
-
private _id: string = "lazy";
|
|
716
|
-
|
|
717
|
-
constructor(crewRef: any, agentName: string, crewConfig: AxCrewConfig) {
|
|
718
|
-
this.crewRef = crewRef;
|
|
719
|
-
this.agentName = agentName;
|
|
720
|
-
|
|
721
|
-
const agentDef = parseCrewConfig(crewConfig).crew.find(
|
|
722
|
-
(a) => a.name === agentName
|
|
723
|
-
);
|
|
724
|
-
if (!agentDef) {
|
|
725
|
-
throw new Error(`Agent "${agentName}" not found in crew config`);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
this.description = agentDef.description;
|
|
729
|
-
this.signatureStr = agentDef.signature as string;
|
|
730
|
-
|
|
731
|
-
// Build the tool schema from the signature's input fields
|
|
732
|
-
const sig = new AxSignatureClass(this.signatureStr);
|
|
733
|
-
const parameters = sig.toInputJSONSchema();
|
|
734
|
-
|
|
735
|
-
this.func = {
|
|
736
|
-
name: agentName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase(),
|
|
737
|
-
description: this.description,
|
|
738
|
-
parameters,
|
|
739
|
-
func: async (args?: any) => {
|
|
740
|
-
const agent = await this.resolve();
|
|
741
|
-
return agent.forward(args);
|
|
742
|
-
},
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
private async resolve(): Promise<StatefulAxAgent> {
|
|
747
|
-
if (!this.realAgent) {
|
|
748
|
-
const agent = await this.crewRef.createAgent(this.agentName) as StatefulAxAgent;
|
|
749
|
-
agent.setId(this._id);
|
|
750
|
-
this.realAgent = agent;
|
|
751
|
-
}
|
|
752
|
-
return this.realAgent!;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// AxAgentic interface
|
|
756
|
-
getFunction(): AxFunction {
|
|
757
|
-
return this.func;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
getSignature() {
|
|
761
|
-
return new AxSignatureClass(this.signatureStr);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// AxProgrammable / AxTunable stubs
|
|
765
|
-
getId(): string { return this._id; }
|
|
766
|
-
setId(id: string): void { this._id = id; }
|
|
767
|
-
getTraces(): any[] { return this.realAgent?.getTraces() ?? []; }
|
|
768
|
-
setDemos(): void { /* no-op until resolved */ }
|
|
769
|
-
getUsage(): any[] { return this.realAgent?.getUsage() ?? []; }
|
|
770
|
-
resetUsage(): void { this.realAgent?.resetUsage(); }
|
|
771
|
-
|
|
772
|
-
// Forward / streaming — resolve on demand
|
|
773
|
-
async forward(...args: any[]): Promise<any> {
|
|
774
|
-
const agent = await this.resolve();
|
|
775
|
-
return (agent as any).forward(...args);
|
|
776
|
-
}
|
|
777
|
-
streamingForward(...args: any[]): any {
|
|
778
|
-
// Must be sync to match the interface, so we wrap in an async generator
|
|
779
|
-
const self = this;
|
|
780
|
-
async function* lazyStream() {
|
|
781
|
-
const agent = await self.resolve();
|
|
782
|
-
yield* (agent as any).streamingForward(...args);
|
|
783
|
-
}
|
|
784
|
-
return lazyStream();
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* AxCrew orchestrates a set of Ax agents that share state,
|
|
790
|
-
* tools (functions), optional MCP servers, streaming, and a built-in metrics
|
|
791
|
-
* registry for tokens, requests, and estimated cost.
|
|
792
|
-
*
|
|
793
|
-
* Typical usage:
|
|
794
|
-
* const crew = new AxCrew(config, AxCrewFunctions)
|
|
795
|
-
* await crew.addAllAgents()
|
|
796
|
-
* const planner = crew.agents?.get("Planner")
|
|
797
|
-
* const res = await planner?.forward({ task: "Plan something" })
|
|
798
|
-
*
|
|
799
|
-
* Key behaviors:
|
|
800
|
-
* - Validates and instantiates agents from a config-first model
|
|
801
|
-
* - Shares a mutable state object across all agents in the crew
|
|
802
|
-
* - Supports sub-agents and a function registry per agent
|
|
803
|
-
* - Tracks per-agent and crew-level metrics via MetricsRegistry
|
|
804
|
-
* - Provides helpers to add agents (individually, a subset, or all) and
|
|
805
|
-
* to reset metrics/costs when needed
|
|
806
|
-
*/
|
|
807
|
-
class AxCrew {
|
|
808
|
-
private crewConfig: AxCrewConfig;
|
|
809
|
-
private options?: AxCrewOptions;
|
|
810
|
-
functionsRegistry: FunctionRegistryType = {};
|
|
811
|
-
crewId: string;
|
|
812
|
-
agents: Map<string, StatefulAxAgent> | null;
|
|
813
|
-
state: StateInstance;
|
|
814
|
-
// Execution history for ACE feedback routing
|
|
815
|
-
private executionHistory: Map<string, {
|
|
816
|
-
taskId: string;
|
|
817
|
-
rootAgent: string;
|
|
818
|
-
involvedAgents: Set<string>;
|
|
819
|
-
taskInput: any;
|
|
820
|
-
agentResults: Map<string, any>;
|
|
821
|
-
startTime: number;
|
|
822
|
-
endTime?: number;
|
|
823
|
-
}> = new Map();
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Creates an instance of AxCrew.
|
|
827
|
-
* @param {AxCrewConfig} crewConfig - JSON object with crew configuration.
|
|
828
|
-
* @param {FunctionRegistryType} [functionsRegistry={}] - The registry of functions to use in the crew.
|
|
829
|
-
* @param {AxCrewOptions} [options] - Optional settings for the crew (e.g., telemetry).
|
|
830
|
-
* @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
|
|
831
|
-
*/
|
|
832
|
-
constructor(
|
|
833
|
-
crewConfig: AxCrewConfig,
|
|
834
|
-
functionsRegistry: FunctionRegistryType = {},
|
|
835
|
-
options?: AxCrewOptions,
|
|
836
|
-
crewId: string = uuidv4(),
|
|
837
|
-
) {
|
|
838
|
-
// Basic validation of crew configuration
|
|
839
|
-
if (!crewConfig || typeof crewConfig !== 'object' || !('crew' in crewConfig)) {
|
|
840
|
-
throw new Error('Invalid crew configuration');
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Validate each agent in the crew
|
|
844
|
-
crewConfig.crew.forEach((agent: any) => {
|
|
845
|
-
if (!agent.name || agent.name.trim() === '') {
|
|
846
|
-
throw new Error('Agent name cannot be empty');
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
this.crewConfig = crewConfig;
|
|
851
|
-
this.functionsRegistry = functionsRegistry;
|
|
852
|
-
this.crewId = crewId;
|
|
853
|
-
this.options = options;
|
|
854
|
-
this.agents = new Map<string, StatefulAxAgent>();
|
|
855
|
-
this.state = createState(crewId);
|
|
856
|
-
// Make crewId discoverable to metrics
|
|
857
|
-
this.state.set('crewId', crewId);
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
/**
|
|
861
|
-
* Factory function for creating an agent.
|
|
862
|
-
* @param {string} agentName - The name of the agent to create.
|
|
863
|
-
* @returns {StatefulAxAgent} The created StatefulAxAgent instance.
|
|
864
|
-
* @throws Will throw an error if the agent creation fails.
|
|
865
|
-
*/
|
|
866
|
-
createAgent = async (agentName: string): Promise<StatefulAxAgent> => {
|
|
867
|
-
try {
|
|
868
|
-
const agentConfig: ParsedAgentConfig = await parseAgentConfig(
|
|
869
|
-
agentName,
|
|
870
|
-
this.crewConfig,
|
|
871
|
-
this.functionsRegistry,
|
|
872
|
-
this.state,
|
|
873
|
-
this.options
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
// Destructure with type assertion
|
|
877
|
-
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
878
|
-
|
|
879
|
-
// Get subagents for the AI agent
|
|
880
|
-
const subAgents = subAgentNames.map((subAgentName: string) => {
|
|
881
|
-
if (!this.agents?.get(subAgentName)) {
|
|
882
|
-
throw new Error(
|
|
883
|
-
`Sub-agent '${subAgentName}' does not exist in available agents.`
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
return this.agents?.get(subAgentName);
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
// Dedupe sub-agents by name (defensive)
|
|
890
|
-
const subAgentSet = new Map<string, StatefulAxAgent>();
|
|
891
|
-
for (const sa of subAgents.filter((agent): agent is StatefulAxAgent => agent !== undefined)) {
|
|
892
|
-
const n = (sa as any)?.agentName ?? (sa as any)?.name ?? '';
|
|
893
|
-
if (!subAgentSet.has(n)) subAgentSet.set(n, sa);
|
|
894
|
-
}
|
|
895
|
-
const uniqueSubAgents = Array.from(subAgentSet.values());
|
|
896
|
-
|
|
897
|
-
// Dedupe functions by name and avoid collision with sub-agent names
|
|
898
|
-
const subAgentNameSet = new Set(uniqueSubAgents.map((sa: any) => sa?.agentName ?? sa?.name).filter(Boolean));
|
|
899
|
-
const uniqueFunctions: AxFunction[] = [];
|
|
900
|
-
const seenFn = new Set<string>();
|
|
901
|
-
for (const fn of functions.filter((fn): fn is AxFunction => fn !== undefined)) {
|
|
902
|
-
const fnName = fn.name;
|
|
903
|
-
if (subAgentNameSet.has(fnName)) {
|
|
904
|
-
// Skip function that collides with a sub-agent name
|
|
905
|
-
continue;
|
|
906
|
-
}
|
|
907
|
-
if (!seenFn.has(fnName)) {
|
|
908
|
-
seenFn.add(fnName);
|
|
909
|
-
uniqueFunctions.push(fn);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Resolve factory functions (e.g., () => new Foo(state).toFunction()) into
|
|
914
|
-
// AxFunction objects before instrumenting, since spreading a JS function
|
|
915
|
-
// does not copy non-enumerable properties like `name`.
|
|
916
|
-
const resolvedFunctions: AxFunction[] = uniqueFunctions.map(fn =>
|
|
917
|
-
typeof fn === 'function' ? (fn as () => AxFunction)() : fn
|
|
918
|
-
);
|
|
919
|
-
|
|
920
|
-
// Wrap each function handler to record call count and latency in MetricsRegistry
|
|
921
|
-
const crewId = this.crewId;
|
|
922
|
-
const agentNameForMetrics = name;
|
|
923
|
-
const instrumentedFunctions: AxFunction[] = resolvedFunctions.map(fn => ({
|
|
924
|
-
...fn,
|
|
925
|
-
func: async (args?: any, extra?: any) => {
|
|
926
|
-
const fnStart = performance.now();
|
|
927
|
-
try {
|
|
928
|
-
return await fn.func(args, extra);
|
|
929
|
-
} finally {
|
|
930
|
-
const latencyMs = performance.now() - fnStart;
|
|
931
|
-
MetricsRegistry.recordFunctionCall(
|
|
932
|
-
{ crewId, agent: agentNameForMetrics },
|
|
933
|
-
latencyMs,
|
|
934
|
-
fn.name
|
|
935
|
-
);
|
|
936
|
-
}
|
|
937
|
-
},
|
|
938
|
-
}));
|
|
939
|
-
|
|
940
|
-
// Create an instance of StatefulAxAgent
|
|
941
|
-
// Set crew reference in state for execution tracking (ACE feedback routing)
|
|
942
|
-
const agentState = { ...this.state, crew: this };
|
|
943
|
-
const agent = new StatefulAxAgent(
|
|
944
|
-
ai,
|
|
945
|
-
{
|
|
946
|
-
name,
|
|
947
|
-
executionMode,
|
|
948
|
-
axAgentOptions,
|
|
949
|
-
description,
|
|
950
|
-
definition: (agentConfig as any).definition,
|
|
951
|
-
signature,
|
|
952
|
-
functions: instrumentedFunctions,
|
|
953
|
-
agents: uniqueSubAgents,
|
|
954
|
-
examples,
|
|
955
|
-
debug: (agentConfig as any).debug,
|
|
956
|
-
},
|
|
957
|
-
agentState as StateInstance
|
|
958
|
-
);
|
|
959
|
-
(agent as any).costTracker = tracker;
|
|
960
|
-
(agent as any).__functionsRegistry = this.functionsRegistry;
|
|
961
|
-
|
|
962
|
-
// Initialize ACE if configured
|
|
963
|
-
try {
|
|
964
|
-
const crewAgent = parseCrewConfig(this.crewConfig).crew.find(a => a.name === name) as any;
|
|
965
|
-
const ace: ACEConfig | undefined = crewAgent?.ace;
|
|
966
|
-
if (ace) {
|
|
967
|
-
await (agent as any).initACE?.(ace);
|
|
968
|
-
if (ace.compileOnStart) {
|
|
969
|
-
const { resolveMetric } = await import('./ace.js');
|
|
970
|
-
const metric = resolveMetric(ace.metric, this.functionsRegistry);
|
|
971
|
-
await (agent as any).optimizeOffline?.({ metric, examples });
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
} catch {}
|
|
975
|
-
|
|
976
|
-
return agent;
|
|
977
|
-
} catch (error) {
|
|
978
|
-
throw error;
|
|
979
|
-
}
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
/**
|
|
983
|
-
* Adds an agent to the crew by name.
|
|
984
|
-
* @param {string} agentName - The name of the agent to add.
|
|
985
|
-
*/
|
|
986
|
-
async addAgent(agentName: string): Promise<void> {
|
|
987
|
-
try {
|
|
988
|
-
if (!this.agents) {
|
|
989
|
-
this.agents = new Map<string, StatefulAxAgent>();
|
|
990
|
-
}
|
|
991
|
-
if (!this.agents.has(agentName)) {
|
|
992
|
-
this.agents.set(agentName, await this.createAgent(agentName));
|
|
993
|
-
}
|
|
994
|
-
if (this.agents && !this.agents.has(agentName)) {
|
|
995
|
-
this.agents.set(agentName, await this.createAgent(agentName));
|
|
996
|
-
}
|
|
997
|
-
} catch (error) {
|
|
998
|
-
console.error(`Failed to create agent '${agentName}':`);
|
|
999
|
-
throw new Error(`Failed to add agent ${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* Adds a lazy agent to the crew by name.
|
|
1005
|
-
* The agent's tool schema is built immediately from the crew config,
|
|
1006
|
-
* but the expensive initialization (MCP servers, AI client, etc.) is
|
|
1007
|
-
* deferred until the Manager actually delegates to this agent.
|
|
1008
|
-
*
|
|
1009
|
-
* Use this for sub-agents that may not be needed on every request
|
|
1010
|
-
* (e.g., agents with stdio MCP servers that spawn a process).
|
|
1011
|
-
*
|
|
1012
|
-
* @param {string} agentName - The name of the agent to add lazily.
|
|
1013
|
-
*/
|
|
1014
|
-
addLazyAgent(agentName: string): void {
|
|
1015
|
-
if (!this.agents) {
|
|
1016
|
-
this.agents = new Map<string, StatefulAxAgent>();
|
|
1017
|
-
}
|
|
1018
|
-
if (!this.agents.has(agentName)) {
|
|
1019
|
-
this.agents.set(
|
|
1020
|
-
agentName,
|
|
1021
|
-
new LazyStatefulAxAgent(this, agentName, this.crewConfig) as any
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Sets up agents in the crew by name.
|
|
1028
|
-
* For an array of Agent names provided, it adds
|
|
1029
|
-
* the agent to the crew if not already present.
|
|
1030
|
-
* @param {string[]} agentNames - An array of agent names to configure.
|
|
1031
|
-
* @returns {Map<string, StatefulAxAgent> | null} A map of agent names to their corresponding instances.
|
|
1032
|
-
*/
|
|
1033
|
-
async addAgentsToCrew(agentNames: string[]): Promise<Map<string, StatefulAxAgent> | null> {
|
|
1034
|
-
try {
|
|
1035
|
-
// Parse the crew config to get agent dependencies
|
|
1036
|
-
const parsedConfig = parseCrewConfig(this.crewConfig);
|
|
1037
|
-
const dependencyMap = new Map<string, string[]>();
|
|
1038
|
-
parsedConfig.crew.forEach(agent => {
|
|
1039
|
-
dependencyMap.set(agent.name, agent.agents || []);
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
// Function to check if all dependencies are initialized
|
|
1043
|
-
const areDependenciesInitialized = (agentName: string): boolean => {
|
|
1044
|
-
const dependencies = dependencyMap.get(agentName) || [];
|
|
1045
|
-
return dependencies.every(dep => this.agents?.has(dep));
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// Initialize agents sequentially based on dependencies
|
|
1049
|
-
const initializedAgents = new Set<string>();
|
|
1050
|
-
|
|
1051
|
-
while (initializedAgents.size < agentNames.length) {
|
|
1052
|
-
let madeProgress = false;
|
|
1053
|
-
|
|
1054
|
-
for (const agentName of agentNames) {
|
|
1055
|
-
// Skip if already initialized
|
|
1056
|
-
if (initializedAgents.has(agentName)) continue;
|
|
1057
|
-
|
|
1058
|
-
// Check if all dependencies are initialized
|
|
1059
|
-
if (areDependenciesInitialized(agentName)) {
|
|
1060
|
-
await this.addAgent(agentName);
|
|
1061
|
-
initializedAgents.add(agentName);
|
|
1062
|
-
madeProgress = true;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// If we couldn't initialize any agents in this iteration, we have a circular dependency
|
|
1067
|
-
if (!madeProgress) {
|
|
1068
|
-
const remaining = agentNames.filter(agent => !initializedAgents.has(agent));
|
|
1069
|
-
throw new Error(`Failed to initialize agents due to missing dependencies: ${remaining.join(', ')}`);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
return this.agents;
|
|
1074
|
-
} catch (error) {
|
|
1075
|
-
throw error;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
async addAllAgents(): Promise<Map<string, StatefulAxAgent> | null> {
|
|
1080
|
-
try {
|
|
1081
|
-
// Parse the crew config and get all agent configs
|
|
1082
|
-
const parsedConfig = parseCrewConfig(this.crewConfig);
|
|
1083
|
-
|
|
1084
|
-
// Create a map of agent dependencies
|
|
1085
|
-
const dependencyMap = new Map<string, string[]>();
|
|
1086
|
-
parsedConfig.crew.forEach(agent => {
|
|
1087
|
-
dependencyMap.set(agent.name, agent.agents || []);
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
// Function to check if all dependencies are initialized
|
|
1091
|
-
const areDependenciesInitialized = (agentName: string): boolean => {
|
|
1092
|
-
const dependencies = dependencyMap.get(agentName) || [];
|
|
1093
|
-
return dependencies.every(dep => this.agents?.has(dep));
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
// Get all agent names
|
|
1097
|
-
const allAgents = parsedConfig.crew.map(agent => agent.name);
|
|
1098
|
-
const initializedAgents = new Set<string>();
|
|
1099
|
-
|
|
1100
|
-
// Keep trying to initialize agents until all are done or we can't make progress
|
|
1101
|
-
while (initializedAgents.size < allAgents.length) {
|
|
1102
|
-
let madeProgress = false;
|
|
1103
|
-
|
|
1104
|
-
for (const agentName of allAgents) {
|
|
1105
|
-
// Skip if already initialized
|
|
1106
|
-
if (initializedAgents.has(agentName)) continue;
|
|
1107
|
-
|
|
1108
|
-
// Check if all dependencies are initialized
|
|
1109
|
-
if (areDependenciesInitialized(agentName)) {
|
|
1110
|
-
await this.addAgent(agentName);
|
|
1111
|
-
initializedAgents.add(agentName);
|
|
1112
|
-
madeProgress = true;
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// If we couldn't initialize any agents in this iteration, we have a circular dependency
|
|
1117
|
-
if (!madeProgress) {
|
|
1118
|
-
const remaining = allAgents.filter(agent => !initializedAgents.has(agent));
|
|
1119
|
-
throw new Error(`Circular dependency detected or missing dependencies for agents: ${remaining.join(', ')}`);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
return this.agents;
|
|
1124
|
-
} catch (error) {
|
|
1125
|
-
throw error;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Track agent execution for ACE feedback routing
|
|
1131
|
-
*/
|
|
1132
|
-
trackAgentExecution(taskId: string, agentName: string, input: any): void {
|
|
1133
|
-
if (!this.executionHistory.has(taskId)) {
|
|
1134
|
-
this.executionHistory.set(taskId, {
|
|
1135
|
-
taskId,
|
|
1136
|
-
rootAgent: agentName,
|
|
1137
|
-
involvedAgents: new Set([agentName]),
|
|
1138
|
-
taskInput: input,
|
|
1139
|
-
agentResults: new Map(),
|
|
1140
|
-
startTime: Date.now()
|
|
1141
|
-
});
|
|
1142
|
-
} else {
|
|
1143
|
-
// Add to involved agents if not already present
|
|
1144
|
-
const context = this.executionHistory.get(taskId)!;
|
|
1145
|
-
context.involvedAgents.add(agentName);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
/**
|
|
1150
|
-
* Record agent result for ACE feedback routing
|
|
1151
|
-
*/
|
|
1152
|
-
recordAgentResult(taskId: string, agentName: string, result: any): void {
|
|
1153
|
-
const context = this.executionHistory.get(taskId);
|
|
1154
|
-
if (context) {
|
|
1155
|
-
context.agentResults.set(agentName, result);
|
|
1156
|
-
context.endTime = Date.now();
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
/**
|
|
1161
|
-
* Get agent involvement for a task (used for ACE feedback routing)
|
|
1162
|
-
*/
|
|
1163
|
-
getTaskAgentInvolvement(taskId: string): {
|
|
1164
|
-
rootAgent: string;
|
|
1165
|
-
involvedAgents: string[];
|
|
1166
|
-
taskInput: any;
|
|
1167
|
-
agentResults: Map<string, any>;
|
|
1168
|
-
duration?: number;
|
|
1169
|
-
} | null {
|
|
1170
|
-
const context = this.executionHistory.get(taskId);
|
|
1171
|
-
if (!context) return null;
|
|
1172
|
-
|
|
1173
|
-
return {
|
|
1174
|
-
rootAgent: context.rootAgent,
|
|
1175
|
-
involvedAgents: Array.from(context.involvedAgents),
|
|
1176
|
-
taskInput: context.taskInput,
|
|
1177
|
-
agentResults: context.agentResults,
|
|
1178
|
-
duration: context.endTime ? context.endTime - context.startTime : undefined
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* Apply feedback to agents involved in a task for ACE online learning
|
|
1184
|
-
*/
|
|
1185
|
-
async applyTaskFeedback(params: {
|
|
1186
|
-
taskId: string;
|
|
1187
|
-
feedback: string;
|
|
1188
|
-
strategy?: 'all' | 'primary' | 'weighted';
|
|
1189
|
-
}): Promise<void> {
|
|
1190
|
-
const involvement = this.getTaskAgentInvolvement(params.taskId);
|
|
1191
|
-
if (!involvement) {
|
|
1192
|
-
console.warn(`No execution history found for task ${params.taskId}`);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
const { involvedAgents, taskInput, agentResults } = involvement;
|
|
1197
|
-
const strategy = params.strategy || 'all';
|
|
1198
|
-
|
|
1199
|
-
// Determine which agents to update based on strategy
|
|
1200
|
-
let agentsToUpdate: string[] = [];
|
|
1201
|
-
if (strategy === 'primary') {
|
|
1202
|
-
agentsToUpdate = [involvement.rootAgent];
|
|
1203
|
-
} else if (strategy === 'all' || strategy === 'weighted') {
|
|
1204
|
-
agentsToUpdate = involvedAgents;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// Apply feedback to each involved agent
|
|
1208
|
-
for (const agentName of agentsToUpdate) {
|
|
1209
|
-
const agent = this.agents?.get(agentName);
|
|
1210
|
-
if (agent && typeof (agent as any).applyOnlineUpdate === 'function') {
|
|
1211
|
-
try {
|
|
1212
|
-
await (agent as any).applyOnlineUpdate({
|
|
1213
|
-
example: taskInput,
|
|
1214
|
-
prediction: agentResults.get(agentName),
|
|
1215
|
-
feedback: params.feedback
|
|
1216
|
-
});
|
|
1217
|
-
} catch (error) {
|
|
1218
|
-
console.warn(`Failed to apply ACE feedback to agent ${agentName}:`, error);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
/**
|
|
1225
|
-
* Clean up old execution history (call periodically to prevent memory leaks)
|
|
1226
|
-
*/
|
|
1227
|
-
cleanupOldExecutions(maxAgeMs: number = 3600000): void { // Default 1 hour
|
|
1228
|
-
const cutoffTime = Date.now() - maxAgeMs;
|
|
1229
|
-
for (const [taskId, context] of this.executionHistory) {
|
|
1230
|
-
if (context.startTime < cutoffTime) {
|
|
1231
|
-
this.executionHistory.delete(taskId);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Cleans up the crew by dereferencing agents and resetting the state.
|
|
1238
|
-
*/
|
|
1239
|
-
destroy() {
|
|
1240
|
-
this.agents = null;
|
|
1241
|
-
this.executionHistory.clear();
|
|
1242
|
-
this.state.reset();
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
* Resets all cost and usage tracking for the entire crew.
|
|
1248
|
-
* Also calls each agent's `resetUsage` (if available) and clears crew-level metrics.
|
|
1249
|
-
*/
|
|
1250
|
-
resetCosts(): void {
|
|
1251
|
-
// Reset AxAgent built-in usage and our metrics registry
|
|
1252
|
-
if (this.agents) {
|
|
1253
|
-
for (const [, agent] of this.agents) {
|
|
1254
|
-
try { (agent as any).resetUsage?.(); } catch {}
|
|
1255
|
-
try { (agent as any).resetMetrics?.(); } catch {}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
MetricsRegistry.reset({ crewId: this.crewId });
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
// Metrics API
|
|
1262
|
-
/**
|
|
1263
|
-
* Get an aggregate metrics snapshot for the entire crew.
|
|
1264
|
-
* Sums requests, errors, tokens, and estimated cost across all agents in the crew.
|
|
1265
|
-
*
|
|
1266
|
-
* @returns Crew-level metrics snapshot.
|
|
1267
|
-
*/
|
|
1268
|
-
getCrewMetrics() {
|
|
1269
|
-
return MetricsRegistry.snapshotCrew(this.crewId);
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Reset all tracked metrics for the entire crew.
|
|
1273
|
-
* Use to clear totals before a new measurement period.
|
|
1274
|
-
*/
|
|
1275
|
-
resetCrewMetrics(): void {
|
|
1276
|
-
MetricsRegistry.reset({ crewId: this.crewId });
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
export { AxCrew, LazyStatefulAxAgent };
|
|
1281
|
-
export type { StatefulAxAgent };
|
|
1
|
+
export { StatefulAxAgent } from './statefulAgent.js';
|
|
2
|
+
export type { ParsedAgentConfig } from './statefulAgent.js';
|
|
3
|
+
export { LazyStatefulAxAgent } from './lazyAgent.js';
|
|
4
|
+
export { AxCrew } from './crew.js';
|