From 34ca8602d0f848e96fc2b2b460c138850076afea Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:52:23 -0700 Subject: [PATCH 1/2] feat: add multi-source research aggregation example Demonstrates parallel analyst execution with dependency-based synthesis using runTasks(), sharedMemory, and dependsOn: 1. Three analyst agents research the same topic in parallel (technical, market, community perspectives) 2. Synthesizer waits for all analysts via dependsOn, reads shared memory, cross-references findings, and produces a unified report Fixes #76 --- examples/13-research-aggregation.ts | 167 ++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 examples/13-research-aggregation.ts diff --git a/examples/13-research-aggregation.ts b/examples/13-research-aggregation.ts new file mode 100644 index 0000000..a2113c2 --- /dev/null +++ b/examples/13-research-aggregation.ts @@ -0,0 +1,167 @@ +/** + * Example 13 — Multi-Source Research Aggregation + * + * Demonstrates: + * - Parallel execution: three analyst agents research the same topic independently + * - Dependency chain: synthesizer waits for all analysts to finish + * - Shared memory: analysts write findings, synthesizer reads and cross-references + * + * Flow: + * [technical-analyst, market-analyst, community-analyst] (parallel) → synthesizer + * + * Run: + * npx tsx examples/13-research-aggregation.ts + * + * Prerequisites: + * ANTHROPIC_API_KEY env var must be set. + */ + +import { OpenMultiAgent } from '../src/index.js' +import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js' + +// --------------------------------------------------------------------------- +// Topic +// --------------------------------------------------------------------------- + +const TOPIC = 'WebAssembly adoption in 2026' + +// --------------------------------------------------------------------------- +// Agents — three analysts + one synthesizer +// --------------------------------------------------------------------------- + +const technicalAnalyst: AgentConfig = { + name: 'technical-analyst', + model: 'claude-sonnet-4-6', + systemPrompt: `You are a technical analyst. Given a topic, research its technical +capabilities, limitations, performance characteristics, and architectural patterns. +Write your findings as structured markdown. Store your analysis in shared memory +under the key "technical_findings". Keep it to 200-300 words.`, + maxTurns: 2, +} + +const marketAnalyst: AgentConfig = { + name: 'market-analyst', + model: 'claude-sonnet-4-6', + systemPrompt: `You are a market analyst. Given a topic, research industry adoption +rates, key companies using the technology, market size estimates, and competitive +landscape. Write your findings as structured markdown. Store your analysis in +shared memory under the key "market_findings". Keep it to 200-300 words.`, + maxTurns: 2, +} + +const communityAnalyst: AgentConfig = { + name: 'community-analyst', + model: 'claude-sonnet-4-6', + systemPrompt: `You are a developer community analyst. Given a topic, research +developer sentiment, ecosystem maturity, learning resources, community size, +and conference/meetup activity. Write your findings as structured markdown. +Store your analysis in shared memory under the key "community_findings". +Keep it to 200-300 words.`, + maxTurns: 2, +} + +const synthesizer: AgentConfig = { + name: 'synthesizer', + model: 'claude-sonnet-4-6', + systemPrompt: `You are a research director who synthesizes multiple analyst reports +into a single cohesive document. Read all findings from shared memory, then: + +1. Cross-reference claims across reports — flag agreements and contradictions +2. Identify the 3 most important insights +3. Produce a structured report with: Executive Summary, Key Findings, + Areas of Agreement, Open Questions, and Recommendation + +Keep the final report to 300-400 words.`, + maxTurns: 2, +} + +// --------------------------------------------------------------------------- +// Orchestrator + team +// --------------------------------------------------------------------------- + +function handleProgress(event: OrchestratorEvent): void { + if (event.type === 'task:start') { + console.log(` [START] ${event.taskTitle} → ${event.agentName}`) + } + if (event.type === 'task:complete') { + console.log(` [DONE] ${event.taskTitle} (${event.success ? 'OK' : 'FAIL'})`) + } +} + +const orchestrator = new OpenMultiAgent({ + defaultModel: 'claude-sonnet-4-6', + onProgress: handleProgress, +}) + +const team = orchestrator.createTeam('research-team', { + name: 'research-team', + agents: [technicalAnalyst, marketAnalyst, communityAnalyst, synthesizer], + sharedMemory: true, +}) + +// --------------------------------------------------------------------------- +// Tasks — three analysts run in parallel, synthesizer depends on all three +// --------------------------------------------------------------------------- + +const tasks: Task[] = [ + { + title: 'Technical analysis', + description: `Research the technical aspects of ${TOPIC}. Focus on capabilities, limitations, performance, and architecture. Store findings in shared memory as "technical_findings".`, + assignee: 'technical-analyst', + }, + { + title: 'Market analysis', + description: `Research the market landscape for ${TOPIC}. Focus on adoption rates, key players, market size, and competition. Store findings in shared memory as "market_findings".`, + assignee: 'market-analyst', + }, + { + title: 'Community analysis', + description: `Research the developer community around ${TOPIC}. Focus on sentiment, ecosystem maturity, learning resources, and community activity. Store findings in shared memory as "community_findings".`, + assignee: 'community-analyst', + }, + { + title: 'Synthesize report', + description: `Read all analyst findings from shared memory (technical_findings, market_findings, community_findings). Cross-reference claims, identify key insights, flag contradictions, and produce a unified research report.`, + assignee: 'synthesizer', + dependsOn: ['Technical analysis', 'Market analysis', 'Community analysis'], + }, +] + +// --------------------------------------------------------------------------- +// Run +// --------------------------------------------------------------------------- + +console.log('Multi-Source Research Aggregation') +console.log('='.repeat(60)) +console.log(`Topic: ${TOPIC}`) +console.log('Pipeline: 3 analysts (parallel) → synthesizer') +console.log('='.repeat(60)) +console.log() + +const result = await orchestrator.runTasks(team, tasks) + +// --------------------------------------------------------------------------- +// Output +// --------------------------------------------------------------------------- + +console.log('\n' + '='.repeat(60)) +console.log(`Overall success: ${result.success}`) +console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`) +console.log() + +for (const [name, r] of result.agentResults) { + const icon = r.success ? 'OK ' : 'FAIL' + const tokens = `in:${r.tokenUsage.input_tokens} out:${r.tokenUsage.output_tokens}` + console.log(` [${icon}] ${name.padEnd(20)} ${tokens}`) +} + +const synthResult = result.agentResults.get('synthesizer') +if (synthResult?.success) { + console.log('\n' + '='.repeat(60)) + console.log('SYNTHESIZED REPORT') + console.log('='.repeat(60)) + console.log() + console.log(synthResult.output) +} + +console.log('\nDone.') From 54bfe2ed2dfca3e30d44a2b7515d9bad55d4a80a Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:22:31 -0700 Subject: [PATCH 2/2] fix: address review feedback on research aggregation example - Fix handleProgress: use underscore event types (task_start/task_complete) and correct property names (event.task/event.agent) per OrchestratorEvent - Remove Task[] annotation (Task requires id, status, createdAt, updatedAt) - Rename 13-research-aggregation.ts to 14-research-aggregation.ts - Remove shared memory key references from prompts (framework handles this) - Add header note differentiating from example 07 (runTasks vs AgentPool) --- ...regation.ts => 14-research-aggregation.ts} | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) rename examples/{13-research-aggregation.ts => 14-research-aggregation.ts} (76%) diff --git a/examples/13-research-aggregation.ts b/examples/14-research-aggregation.ts similarity index 76% rename from examples/13-research-aggregation.ts rename to examples/14-research-aggregation.ts index a2113c2..1e07f39 100644 --- a/examples/13-research-aggregation.ts +++ b/examples/14-research-aggregation.ts @@ -1,23 +1,27 @@ /** - * Example 13 — Multi-Source Research Aggregation + * Example 14 — Multi-Source Research Aggregation * - * Demonstrates: + * Demonstrates runTasks() with explicit dependency chains: * - Parallel execution: three analyst agents research the same topic independently - * - Dependency chain: synthesizer waits for all analysts to finish - * - Shared memory: analysts write findings, synthesizer reads and cross-references + * - Dependency chain via dependsOn: synthesizer waits for all analysts to finish + * - Automatic shared memory: agent output flows to downstream agents via the framework + * + * Compare with example 07 (fan-out-aggregate) which uses AgentPool.runParallel() + * for the same 3-analysts + synthesizer pattern. This example shows the runTasks() + * API with explicit dependsOn declarations instead. * * Flow: * [technical-analyst, market-analyst, community-analyst] (parallel) → synthesizer * * Run: - * npx tsx examples/13-research-aggregation.ts + * npx tsx examples/14-research-aggregation.ts * * Prerequisites: * ANTHROPIC_API_KEY env var must be set. */ import { OpenMultiAgent } from '../src/index.js' -import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js' +import type { AgentConfig, OrchestratorEvent } from '../src/types.js' // --------------------------------------------------------------------------- // Topic @@ -34,8 +38,7 @@ const technicalAnalyst: AgentConfig = { model: 'claude-sonnet-4-6', systemPrompt: `You are a technical analyst. Given a topic, research its technical capabilities, limitations, performance characteristics, and architectural patterns. -Write your findings as structured markdown. Store your analysis in shared memory -under the key "technical_findings". Keep it to 200-300 words.`, +Write your findings as structured markdown. Keep it to 200-300 words.`, maxTurns: 2, } @@ -44,8 +47,7 @@ const marketAnalyst: AgentConfig = { model: 'claude-sonnet-4-6', systemPrompt: `You are a market analyst. Given a topic, research industry adoption rates, key companies using the technology, market size estimates, and competitive -landscape. Write your findings as structured markdown. Store your analysis in -shared memory under the key "market_findings". Keep it to 200-300 words.`, +landscape. Write your findings as structured markdown. Keep it to 200-300 words.`, maxTurns: 2, } @@ -55,7 +57,6 @@ const communityAnalyst: AgentConfig = { systemPrompt: `You are a developer community analyst. Given a topic, research developer sentiment, ecosystem maturity, learning resources, community size, and conference/meetup activity. Write your findings as structured markdown. -Store your analysis in shared memory under the key "community_findings". Keep it to 200-300 words.`, maxTurns: 2, } @@ -64,9 +65,10 @@ const synthesizer: AgentConfig = { name: 'synthesizer', model: 'claude-sonnet-4-6', systemPrompt: `You are a research director who synthesizes multiple analyst reports -into a single cohesive document. Read all findings from shared memory, then: +into a single cohesive document. You will receive all prior analyst outputs +automatically. Then: -1. Cross-reference claims across reports — flag agreements and contradictions +1. Cross-reference claims across reports - flag agreements and contradictions 2. Identify the 3 most important insights 3. Produce a structured report with: Executive Summary, Key Findings, Areas of Agreement, Open Questions, and Recommendation @@ -80,11 +82,11 @@ Keep the final report to 300-400 words.`, // --------------------------------------------------------------------------- function handleProgress(event: OrchestratorEvent): void { - if (event.type === 'task:start') { - console.log(` [START] ${event.taskTitle} → ${event.agentName}`) + if (event.type === 'task_start') { + console.log(` [START] ${event.task ?? ''} → ${event.agent ?? ''}`) } - if (event.type === 'task:complete') { - console.log(` [DONE] ${event.taskTitle} (${event.success ? 'OK' : 'FAIL'})`) + if (event.type === 'task_complete') { + console.log(` [DONE] ${event.task ?? ''}`) } } @@ -103,25 +105,25 @@ const team = orchestrator.createTeam('research-team', { // Tasks — three analysts run in parallel, synthesizer depends on all three // --------------------------------------------------------------------------- -const tasks: Task[] = [ +const tasks = [ { title: 'Technical analysis', - description: `Research the technical aspects of ${TOPIC}. Focus on capabilities, limitations, performance, and architecture. Store findings in shared memory as "technical_findings".`, + description: `Research the technical aspects of ${TOPIC}. Focus on capabilities, limitations, performance, and architecture.`, assignee: 'technical-analyst', }, { title: 'Market analysis', - description: `Research the market landscape for ${TOPIC}. Focus on adoption rates, key players, market size, and competition. Store findings in shared memory as "market_findings".`, + description: `Research the market landscape for ${TOPIC}. Focus on adoption rates, key players, market size, and competition.`, assignee: 'market-analyst', }, { title: 'Community analysis', - description: `Research the developer community around ${TOPIC}. Focus on sentiment, ecosystem maturity, learning resources, and community activity. Store findings in shared memory as "community_findings".`, + description: `Research the developer community around ${TOPIC}. Focus on sentiment, ecosystem maturity, learning resources, and community activity.`, assignee: 'community-analyst', }, { title: 'Synthesize report', - description: `Read all analyst findings from shared memory (technical_findings, market_findings, community_findings). Cross-reference claims, identify key insights, flag contradictions, and produce a unified research report.`, + description: `Cross-reference all analyst findings, identify key insights, flag contradictions, and produce a unified research report.`, assignee: 'synthesizer', dependsOn: ['Technical analysis', 'Market analysis', 'Community analysis'], },