@amitdeshmukh/ax-crew 8.7.1 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +10 -10
  3. package/axcrew.webp +0 -0
  4. package/dist/agents/agentConfig.d.ts +2 -0
  5. package/dist/agents/agentConfig.js +11 -4
  6. package/dist/agents/crew.d.ts +59 -0
  7. package/dist/agents/crew.js +355 -0
  8. package/dist/agents/deferredTools.d.ts +49 -0
  9. package/dist/agents/deferredTools.js +237 -0
  10. package/dist/agents/index.d.ts +4 -266
  11. package/dist/agents/index.js +3 -1014
  12. package/dist/agents/lazyAgent.d.ts +33 -0
  13. package/dist/agents/lazyAgent.js +78 -0
  14. package/dist/agents/statefulAgent.d.ts +93 -0
  15. package/dist/agents/statefulAgent.js +473 -0
  16. package/dist/index.d.ts +2 -2
  17. package/dist/types.d.ts +18 -1
  18. package/examples/graphjin-database-agent.ts +68 -57
  19. package/examples/write-post-and-publish-to-wordpress.ts +1 -1
  20. package/package.json +1 -1
  21. package/scripts/install-skills.mjs +62 -30
  22. package/scripts/uninstall-skills.mjs +19 -13
  23. package/src/agents/agentConfig.ts +14 -8
  24. package/src/agents/crew.ts +443 -0
  25. package/src/agents/deferredTools.ts +275 -0
  26. package/src/agents/index.ts +4 -1281
  27. package/src/agents/lazyAgent.ts +95 -0
  28. package/src/agents/statefulAgent.ts +659 -0
  29. package/src/index.ts +7 -4
  30. package/src/skills/{ax-crew-ace.md → axcrew-ace.md} +9 -4
  31. package/src/skills/{ax-crew-agent-config.md → axcrew-agent-config.md} +7 -4
  32. package/src/skills/{ax-crew-code-execution.md → axcrew-code-execution.md} +7 -4
  33. package/src/skills/{ax-crew-execution-modes.md → axcrew-execution-modes.md} +10 -6
  34. package/src/skills/{ax-crew-few-shot.md → axcrew-few-shot.md} +8 -4
  35. package/src/skills/{ax-crew-functions.md → axcrew-functions.md} +10 -7
  36. package/src/skills/{ax-crew-mcp.md → axcrew-mcp.md} +50 -6
  37. package/src/skills/{ax-crew-metrics.md → axcrew-metrics.md} +9 -6
  38. package/src/skills/{ax-crew-patterns.md → axcrew-patterns.md} +56 -8
  39. package/src/skills/{ax-crew-providers.md → axcrew-providers.md} +12 -7
  40. package/src/skills/{ax-crew-signatures.md → axcrew-signatures.md} +5 -5
  41. package/src/skills/{ax-crew-state.md → axcrew-state.md} +23 -20
  42. package/src/skills/{ax-crew-streaming.md → axcrew-streaming.md} +7 -4
  43. package/src/skills/{ax-crew-sub-agents.md → axcrew-sub-agents.md} +8 -4
  44. package/src/skills/{ax-crew-telemetry.md → axcrew-telemetry.md} +6 -3
  45. package/src/skills/{ax-crew.md → axcrew.md} +22 -6
  46. package/src/types.ts +19 -0
  47. package/.claude/settings.local.json +0 -13
  48. package/.claude/skills/ax-crew/SKILL.md +0 -466
  49. package/axcrew.png +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
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
+
37
+ ## [8.7.3] - 2026-03-17
38
+
39
+ ### Added
40
+ - **Supporting example files** bundled with each skill. The postinstall script now copies relevant example `.ts` files into each skill's `examples/` directory. Claude Code can load these on demand for complete runnable references.
41
+ - SKILL.md files now reference local `examples/` paths instead of GitHub URLs, enabling Claude to read examples directly.
42
+
43
+ ### Changed
44
+ - Renamed all skill files from `ax-crew-*` to `axcrew-*` (e.g., `axcrew-ace.md`, `axcrew-mcp.md`). Skill directory names in `.claude/skills/` updated accordingly.
45
+ - Updated version in all skill frontmatter to `8.7.3`.
46
+
47
+ ## [8.7.2] - 2026-03-17
48
+
49
+ ### Fixed
50
+ - Skill install now creates proper directory structure for Claude Code discovery. Each skill gets its own directory with a `SKILL.md` file (e.g., `.claude/skills/ax-crew/SKILL.md`, `.claude/skills/ax-crew-ace/SKILL.md`). Previously installed loose `.md` files in a single directory which Claude Code couldn't discover.
51
+
3
52
  ## [8.7.1] - 2026-03-17
4
53
 
5
54
  ### Fixed
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![image](axcrew.png)
1
+ ![AxCrew](axcrew.webp)
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@amitdeshmukh/ax-crew.svg)](https://www.npmjs.com/package/@amitdeshmukh/ax-crew) [![npm downloads](https://img.shields.io/npm/dm/@amitdeshmukh/ax-crew.svg)](https://www.npmjs.com/package/@amitdeshmukh/ax-crew)
4
4
 
@@ -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.state.set('key', 'value');
96
- const value: string = crew.state.get('key');
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.state.set/get/getAll()` shared across all agents.
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.state.set('name', 'Crew1');
447
- crew.state.set('location', 'Earth');
446
+ crew.crewState.set('name', 'Crew1');
447
+ crew.crewState.set('location', 'Earth');
448
448
 
449
449
  // Get the state for the crew
450
- crew.state.get('name'); // 'Crew1'
451
- crew.state.getAll(); // { name: 'Crew1', location: 'Earth' }
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.state.set('plan', 'Fly to Mars');
458
- console.log(Manager.state.getAll()); // { name: 'Crew1', location: 'Earth', plan: 'Fly to Mars' }
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
package/axcrew.webp ADDED
Binary file
@@ -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
- let mcpFns = mcpClient.toFunction().map(fn => ({
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
- mcpFns = mcpFns.filter(fn => allowed.has(fn.name));
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
+ }