← All examples
// Use-case Recipe

Meeting Summarizer (Parallel Post-Processing)

Transform one transcript into a summary, structured action items, sentiment, and a single Markdown report.

Useful forTeams turning meeting transcripts into useful follow-up

Enterprise support
01 The scenario

A useful meeting record requires several kinds of reading. Asking one pass to summarize, extract commitments, and judge tone can blur those objectives and slow the result.

One meeting transcript

The same transcript is shared with three specialists, each with a different responsibility.

02 How it works

A team, with a clear handoff.

Each specialist sees the evidence it needs. Independent work runs together; dependent work waits for the right inputs.

  1. Read for three different purposes

    Each specialist works on the same transcript at the same time.

    runs in parallel
    Summary specialist

    Captures the main discussion and decisions.

    Action-item specialist

    Returns schema-validated owners and follow-up work.

    Sentiment specialist

    Returns a structured view of meeting sentiment.

  2. Assemble the meeting record

    The aggregator waits for every specialist result.

    Report aggregator

    Merges the three perspectives into one Markdown report.

03 The result

A ready-to-share meeting report

The example also compares parallel wall-clock time with the sum of the individual agent durations.

  1. 01Concise meeting summary
  2. 02Structured action items
  3. 03Sentiment and one combined Markdown report
  • Different reading goals remain explicit.
  • All three analyses run concurrently.
  • Structured outputs make action items and sentiment easier to use downstream.
Scope and limits
The recipe demonstrates transcript post-processing. Production use still needs decisions about recording consent, transcript retention, and who can access meeting content.
04 For builders

Run it, then inspect the implementation.

The business view above is editorial. The commands, prerequisites, APIs, and source below stay synchronized with the real repository.

Implementation facts AgentAgentPoolToolRegistryToolExecutorregisterBuiltInTools
Use-case Recipe 284 lines

Run it

From a clone of the repo — this exact file:

terminal
npx tsx packages/core/examples/cookbook/meeting-summarizer.ts
Prerequisites
  • ANTHROPIC_API_KEY env var must be set.

OMA is provider-agnostic — this example is written for the key above, but you can run it on OpenAI, Gemini, Groq and others. All providers →

Open the complete, synchronized source · 284 lines

The complete example, synchronized from the pinned Framework commit.

