@amitdeshmukh/ax-crew 8.6.0 → 8.7.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/.claude/settings.local.json +3 -1
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +7 -7
- package/package.json +3 -1
- package/scripts/install-skills.mjs +59 -0
- package/scripts/uninstall-skills.mjs +25 -0
- package/src/skills/ax-crew-ace.md +165 -0
- package/src/skills/ax-crew-agent-config.md +181 -0
- package/src/skills/ax-crew-code-execution.md +166 -0
- package/src/skills/ax-crew-execution-modes.md +287 -0
- package/src/skills/ax-crew-few-shot.md +165 -0
- package/src/skills/ax-crew-functions.md +218 -0
- package/src/skills/ax-crew-mcp.md +221 -0
- package/src/skills/ax-crew-metrics.md +170 -0
- package/src/skills/ax-crew-patterns.md +286 -0
- package/src/skills/ax-crew-providers.md +204 -0
- package/src/skills/ax-crew-signatures.md +169 -0
- package/src/skills/ax-crew-state.md +168 -0
- package/src/skills/ax-crew-streaming.md +143 -0
- package/src/skills/ax-crew-sub-agents.md +203 -0
- package/src/skills/ax-crew-telemetry.md +161 -0
- package/src/skills/ax-crew.md +124 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ax-crew-state
|
|
3
|
+
version: __VERSION__
|
|
4
|
+
description: "State management: shared state, StateInstance, set, get, getAll, reset, accessing state from class-based functions"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# State
|
|
8
|
+
|
|
9
|
+
Every `AxCrew` instance has a shared `StateInstance` at `crew.state`. All agents and class-based functions in the crew can access the same state.
|
|
10
|
+
|
|
11
|
+
## StateInstance API
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface StateInstance {
|
|
15
|
+
set(key: string, value: any): void; // Set a value
|
|
16
|
+
get(key: string): any; // Get a value
|
|
17
|
+
getAll(): Record<string, any>; // Get all key-value pairs (shallow copy)
|
|
18
|
+
reset(): void; // Clear all state
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Core Behavior
|
|
23
|
+
|
|
24
|
+
- `crew.state` is created automatically when `new AxCrew(config)` is called.
|
|
25
|
+
- State is keyed by `crewId` (auto-generated UUID). Each crew instance has its own isolated state.
|
|
26
|
+
- State persists for the lifetime of the crew. Calling `crew.destroy()` calls `state.reset()`.
|
|
27
|
+
- Values can be any type: strings, objects, arrays, etc.
|
|
28
|
+
|
|
29
|
+
## Canonical Pattern
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { AxCrew } from '@amitdeshmukh/ax-crew';
|
|
33
|
+
import type { AxCrewConfig, FunctionRegistryType } from '@amitdeshmukh/ax-crew';
|
|
34
|
+
import type { AxFunction } from '@ax-llm/ax';
|
|
35
|
+
import dotenv from 'dotenv';
|
|
36
|
+
dotenv.config();
|
|
37
|
+
|
|
38
|
+
// Class-based function that reads from shared state
|
|
39
|
+
class SendEmail {
|
|
40
|
+
private state: Record<string, any>;
|
|
41
|
+
constructor(state: Record<string, any>) { this.state = state; }
|
|
42
|
+
toFunction(): AxFunction {
|
|
43
|
+
return {
|
|
44
|
+
name: 'SendEmail',
|
|
45
|
+
description: 'Send an email using configured SMTP credentials',
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
to: { type: 'string', description: 'Recipient email' },
|
|
50
|
+
subject: { type: 'string', description: 'Email subject' },
|
|
51
|
+
body: { type: 'string', description: 'Email body' },
|
|
52
|
+
},
|
|
53
|
+
required: ['to', 'subject', 'body']
|
|
54
|
+
},
|
|
55
|
+
func: async ({ to, subject, body }: { to: string; subject: string; body: string }) => {
|
|
56
|
+
// Access env credentials set via crew.state.set("env", {...})
|
|
57
|
+
const env = this.state.env || {};
|
|
58
|
+
const smtpHost = env.SMTP_HOST;
|
|
59
|
+
const smtpUser = env.SMTP_USER;
|
|
60
|
+
const smtpPass = env.SMTP_PASS;
|
|
61
|
+
// ... send email using credentials
|
|
62
|
+
return { sent: true, to };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const config: AxCrewConfig = {
|
|
69
|
+
crew: [
|
|
70
|
+
{
|
|
71
|
+
name: "notifier",
|
|
72
|
+
description: "An agent that sends email notifications",
|
|
73
|
+
signature: "task:string -> result:string",
|
|
74
|
+
provider: "openai",
|
|
75
|
+
providerKeyName: "OPENAI_API_KEY",
|
|
76
|
+
ai: { model: "gpt-4o-mini", temperature: 0 },
|
|
77
|
+
functions: ["SendEmail"],
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
const functions: FunctionRegistryType = { SendEmail };
|
|
84
|
+
const crew = new AxCrew(config, functions);
|
|
85
|
+
|
|
86
|
+
// Set environment variables in shared state BEFORE adding agents
|
|
87
|
+
crew.state.set("env", {
|
|
88
|
+
SMTP_HOST: "smtp.example.com",
|
|
89
|
+
SMTP_USER: "user@example.com",
|
|
90
|
+
SMTP_PASS: "secret",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// You can also set arbitrary data
|
|
94
|
+
crew.state.set("company", "Acme Corp");
|
|
95
|
+
crew.state.set("maxRetries", 3);
|
|
96
|
+
|
|
97
|
+
await crew.addAllAgents();
|
|
98
|
+
const notifier = crew.agents?.get("notifier");
|
|
99
|
+
|
|
100
|
+
const result = await notifier?.forward({ task: "Notify user about order shipped" });
|
|
101
|
+
console.log(result?.result);
|
|
102
|
+
|
|
103
|
+
// Read state back
|
|
104
|
+
console.log("All state:", crew.state.getAll());
|
|
105
|
+
console.log("Company:", crew.state.get("company"));
|
|
106
|
+
|
|
107
|
+
// Reset state (clear all)
|
|
108
|
+
crew.state.reset();
|
|
109
|
+
|
|
110
|
+
crew.destroy();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
main().catch(console.error);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Setting Environment Credentials
|
|
117
|
+
|
|
118
|
+
The common pattern for passing credentials to class-based functions:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
crew.state.set("env", {
|
|
122
|
+
WORDPRESS_URL: "http://my-wordpress-site.com",
|
|
123
|
+
WORDPRESS_USERNAME: "my-username",
|
|
124
|
+
WORDPRESS_PASSWORD: "my-password",
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Inside the class-based function, access via `this.state.env`:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
class MyFunction {
|
|
132
|
+
private state: Record<string, any>;
|
|
133
|
+
constructor(state: Record<string, any>) { this.state = state; }
|
|
134
|
+
toFunction(): AxFunction {
|
|
135
|
+
return {
|
|
136
|
+
name: 'MyFunction',
|
|
137
|
+
description: 'Does something',
|
|
138
|
+
parameters: { type: 'object', properties: {} },
|
|
139
|
+
func: () => {
|
|
140
|
+
const url = this.state.env?.WORDPRESS_URL; // reads from shared state
|
|
141
|
+
return url;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Accessing State from Agents
|
|
149
|
+
|
|
150
|
+
Each `StatefulAxAgent` has a `state` property that references the crew's shared state:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const agent = crew.agents?.get("myAgent");
|
|
154
|
+
// agent.state is the same StateInstance as crew.state
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Do Not Generate
|
|
158
|
+
|
|
159
|
+
- Do NOT assume state values exist without checking; always use optional chaining (e.g. `this.state.env?.KEY`).
|
|
160
|
+
- Do NOT call `crew.state.set("env", ...)` after `addAllAgents()` if class-based functions read state during construction -- set state first.
|
|
161
|
+
- Do NOT confuse `crew.state` (StateInstance with `set`/`get`/`getAll`/`reset`) with plain objects. The state object passed to class-based function constructors is a plain record, not the `StateInstance` interface.
|
|
162
|
+
- Do NOT store sensitive credentials in state if the state object might be logged or serialized. The `getAll()` method returns all stored values.
|
|
163
|
+
|
|
164
|
+
## References
|
|
165
|
+
|
|
166
|
+
- [write-post-and-publish-to-wordpress.ts](https://github.com/amitdeshmukh/ax-crew/blob/main/examples/write-post-and-publish-to-wordpress.ts)
|
|
167
|
+
- [src/state/index.ts](https://github.com/amitdeshmukh/ax-crew/blob/main/src/state/index.ts)
|
|
168
|
+
- [src/types.ts](https://github.com/amitdeshmukh/ax-crew/blob/main/src/types.ts)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ax-crew-streaming
|
|
3
|
+
version: "__VERSION__"
|
|
4
|
+
description: "ax-crew streaming patterns: streaming, streamingForward, stream, delta, real-time, chunks, async generator consumption"
|
|
5
|
+
argument-hint: [topic]
|
|
6
|
+
allowed-tools: Read, Grep, Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ax-crew Streaming
|
|
10
|
+
|
|
11
|
+
## streamingForward() vs forward()
|
|
12
|
+
|
|
13
|
+
`forward()` returns `Promise<Record<string, any>>` -- waits for full response.
|
|
14
|
+
`streamingForward()` returns `AxGenStreamingOut<any>` -- an async generator yielding chunks as they arrive.
|
|
15
|
+
|
|
16
|
+
Both accept the same input values object. Both work with `axgen` and `axagent` execution modes.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// Blocking
|
|
20
|
+
const result = await agent.forward({ question: "What is AI?" });
|
|
21
|
+
console.log(result.answer);
|
|
22
|
+
|
|
23
|
+
// Streaming
|
|
24
|
+
const stream = agent.streamingForward({ question: "What is AI?" });
|
|
25
|
+
for await (const chunk of stream) {
|
|
26
|
+
if (chunk.delta && 'answer' in chunk.delta) {
|
|
27
|
+
process.stdout.write(chunk.delta.answer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## chunk.delta Structure
|
|
33
|
+
|
|
34
|
+
Each yielded chunk has a `delta` property containing partial values keyed by output field names from the agent's signature.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// For signature: 'question:string -> answer:string'
|
|
38
|
+
// chunk.delta = { answer: "partial text..." }
|
|
39
|
+
|
|
40
|
+
// For signature: 'query:string -> title:string, summary:string'
|
|
41
|
+
// chunk.delta may contain { title: "..." } or { summary: "..." }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Always check for the field's existence before accessing:
|
|
45
|
+
```typescript
|
|
46
|
+
if (chunk.delta && typeof chunk.delta === 'object' && 'answer' in chunk.delta) {
|
|
47
|
+
process.stdout.write(chunk.delta.answer);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Stream Config in Agent Config
|
|
52
|
+
|
|
53
|
+
Enable streaming at the provider level via `ai.stream` or `options.stream`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
{
|
|
57
|
+
name: "StreamAgent",
|
|
58
|
+
ai: { model: "gemini-2.5-flash", stream: true }, // provider-level
|
|
59
|
+
options: { stream: true }, // forward options level
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Overload Signatures
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Without explicit AI instance (uses agent's configured AI)
|
|
67
|
+
agent.streamingForward(values: Record<string, any>, options?: AxProgramStreamingForwardOptions): AxGenStreamingOut<any>;
|
|
68
|
+
|
|
69
|
+
// With explicit AI instance
|
|
70
|
+
agent.streamingForward(ai: AxAI, values: Record<string, any>, options?: AxProgramStreamingForwardOptions): AxGenStreamingOut<any>;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Canonical Pattern
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { AxCrew } from '@amitdeshmukh/ax-crew';
|
|
77
|
+
import type { AxCrewConfig } from '@amitdeshmukh/ax-crew';
|
|
78
|
+
|
|
79
|
+
const config: AxCrewConfig = {
|
|
80
|
+
crew: [
|
|
81
|
+
{
|
|
82
|
+
name: "ManagerAgent",
|
|
83
|
+
description: "Completes a user specified task",
|
|
84
|
+
signature:
|
|
85
|
+
'question:string "a question to be answered" -> answer:string "the answer to the question"',
|
|
86
|
+
provider: "google-gemini",
|
|
87
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
88
|
+
ai: { model: "gemini-2.5-flash", maxTokens: 1000, temperature: 0 },
|
|
89
|
+
options: { debug: true, stream: true },
|
|
90
|
+
agents: ["MathAgent"],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "MathAgent",
|
|
94
|
+
description: "Solves math problems",
|
|
95
|
+
signature:
|
|
96
|
+
'mathProblem:string "a math problem" -> solution:string "the answer"',
|
|
97
|
+
provider: "google-gemini",
|
|
98
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
99
|
+
ai: { model: "gemini-2.5-pro", temperature: 0 },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
const crew = new AxCrew(config);
|
|
106
|
+
await crew.addAllAgents();
|
|
107
|
+
|
|
108
|
+
const agent = crew.agents?.get("ManagerAgent");
|
|
109
|
+
if (!agent) throw new Error("Agent not found");
|
|
110
|
+
|
|
111
|
+
const stream = agent.streamingForward({
|
|
112
|
+
question: "Get me the first 5 fibonacci numbers divided by 2",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for await (const chunk of stream) {
|
|
116
|
+
if (chunk.delta && typeof chunk.delta === 'object' && 'answer' in chunk.delta) {
|
|
117
|
+
process.stdout.write(chunk.delta.answer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
console.log('\n');
|
|
121
|
+
|
|
122
|
+
// Metrics still available after streaming
|
|
123
|
+
console.log("Agent Metrics:", JSON.stringify(agent.getMetrics?.(), null, 2));
|
|
124
|
+
console.log("Crew Metrics:", JSON.stringify(crew.getCrewMetrics(), null, 2));
|
|
125
|
+
|
|
126
|
+
crew.destroy();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main().catch(console.error);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Do Not Generate
|
|
133
|
+
|
|
134
|
+
- Do NOT `await` the return of `streamingForward()` -- it returns the async generator directly, not a promise.
|
|
135
|
+
- Do NOT assume `chunk.delta` is always a string -- it is an object keyed by output field names.
|
|
136
|
+
- Do NOT forget to check field existence in `chunk.delta` before accessing (`'fieldName' in chunk.delta`).
|
|
137
|
+
- Do NOT use `forward()` with the expectation of receiving streaming chunks -- use `streamingForward()`.
|
|
138
|
+
- Do NOT skip `crew.destroy()` -- it cleans up MCP servers and resources.
|
|
139
|
+
|
|
140
|
+
## References
|
|
141
|
+
|
|
142
|
+
- [streaming.ts example](https://github.com/amitdeshmukh/ax-crew/blob/main/examples/streaming.ts)
|
|
143
|
+
- [Source: StatefulAxAgent.streamingForward](https://github.com/amitdeshmukh/ax-crew/blob/main/src/agents/index.ts)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ax-crew-sub-agents
|
|
3
|
+
description: AxCrew sub-agent composition via agents[] field. Covers agent delegation, dependency resolution, lazy agents (addLazyAgent), parent-child tool integration, and multi-agent orchestration patterns.
|
|
4
|
+
version: "__VERSION__"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AxCrew Sub-Agents
|
|
8
|
+
|
|
9
|
+
## Core Concept
|
|
10
|
+
|
|
11
|
+
The `agents[]` field in `AgentConfig` lists names of other agents that become available as tools to the parent agent. AxCrew resolves dependencies and initializes sub-agents before their parent.
|
|
12
|
+
|
|
13
|
+
## agents[] Field
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
{
|
|
17
|
+
name: "ParentAgent",
|
|
18
|
+
description: "Orchestrates sub-agents",
|
|
19
|
+
signature: "query:string -> answer:string",
|
|
20
|
+
provider: "openai",
|
|
21
|
+
ai: { model: "gpt-4o" },
|
|
22
|
+
agents: ["SubAgentA", "SubAgentB"], // these become callable tools
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The parent agent can call sub-agents as tools automatically. Each sub-agent's signature inputs become the tool's parameters, and its outputs are returned.
|
|
27
|
+
|
|
28
|
+
## Simple Delegation (Researcher-Writer)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { AxCrew } from 'ax-crew';
|
|
32
|
+
import type { AxCrewConfig } from 'ax-crew';
|
|
33
|
+
|
|
34
|
+
const config: AxCrewConfig = {
|
|
35
|
+
crew: [
|
|
36
|
+
{
|
|
37
|
+
name: "researcher",
|
|
38
|
+
description: "A research agent that finds information",
|
|
39
|
+
signature: "query:string -> research:string",
|
|
40
|
+
provider: "google-gemini",
|
|
41
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
42
|
+
ai: { model: "gemini-2.5-flash-lite", maxTokens: 4000 },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "writer",
|
|
46
|
+
description: "A writing agent that creates content",
|
|
47
|
+
signature: "topic:string -> article:string",
|
|
48
|
+
provider: "google-gemini",
|
|
49
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
50
|
+
ai: { model: "gemini-2.5-flash-lite", maxTokens: 4000 },
|
|
51
|
+
agents: ["researcher"], // writer delegates research to researcher
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const crew = new AxCrew(config);
|
|
58
|
+
// addAllAgents resolves dependency order automatically
|
|
59
|
+
await crew.addAllAgents();
|
|
60
|
+
|
|
61
|
+
const writer = crew.agents?.get("writer");
|
|
62
|
+
const { article } = await writer!.forward({
|
|
63
|
+
topic: "Quantum Computing Benefits",
|
|
64
|
+
});
|
|
65
|
+
console.log(article);
|
|
66
|
+
crew.destroy();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch(console.error);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Multi-Agent Orchestration (Customer Support)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { AxJSRuntime, AxJSRuntimePermission } from '@ax-llm/ax';
|
|
76
|
+
import { AxCrew } from 'ax-crew';
|
|
77
|
+
import type { AxCrewConfig } from 'ax-crew';
|
|
78
|
+
|
|
79
|
+
const runtime = new AxJSRuntime({
|
|
80
|
+
permissions: [AxJSRuntimePermission.TIMING],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const config: AxCrewConfig = {
|
|
84
|
+
crew: [
|
|
85
|
+
{
|
|
86
|
+
name: "PolicyLookupAgent",
|
|
87
|
+
description: "Looks up policy details in the provided knowledge base.",
|
|
88
|
+
executionMode: "axagent",
|
|
89
|
+
signature:
|
|
90
|
+
'question:string -> answer:string "Looks up company policies and returns a concise answer"',
|
|
91
|
+
provider: "google-gemini",
|
|
92
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
93
|
+
ai: { model: "gemini-2.5-flash", temperature: 0 },
|
|
94
|
+
axAgentOptions: { contextFields: [], runtime },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "BillingHelperAgent",
|
|
98
|
+
description: "Answers billing and account questions.",
|
|
99
|
+
executionMode: "axagent",
|
|
100
|
+
signature:
|
|
101
|
+
'question:string -> answer:string "Resolves billing and account questions"',
|
|
102
|
+
provider: "google-gemini",
|
|
103
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
104
|
+
ai: { model: "gemini-2.5-flash", temperature: 0 },
|
|
105
|
+
axAgentOptions: { contextFields: [], runtime },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "SentimentClassifierAgent",
|
|
109
|
+
description: "Classifies customer message sentiment.",
|
|
110
|
+
executionMode: "axagent",
|
|
111
|
+
signature: 'question:string -> sentiment:string "positive, negative, or neutral"',
|
|
112
|
+
provider: "google-gemini",
|
|
113
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
114
|
+
ai: { model: "gemini-2.5-flash", temperature: 0 },
|
|
115
|
+
axAgentOptions: {
|
|
116
|
+
contextFields: [],
|
|
117
|
+
fields: { excluded: ["knowledgeBase", "userId"] },
|
|
118
|
+
runtime,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "CustomerSupportAgent",
|
|
123
|
+
description: "Routes queries to specialists and returns a final answer.",
|
|
124
|
+
executionMode: "axagent",
|
|
125
|
+
signature: "query:string, knowledgeBase:string, userId:string -> answer:string",
|
|
126
|
+
provider: "google-gemini",
|
|
127
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
128
|
+
ai: { model: "gemini-2.5-flash", temperature: 0 },
|
|
129
|
+
agents: [
|
|
130
|
+
"PolicyLookupAgent",
|
|
131
|
+
"BillingHelperAgent",
|
|
132
|
+
"SentimentClassifierAgent",
|
|
133
|
+
],
|
|
134
|
+
axAgentOptions: {
|
|
135
|
+
contextFields: ["knowledgeBase"],
|
|
136
|
+
fields: { shared: ["knowledgeBase", "userId"] },
|
|
137
|
+
runtime,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
async function main() {
|
|
144
|
+
const crew = new AxCrew(config);
|
|
145
|
+
await crew.addAllAgents();
|
|
146
|
+
|
|
147
|
+
const support = crew.agents?.get("CustomerSupportAgent");
|
|
148
|
+
const result = await support!.forward({
|
|
149
|
+
query: "What is your refund policy?",
|
|
150
|
+
knowledgeBase: "Full refund within 30 days...",
|
|
151
|
+
userId: "cust-42",
|
|
152
|
+
});
|
|
153
|
+
console.log(result.answer);
|
|
154
|
+
crew.destroy();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main().catch(console.error);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Dependency Resolution
|
|
161
|
+
|
|
162
|
+
`addAllAgents()` and `addAgentsToCrew()` resolve dependencies automatically:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// All agents initialized in dependency order
|
|
166
|
+
await crew.addAllAgents();
|
|
167
|
+
|
|
168
|
+
// Or add specific agents -- dependencies must be included or already added
|
|
169
|
+
await crew.addAgentsToCrew(["researcher"]); // add leaf first
|
|
170
|
+
await crew.addAgentsToCrew(["writer"]); // then parent
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Dependencies are checked: if agent A lists agent B in `agents[]`, B must be initialized first. `addAllAgents()` handles this automatically via topological ordering.
|
|
174
|
+
|
|
175
|
+
## Lazy Agents (addLazyAgent)
|
|
176
|
+
|
|
177
|
+
Defers expensive initialization (MCP servers, AI client) until the agent is first called. Useful for sub-agents that may not be needed on every request.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const crew = new AxCrew(config);
|
|
181
|
+
|
|
182
|
+
// Eager: initialize immediately
|
|
183
|
+
await crew.addAgentsToCrew(["MainAgent"]);
|
|
184
|
+
|
|
185
|
+
// Lazy: tool schema is registered immediately, but actual agent
|
|
186
|
+
// initialization (MCP server startup, etc.) is deferred until first call
|
|
187
|
+
crew.addLazyAgent("RarelyUsedAgent");
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The lazy agent exposes the same `getFunction()` interface. When the parent agent delegates to it, the real agent is created on-demand.
|
|
191
|
+
|
|
192
|
+
## Do Not Generate
|
|
193
|
+
|
|
194
|
+
- Do NOT list an agent in `agents[]` that is not defined in the crew config
|
|
195
|
+
- Do NOT manually wire sub-agent tool calls -- AxCrew does this automatically from the `agents[]` field
|
|
196
|
+
- Do NOT assume sub-agents share parent state by default -- use `axAgentOptions.fields.shared` for shared fields
|
|
197
|
+
- Do NOT add sub-agents after their parent without using `addAllAgents()` or correct ordering in `addAgentsToCrew()`
|
|
198
|
+
- Do NOT use `addLazyAgent()` for agents that are always needed -- it adds latency on first call
|
|
199
|
+
|
|
200
|
+
## References
|
|
201
|
+
|
|
202
|
+
- [basic-researcher-writer.ts](../examples/basic-researcher-writer.ts) -- simple delegation
|
|
203
|
+
- [rlm-shared-fields.ts](../examples/rlm-shared-fields.ts) -- multi-agent with shared fields
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ax-crew-telemetry
|
|
3
|
+
version: __VERSION__
|
|
4
|
+
description: "AxCrew telemetry: OpenTelemetry tracing, metrics, observability with tracer and meter injection."
|
|
5
|
+
tags: [telemetry, opentelemetry, tracing, metrics, observability, tracer, meter]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Telemetry
|
|
9
|
+
|
|
10
|
+
AxCrew accepts OpenTelemetry `tracer` and `meter` instances via the `AxCrewOptions.telemetry` object. When provided, all agent `forward()` and `streamingForward()` calls emit spans and record token/cost metrics automatically.
|
|
11
|
+
|
|
12
|
+
## AxCrewOptions.telemetry
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
interface AxCrewOptions {
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
telemetry?: {
|
|
18
|
+
tracer?: any; // OpenTelemetry Tracer instance
|
|
19
|
+
meter?: any; // OpenTelemetry Meter instance
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// Required packages:
|
|
28
|
+
// npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics
|
|
29
|
+
|
|
30
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
31
|
+
import { ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
32
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
33
|
+
import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
34
|
+
|
|
35
|
+
// Tracing
|
|
36
|
+
const tracerProvider = new NodeTracerProvider({
|
|
37
|
+
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
|
|
38
|
+
});
|
|
39
|
+
tracerProvider.register();
|
|
40
|
+
|
|
41
|
+
// Metrics
|
|
42
|
+
const meterProvider = new MeterProvider({
|
|
43
|
+
readers: [
|
|
44
|
+
new PeriodicExportingMetricReader({
|
|
45
|
+
exporter: new ConsoleMetricExporter(),
|
|
46
|
+
exportIntervalMillis: 5000,
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
51
|
+
|
|
52
|
+
// Get instances
|
|
53
|
+
const tracer = trace.getTracer("ax-crew-example");
|
|
54
|
+
const meter = metrics.getMeter("ax-crew-example");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Passing to AxCrew
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const crew = new AxCrew(crewConfig, functionsRegistry, {
|
|
61
|
+
telemetry: { tracer, meter },
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The third argument to `AxCrew` is `AxCrewOptions`. Telemetry is optional -- omit it and no spans or metrics are emitted.
|
|
66
|
+
|
|
67
|
+
## Canonical Pattern
|
|
68
|
+
|
|
69
|
+
Full working example from `examples/telemetry-demo.ts`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { AxCrew } from "ax-crew";
|
|
73
|
+
import { AxCrewFunctions } from "ax-crew/functions";
|
|
74
|
+
import type { AxCrewConfig, Provider } from "ax-crew";
|
|
75
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
76
|
+
import { ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
77
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
78
|
+
import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
79
|
+
|
|
80
|
+
// Setup OpenTelemetry
|
|
81
|
+
const tracerProvider = new NodeTracerProvider({
|
|
82
|
+
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
|
|
83
|
+
});
|
|
84
|
+
tracerProvider.register();
|
|
85
|
+
|
|
86
|
+
const meterProvider = new MeterProvider({
|
|
87
|
+
readers: [
|
|
88
|
+
new PeriodicExportingMetricReader({
|
|
89
|
+
exporter: new ConsoleMetricExporter(),
|
|
90
|
+
exportIntervalMillis: 5000,
|
|
91
|
+
}),
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
95
|
+
|
|
96
|
+
const tracer = trace.getTracer("my-app");
|
|
97
|
+
const meter = metrics.getMeter("my-app");
|
|
98
|
+
|
|
99
|
+
// Crew config
|
|
100
|
+
const crewConfig: AxCrewConfig = {
|
|
101
|
+
crew: [
|
|
102
|
+
{
|
|
103
|
+
name: "Researcher",
|
|
104
|
+
description: "Researches a topic and provides facts.",
|
|
105
|
+
signature: "topic:string -> facts:string[]",
|
|
106
|
+
provider: "openai" as Provider,
|
|
107
|
+
providerKeyName: "OPENAI_API_KEY",
|
|
108
|
+
ai: { model: "gpt-4o-mini", temperature: 0.7 },
|
|
109
|
+
functions: ["CurrentDateTime"],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "Writer",
|
|
113
|
+
description: "Writes a blog post from facts.",
|
|
114
|
+
signature: "facts:string[] -> blogPost:string",
|
|
115
|
+
provider: "google-gemini" as Provider,
|
|
116
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
117
|
+
ai: { model: "gemini-flash-latest", temperature: 0.7 },
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const crew = new AxCrew(crewConfig, AxCrewFunctions, {
|
|
124
|
+
telemetry: { tracer, meter },
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await crew.addAgent("Researcher");
|
|
128
|
+
await crew.addAgent("Writer");
|
|
129
|
+
|
|
130
|
+
const researcher = crew.agents!.get("Researcher")!;
|
|
131
|
+
const writer = crew.agents!.get("Writer")!;
|
|
132
|
+
|
|
133
|
+
const researchResult = await researcher.forward({
|
|
134
|
+
topic: "The future of AI agents",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const writerResult = await writer.forward({
|
|
138
|
+
facts: researchResult.facts,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log(writerResult.blogPost);
|
|
142
|
+
|
|
143
|
+
// Wait for metric export flush
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 6000));
|
|
145
|
+
crew.destroy();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main().catch(console.error);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Do Not Generate
|
|
152
|
+
|
|
153
|
+
- Do NOT import OpenTelemetry types from `ax-crew` -- import them from `@opentelemetry/api` and the SDK packages.
|
|
154
|
+
- Do NOT pass the `MeterProvider` or `NodeTracerProvider` directly -- pass the `tracer` and `meter` instances obtained via `trace.getTracer()` and `metrics.getMeter()`.
|
|
155
|
+
- Do NOT forget to call `tracerProvider.register()` before creating the crew -- spans will not be emitted otherwise.
|
|
156
|
+
- Do NOT expect telemetry to work without installing `@opentelemetry/api`, `@opentelemetry/sdk-trace-node`, and `@opentelemetry/sdk-metrics`.
|
|
157
|
+
|
|
158
|
+
## References
|
|
159
|
+
|
|
160
|
+
- [telemetry-demo.ts](https://github.com/amitdeshmukh/ax-crew/blob/main/examples/telemetry-demo.ts)
|
|
161
|
+
- [OpenTelemetry JS](https://opentelemetry.io/docs/languages/js/)
|