@jackchen_me/open-multi-agent 0.2.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -20
- package/dist/agent/agent.d.ts +15 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +144 -10
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/loop-detector.d.ts +39 -0
- package/dist/agent/loop-detector.d.ts.map +1 -0
- package/dist/agent/loop-detector.js +122 -0
- package/dist/agent/loop-detector.js.map +1 -0
- package/dist/agent/pool.d.ts +2 -1
- package/dist/agent/pool.d.ts.map +1 -1
- package/dist/agent/pool.js +4 -2
- package/dist/agent/pool.js.map +1 -1
- package/dist/agent/runner.d.ts +23 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +113 -12
- package/dist/agent/runner.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/adapter.d.ts +4 -1
- package/dist/llm/adapter.d.ts.map +1 -1
- package/dist/llm/adapter.js +11 -0
- package/dist/llm/adapter.js.map +1 -1
- package/dist/llm/copilot.d.ts.map +1 -1
- package/dist/llm/copilot.js +2 -1
- package/dist/llm/copilot.js.map +1 -1
- package/dist/llm/gemini.d.ts +65 -0
- package/dist/llm/gemini.d.ts.map +1 -0
- package/dist/llm/gemini.js +317 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/grok.d.ts +21 -0
- package/dist/llm/grok.d.ts.map +1 -0
- package/dist/llm/grok.js +24 -0
- package/dist/llm/grok.js.map +1 -0
- package/dist/llm/openai-common.d.ts +8 -1
- package/dist/llm/openai-common.d.ts.map +1 -1
- package/dist/llm/openai-common.js +35 -2
- package/dist/llm/openai-common.js.map +1 -1
- package/dist/llm/openai.d.ts +1 -1
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +20 -2
- package/dist/llm/openai.js.map +1 -1
- package/dist/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.js +89 -9
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/task/queue.d.ts +31 -2
- package/dist/task/queue.d.ts.map +1 -1
- package/dist/task/queue.js +69 -2
- package/dist/task/queue.js.map +1 -1
- package/dist/tool/text-tool-extractor.d.ts +32 -0
- package/dist/tool/text-tool-extractor.d.ts.map +1 -0
- package/dist/tool/text-tool-extractor.js +187 -0
- package/dist/tool/text-tool-extractor.js.map +1 -0
- package/dist/types.d.ts +139 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/trace.d.ts +12 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +30 -0
- package/dist/utils/trace.js.map +1 -0
- package/package.json +18 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
- package/.github/pull_request_template.md +0 -14
- package/.github/workflows/ci.yml +0 -23
- package/CLAUDE.md +0 -72
- package/CODE_OF_CONDUCT.md +0 -48
- package/CONTRIBUTING.md +0 -72
- package/DECISIONS.md +0 -43
- package/README_zh.md +0 -217
- package/SECURITY.md +0 -17
- package/examples/01-single-agent.ts +0 -131
- package/examples/02-team-collaboration.ts +0 -167
- package/examples/03-task-pipeline.ts +0 -201
- package/examples/04-multi-model-team.ts +0 -261
- package/examples/05-copilot-test.ts +0 -49
- package/examples/06-local-model.ts +0 -199
- package/examples/07-fan-out-aggregate.ts +0 -209
- package/examples/08-gemma4-local.ts +0 -203
- package/examples/09-gemma4-auto-orchestration.ts +0 -162
- package/src/agent/agent.ts +0 -473
- package/src/agent/pool.ts +0 -278
- package/src/agent/runner.ts +0 -413
- package/src/agent/structured-output.ts +0 -126
- package/src/index.ts +0 -167
- package/src/llm/adapter.ts +0 -87
- package/src/llm/anthropic.ts +0 -389
- package/src/llm/copilot.ts +0 -551
- package/src/llm/openai-common.ts +0 -255
- package/src/llm/openai.ts +0 -272
- package/src/memory/shared.ts +0 -181
- package/src/memory/store.ts +0 -124
- package/src/orchestrator/orchestrator.ts +0 -977
- package/src/orchestrator/scheduler.ts +0 -352
- package/src/task/queue.ts +0 -394
- package/src/task/task.ts +0 -239
- package/src/team/messaging.ts +0 -232
- package/src/team/team.ts +0 -334
- package/src/tool/built-in/bash.ts +0 -187
- package/src/tool/built-in/file-edit.ts +0 -154
- package/src/tool/built-in/file-read.ts +0 -105
- package/src/tool/built-in/file-write.ts +0 -81
- package/src/tool/built-in/grep.ts +0 -362
- package/src/tool/built-in/index.ts +0 -50
- package/src/tool/executor.ts +0 -178
- package/src/tool/framework.ts +0 -557
- package/src/types.ts +0 -391
- package/src/utils/semaphore.ts +0 -89
- package/tests/semaphore.test.ts +0 -57
- package/tests/shared-memory.test.ts +0 -122
- package/tests/structured-output.test.ts +0 -331
- package/tests/task-queue.test.ts +0 -244
- package/tests/task-retry.test.ts +0 -368
- package/tests/task-utils.test.ts +0 -155
- package/tests/tool-executor.test.ts +0 -193
- package/tsconfig.json +0 -25
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example 07 — Fan-Out / Aggregate (MapReduce) Pattern
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates:
|
|
5
|
-
* - Fan-out: send the same question to N "analyst" agents in parallel
|
|
6
|
-
* - Aggregate: a "synthesizer" agent reads all analyst outputs and produces
|
|
7
|
-
* a balanced final report
|
|
8
|
-
* - AgentPool with runParallel() for concurrent fan-out
|
|
9
|
-
* - No tools needed — pure LLM reasoning to keep the focus on the pattern
|
|
10
|
-
*
|
|
11
|
-
* Run:
|
|
12
|
-
* npx tsx examples/07-fan-out-aggregate.ts
|
|
13
|
-
*
|
|
14
|
-
* Prerequisites:
|
|
15
|
-
* ANTHROPIC_API_KEY env var must be set.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { Agent, AgentPool, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../src/index.js'
|
|
19
|
-
import type { AgentConfig, AgentRunResult } from '../src/types.js'
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Analysis topic
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
const TOPIC = `Should a solo developer build a SaaS product that uses AI agents
|
|
26
|
-
for automated customer support? Consider the current state of AI technology,
|
|
27
|
-
market demand, competition, costs, and the unique constraints of being a solo
|
|
28
|
-
founder with limited time (~6 hours/day of productive work).`
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Analyst agent configs — three perspectives on the same question
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
const optimistConfig: AgentConfig = {
|
|
35
|
-
name: 'optimist',
|
|
36
|
-
model: 'claude-sonnet-4-6',
|
|
37
|
-
systemPrompt: `You are an optimistic technology analyst who focuses on
|
|
38
|
-
opportunities, upside potential, and emerging trends. You see possibilities
|
|
39
|
-
where others see obstacles. Back your optimism with concrete reasoning —
|
|
40
|
-
cite market trends, cost curves, and real capabilities. Keep your analysis
|
|
41
|
-
to 200-300 words.`,
|
|
42
|
-
maxTurns: 1,
|
|
43
|
-
temperature: 0.4,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const skepticConfig: AgentConfig = {
|
|
47
|
-
name: 'skeptic',
|
|
48
|
-
model: 'claude-sonnet-4-6',
|
|
49
|
-
systemPrompt: `You are a skeptical technology analyst who focuses on risks,
|
|
50
|
-
challenges, failure modes, and hidden costs. You stress-test assumptions and
|
|
51
|
-
ask "what could go wrong?" Back your skepticism with concrete reasoning —
|
|
52
|
-
cite failure rates, technical limitations, and market realities. Keep your
|
|
53
|
-
analysis to 200-300 words.`,
|
|
54
|
-
maxTurns: 1,
|
|
55
|
-
temperature: 0.4,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const pragmatistConfig: AgentConfig = {
|
|
59
|
-
name: 'pragmatist',
|
|
60
|
-
model: 'claude-sonnet-4-6',
|
|
61
|
-
systemPrompt: `You are a pragmatic technology analyst who focuses on practical
|
|
62
|
-
feasibility, execution complexity, and resource requirements. You care about
|
|
63
|
-
what works today, not what might work someday. You think in terms of MVPs,
|
|
64
|
-
timelines, and concrete tradeoffs. Keep your analysis to 200-300 words.`,
|
|
65
|
-
maxTurns: 1,
|
|
66
|
-
temperature: 0.4,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const synthesizerConfig: AgentConfig = {
|
|
70
|
-
name: 'synthesizer',
|
|
71
|
-
model: 'claude-sonnet-4-6',
|
|
72
|
-
systemPrompt: `You are a senior strategy advisor who synthesizes multiple
|
|
73
|
-
perspectives into a balanced, actionable recommendation. You do not simply
|
|
74
|
-
summarise — you weigh the arguments, identify where they agree and disagree,
|
|
75
|
-
and produce a clear verdict with next steps. Structure your output as:
|
|
76
|
-
|
|
77
|
-
1. Key agreements across perspectives
|
|
78
|
-
2. Key disagreements and how you weigh them
|
|
79
|
-
3. Verdict (go / no-go / conditional go)
|
|
80
|
-
4. Recommended next steps (3-5 bullet points)
|
|
81
|
-
|
|
82
|
-
Keep the final report to 300-400 words.`,
|
|
83
|
-
maxTurns: 1,
|
|
84
|
-
temperature: 0.3,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Build agents — no tools needed for pure reasoning
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
function buildAgent(config: AgentConfig): Agent {
|
|
92
|
-
const registry = new ToolRegistry()
|
|
93
|
-
registerBuiltInTools(registry) // not needed here, but safe if tools are added later
|
|
94
|
-
const executor = new ToolExecutor(registry)
|
|
95
|
-
return new Agent(config, registry, executor)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const optimist = buildAgent(optimistConfig)
|
|
99
|
-
const skeptic = buildAgent(skepticConfig)
|
|
100
|
-
const pragmatist = buildAgent(pragmatistConfig)
|
|
101
|
-
const synthesizer = buildAgent(synthesizerConfig)
|
|
102
|
-
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
// Set up the pool
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
const pool = new AgentPool(3) // 3 analysts can run simultaneously
|
|
108
|
-
pool.add(optimist)
|
|
109
|
-
pool.add(skeptic)
|
|
110
|
-
pool.add(pragmatist)
|
|
111
|
-
pool.add(synthesizer)
|
|
112
|
-
|
|
113
|
-
console.log('Fan-Out / Aggregate (MapReduce) Pattern')
|
|
114
|
-
console.log('='.repeat(60))
|
|
115
|
-
console.log(`\nTopic: ${TOPIC.replace(/\n/g, ' ').trim()}\n`)
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Step 1: Fan-out — run all 3 analysts in parallel
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
console.log('[Step 1] Fan-out: 3 analysts running in parallel...\n')
|
|
122
|
-
|
|
123
|
-
const analystResults: Map<string, AgentRunResult> = await pool.runParallel([
|
|
124
|
-
{ agent: 'optimist', prompt: TOPIC },
|
|
125
|
-
{ agent: 'skeptic', prompt: TOPIC },
|
|
126
|
-
{ agent: 'pragmatist', prompt: TOPIC },
|
|
127
|
-
])
|
|
128
|
-
|
|
129
|
-
// Print each analyst's output (truncated)
|
|
130
|
-
const analysts = ['optimist', 'skeptic', 'pragmatist'] as const
|
|
131
|
-
for (const name of analysts) {
|
|
132
|
-
const result = analystResults.get(name)!
|
|
133
|
-
const status = result.success ? 'OK' : 'FAILED'
|
|
134
|
-
console.log(` ${name} [${status}] — ${result.tokenUsage.output_tokens} output tokens`)
|
|
135
|
-
console.log(` ${result.output.slice(0, 150).replace(/\n/g, ' ')}...`)
|
|
136
|
-
console.log()
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Check all analysts succeeded
|
|
140
|
-
for (const name of analysts) {
|
|
141
|
-
if (!analystResults.get(name)!.success) {
|
|
142
|
-
console.error(`Analyst '${name}' failed: ${analystResults.get(name)!.output}`)
|
|
143
|
-
process.exit(1)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// Step 2: Aggregate — synthesizer reads all 3 analyses
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
console.log('[Step 2] Aggregate: synthesizer producing final report...\n')
|
|
152
|
-
|
|
153
|
-
const synthesizerPrompt = `Three analysts have independently evaluated the same question.
|
|
154
|
-
Read their analyses below and produce your synthesis report.
|
|
155
|
-
|
|
156
|
-
--- OPTIMIST ---
|
|
157
|
-
${analystResults.get('optimist')!.output}
|
|
158
|
-
|
|
159
|
-
--- SKEPTIC ---
|
|
160
|
-
${analystResults.get('skeptic')!.output}
|
|
161
|
-
|
|
162
|
-
--- PRAGMATIST ---
|
|
163
|
-
${analystResults.get('pragmatist')!.output}
|
|
164
|
-
|
|
165
|
-
Now synthesize these three perspectives into a balanced recommendation.`
|
|
166
|
-
|
|
167
|
-
const synthResult = await pool.run('synthesizer', synthesizerPrompt)
|
|
168
|
-
|
|
169
|
-
if (!synthResult.success) {
|
|
170
|
-
console.error('Synthesizer failed:', synthResult.output)
|
|
171
|
-
process.exit(1)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// Final output
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
console.log('='.repeat(60))
|
|
179
|
-
console.log('SYNTHESIZED REPORT')
|
|
180
|
-
console.log('='.repeat(60))
|
|
181
|
-
console.log()
|
|
182
|
-
console.log(synthResult.output)
|
|
183
|
-
console.log()
|
|
184
|
-
console.log('-'.repeat(60))
|
|
185
|
-
|
|
186
|
-
// ---------------------------------------------------------------------------
|
|
187
|
-
// Token usage comparison
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
|
|
190
|
-
console.log('\nToken Usage Summary:')
|
|
191
|
-
console.log('-'.repeat(60))
|
|
192
|
-
|
|
193
|
-
let totalInput = 0
|
|
194
|
-
let totalOutput = 0
|
|
195
|
-
|
|
196
|
-
for (const name of analysts) {
|
|
197
|
-
const r = analystResults.get(name)!
|
|
198
|
-
totalInput += r.tokenUsage.input_tokens
|
|
199
|
-
totalOutput += r.tokenUsage.output_tokens
|
|
200
|
-
console.log(` ${name.padEnd(12)} — input: ${r.tokenUsage.input_tokens}, output: ${r.tokenUsage.output_tokens}`)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
totalInput += synthResult.tokenUsage.input_tokens
|
|
204
|
-
totalOutput += synthResult.tokenUsage.output_tokens
|
|
205
|
-
console.log(` ${'synthesizer'.padEnd(12)} — input: ${synthResult.tokenUsage.input_tokens}, output: ${synthResult.tokenUsage.output_tokens}`)
|
|
206
|
-
console.log('-'.repeat(60))
|
|
207
|
-
console.log(` ${'TOTAL'.padEnd(12)} — input: ${totalInput}, output: ${totalOutput}`)
|
|
208
|
-
|
|
209
|
-
console.log('\nDone.')
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example 08 — Gemma 4 Local Agent Team (100% Local, Zero API Cost)
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates a fully local multi-agent team using Google's Gemma 4 via
|
|
5
|
-
* Ollama. No cloud API keys needed — everything runs on your machine.
|
|
6
|
-
*
|
|
7
|
-
* Two agents collaborate through a task pipeline:
|
|
8
|
-
* - researcher: uses bash + file_write to gather system info and write a report
|
|
9
|
-
* - summarizer: uses file_read to read the report and produce a concise summary
|
|
10
|
-
*
|
|
11
|
-
* This pattern works with any Ollama model that supports tool-calling.
|
|
12
|
-
* Gemma 4 (released 2026-04-02) has native tool-calling support.
|
|
13
|
-
*
|
|
14
|
-
* Run:
|
|
15
|
-
* no_proxy=localhost npx tsx examples/08-gemma4-local.ts
|
|
16
|
-
*
|
|
17
|
-
* Prerequisites:
|
|
18
|
-
* 1. Ollama >= 0.20.0 installed and running: https://ollama.com
|
|
19
|
-
* 2. Pull the model: ollama pull gemma4:e2b
|
|
20
|
-
* (or gemma4:e4b for better quality on machines with more RAM)
|
|
21
|
-
* 3. No API keys needed!
|
|
22
|
-
*
|
|
23
|
-
* Note: The no_proxy=localhost prefix is needed if you have an HTTP proxy
|
|
24
|
-
* configured, since the OpenAI SDK would otherwise route Ollama requests
|
|
25
|
-
* through the proxy.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { OpenMultiAgent } from '../src/index.js'
|
|
29
|
-
import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Configuration — change this to match your Ollama setup
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
// See available tags at https://ollama.com/library/gemma4
|
|
36
|
-
const OLLAMA_MODEL = 'gemma4:e2b' // or 'gemma4:e4b', 'gemma4:26b'
|
|
37
|
-
const OLLAMA_BASE_URL = 'http://localhost:11434/v1'
|
|
38
|
-
const OUTPUT_DIR = '/tmp/gemma4-demo'
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Agents — both use Gemma 4 locally
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Researcher — gathers system information using shell commands.
|
|
46
|
-
*/
|
|
47
|
-
const researcher: AgentConfig = {
|
|
48
|
-
name: 'researcher',
|
|
49
|
-
model: OLLAMA_MODEL,
|
|
50
|
-
provider: 'openai',
|
|
51
|
-
baseURL: OLLAMA_BASE_URL,
|
|
52
|
-
apiKey: 'ollama', // placeholder — Ollama ignores this, but the OpenAI SDK requires a non-empty value
|
|
53
|
-
systemPrompt: `You are a system researcher. Your job is to gather information
|
|
54
|
-
about the current machine using shell commands and write a structured report.
|
|
55
|
-
|
|
56
|
-
Use the bash tool to run commands like: uname -a, df -h, uptime, and similar
|
|
57
|
-
non-destructive read-only commands.
|
|
58
|
-
On macOS you can also use: sw_vers, sysctl -n hw.memsize.
|
|
59
|
-
On Linux you can also use: cat /etc/os-release, free -h.
|
|
60
|
-
|
|
61
|
-
Then use file_write to save a Markdown report to ${OUTPUT_DIR}/system-report.md.
|
|
62
|
-
The report should have sections: OS, Hardware, Disk, and Uptime.
|
|
63
|
-
Be concise — one or two lines per section is enough.`,
|
|
64
|
-
tools: ['bash', 'file_write'],
|
|
65
|
-
maxTurns: 8,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Summarizer — reads the report and writes a one-paragraph executive summary.
|
|
70
|
-
*/
|
|
71
|
-
const summarizer: AgentConfig = {
|
|
72
|
-
name: 'summarizer',
|
|
73
|
-
model: OLLAMA_MODEL,
|
|
74
|
-
provider: 'openai',
|
|
75
|
-
baseURL: OLLAMA_BASE_URL,
|
|
76
|
-
apiKey: 'ollama',
|
|
77
|
-
systemPrompt: `You are a technical writer. Read the system report file provided,
|
|
78
|
-
then produce a concise one-paragraph executive summary (3-5 sentences).
|
|
79
|
-
Focus on the key highlights: what OS, how much RAM, disk status, and uptime.`,
|
|
80
|
-
tools: ['file_read'],
|
|
81
|
-
maxTurns: 4,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Progress handler
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
const taskTimes = new Map<string, number>()
|
|
89
|
-
|
|
90
|
-
function handleProgress(event: OrchestratorEvent): void {
|
|
91
|
-
const ts = new Date().toISOString().slice(11, 23)
|
|
92
|
-
|
|
93
|
-
switch (event.type) {
|
|
94
|
-
case 'task_start': {
|
|
95
|
-
taskTimes.set(event.task ?? '', Date.now())
|
|
96
|
-
const task = event.data as Task | undefined
|
|
97
|
-
console.log(`[${ts}] TASK START "${task?.title ?? event.task}" → ${task?.assignee ?? '?'}`)
|
|
98
|
-
break
|
|
99
|
-
}
|
|
100
|
-
case 'task_complete': {
|
|
101
|
-
const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
|
|
102
|
-
console.log(`[${ts}] TASK DONE "${event.task}" in ${(elapsed / 1000).toFixed(1)}s`)
|
|
103
|
-
break
|
|
104
|
-
}
|
|
105
|
-
case 'agent_start':
|
|
106
|
-
console.log(`[${ts}] AGENT START ${event.agent}`)
|
|
107
|
-
break
|
|
108
|
-
case 'agent_complete':
|
|
109
|
-
console.log(`[${ts}] AGENT DONE ${event.agent}`)
|
|
110
|
-
break
|
|
111
|
-
case 'error':
|
|
112
|
-
console.error(`[${ts}] ERROR ${event.agent ?? ''} task=${event.task ?? '?'}`)
|
|
113
|
-
break
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Orchestrator + Team
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
const orchestrator = new OpenMultiAgent({
|
|
122
|
-
defaultModel: OLLAMA_MODEL,
|
|
123
|
-
maxConcurrency: 1, // run agents sequentially — local model can only serve one at a time
|
|
124
|
-
onProgress: handleProgress,
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const team = orchestrator.createTeam('gemma4-team', {
|
|
128
|
-
name: 'gemma4-team',
|
|
129
|
-
agents: [researcher, summarizer],
|
|
130
|
-
sharedMemory: true,
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// Task pipeline: research → summarize
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
|
|
137
|
-
const tasks: Array<{
|
|
138
|
-
title: string
|
|
139
|
-
description: string
|
|
140
|
-
assignee?: string
|
|
141
|
-
dependsOn?: string[]
|
|
142
|
-
}> = [
|
|
143
|
-
{
|
|
144
|
-
title: 'Gather system information',
|
|
145
|
-
description: `Use bash to run system info commands (uname -a, sw_vers, sysctl, df -h, uptime).
|
|
146
|
-
Then write a structured Markdown report to ${OUTPUT_DIR}/system-report.md with sections:
|
|
147
|
-
OS, Hardware, Disk, and Uptime.`,
|
|
148
|
-
assignee: 'researcher',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
title: 'Summarize the report',
|
|
152
|
-
description: `Read the file at ${OUTPUT_DIR}/system-report.md.
|
|
153
|
-
Produce a concise one-paragraph executive summary of the system information.`,
|
|
154
|
-
assignee: 'summarizer',
|
|
155
|
-
dependsOn: ['Gather system information'],
|
|
156
|
-
},
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// Run
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
|
|
163
|
-
console.log('Gemma 4 Local Agent Team — Zero API Cost')
|
|
164
|
-
console.log('='.repeat(60))
|
|
165
|
-
console.log(` model → ${OLLAMA_MODEL} via Ollama`)
|
|
166
|
-
console.log(` researcher → bash + file_write`)
|
|
167
|
-
console.log(` summarizer → file_read`)
|
|
168
|
-
console.log(` output dir → ${OUTPUT_DIR}`)
|
|
169
|
-
console.log()
|
|
170
|
-
console.log('Pipeline: researcher gathers info → summarizer writes summary')
|
|
171
|
-
console.log('='.repeat(60))
|
|
172
|
-
|
|
173
|
-
const start = Date.now()
|
|
174
|
-
const result = await orchestrator.runTasks(team, tasks)
|
|
175
|
-
const totalTime = Date.now() - start
|
|
176
|
-
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
// Summary
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
console.log('\n' + '='.repeat(60))
|
|
182
|
-
console.log('Pipeline complete.\n')
|
|
183
|
-
console.log(`Overall success: ${result.success}`)
|
|
184
|
-
console.log(`Total time: ${(totalTime / 1000).toFixed(1)}s`)
|
|
185
|
-
console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
|
|
186
|
-
|
|
187
|
-
console.log('\nPer-agent results:')
|
|
188
|
-
for (const [name, r] of result.agentResults) {
|
|
189
|
-
const icon = r.success ? 'OK ' : 'FAIL'
|
|
190
|
-
const tools = r.toolCalls.map(c => c.toolName).join(', ')
|
|
191
|
-
console.log(` [${icon}] ${name.padEnd(12)} tools: ${tools || '(none)'}`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Print the summarizer's output
|
|
195
|
-
const summary = result.agentResults.get('summarizer')
|
|
196
|
-
if (summary?.success) {
|
|
197
|
-
console.log('\nExecutive Summary (from local Gemma 4):')
|
|
198
|
-
console.log('-'.repeat(60))
|
|
199
|
-
console.log(summary.output)
|
|
200
|
-
console.log('-'.repeat(60))
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log('\nAll processing done locally. $0 API cost.')
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example 09 — Gemma 4 Auto-Orchestration (runTeam, 100% Local)
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates the framework's key feature — automatic task decomposition —
|
|
5
|
-
* powered entirely by a local Gemma 4 model. No cloud API needed.
|
|
6
|
-
*
|
|
7
|
-
* What happens:
|
|
8
|
-
* 1. A Gemma 4 "coordinator" receives the goal + agent roster
|
|
9
|
-
* 2. It outputs a structured JSON task array (title, description, assignee, dependsOn)
|
|
10
|
-
* 3. The framework resolves dependencies, schedules tasks, and runs agents
|
|
11
|
-
* 4. The coordinator synthesises all task results into a final answer
|
|
12
|
-
*
|
|
13
|
-
* This is the hardest test for a local model — it must produce valid JSON
|
|
14
|
-
* for task decomposition AND do tool-calling for actual task execution.
|
|
15
|
-
* Gemma 4 e2b (5.1B params) handles both reliably.
|
|
16
|
-
*
|
|
17
|
-
* Run:
|
|
18
|
-
* no_proxy=localhost npx tsx examples/09-gemma4-auto-orchestration.ts
|
|
19
|
-
*
|
|
20
|
-
* Prerequisites:
|
|
21
|
-
* 1. Ollama >= 0.20.0 installed and running: https://ollama.com
|
|
22
|
-
* 2. Pull the model: ollama pull gemma4:e2b
|
|
23
|
-
* 3. No API keys needed!
|
|
24
|
-
*
|
|
25
|
-
* Note: The no_proxy=localhost prefix is needed if you have an HTTP proxy
|
|
26
|
-
* configured, since the OpenAI SDK would otherwise route Ollama requests
|
|
27
|
-
* through the proxy.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { OpenMultiAgent } from '../src/index.js'
|
|
31
|
-
import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Configuration
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
// See available tags at https://ollama.com/library/gemma4
|
|
38
|
-
const OLLAMA_MODEL = 'gemma4:e2b' // or 'gemma4:e4b', 'gemma4:26b'
|
|
39
|
-
const OLLAMA_BASE_URL = 'http://localhost:11434/v1'
|
|
40
|
-
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
// Agents — the coordinator is created automatically by runTeam()
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
const researcher: AgentConfig = {
|
|
46
|
-
name: 'researcher',
|
|
47
|
-
model: OLLAMA_MODEL,
|
|
48
|
-
provider: 'openai',
|
|
49
|
-
baseURL: OLLAMA_BASE_URL,
|
|
50
|
-
apiKey: 'ollama',
|
|
51
|
-
systemPrompt: `You are a system researcher. Use bash to run non-destructive,
|
|
52
|
-
read-only commands and report the results concisely.`,
|
|
53
|
-
tools: ['bash'],
|
|
54
|
-
maxTurns: 4,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const writer: AgentConfig = {
|
|
58
|
-
name: 'writer',
|
|
59
|
-
model: OLLAMA_MODEL,
|
|
60
|
-
provider: 'openai',
|
|
61
|
-
baseURL: OLLAMA_BASE_URL,
|
|
62
|
-
apiKey: 'ollama',
|
|
63
|
-
systemPrompt: `You are a technical writer. Use file_write to create clear,
|
|
64
|
-
structured Markdown reports based on the information provided.`,
|
|
65
|
-
tools: ['file_write'],
|
|
66
|
-
maxTurns: 4,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Progress handler
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
function handleProgress(event: OrchestratorEvent): void {
|
|
74
|
-
const ts = new Date().toISOString().slice(11, 23)
|
|
75
|
-
switch (event.type) {
|
|
76
|
-
case 'task_start': {
|
|
77
|
-
const task = event.data as Task | undefined
|
|
78
|
-
console.log(`[${ts}] TASK START "${task?.title ?? event.task}" → ${task?.assignee ?? '?'}`)
|
|
79
|
-
break
|
|
80
|
-
}
|
|
81
|
-
case 'task_complete':
|
|
82
|
-
console.log(`[${ts}] TASK DONE "${event.task}"`)
|
|
83
|
-
break
|
|
84
|
-
case 'agent_start':
|
|
85
|
-
console.log(`[${ts}] AGENT START ${event.agent}`)
|
|
86
|
-
break
|
|
87
|
-
case 'agent_complete':
|
|
88
|
-
console.log(`[${ts}] AGENT DONE ${event.agent}`)
|
|
89
|
-
break
|
|
90
|
-
case 'error':
|
|
91
|
-
console.error(`[${ts}] ERROR ${event.agent ?? ''} task=${event.task ?? '?'}`)
|
|
92
|
-
break
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Orchestrator — defaultModel is used for the coordinator agent
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
const orchestrator = new OpenMultiAgent({
|
|
101
|
-
defaultModel: OLLAMA_MODEL,
|
|
102
|
-
defaultProvider: 'openai',
|
|
103
|
-
defaultBaseURL: OLLAMA_BASE_URL,
|
|
104
|
-
defaultApiKey: 'ollama',
|
|
105
|
-
maxConcurrency: 1, // local model serves one request at a time
|
|
106
|
-
onProgress: handleProgress,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
const team = orchestrator.createTeam('gemma4-auto', {
|
|
110
|
-
name: 'gemma4-auto',
|
|
111
|
-
agents: [researcher, writer],
|
|
112
|
-
sharedMemory: true,
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// Give a goal — the framework handles the rest
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
|
|
119
|
-
const goal = `Check this machine's Node.js version, npm version, and OS info,
|
|
120
|
-
then write a short Markdown summary report to /tmp/gemma4-auto/report.md`
|
|
121
|
-
|
|
122
|
-
console.log('Gemma 4 Auto-Orchestration — Zero API Cost')
|
|
123
|
-
console.log('='.repeat(60))
|
|
124
|
-
console.log(` model → ${OLLAMA_MODEL} via Ollama (all agents + coordinator)`)
|
|
125
|
-
console.log(` researcher → bash`)
|
|
126
|
-
console.log(` writer → file_write`)
|
|
127
|
-
console.log(` coordinator → auto-created by runTeam()`)
|
|
128
|
-
console.log()
|
|
129
|
-
console.log(`Goal: ${goal.replace(/\n/g, ' ').trim()}`)
|
|
130
|
-
console.log('='.repeat(60))
|
|
131
|
-
|
|
132
|
-
const start = Date.now()
|
|
133
|
-
const result = await orchestrator.runTeam(team, goal)
|
|
134
|
-
const totalTime = Date.now() - start
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// Results
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
console.log('\n' + '='.repeat(60))
|
|
141
|
-
console.log('Pipeline complete.\n')
|
|
142
|
-
console.log(`Overall success: ${result.success}`)
|
|
143
|
-
console.log(`Total time: ${(totalTime / 1000).toFixed(1)}s`)
|
|
144
|
-
console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
|
|
145
|
-
|
|
146
|
-
console.log('\nPer-agent results:')
|
|
147
|
-
for (const [name, r] of result.agentResults) {
|
|
148
|
-
const icon = r.success ? 'OK ' : 'FAIL'
|
|
149
|
-
const tools = r.toolCalls.length > 0 ? r.toolCalls.map(c => c.toolName).join(', ') : '(none)'
|
|
150
|
-
console.log(` [${icon}] ${name.padEnd(24)} tools: ${tools}`)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Print the coordinator's final synthesis
|
|
154
|
-
const coordResult = result.agentResults.get('coordinator')
|
|
155
|
-
if (coordResult?.success) {
|
|
156
|
-
console.log('\nFinal synthesis (from local Gemma 4 coordinator):')
|
|
157
|
-
console.log('-'.repeat(60))
|
|
158
|
-
console.log(coordResult.output)
|
|
159
|
-
console.log('-'.repeat(60))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
console.log('\nAll processing done locally. $0 API cost.')
|