cookbook/meeting-summarizer.ts
/**
* Meeting Summarizer (Parallel Post-Processing)
*
* Demonstrates:
* - Fan-out of three specialized agents on the same meeting transcript
* - Structured output (Zod schemas) for action items and sentiment
* - Parallel timing check: wall time vs sum of per-agent durations
* - Aggregator merges into a single Markdown report
*
* Run:
* npx tsx packages/core/examples/cookbook/meeting-summarizer.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY env var must be set.
*/
 
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import { z } from 'zod'
import { Agent, AgentPool, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../../src/index.js'
import type { AgentConfig, AgentRunResult } from '../../src/types.js'
 
// ---------------------------------------------------------------------------
// Load the transcript fixture
// ---------------------------------------------------------------------------
 
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const TRANSCRIPT = readFileSync(
path.join(__dirname, '../fixtures/meeting-transcript.txt'),
'utf-8',
)
 
// ---------------------------------------------------------------------------
// Zod schemas for structured agents
// ---------------------------------------------------------------------------
 
const ActionItemList = z.object({
items: z.array(
z.object({
task: z.string().describe('The action to be taken'),
owner: z.string().describe('Name of the person responsible'),
due_date: z.string().optional().describe('ISO date or human-readable due date if mentioned'),
}),
),
})
type ActionItemList = z.infer<typeof ActionItemList>
 
const SentimentReport = z.object({
participants: z.array(
z.object({
participant: z.string().describe('Name as it appears in the transcript'),
tone: z.enum(['positive', 'neutral', 'negative', 'mixed']),
evidence: z.string().describe('Direct quote or brief paraphrase supporting the tone'),
}),
),
})
type SentimentReport = z.infer<typeof SentimentReport>
 
// ---------------------------------------------------------------------------
// Agent configs
// ---------------------------------------------------------------------------
 
const summaryConfig: AgentConfig = {
name: 'summary',
model: 'claude-sonnet-4-6',
systemPrompt: `You are a meeting note-taker. Given a transcript, produce a
three-paragraph summary:
1. What was discussed (the agenda).
2. Decisions made.
3. Notable context or risk the team should remember.
Plain prose. No bullet points. 200-300 words total.`,
maxTurns: 1,
temperature: 0.3,
}
 
const actionItemsConfig: AgentConfig = {
name: 'action-items',
model: 'claude-sonnet-4-6',
systemPrompt: `You extract action items from meeting transcripts. An action
item is a concrete task with a clear owner. Skip vague intentions ("we should
think about X"). Include due dates only when the speaker named one explicitly.
Return JSON matching the schema.`,
maxTurns: 1,
temperature: 0.1,
outputSchema: ActionItemList,
}
 
const sentimentConfig: AgentConfig = {
name: 'sentiment',
model: 'claude-sonnet-4-6',
systemPrompt: `You analyze the tone of each participant in a meeting. For
every named speaker, classify their overall tone as positive, neutral,
negative, or mixed, and include one short quote or paraphrase as evidence.
Return JSON matching the schema.`,
maxTurns: 1,
temperature: 0.2,
outputSchema: SentimentReport,
}
 
const aggregatorConfig: AgentConfig = {
name: 'aggregator',
model: 'claude-sonnet-4-6',
systemPrompt: `You are a report writer. You receive three pre-computed
analyses of the same meeting: a summary, an action-item list, and a sentiment
report. Your job is to merge them into a single Markdown report.
Output structure — use exactly these four H2 headings, in order:
## Summary
## Action Items
## Sentiment
## Next Steps
Under "Action Items" render a Markdown table with columns: Task, Owner, Due.
Under "Sentiment" render one bullet per participant.
Under "Next Steps" synthesize 3-5 concrete follow-ups based on the other
sections. Do not invent action items that are not grounded in the other data.`,
maxTurns: 1,
temperature: 0.3,
}
 
// ---------------------------------------------------------------------------
// Build agents
// ---------------------------------------------------------------------------
 
function buildAgent(config: AgentConfig): Agent {
const registry = new ToolRegistry()
registerBuiltInTools(registry)
const executor = new ToolExecutor(registry)
return new Agent(config, registry, executor)
}
 
const summary = buildAgent(summaryConfig)
const actionItems = buildAgent(actionItemsConfig)
const sentiment = buildAgent(sentimentConfig)
const aggregator = buildAgent(aggregatorConfig)
 
const pool = new AgentPool(3) // three specialists can run concurrently
pool.add(summary)
pool.add(actionItems)
pool.add(sentiment)
pool.add(aggregator)
 
console.log('Meeting Summarizer (Parallel Post-Processing)')
console.log('='.repeat(60))
console.log(`\nTranscript: ${TRANSCRIPT.split('\n')[0]}`)
console.log(`Length: ${TRANSCRIPT.split(/\s+/).length} words\n`)
 
// ---------------------------------------------------------------------------
// Step 1: Parallel fan-out with per-agent timing
// ---------------------------------------------------------------------------
 
console.log('[Step 1] Running 3 agents in parallel...\n')
 
const specialists = ['summary', 'action-items', 'sentiment'] as const
 
// Kick off all three concurrently and record each one's own wall duration.
// Sum-of-per-agent beats a separate serial pass: half the LLM cost, and the
// sum is the work parallelism saved.
const parallelStart = performance.now()
const timed = await Promise.all(
specialists.map(async (name) => {
const t = performance.now()
const result = await pool.run(name, TRANSCRIPT)
return { name, result, durationMs: performance.now() - t }
}),
)
const parallelElapsed = performance.now() - parallelStart
 
const byName = new Map<string, AgentRunResult>()
const serialSum = timed.reduce((acc, r) => {
byName.set(r.name, r.result)
return acc + r.durationMs
}, 0)
 
for (const { name, result, durationMs } of timed) {
const status = result.success ? 'OK' : 'FAILED'
console.log(
` ${name.padEnd(14)} [${status}] — ${Math.round(durationMs)}ms, ${result.tokenUsage.output_tokens} out tokens`,
)
}
console.log()
 
for (const { name, result } of timed) {
if (!result.success) {
console.error(`Specialist '${name}' failed: ${result.output}`)
process.exit(1)
}
}
 
const actionData = byName.get('action-items')!.structured as ActionItemList | undefined
const sentimentData = byName.get('sentiment')!.structured as SentimentReport | undefined
 
if (!actionData || !sentimentData) {
console.error('Structured output missing: action-items or sentiment failed schema validation')
process.exit(1)
}
 
// ---------------------------------------------------------------------------
// Step 2: Parallelism assertion
// ---------------------------------------------------------------------------
 
console.log('[Step 2] Parallelism check')
console.log(` Parallel wall time: ${Math.round(parallelElapsed)}ms`)
console.log(` Serial sum (per-agent): ${Math.round(serialSum)}ms`)
console.log(` Speedup: ${(serialSum / parallelElapsed).toFixed(2)}x\n`)
 
if (parallelElapsed >= serialSum * 0.7) {
console.error(
`ASSERTION FAILED: parallel wall time (${Math.round(parallelElapsed)}ms) is not ` +
`less than 70% of serial sum (${Math.round(serialSum)}ms). Expected substantial ` +
`speedup from fan-out.`,
)
process.exit(1)
}
 
// ---------------------------------------------------------------------------
// Step 3: Aggregate into Markdown report
// ---------------------------------------------------------------------------
 
console.log('[Step 3] Aggregating into Markdown report...\n')
 
const aggregatorPrompt = `Merge the three analyses below into a single Markdown report.
--- SUMMARY (prose) ---
${byName.get('summary')!.output}
--- ACTION ITEMS (JSON) ---
${JSON.stringify(actionData, null, 2)}
--- SENTIMENT (JSON) ---
${JSON.stringify(sentimentData, null, 2)}
Produce the Markdown report per the system instructions.`
 
const reportResult = await pool.run('aggregator', aggregatorPrompt)
 
if (!reportResult.success) {
console.error('Aggregator failed:', reportResult.output)
process.exit(1)
}
 
// ---------------------------------------------------------------------------
// Final output
// ---------------------------------------------------------------------------
 
console.log('='.repeat(60))
console.log('MEETING REPORT')
console.log('='.repeat(60))
console.log()
console.log(reportResult.output)
console.log()
console.log('-'.repeat(60))
 
// ---------------------------------------------------------------------------
// Token usage summary
// ---------------------------------------------------------------------------
 
console.log('\nToken Usage Summary:')
console.log('-'.repeat(60))
 
let totalInput = 0
let totalOutput = 0
for (const { name, result } of timed) {
totalInput += result.tokenUsage.input_tokens
totalOutput += result.tokenUsage.output_tokens
console.log(
` ${name.padEnd(14)} — input: ${result.tokenUsage.input_tokens}, output: ${result.tokenUsage.output_tokens}`,
)
}
totalInput += reportResult.tokenUsage.input_tokens
totalOutput += reportResult.tokenUsage.output_tokens
console.log(
` ${'aggregator'.padEnd(14)} — input: ${reportResult.tokenUsage.input_tokens}, output: ${reportResult.tokenUsage.output_tokens}`,
)
console.log('-'.repeat(60))
console.log(` ${'TOTAL'.padEnd(14)} — input: ${totalInput}, output: ${totalOutput}`)
 
console.log('\nDone.')
View & edit on GitHub
// Enterprise

Taking this to production?

open-multi-agent is MIT-licensed and free to run yourself. When you need it delivered, integrated, or supported on a deadline, 元定义科技 (YuanASI) offers commercial delivery and support.

Enterprise support