@jackchen_me/open-multi-agent 0.1.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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/agent/agent.d.ts +121 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +294 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/pool.d.ts +128 -0
- package/dist/agent/pool.d.ts.map +1 -0
- package/dist/agent/pool.js +236 -0
- package/dist/agent/pool.js.map +1 -0
- package/dist/agent/runner.d.ts +120 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +274 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/adapter.d.ts +38 -0
- package/dist/llm/adapter.d.ts.map +1 -0
- package/dist/llm/adapter.js +46 -0
- package/dist/llm/adapter.js.map +1 -0
- package/dist/llm/anthropic.d.ts +56 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +307 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/openai.d.ts +62 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +424 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/memory/shared.d.ts +86 -0
- package/dist/memory/shared.d.ts.map +1 -0
- package/dist/memory/shared.js +155 -0
- package/dist/memory/shared.js.map +1 -0
- package/dist/memory/store.d.ts +64 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +103 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +173 -0
- package/dist/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator.js +698 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/scheduler.d.ts +112 -0
- package/dist/orchestrator/scheduler.d.ts.map +1 -0
- package/dist/orchestrator/scheduler.js +282 -0
- package/dist/orchestrator/scheduler.js.map +1 -0
- package/dist/task/queue.d.ts +160 -0
- package/dist/task/queue.d.ts.map +1 -0
- package/dist/task/queue.js +337 -0
- package/dist/task/queue.js.map +1 -0
- package/dist/task/task.d.ts +86 -0
- package/dist/task/task.d.ts.map +1 -0
- package/dist/task/task.js +201 -0
- package/dist/task/task.js.map +1 -0
- package/dist/team/messaging.d.ts +106 -0
- package/dist/team/messaging.d.ts.map +1 -0
- package/dist/team/messaging.js +182 -0
- package/dist/team/messaging.js.map +1 -0
- package/dist/team/team.d.ts +141 -0
- package/dist/team/team.d.ts.map +1 -0
- package/dist/team/team.js +282 -0
- package/dist/team/team.js.map +1 -0
- package/dist/tool/built-in/bash.d.ts +12 -0
- package/dist/tool/built-in/bash.d.ts.map +1 -0
- package/dist/tool/built-in/bash.js +133 -0
- package/dist/tool/built-in/bash.js.map +1 -0
- package/dist/tool/built-in/file-edit.d.ts +14 -0
- package/dist/tool/built-in/file-edit.d.ts.map +1 -0
- package/dist/tool/built-in/file-edit.js +130 -0
- package/dist/tool/built-in/file-edit.js.map +1 -0
- package/dist/tool/built-in/file-read.d.ts +12 -0
- package/dist/tool/built-in/file-read.d.ts.map +1 -0
- package/dist/tool/built-in/file-read.js +82 -0
- package/dist/tool/built-in/file-read.js.map +1 -0
- package/dist/tool/built-in/file-write.d.ts +11 -0
- package/dist/tool/built-in/file-write.d.ts.map +1 -0
- package/dist/tool/built-in/file-write.js +70 -0
- package/dist/tool/built-in/file-write.js.map +1 -0
- package/dist/tool/built-in/grep.d.ts +15 -0
- package/dist/tool/built-in/grep.d.ts.map +1 -0
- package/dist/tool/built-in/grep.js +287 -0
- package/dist/tool/built-in/grep.js.map +1 -0
- package/dist/tool/built-in/index.d.ts +36 -0
- package/dist/tool/built-in/index.d.ts.map +1 -0
- package/dist/tool/built-in/index.js +45 -0
- package/dist/tool/built-in/index.js.map +1 -0
- package/dist/tool/executor.d.ts +71 -0
- package/dist/tool/executor.d.ts.map +1 -0
- package/dist/tool/executor.js +116 -0
- package/dist/tool/executor.js.map +1 -0
- package/dist/tool/framework.d.ts +143 -0
- package/dist/tool/framework.d.ts.map +1 -0
- package/dist/tool/framework.js +371 -0
- package/dist/tool/framework.js.map +1 -0
- package/dist/types.d.ts +285 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/semaphore.d.ts +47 -0
- package/dist/utils/semaphore.d.ts.map +1 -0
- package/dist/utils/semaphore.js +85 -0
- package/dist/utils/semaphore.js.map +1 -0
- package/examples/01-single-agent.ts +131 -0
- package/examples/02-team-collaboration.ts +167 -0
- package/examples/03-task-pipeline.ts +201 -0
- package/examples/04-multi-model-team.ts +261 -0
- package/package.json +49 -0
- package/src/agent/agent.ts +364 -0
- package/src/agent/pool.ts +278 -0
- package/src/agent/runner.ts +413 -0
- package/src/index.ts +166 -0
- package/src/llm/adapter.ts +74 -0
- package/src/llm/anthropic.ts +388 -0
- package/src/llm/openai.ts +522 -0
- package/src/memory/shared.ts +181 -0
- package/src/memory/store.ts +124 -0
- package/src/orchestrator/orchestrator.ts +851 -0
- package/src/orchestrator/scheduler.ts +352 -0
- package/src/task/queue.ts +394 -0
- package/src/task/task.ts +232 -0
- package/src/team/messaging.ts +230 -0
- package/src/team/team.ts +334 -0
- package/src/tool/built-in/bash.ts +187 -0
- package/src/tool/built-in/file-edit.ts +154 -0
- package/src/tool/built-in/file-read.ts +105 -0
- package/src/tool/built-in/file-write.ts +81 -0
- package/src/tool/built-in/grep.ts +362 -0
- package/src/tool/built-in/index.ts +50 -0
- package/src/tool/executor.ts +178 -0
- package/src/tool/framework.ts +557 -0
- package/src/types.ts +362 -0
- package/src/utils/semaphore.ts +89 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview High-level Agent class for open-multi-agent.
|
|
3
|
+
*
|
|
4
|
+
* {@link Agent} is the primary interface most consumers interact with.
|
|
5
|
+
* It wraps {@link AgentRunner} with:
|
|
6
|
+
* - Persistent conversation history (`prompt()`)
|
|
7
|
+
* - Fresh-conversation semantics (`run()`)
|
|
8
|
+
* - Streaming support (`stream()`)
|
|
9
|
+
* - Dynamic tool registration at runtime
|
|
10
|
+
* - Full lifecycle state tracking (`idle → running → completed | error`)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const agent = new Agent({
|
|
15
|
+
* name: 'researcher',
|
|
16
|
+
* model: 'claude-opus-4-6',
|
|
17
|
+
* systemPrompt: 'You are a rigorous research assistant.',
|
|
18
|
+
* tools: ['web_search', 'read_file'],
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* const result = await agent.run('Summarise the last 3 IPCC reports.')
|
|
22
|
+
* console.log(result.output)
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { createAdapter } from '../llm/adapter.js';
|
|
26
|
+
import { AgentRunner } from './runner.js';
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Internal helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const ZERO_USAGE = { input_tokens: 0, output_tokens: 0 };
|
|
31
|
+
function addUsage(a, b) {
|
|
32
|
+
return {
|
|
33
|
+
input_tokens: a.input_tokens + b.input_tokens,
|
|
34
|
+
output_tokens: a.output_tokens + b.output_tokens,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Agent
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* High-level wrapper around {@link AgentRunner} that manages conversation
|
|
42
|
+
* history, state transitions, and tool lifecycle.
|
|
43
|
+
*/
|
|
44
|
+
export class Agent {
|
|
45
|
+
name;
|
|
46
|
+
config;
|
|
47
|
+
runner = null;
|
|
48
|
+
state;
|
|
49
|
+
_toolRegistry;
|
|
50
|
+
_toolExecutor;
|
|
51
|
+
messageHistory = [];
|
|
52
|
+
/**
|
|
53
|
+
* @param config - Static configuration for this agent.
|
|
54
|
+
* @param toolRegistry - Registry used to resolve and manage tools.
|
|
55
|
+
* @param toolExecutor - Executor that dispatches tool calls.
|
|
56
|
+
*
|
|
57
|
+
* `toolRegistry` and `toolExecutor` are injected rather than instantiated
|
|
58
|
+
* internally so that teams of agents can share a single registry.
|
|
59
|
+
*/
|
|
60
|
+
constructor(config, toolRegistry, toolExecutor) {
|
|
61
|
+
this.name = config.name;
|
|
62
|
+
this.config = config;
|
|
63
|
+
this._toolRegistry = toolRegistry;
|
|
64
|
+
this._toolExecutor = toolExecutor;
|
|
65
|
+
this.state = {
|
|
66
|
+
status: 'idle',
|
|
67
|
+
messages: [],
|
|
68
|
+
tokenUsage: ZERO_USAGE,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
// Initialisation (async, called lazily)
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
/**
|
|
75
|
+
* Lazily create the {@link AgentRunner}.
|
|
76
|
+
*
|
|
77
|
+
* The adapter is created asynchronously (it may lazy-import provider SDKs),
|
|
78
|
+
* so we defer construction until the first `run` / `prompt` / `stream` call.
|
|
79
|
+
*/
|
|
80
|
+
async getRunner() {
|
|
81
|
+
if (this.runner !== null) {
|
|
82
|
+
return this.runner;
|
|
83
|
+
}
|
|
84
|
+
const provider = this.config.provider ?? 'anthropic';
|
|
85
|
+
const adapter = await createAdapter(provider);
|
|
86
|
+
const runnerOptions = {
|
|
87
|
+
model: this.config.model,
|
|
88
|
+
systemPrompt: this.config.systemPrompt,
|
|
89
|
+
maxTurns: this.config.maxTurns,
|
|
90
|
+
maxTokens: this.config.maxTokens,
|
|
91
|
+
temperature: this.config.temperature,
|
|
92
|
+
allowedTools: this.config.tools,
|
|
93
|
+
agentName: this.name,
|
|
94
|
+
agentRole: this.config.systemPrompt?.slice(0, 50) ?? 'assistant',
|
|
95
|
+
};
|
|
96
|
+
this.runner = new AgentRunner(adapter, this._toolRegistry, this._toolExecutor, runnerOptions);
|
|
97
|
+
return this.runner;
|
|
98
|
+
}
|
|
99
|
+
// -------------------------------------------------------------------------
|
|
100
|
+
// Primary execution methods
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
/**
|
|
103
|
+
* Run `prompt` in a fresh conversation (history is NOT used).
|
|
104
|
+
*
|
|
105
|
+
* Equivalent to constructing a brand-new messages array `[{ role:'user', … }]`
|
|
106
|
+
* and calling the runner once. The agent's persistent history is not modified.
|
|
107
|
+
*
|
|
108
|
+
* Use this for one-shot queries where past context is irrelevant.
|
|
109
|
+
*/
|
|
110
|
+
async run(prompt) {
|
|
111
|
+
const messages = [
|
|
112
|
+
{ role: 'user', content: [{ type: 'text', text: prompt }] },
|
|
113
|
+
];
|
|
114
|
+
return this.executeRun(messages);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Run `prompt` as part of the ongoing conversation.
|
|
118
|
+
*
|
|
119
|
+
* Appends the user message to the persistent history, runs the agent, then
|
|
120
|
+
* appends the resulting messages to the history for the next call.
|
|
121
|
+
*
|
|
122
|
+
* Use this for multi-turn interactions.
|
|
123
|
+
*/
|
|
124
|
+
async prompt(message) {
|
|
125
|
+
const userMessage = {
|
|
126
|
+
role: 'user',
|
|
127
|
+
content: [{ type: 'text', text: message }],
|
|
128
|
+
};
|
|
129
|
+
this.messageHistory.push(userMessage);
|
|
130
|
+
const result = await this.executeRun([...this.messageHistory]);
|
|
131
|
+
// Persist the new messages into history so the next `prompt` sees them.
|
|
132
|
+
for (const msg of result.messages) {
|
|
133
|
+
this.messageHistory.push(msg);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Stream a fresh-conversation response, yielding {@link StreamEvent}s.
|
|
139
|
+
*
|
|
140
|
+
* Like {@link run}, this does not use or update the persistent history.
|
|
141
|
+
*/
|
|
142
|
+
async *stream(prompt) {
|
|
143
|
+
const messages = [
|
|
144
|
+
{ role: 'user', content: [{ type: 'text', text: prompt }] },
|
|
145
|
+
];
|
|
146
|
+
yield* this.executeStream(messages);
|
|
147
|
+
}
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
// State management
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
/** Return a snapshot of the current agent state (does not clone nested objects). */
|
|
152
|
+
getState() {
|
|
153
|
+
return { ...this.state, messages: [...this.state.messages] };
|
|
154
|
+
}
|
|
155
|
+
/** Return a copy of the persistent message history. */
|
|
156
|
+
getHistory() {
|
|
157
|
+
return [...this.messageHistory];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Clear the persistent conversation history and reset state to `idle`.
|
|
161
|
+
* Does NOT discard the runner instance — the adapter connection is reused.
|
|
162
|
+
*/
|
|
163
|
+
reset() {
|
|
164
|
+
this.messageHistory = [];
|
|
165
|
+
this.state = {
|
|
166
|
+
status: 'idle',
|
|
167
|
+
messages: [],
|
|
168
|
+
tokenUsage: ZERO_USAGE,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
// Dynamic tool management
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
/**
|
|
175
|
+
* Register a new tool with this agent's tool registry at runtime.
|
|
176
|
+
*
|
|
177
|
+
* The tool becomes available to the next LLM call — no restart required.
|
|
178
|
+
*/
|
|
179
|
+
addTool(tool) {
|
|
180
|
+
this._toolRegistry.register(tool);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Deregister a tool by name.
|
|
184
|
+
* If the tool is not registered this is a no-op (no error is thrown).
|
|
185
|
+
*/
|
|
186
|
+
removeTool(name) {
|
|
187
|
+
this._toolRegistry.deregister(name);
|
|
188
|
+
}
|
|
189
|
+
/** Return the names of all currently registered tools. */
|
|
190
|
+
getTools() {
|
|
191
|
+
return this._toolRegistry.list().map((t) => t.name);
|
|
192
|
+
}
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
// Private execution core
|
|
195
|
+
// -------------------------------------------------------------------------
|
|
196
|
+
/**
|
|
197
|
+
* Shared execution path used by both `run` and `prompt`.
|
|
198
|
+
* Handles state transitions and error wrapping.
|
|
199
|
+
*/
|
|
200
|
+
async executeRun(messages) {
|
|
201
|
+
this.transitionTo('running');
|
|
202
|
+
try {
|
|
203
|
+
const runner = await this.getRunner();
|
|
204
|
+
const runOptions = {
|
|
205
|
+
onMessage: msg => {
|
|
206
|
+
this.state.messages.push(msg);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
const result = await runner.run(messages, runOptions);
|
|
210
|
+
this.state.tokenUsage = addUsage(this.state.tokenUsage, result.tokenUsage);
|
|
211
|
+
this.transitionTo('completed');
|
|
212
|
+
return this.toAgentRunResult(result, true);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
216
|
+
this.transitionToError(error);
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
output: error.message,
|
|
220
|
+
messages: [],
|
|
221
|
+
tokenUsage: ZERO_USAGE,
|
|
222
|
+
toolCalls: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Shared streaming path used by `stream`.
|
|
228
|
+
* Handles state transitions and error wrapping.
|
|
229
|
+
*/
|
|
230
|
+
async *executeStream(messages) {
|
|
231
|
+
this.transitionTo('running');
|
|
232
|
+
try {
|
|
233
|
+
const runner = await this.getRunner();
|
|
234
|
+
for await (const event of runner.stream(messages)) {
|
|
235
|
+
if (event.type === 'done') {
|
|
236
|
+
const result = event.data;
|
|
237
|
+
this.state.tokenUsage = addUsage(this.state.tokenUsage, result.tokenUsage);
|
|
238
|
+
this.transitionTo('completed');
|
|
239
|
+
}
|
|
240
|
+
else if (event.type === 'error') {
|
|
241
|
+
const error = event.data instanceof Error
|
|
242
|
+
? event.data
|
|
243
|
+
: new Error(String(event.data));
|
|
244
|
+
this.transitionToError(error);
|
|
245
|
+
}
|
|
246
|
+
yield event;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
251
|
+
this.transitionToError(error);
|
|
252
|
+
yield { type: 'error', data: error };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// -------------------------------------------------------------------------
|
|
256
|
+
// State transition helpers
|
|
257
|
+
// -------------------------------------------------------------------------
|
|
258
|
+
transitionTo(status) {
|
|
259
|
+
this.state = { ...this.state, status };
|
|
260
|
+
}
|
|
261
|
+
transitionToError(error) {
|
|
262
|
+
this.state = { ...this.state, status: 'error', error };
|
|
263
|
+
}
|
|
264
|
+
// -------------------------------------------------------------------------
|
|
265
|
+
// Result mapping
|
|
266
|
+
// -------------------------------------------------------------------------
|
|
267
|
+
toAgentRunResult(result, success) {
|
|
268
|
+
return {
|
|
269
|
+
success,
|
|
270
|
+
output: result.output,
|
|
271
|
+
messages: result.messages,
|
|
272
|
+
tokenUsage: result.tokenUsage,
|
|
273
|
+
toolCalls: result.toolCalls,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
// ToolUseContext builder (for direct use by subclasses or advanced callers)
|
|
278
|
+
// -------------------------------------------------------------------------
|
|
279
|
+
/**
|
|
280
|
+
* Build a {@link ToolUseContext} that identifies this agent.
|
|
281
|
+
* Exposed so team orchestrators can inject richer context (e.g. `TeamInfo`).
|
|
282
|
+
*/
|
|
283
|
+
buildToolContext(abortSignal) {
|
|
284
|
+
return {
|
|
285
|
+
agent: {
|
|
286
|
+
name: this.name,
|
|
287
|
+
role: this.config.systemPrompt?.slice(0, 60) ?? 'assistant',
|
|
288
|
+
model: this.config.model,
|
|
289
|
+
},
|
|
290
|
+
abortSignal,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/agent/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent pool for managing and scheduling multiple agents.
|
|
3
|
+
*
|
|
4
|
+
* {@link AgentPool} is a registry + scheduler that:
|
|
5
|
+
* - Holds any number of named {@link Agent} instances
|
|
6
|
+
* - Enforces a concurrency cap across parallel runs via {@link Semaphore}
|
|
7
|
+
* - Provides `runParallel` for fan-out and `runAny` for round-robin dispatch
|
|
8
|
+
* - Reports aggregate pool health via `getStatus()`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const pool = new AgentPool(3)
|
|
13
|
+
* pool.add(researchAgent)
|
|
14
|
+
* pool.add(writerAgent)
|
|
15
|
+
*
|
|
16
|
+
* const results = await pool.runParallel([
|
|
17
|
+
* { agent: 'researcher', prompt: 'Find recent AI papers.' },
|
|
18
|
+
* { agent: 'writer', prompt: 'Draft an intro section.' },
|
|
19
|
+
* ])
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { AgentRunResult } from '../types.js';
|
|
23
|
+
import type { Agent } from './agent.js';
|
|
24
|
+
export { Semaphore } from '../utils/semaphore.js';
|
|
25
|
+
export interface PoolStatus {
|
|
26
|
+
/** Total number of agents registered in the pool. */
|
|
27
|
+
readonly total: number;
|
|
28
|
+
/** Agents currently in `idle` state. */
|
|
29
|
+
readonly idle: number;
|
|
30
|
+
/** Agents currently in `running` state. */
|
|
31
|
+
readonly running: number;
|
|
32
|
+
/** Agents currently in `completed` state. */
|
|
33
|
+
readonly completed: number;
|
|
34
|
+
/** Agents currently in `error` state. */
|
|
35
|
+
readonly error: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Registry and scheduler for a collection of {@link Agent} instances.
|
|
39
|
+
*
|
|
40
|
+
* Thread-safety note: Node.js is single-threaded, so the semaphore approach
|
|
41
|
+
* is safe — no atomics or mutex primitives are needed. The semaphore gates
|
|
42
|
+
* concurrent async operations, not CPU threads.
|
|
43
|
+
*/
|
|
44
|
+
export declare class AgentPool {
|
|
45
|
+
private readonly maxConcurrency;
|
|
46
|
+
private readonly agents;
|
|
47
|
+
private readonly semaphore;
|
|
48
|
+
/** Cursor used by `runAny` for round-robin dispatch. */
|
|
49
|
+
private roundRobinIndex;
|
|
50
|
+
/**
|
|
51
|
+
* @param maxConcurrency - Maximum number of agent runs allowed at the same
|
|
52
|
+
* time across the whole pool. Defaults to `5`.
|
|
53
|
+
*/
|
|
54
|
+
constructor(maxConcurrency?: number);
|
|
55
|
+
/**
|
|
56
|
+
* Register an agent with the pool.
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} If an agent with the same name is already registered.
|
|
59
|
+
*/
|
|
60
|
+
add(agent: Agent): void;
|
|
61
|
+
/**
|
|
62
|
+
* Unregister an agent by name.
|
|
63
|
+
*
|
|
64
|
+
* @throws {Error} If the agent is not found.
|
|
65
|
+
*/
|
|
66
|
+
remove(name: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Retrieve a registered agent by name, or `undefined` if not found.
|
|
69
|
+
*/
|
|
70
|
+
get(name: string): Agent | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Return all registered agents in insertion order.
|
|
73
|
+
*/
|
|
74
|
+
list(): Agent[];
|
|
75
|
+
/**
|
|
76
|
+
* Run a single prompt on the named agent, respecting the pool concurrency
|
|
77
|
+
* limit.
|
|
78
|
+
*
|
|
79
|
+
* @throws {Error} If the agent name is not found.
|
|
80
|
+
*/
|
|
81
|
+
run(agentName: string, prompt: string): Promise<AgentRunResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Run prompts on multiple agents in parallel, subject to the concurrency
|
|
84
|
+
* cap set at construction time.
|
|
85
|
+
*
|
|
86
|
+
* Results are returned as a `Map<agentName, AgentRunResult>`. If two tasks
|
|
87
|
+
* target the same agent name, the map will only contain the last result.
|
|
88
|
+
* Use unique agent names or run tasks sequentially in that case.
|
|
89
|
+
*
|
|
90
|
+
* @param tasks - Array of `{ agent, prompt }` descriptors.
|
|
91
|
+
*/
|
|
92
|
+
runParallel(tasks: ReadonlyArray<{
|
|
93
|
+
readonly agent: string;
|
|
94
|
+
readonly prompt: string;
|
|
95
|
+
}>): Promise<Map<string, AgentRunResult>>;
|
|
96
|
+
/**
|
|
97
|
+
* Run a prompt on the "best available" agent using round-robin selection.
|
|
98
|
+
*
|
|
99
|
+
* Agents are selected in insertion order, cycling back to the start. The
|
|
100
|
+
* concurrency limit is still enforced — if the selected agent is busy the
|
|
101
|
+
* call will queue via the semaphore.
|
|
102
|
+
*
|
|
103
|
+
* @throws {Error} If the pool is empty.
|
|
104
|
+
*/
|
|
105
|
+
runAny(prompt: string): Promise<AgentRunResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Snapshot of how many agents are in each lifecycle state.
|
|
108
|
+
*/
|
|
109
|
+
getStatus(): PoolStatus;
|
|
110
|
+
/**
|
|
111
|
+
* Reset all agents in the pool.
|
|
112
|
+
*
|
|
113
|
+
* Clears their conversation histories and returns them to `idle` state.
|
|
114
|
+
* Does not remove agents from the pool.
|
|
115
|
+
*
|
|
116
|
+
* Async for forward compatibility — shutdown may need to perform async
|
|
117
|
+
* cleanup (e.g. draining in-flight requests) in future versions.
|
|
118
|
+
*/
|
|
119
|
+
shutdown(): Promise<void>;
|
|
120
|
+
private requireAgent;
|
|
121
|
+
/**
|
|
122
|
+
* Build a failure {@link AgentRunResult} from a caught rejection reason.
|
|
123
|
+
* This keeps `runParallel` returning a complete map even when individual
|
|
124
|
+
* agents fail.
|
|
125
|
+
*/
|
|
126
|
+
private errorResult;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/agent/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAGvC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMjD,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAMD;;;;;;GAMG;AACH,qBAAa,SAAS;IAUR,OAAO,CAAC,QAAQ,CAAC,cAAc;IAT3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,wDAAwD;IACxD,OAAO,CAAC,eAAe,CAAI;IAE3B;;;OAGG;gBAC0B,cAAc,GAAE,MAAU;IAQvD;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAUvB;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAO1B;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAIpC;;OAEG;IACH,IAAI,IAAI,KAAK,EAAE;IAQf;;;;;OAKG;IACG,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAWrE;;;;;;;;;OASG;IACG,WAAW,CACf,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GACxE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IA2BvC;;;;;;;;OAQG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuBrD;;OAEG;IACH,SAAS,IAAI,UAAU;IAsBvB;;;;;;;;OAQG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAU/B,OAAO,CAAC,YAAY;IAWpB;;;;OAIG;IACH,OAAO,CAAC,WAAW;CAUpB"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent pool for managing and scheduling multiple agents.
|
|
3
|
+
*
|
|
4
|
+
* {@link AgentPool} is a registry + scheduler that:
|
|
5
|
+
* - Holds any number of named {@link Agent} instances
|
|
6
|
+
* - Enforces a concurrency cap across parallel runs via {@link Semaphore}
|
|
7
|
+
* - Provides `runParallel` for fan-out and `runAny` for round-robin dispatch
|
|
8
|
+
* - Reports aggregate pool health via `getStatus()`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const pool = new AgentPool(3)
|
|
13
|
+
* pool.add(researchAgent)
|
|
14
|
+
* pool.add(writerAgent)
|
|
15
|
+
*
|
|
16
|
+
* const results = await pool.runParallel([
|
|
17
|
+
* { agent: 'researcher', prompt: 'Find recent AI papers.' },
|
|
18
|
+
* { agent: 'writer', prompt: 'Draft an intro section.' },
|
|
19
|
+
* ])
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import { Semaphore } from '../utils/semaphore.js';
|
|
23
|
+
export { Semaphore } from '../utils/semaphore.js';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// AgentPool
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Registry and scheduler for a collection of {@link Agent} instances.
|
|
29
|
+
*
|
|
30
|
+
* Thread-safety note: Node.js is single-threaded, so the semaphore approach
|
|
31
|
+
* is safe — no atomics or mutex primitives are needed. The semaphore gates
|
|
32
|
+
* concurrent async operations, not CPU threads.
|
|
33
|
+
*/
|
|
34
|
+
export class AgentPool {
|
|
35
|
+
maxConcurrency;
|
|
36
|
+
agents = new Map();
|
|
37
|
+
semaphore;
|
|
38
|
+
/** Cursor used by `runAny` for round-robin dispatch. */
|
|
39
|
+
roundRobinIndex = 0;
|
|
40
|
+
/**
|
|
41
|
+
* @param maxConcurrency - Maximum number of agent runs allowed at the same
|
|
42
|
+
* time across the whole pool. Defaults to `5`.
|
|
43
|
+
*/
|
|
44
|
+
constructor(maxConcurrency = 5) {
|
|
45
|
+
this.maxConcurrency = maxConcurrency;
|
|
46
|
+
this.semaphore = new Semaphore(maxConcurrency);
|
|
47
|
+
}
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
// Registry operations
|
|
50
|
+
// -------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Register an agent with the pool.
|
|
53
|
+
*
|
|
54
|
+
* @throws {Error} If an agent with the same name is already registered.
|
|
55
|
+
*/
|
|
56
|
+
add(agent) {
|
|
57
|
+
if (this.agents.has(agent.name)) {
|
|
58
|
+
throw new Error(`AgentPool: agent '${agent.name}' is already registered. ` +
|
|
59
|
+
`Call remove('${agent.name}') before re-adding.`);
|
|
60
|
+
}
|
|
61
|
+
this.agents.set(agent.name, agent);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Unregister an agent by name.
|
|
65
|
+
*
|
|
66
|
+
* @throws {Error} If the agent is not found.
|
|
67
|
+
*/
|
|
68
|
+
remove(name) {
|
|
69
|
+
if (!this.agents.has(name)) {
|
|
70
|
+
throw new Error(`AgentPool: agent '${name}' is not registered.`);
|
|
71
|
+
}
|
|
72
|
+
this.agents.delete(name);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Retrieve a registered agent by name, or `undefined` if not found.
|
|
76
|
+
*/
|
|
77
|
+
get(name) {
|
|
78
|
+
return this.agents.get(name);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Return all registered agents in insertion order.
|
|
82
|
+
*/
|
|
83
|
+
list() {
|
|
84
|
+
return Array.from(this.agents.values());
|
|
85
|
+
}
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
// Execution API
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Run a single prompt on the named agent, respecting the pool concurrency
|
|
91
|
+
* limit.
|
|
92
|
+
*
|
|
93
|
+
* @throws {Error} If the agent name is not found.
|
|
94
|
+
*/
|
|
95
|
+
async run(agentName, prompt) {
|
|
96
|
+
const agent = this.requireAgent(agentName);
|
|
97
|
+
await this.semaphore.acquire();
|
|
98
|
+
try {
|
|
99
|
+
return await agent.run(prompt);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
this.semaphore.release();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run prompts on multiple agents in parallel, subject to the concurrency
|
|
107
|
+
* cap set at construction time.
|
|
108
|
+
*
|
|
109
|
+
* Results are returned as a `Map<agentName, AgentRunResult>`. If two tasks
|
|
110
|
+
* target the same agent name, the map will only contain the last result.
|
|
111
|
+
* Use unique agent names or run tasks sequentially in that case.
|
|
112
|
+
*
|
|
113
|
+
* @param tasks - Array of `{ agent, prompt }` descriptors.
|
|
114
|
+
*/
|
|
115
|
+
async runParallel(tasks) {
|
|
116
|
+
const resultMap = new Map();
|
|
117
|
+
const settledResults = await Promise.allSettled(tasks.map(async (task) => {
|
|
118
|
+
const result = await this.run(task.agent, task.prompt);
|
|
119
|
+
return { name: task.agent, result };
|
|
120
|
+
}));
|
|
121
|
+
for (const settled of settledResults) {
|
|
122
|
+
if (settled.status === 'fulfilled') {
|
|
123
|
+
resultMap.set(settled.value.name, settled.value.result);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// A rejected run is surfaced as an error AgentRunResult so the caller
|
|
127
|
+
// sees it in the map rather than needing to catch Promise.allSettled.
|
|
128
|
+
// We cannot know the agent name from the rejection alone — find it via
|
|
129
|
+
// the original task list index.
|
|
130
|
+
const idx = settledResults.indexOf(settled);
|
|
131
|
+
const agentName = tasks[idx]?.agent ?? 'unknown';
|
|
132
|
+
resultMap.set(agentName, this.errorResult(settled.reason));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return resultMap;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run a prompt on the "best available" agent using round-robin selection.
|
|
139
|
+
*
|
|
140
|
+
* Agents are selected in insertion order, cycling back to the start. The
|
|
141
|
+
* concurrency limit is still enforced — if the selected agent is busy the
|
|
142
|
+
* call will queue via the semaphore.
|
|
143
|
+
*
|
|
144
|
+
* @throws {Error} If the pool is empty.
|
|
145
|
+
*/
|
|
146
|
+
async runAny(prompt) {
|
|
147
|
+
const allAgents = this.list();
|
|
148
|
+
if (allAgents.length === 0) {
|
|
149
|
+
throw new Error('AgentPool: cannot call runAny on an empty pool.');
|
|
150
|
+
}
|
|
151
|
+
// Wrap the index to keep it in bounds even if agents were removed.
|
|
152
|
+
this.roundRobinIndex = this.roundRobinIndex % allAgents.length;
|
|
153
|
+
const agent = allAgents[this.roundRobinIndex];
|
|
154
|
+
this.roundRobinIndex = (this.roundRobinIndex + 1) % allAgents.length;
|
|
155
|
+
await this.semaphore.acquire();
|
|
156
|
+
try {
|
|
157
|
+
return await agent.run(prompt);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
this.semaphore.release();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
// Observability
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
/**
|
|
167
|
+
* Snapshot of how many agents are in each lifecycle state.
|
|
168
|
+
*/
|
|
169
|
+
getStatus() {
|
|
170
|
+
let idle = 0;
|
|
171
|
+
let running = 0;
|
|
172
|
+
let completed = 0;
|
|
173
|
+
let error = 0;
|
|
174
|
+
for (const agent of this.agents.values()) {
|
|
175
|
+
switch (agent.getState().status) {
|
|
176
|
+
case 'idle':
|
|
177
|
+
idle++;
|
|
178
|
+
break;
|
|
179
|
+
case 'running':
|
|
180
|
+
running++;
|
|
181
|
+
break;
|
|
182
|
+
case 'completed':
|
|
183
|
+
completed++;
|
|
184
|
+
break;
|
|
185
|
+
case 'error':
|
|
186
|
+
error++;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { total: this.agents.size, idle, running, completed, error };
|
|
191
|
+
}
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
// Lifecycle
|
|
194
|
+
// -------------------------------------------------------------------------
|
|
195
|
+
/**
|
|
196
|
+
* Reset all agents in the pool.
|
|
197
|
+
*
|
|
198
|
+
* Clears their conversation histories and returns them to `idle` state.
|
|
199
|
+
* Does not remove agents from the pool.
|
|
200
|
+
*
|
|
201
|
+
* Async for forward compatibility — shutdown may need to perform async
|
|
202
|
+
* cleanup (e.g. draining in-flight requests) in future versions.
|
|
203
|
+
*/
|
|
204
|
+
async shutdown() {
|
|
205
|
+
for (const agent of this.agents.values()) {
|
|
206
|
+
agent.reset();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// -------------------------------------------------------------------------
|
|
210
|
+
// Private helpers
|
|
211
|
+
// -------------------------------------------------------------------------
|
|
212
|
+
requireAgent(name) {
|
|
213
|
+
const agent = this.agents.get(name);
|
|
214
|
+
if (agent === undefined) {
|
|
215
|
+
throw new Error(`AgentPool: agent '${name}' is not registered. ` +
|
|
216
|
+
`Registered agents: [${Array.from(this.agents.keys()).join(', ')}]`);
|
|
217
|
+
}
|
|
218
|
+
return agent;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Build a failure {@link AgentRunResult} from a caught rejection reason.
|
|
222
|
+
* This keeps `runParallel` returning a complete map even when individual
|
|
223
|
+
* agents fail.
|
|
224
|
+
*/
|
|
225
|
+
errorResult(reason) {
|
|
226
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
output: message,
|
|
230
|
+
messages: [],
|
|
231
|
+
tokenUsage: { input_tokens: 0, output_tokens: 0 },
|
|
232
|
+
toolCalls: [],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/agent/pool.ts"],"names":[],"mappings":"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"}
|