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