@amitdeshmukh/ax-crew 8.7.3 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +9 -9
- package/dist/agents/agentConfig.d.ts +2 -0
- package/dist/agents/agentConfig.js +11 -4
- package/dist/agents/crew.d.ts +59 -0
- package/dist/agents/crew.js +355 -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 +93 -0
- package/dist/agents/statefulAgent.js +473 -0
- package/dist/index.d.ts +2 -2
- package/dist/types.d.ts +18 -1
- package/examples/graphjin-database-agent.ts +68 -57
- package/examples/write-post-and-publish-to-wordpress.ts +1 -1
- package/package.json +1 -1
- package/src/agents/agentConfig.ts +14 -8
- package/src/agents/crew.ts +443 -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 +659 -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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [9.0.0] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
- **`crew.state` renamed to `crew.crewState`**: The shared state property on `AxCrew` and `StatefulAxAgent` is now `crewState` instead of `state`. This avoids a conflict with ax-llm's new `private state: AxAgentState` on `AxAgent`. Update all `crew.state.set/get/getAll/reset` calls to `crew.crewState.set/get/getAll/reset`. Class-based function constructors still receive `state: Record<string, any>` (unchanged).
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Deferred tool loading**: When an agent has many MCP tools (20+), only core tools + a `search_tools` meta-function are sent to the LLM. The LLM discovers and activates deferred tools on demand. Configurable via `deferredTools` on agent config:
|
|
10
|
+
- `enabled`: force on/off (default: auto when tool count exceeds threshold)
|
|
11
|
+
- `threshold`: tool count to activate (default: 20)
|
|
12
|
+
- `maxSearchResults`: max tools per search (default: 10)
|
|
13
|
+
- `coreTools`: tool names to always keep active
|
|
14
|
+
- **Tool persistence across forward() calls**: Activated tools are re-injected via `beforeStep` hook so the LLM doesn't need to re-discover tools on each delegation.
|
|
15
|
+
- **Auto-activation from results**: When a tool result mentions a deferred tool name (e.g., error response suggests `fix_query_error`), that tool is automatically activated.
|
|
16
|
+
- **Prompt caching**: `contextCache` is now enabled by default on AxGen programs. System prompts and tool definitions are cached across multi-step interactions, reducing token costs by up to 6x for agents with many tools. Works automatically with Anthropic (implicit caching) and Gemini (explicit context caching).
|
|
17
|
+
- **MCP resource result caching**: `resource_*` function calls are cached in-memory so repeated reads of the same MCP resource doc return instantly without re-fetching from the server.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Refactored `agents/index.ts`**: Split 1300+ line monolith into separate modules:
|
|
21
|
+
- `statefulAgent.ts` — `StatefulAxAgent` class
|
|
22
|
+
- `lazyAgent.ts` — `LazyStatefulAxAgent` class
|
|
23
|
+
- `crew.ts` — `AxCrew` class
|
|
24
|
+
- `deferredTools.ts` — `DeferredToolManager` class
|
|
25
|
+
- `index.ts` — barrel exports
|
|
26
|
+
- **Improved GraphJin example** (`examples/graphjin-database-agent.ts`):
|
|
27
|
+
- Strategy-driven DatabaseAgent prompt: check saved workflows first, build JS workflows with `gj.tools.*` for server-side computation, save for reuse
|
|
28
|
+
- Single-delegation ManagerAgent pattern: route full questions to sub-agents in one call instead of decomposing into sub-queries (reduced costs from $128 to $0.17 on same query)
|
|
29
|
+
- Performance timers for setup/query/total wall-clock time
|
|
30
|
+
- Workflow reuse: saved workflows are checked and executed on repeat queries, ensuring reproducible results
|
|
31
|
+
|
|
32
|
+
## [8.7.4] - 2026-03-24
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Auto-include MCP resource tool functions**: `resource_*` functions generated by ax-llm from MCP server resources are now automatically included in an agent's tool list, even when a `tools` allowlist is specified. This ensures agents can always discover and read MCP reference docs (query syntax, guides, etc.) on demand without needing to explicitly allowlist each resource tool.
|
|
36
|
+
|
|
3
37
|
## [8.7.3] - 2026-03-17
|
|
4
38
|
|
|
5
39
|
### Added
|
package/README.md
CHANGED
|
@@ -92,8 +92,8 @@ const myFunctions: FunctionRegistryType = {
|
|
|
92
92
|
const crew = new AxCrew(config, myFunctions);
|
|
93
93
|
|
|
94
94
|
// Set and get state
|
|
95
|
-
crew.
|
|
96
|
-
const value: string = crew.
|
|
95
|
+
crew.crewState.set('key', 'value');
|
|
96
|
+
const value: string = crew.crewState.get('key');
|
|
97
97
|
|
|
98
98
|
// Add agents to the crew
|
|
99
99
|
await crew.addAgentsToCrew(['Planner']);
|
|
@@ -125,7 +125,7 @@ Key TypeScript features:
|
|
|
125
125
|
- **Agent**: An LLM program with a `signature`, `provider`, `ai` model config, optional `examples`, and optional `mcpServers`.
|
|
126
126
|
- **Sub‑agents**: List other agent names in `agents` to compose behaviors.
|
|
127
127
|
- **Functions (tools)**: Register callable functions via a registry and reference by name in agent `functions`.
|
|
128
|
-
- **State**: `crew.
|
|
128
|
+
- **State**: `crew.crewState.set/get/getAll()` shared across all agents.
|
|
129
129
|
- **Persona**: Use `definition` (preferred) or `prompt` to set the system program. If both are present, `definition` wins.
|
|
130
130
|
- **Execution mode**: Set `executionMode` to `axgen` (default) or `axagent` per agent.
|
|
131
131
|
- **Streaming**: Use `streamingForward()` for token streams.
|
|
@@ -443,19 +443,19 @@ The `StatefulAxAgent` class in `src/agents/index.js` allows for shared state fun
|
|
|
443
443
|
|
|
444
444
|
```javascript
|
|
445
445
|
// Set some state (key/value) for this crew
|
|
446
|
-
crew.
|
|
447
|
-
crew.
|
|
446
|
+
crew.crewState.set('name', 'Crew1');
|
|
447
|
+
crew.crewState.set('location', 'Earth');
|
|
448
448
|
|
|
449
449
|
// Get the state for the crew
|
|
450
|
-
crew.
|
|
451
|
-
crew.
|
|
450
|
+
crew.crewState.get('name'); // 'Crew1'
|
|
451
|
+
crew.crewState.getAll(); // { name: 'Crew1', location: 'Earth' }
|
|
452
452
|
```
|
|
453
453
|
|
|
454
454
|
State can also be set/get by individual agents in the crew. This state is shared with all agents. It is also passed to any functions expressed as a class in `FunctionsRegistry`.
|
|
455
455
|
|
|
456
456
|
```javascript
|
|
457
|
-
Planner.
|
|
458
|
-
console.log(Manager.
|
|
457
|
+
Planner.crewState.set('plan', 'Fly to Mars');
|
|
458
|
+
console.log(Manager.crewState.getAll()); // { name: 'Crew1', location: 'Earth', plan: 'Fly to Mars' }
|
|
459
459
|
```
|
|
460
460
|
|
|
461
461
|
## Example Agent task
|
|
@@ -34,9 +34,11 @@ declare const parseAgentConfig: (agentName: string, crewConfig: AxCrewConfig, fu
|
|
|
34
34
|
definition: any;
|
|
35
35
|
signature: string | import("@ax-llm/ax").AxSignature<Record<string, any>, Record<string, any>>;
|
|
36
36
|
functions: AxFunction[];
|
|
37
|
+
mcpFunctionNames: Set<string>;
|
|
37
38
|
subAgentNames: string[];
|
|
38
39
|
examples: Record<string, any>[];
|
|
39
40
|
tracker: AxDefaultCostTracker;
|
|
41
|
+
deferredTools: any;
|
|
40
42
|
debug: any;
|
|
41
43
|
}>;
|
|
42
44
|
export { parseAgentConfig, parseCrewConfig };
|
|
@@ -56,16 +56,19 @@ const initializeMCPServers = async (agentConfigData) => {
|
|
|
56
56
|
initializedClients.push(mcpClient);
|
|
57
57
|
// Normalize MCP tool schemas: some MCP servers omit `parameters` for
|
|
58
58
|
// zero-arg tools, but providers like Gemini require a valid schema.
|
|
59
|
-
|
|
59
|
+
const allFns = mcpClient.toFunction().map(fn => ({
|
|
60
60
|
...fn,
|
|
61
61
|
parameters: fn.parameters ?? { type: 'object', properties: {} },
|
|
62
62
|
}));
|
|
63
|
-
// Filter to allowlisted tools if specified
|
|
63
|
+
// Filter to allowlisted tools if specified, but always include resource_* functions
|
|
64
64
|
if (mcpServerConfig.tools && mcpServerConfig.tools.length > 0) {
|
|
65
65
|
const allowed = new Set(mcpServerConfig.tools);
|
|
66
|
-
|
|
66
|
+
const filtered = allFns.filter(fn => allowed.has(fn.name) || fn.name.startsWith('resource_'));
|
|
67
|
+
functions.push(...filtered);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
functions.push(...allFns);
|
|
67
71
|
}
|
|
68
|
-
functions.push(...mcpFns);
|
|
69
72
|
}
|
|
70
73
|
return functions;
|
|
71
74
|
}
|
|
@@ -180,6 +183,8 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
|
|
|
180
183
|
...mcpFunctions
|
|
181
184
|
];
|
|
182
185
|
const executionMode = agentConfigData.executionMode === 'axagent' ? 'axagent' : 'axgen';
|
|
186
|
+
// Track MCP function names for deferred tool loading
|
|
187
|
+
const mcpFunctionNames = new Set(mcpFunctions.map(fn => fn.name));
|
|
183
188
|
// Return AI instance and Agent parameters
|
|
184
189
|
return {
|
|
185
190
|
ai: aiInstance,
|
|
@@ -190,9 +195,11 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
|
|
|
190
195
|
definition: agentConfigData.definition ?? agentConfigData.prompt,
|
|
191
196
|
signature: agentConfigData.signature,
|
|
192
197
|
functions: agentFunctions,
|
|
198
|
+
mcpFunctionNames,
|
|
193
199
|
subAgentNames: agentConfigData.agents || [],
|
|
194
200
|
examples: agentConfigData.examples || [],
|
|
195
201
|
tracker: costTracker,
|
|
202
|
+
deferredTools: agentConfigData.deferredTools,
|
|
196
203
|
debug: agentConfigData.options?.debug ?? agentConfigData.debug ?? false,
|
|
197
204
|
};
|
|
198
205
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { StateInstance, FunctionRegistryType, AxCrewConfig, AxCrewOptions } from "../types.js";
|
|
2
|
+
import { StatefulAxAgent } from "./statefulAgent.js";
|
|
3
|
+
/**
|
|
4
|
+
* AxCrew orchestrates a set of Ax agents that share state,
|
|
5
|
+
* tools (functions), optional MCP servers, streaming, and a built-in metrics
|
|
6
|
+
* registry for tokens, requests, and estimated cost.
|
|
7
|
+
*
|
|
8
|
+
* Typical usage:
|
|
9
|
+
* const crew = new AxCrew(config, AxCrewFunctions)
|
|
10
|
+
* await crew.addAllAgents()
|
|
11
|
+
* const planner = crew.agents?.get("Planner")
|
|
12
|
+
* const res = await planner?.forward({ task: "Plan something" })
|
|
13
|
+
*
|
|
14
|
+
* Key behaviors:
|
|
15
|
+
* - Validates and instantiates agents from a config-first model
|
|
16
|
+
* - Shares a mutable state object across all agents in the crew
|
|
17
|
+
* - Supports sub-agents and a function registry per agent
|
|
18
|
+
* - Tracks per-agent and crew-level metrics via MetricsRegistry
|
|
19
|
+
* - Provides helpers to add agents (individually, a subset, or all) and
|
|
20
|
+
* to reset metrics/costs when needed
|
|
21
|
+
*/
|
|
22
|
+
declare class AxCrew {
|
|
23
|
+
private crewConfig;
|
|
24
|
+
private options?;
|
|
25
|
+
functionsRegistry: FunctionRegistryType;
|
|
26
|
+
crewId: string;
|
|
27
|
+
agents: Map<string, StatefulAxAgent> | null;
|
|
28
|
+
crewState: StateInstance;
|
|
29
|
+
private executionHistory;
|
|
30
|
+
constructor(crewConfig: AxCrewConfig, functionsRegistry?: FunctionRegistryType, options?: AxCrewOptions, crewId?: string);
|
|
31
|
+
/**
|
|
32
|
+
* Factory function for creating an agent.
|
|
33
|
+
*/
|
|
34
|
+
createAgent: (agentName: string) => Promise<StatefulAxAgent>;
|
|
35
|
+
addAgent(agentName: string): Promise<void>;
|
|
36
|
+
addLazyAgent(agentName: string): void;
|
|
37
|
+
addAgentsToCrew(agentNames: string[]): Promise<Map<string, StatefulAxAgent> | null>;
|
|
38
|
+
addAllAgents(): Promise<Map<string, StatefulAxAgent> | null>;
|
|
39
|
+
trackAgentExecution(taskId: string, agentName: string, input: any): void;
|
|
40
|
+
recordAgentResult(taskId: string, agentName: string, result: any): void;
|
|
41
|
+
getTaskAgentInvolvement(taskId: string): {
|
|
42
|
+
rootAgent: string;
|
|
43
|
+
involvedAgents: string[];
|
|
44
|
+
taskInput: any;
|
|
45
|
+
agentResults: Map<string, any>;
|
|
46
|
+
duration?: number;
|
|
47
|
+
} | null;
|
|
48
|
+
applyTaskFeedback(params: {
|
|
49
|
+
taskId: string;
|
|
50
|
+
feedback: string;
|
|
51
|
+
strategy?: 'all' | 'primary' | 'weighted';
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
cleanupOldExecutions(maxAgeMs?: number): void;
|
|
54
|
+
destroy(): void;
|
|
55
|
+
resetCosts(): void;
|
|
56
|
+
getCrewMetrics(): import("../metrics/types.js").MetricsSnapshot;
|
|
57
|
+
resetCrewMetrics(): void;
|
|
58
|
+
}
|
|
59
|
+
export { AxCrew };
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { createState } from "../state/index.js";
|
|
3
|
+
import { parseCrewConfig, parseAgentConfig } from "./agentConfig.js";
|
|
4
|
+
import { DeferredToolManager } from "./deferredTools.js";
|
|
5
|
+
import { MetricsRegistry } from "../metrics/index.js";
|
|
6
|
+
import { StatefulAxAgent } from "./statefulAgent.js";
|
|
7
|
+
import { LazyStatefulAxAgent } from "./lazyAgent.js";
|
|
8
|
+
/**
|
|
9
|
+
* AxCrew orchestrates a set of Ax agents that share state,
|
|
10
|
+
* tools (functions), optional MCP servers, streaming, and a built-in metrics
|
|
11
|
+
* registry for tokens, requests, and estimated cost.
|
|
12
|
+
*
|
|
13
|
+
* Typical usage:
|
|
14
|
+
* const crew = new AxCrew(config, AxCrewFunctions)
|
|
15
|
+
* await crew.addAllAgents()
|
|
16
|
+
* const planner = crew.agents?.get("Planner")
|
|
17
|
+
* const res = await planner?.forward({ task: "Plan something" })
|
|
18
|
+
*
|
|
19
|
+
* Key behaviors:
|
|
20
|
+
* - Validates and instantiates agents from a config-first model
|
|
21
|
+
* - Shares a mutable state object across all agents in the crew
|
|
22
|
+
* - Supports sub-agents and a function registry per agent
|
|
23
|
+
* - Tracks per-agent and crew-level metrics via MetricsRegistry
|
|
24
|
+
* - Provides helpers to add agents (individually, a subset, or all) and
|
|
25
|
+
* to reset metrics/costs when needed
|
|
26
|
+
*/
|
|
27
|
+
class AxCrew {
|
|
28
|
+
crewConfig;
|
|
29
|
+
options;
|
|
30
|
+
functionsRegistry = {};
|
|
31
|
+
crewId;
|
|
32
|
+
agents;
|
|
33
|
+
crewState;
|
|
34
|
+
// Execution history for ACE feedback routing
|
|
35
|
+
executionHistory = new Map();
|
|
36
|
+
constructor(crewConfig, functionsRegistry = {}, options, crewId = uuidv4()) {
|
|
37
|
+
if (!crewConfig || typeof crewConfig !== 'object' || !('crew' in crewConfig)) {
|
|
38
|
+
throw new Error('Invalid crew configuration');
|
|
39
|
+
}
|
|
40
|
+
crewConfig.crew.forEach((agent) => {
|
|
41
|
+
if (!agent.name || agent.name.trim() === '') {
|
|
42
|
+
throw new Error('Agent name cannot be empty');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.crewConfig = crewConfig;
|
|
46
|
+
this.functionsRegistry = functionsRegistry;
|
|
47
|
+
this.crewId = crewId;
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.agents = new Map();
|
|
50
|
+
this.crewState = createState(crewId);
|
|
51
|
+
this.crewState.set('crewId', crewId);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Factory function for creating an agent.
|
|
55
|
+
*/
|
|
56
|
+
createAgent = async (agentName) => {
|
|
57
|
+
try {
|
|
58
|
+
const agentConfig = await parseAgentConfig(agentName, this.crewConfig, this.functionsRegistry, this.crewState, this.options);
|
|
59
|
+
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
60
|
+
// Get subagents for the AI agent
|
|
61
|
+
const subAgents = subAgentNames.map((subAgentName) => {
|
|
62
|
+
if (!this.agents?.get(subAgentName)) {
|
|
63
|
+
throw new Error(`Sub-agent '${subAgentName}' does not exist in available agents.`);
|
|
64
|
+
}
|
|
65
|
+
return this.agents?.get(subAgentName);
|
|
66
|
+
});
|
|
67
|
+
// Dedupe sub-agents by name
|
|
68
|
+
const subAgentSet = new Map();
|
|
69
|
+
for (const sa of subAgents.filter((agent) => agent !== undefined)) {
|
|
70
|
+
const n = sa?.agentName ?? sa?.name ?? '';
|
|
71
|
+
if (!subAgentSet.has(n))
|
|
72
|
+
subAgentSet.set(n, sa);
|
|
73
|
+
}
|
|
74
|
+
const uniqueSubAgents = Array.from(subAgentSet.values());
|
|
75
|
+
// Dedupe functions by name and avoid collision with sub-agent names
|
|
76
|
+
const subAgentNameSet = new Set(uniqueSubAgents.map((sa) => sa?.agentName ?? sa?.name).filter(Boolean));
|
|
77
|
+
const uniqueFunctions = [];
|
|
78
|
+
const seenFn = new Set();
|
|
79
|
+
for (const fn of functions.filter((fn) => fn !== undefined)) {
|
|
80
|
+
const fnName = fn.name;
|
|
81
|
+
if (subAgentNameSet.has(fnName))
|
|
82
|
+
continue;
|
|
83
|
+
if (!seenFn.has(fnName)) {
|
|
84
|
+
seenFn.add(fnName);
|
|
85
|
+
uniqueFunctions.push(fn);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Resolve factory functions into AxFunction objects
|
|
89
|
+
const resolvedFunctions = uniqueFunctions.map(fn => typeof fn === 'function' ? fn() : fn);
|
|
90
|
+
// Wrap each function handler to record call count and latency
|
|
91
|
+
const crewId = this.crewId;
|
|
92
|
+
const agentNameForMetrics = name;
|
|
93
|
+
const instrumentedFunctions = resolvedFunctions.map(fn => ({
|
|
94
|
+
...fn,
|
|
95
|
+
func: async (args, extra) => {
|
|
96
|
+
const fnStart = performance.now();
|
|
97
|
+
try {
|
|
98
|
+
return await fn.func(args, extra);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
const latencyMs = performance.now() - fnStart;
|
|
102
|
+
MetricsRegistry.recordFunctionCall({ crewId, agent: agentNameForMetrics }, latencyMs, fn.name);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
// Deferred tool loading
|
|
107
|
+
const mcpFnNames = agentConfig.mcpFunctionNames ?? new Set();
|
|
108
|
+
const deferredConfig = agentConfig.deferredTools;
|
|
109
|
+
const deferredManager = new DeferredToolManager(instrumentedFunctions, mcpFnNames, deferredConfig);
|
|
110
|
+
const effectiveFunctions = deferredManager.isActive
|
|
111
|
+
? deferredManager.getInitialFunctions()
|
|
112
|
+
: instrumentedFunctions;
|
|
113
|
+
if (deferredManager.isActive) {
|
|
114
|
+
console.log(`[ax-crew] Deferred tool loading active for "${name}": ` +
|
|
115
|
+
`${effectiveFunctions.length} core + search_tools, ` +
|
|
116
|
+
`${instrumentedFunctions.length - effectiveFunctions.length + 1} deferred`);
|
|
117
|
+
}
|
|
118
|
+
// Create the agent
|
|
119
|
+
const agentState = { ...this.crewState, crew: this };
|
|
120
|
+
const agent = new StatefulAxAgent(ai, {
|
|
121
|
+
name,
|
|
122
|
+
executionMode,
|
|
123
|
+
axAgentOptions,
|
|
124
|
+
description,
|
|
125
|
+
definition: agentConfig.definition,
|
|
126
|
+
signature,
|
|
127
|
+
functions: effectiveFunctions,
|
|
128
|
+
agents: uniqueSubAgents,
|
|
129
|
+
examples,
|
|
130
|
+
debug: agentConfig.debug,
|
|
131
|
+
}, agentState);
|
|
132
|
+
agent.costTracker = tracker;
|
|
133
|
+
if (deferredManager.isActive) {
|
|
134
|
+
agent.deferredToolManager = deferredManager;
|
|
135
|
+
}
|
|
136
|
+
agent.__functionsRegistry = this.functionsRegistry;
|
|
137
|
+
// Initialize ACE if configured
|
|
138
|
+
try {
|
|
139
|
+
const crewAgent = parseCrewConfig(this.crewConfig).crew.find(a => a.name === name);
|
|
140
|
+
const ace = crewAgent?.ace;
|
|
141
|
+
if (ace) {
|
|
142
|
+
await agent.initACE?.(ace);
|
|
143
|
+
if (ace.compileOnStart) {
|
|
144
|
+
const { resolveMetric } = await import('./ace.js');
|
|
145
|
+
const metric = resolveMetric(ace.metric, this.functionsRegistry);
|
|
146
|
+
await agent.optimizeOffline?.({ metric, examples });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch { }
|
|
151
|
+
return agent;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
async addAgent(agentName) {
|
|
158
|
+
try {
|
|
159
|
+
if (!this.agents) {
|
|
160
|
+
this.agents = new Map();
|
|
161
|
+
}
|
|
162
|
+
if (!this.agents.has(agentName)) {
|
|
163
|
+
this.agents.set(agentName, await this.createAgent(agentName));
|
|
164
|
+
}
|
|
165
|
+
if (this.agents && !this.agents.has(agentName)) {
|
|
166
|
+
this.agents.set(agentName, await this.createAgent(agentName));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error(`Failed to create agent '${agentName}':`);
|
|
171
|
+
throw new Error(`Failed to add agent ${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
addLazyAgent(agentName) {
|
|
175
|
+
if (!this.agents) {
|
|
176
|
+
this.agents = new Map();
|
|
177
|
+
}
|
|
178
|
+
if (!this.agents.has(agentName)) {
|
|
179
|
+
this.agents.set(agentName, new LazyStatefulAxAgent(this, agentName, this.crewConfig));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async addAgentsToCrew(agentNames) {
|
|
183
|
+
try {
|
|
184
|
+
const parsedConfig = parseCrewConfig(this.crewConfig);
|
|
185
|
+
const dependencyMap = new Map();
|
|
186
|
+
parsedConfig.crew.forEach(agent => {
|
|
187
|
+
dependencyMap.set(agent.name, agent.agents || []);
|
|
188
|
+
});
|
|
189
|
+
const areDependenciesInitialized = (agentName) => {
|
|
190
|
+
const dependencies = dependencyMap.get(agentName) || [];
|
|
191
|
+
return dependencies.every(dep => this.agents?.has(dep));
|
|
192
|
+
};
|
|
193
|
+
const initializedAgents = new Set();
|
|
194
|
+
while (initializedAgents.size < agentNames.length) {
|
|
195
|
+
let madeProgress = false;
|
|
196
|
+
for (const agentName of agentNames) {
|
|
197
|
+
if (initializedAgents.has(agentName))
|
|
198
|
+
continue;
|
|
199
|
+
if (areDependenciesInitialized(agentName)) {
|
|
200
|
+
await this.addAgent(agentName);
|
|
201
|
+
initializedAgents.add(agentName);
|
|
202
|
+
madeProgress = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (!madeProgress) {
|
|
206
|
+
const remaining = agentNames.filter(agent => !initializedAgents.has(agent));
|
|
207
|
+
throw new Error(`Failed to initialize agents due to missing dependencies: ${remaining.join(', ')}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return this.agents;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async addAllAgents() {
|
|
217
|
+
try {
|
|
218
|
+
const parsedConfig = parseCrewConfig(this.crewConfig);
|
|
219
|
+
const dependencyMap = new Map();
|
|
220
|
+
parsedConfig.crew.forEach(agent => {
|
|
221
|
+
dependencyMap.set(agent.name, agent.agents || []);
|
|
222
|
+
});
|
|
223
|
+
const areDependenciesInitialized = (agentName) => {
|
|
224
|
+
const dependencies = dependencyMap.get(agentName) || [];
|
|
225
|
+
return dependencies.every(dep => this.agents?.has(dep));
|
|
226
|
+
};
|
|
227
|
+
const allAgents = parsedConfig.crew.map(agent => agent.name);
|
|
228
|
+
const initializedAgents = new Set();
|
|
229
|
+
while (initializedAgents.size < allAgents.length) {
|
|
230
|
+
let madeProgress = false;
|
|
231
|
+
for (const agentName of allAgents) {
|
|
232
|
+
if (initializedAgents.has(agentName))
|
|
233
|
+
continue;
|
|
234
|
+
if (areDependenciesInitialized(agentName)) {
|
|
235
|
+
await this.addAgent(agentName);
|
|
236
|
+
initializedAgents.add(agentName);
|
|
237
|
+
madeProgress = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!madeProgress) {
|
|
241
|
+
const remaining = allAgents.filter(agent => !initializedAgents.has(agent));
|
|
242
|
+
throw new Error(`Circular dependency detected or missing dependencies for agents: ${remaining.join(', ')}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return this.agents;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// === ACE execution tracking ===
|
|
252
|
+
trackAgentExecution(taskId, agentName, input) {
|
|
253
|
+
if (!this.executionHistory.has(taskId)) {
|
|
254
|
+
this.executionHistory.set(taskId, {
|
|
255
|
+
taskId,
|
|
256
|
+
rootAgent: agentName,
|
|
257
|
+
involvedAgents: new Set([agentName]),
|
|
258
|
+
taskInput: input,
|
|
259
|
+
agentResults: new Map(),
|
|
260
|
+
startTime: Date.now()
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const context = this.executionHistory.get(taskId);
|
|
265
|
+
context.involvedAgents.add(agentName);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
recordAgentResult(taskId, agentName, result) {
|
|
269
|
+
const context = this.executionHistory.get(taskId);
|
|
270
|
+
if (context) {
|
|
271
|
+
context.agentResults.set(agentName, result);
|
|
272
|
+
context.endTime = Date.now();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
getTaskAgentInvolvement(taskId) {
|
|
276
|
+
const context = this.executionHistory.get(taskId);
|
|
277
|
+
if (!context)
|
|
278
|
+
return null;
|
|
279
|
+
return {
|
|
280
|
+
rootAgent: context.rootAgent,
|
|
281
|
+
involvedAgents: Array.from(context.involvedAgents),
|
|
282
|
+
taskInput: context.taskInput,
|
|
283
|
+
agentResults: context.agentResults,
|
|
284
|
+
duration: context.endTime ? context.endTime - context.startTime : undefined
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async applyTaskFeedback(params) {
|
|
288
|
+
const involvement = this.getTaskAgentInvolvement(params.taskId);
|
|
289
|
+
if (!involvement) {
|
|
290
|
+
console.warn(`No execution history found for task ${params.taskId}`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const { involvedAgents, taskInput, agentResults } = involvement;
|
|
294
|
+
const strategy = params.strategy || 'all';
|
|
295
|
+
let agentsToUpdate = [];
|
|
296
|
+
if (strategy === 'primary') {
|
|
297
|
+
agentsToUpdate = [involvement.rootAgent];
|
|
298
|
+
}
|
|
299
|
+
else if (strategy === 'all' || strategy === 'weighted') {
|
|
300
|
+
agentsToUpdate = involvedAgents;
|
|
301
|
+
}
|
|
302
|
+
for (const agentName of agentsToUpdate) {
|
|
303
|
+
const agent = this.agents?.get(agentName);
|
|
304
|
+
if (agent && typeof agent.applyOnlineUpdate === 'function') {
|
|
305
|
+
try {
|
|
306
|
+
await agent.applyOnlineUpdate({
|
|
307
|
+
example: taskInput,
|
|
308
|
+
prediction: agentResults.get(agentName),
|
|
309
|
+
feedback: params.feedback
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
console.warn(`Failed to apply ACE feedback to agent ${agentName}:`, error);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
cleanupOldExecutions(maxAgeMs = 3600000) {
|
|
319
|
+
const cutoffTime = Date.now() - maxAgeMs;
|
|
320
|
+
for (const [taskId, context] of this.executionHistory) {
|
|
321
|
+
if (context.startTime < cutoffTime) {
|
|
322
|
+
this.executionHistory.delete(taskId);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// === Lifecycle ===
|
|
327
|
+
destroy() {
|
|
328
|
+
this.agents = null;
|
|
329
|
+
this.executionHistory.clear();
|
|
330
|
+
this.crewState.reset();
|
|
331
|
+
}
|
|
332
|
+
// === Metrics ===
|
|
333
|
+
resetCosts() {
|
|
334
|
+
if (this.agents) {
|
|
335
|
+
for (const [, agent] of this.agents) {
|
|
336
|
+
try {
|
|
337
|
+
agent.resetUsage?.();
|
|
338
|
+
}
|
|
339
|
+
catch { }
|
|
340
|
+
try {
|
|
341
|
+
agent.resetMetrics?.();
|
|
342
|
+
}
|
|
343
|
+
catch { }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
347
|
+
}
|
|
348
|
+
getCrewMetrics() {
|
|
349
|
+
return MetricsRegistry.snapshotCrew(this.crewId);
|
|
350
|
+
}
|
|
351
|
+
resetCrewMetrics() {
|
|
352
|
+
MetricsRegistry.reset({ crewId: this.crewId });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
export { AxCrew };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AxFunction, AxStepHooks } from '@ax-llm/ax';
|
|
2
|
+
export interface DeferredToolsConfig {
|
|
3
|
+
/** Enable deferred tool loading. Default: auto (true when tool count > threshold) */
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** Tool count threshold to activate deferred mode. Default: 20 */
|
|
6
|
+
threshold?: number;
|
|
7
|
+
/** Max tools returned per search. Default: 10 */
|
|
8
|
+
maxSearchResults?: number;
|
|
9
|
+
/** Tool names to always keep active (bypasses deferral) */
|
|
10
|
+
coreTools?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Manages deferred tool loading for agents with many tools.
|
|
14
|
+
*
|
|
15
|
+
* When tool count exceeds a threshold, only core tools + a `search_tools`
|
|
16
|
+
* meta-function are visible to the LLM. The LLM calls `search_tools` to
|
|
17
|
+
* discover and activate deferred tools, which are injected via ax-llm
|
|
18
|
+
* step hooks for subsequent turns.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DeferredToolManager {
|
|
21
|
+
private registry;
|
|
22
|
+
private deferredNames;
|
|
23
|
+
private activatedNames;
|
|
24
|
+
private coreNames;
|
|
25
|
+
private readonly maxSearchResults;
|
|
26
|
+
private readonly _isActive;
|
|
27
|
+
private resourceCache;
|
|
28
|
+
constructor(allFunctions: readonly AxFunction[], mcpFunctionNames: ReadonlySet<string>, config?: DeferredToolsConfig);
|
|
29
|
+
/** Whether deferred mode is active */
|
|
30
|
+
get isActive(): boolean;
|
|
31
|
+
/** Get the initial function set (core tools + search_tools) */
|
|
32
|
+
getInitialFunctions(): AxFunction[];
|
|
33
|
+
/** Wrap a resource function so repeated calls return cached results */
|
|
34
|
+
private wrapWithCache;
|
|
35
|
+
/**
|
|
36
|
+
* Get step hooks for dynamic tool activation.
|
|
37
|
+
* - beforeStep: re-injects previously activated tools at the start of each
|
|
38
|
+
* forward() call so tools discovered in earlier calls persist.
|
|
39
|
+
* - afterFunctionExecution: injects newly discovered tools after search_tools
|
|
40
|
+
* runs, and auto-activates tools mentioned in function results.
|
|
41
|
+
*/
|
|
42
|
+
getStepHooks(): AxStepHooks;
|
|
43
|
+
/** Scan function results for mentions of deferred tool names and auto-activate them. */
|
|
44
|
+
private autoActivateFromResults;
|
|
45
|
+
/** Search deferred tools by keyword matching on name + description */
|
|
46
|
+
private search;
|
|
47
|
+
/** Create the search_tools meta-function */
|
|
48
|
+
private createSearchToolFunction;
|
|
49
|
+
}
|