202 lines
7.1 KiB
TypeScript
202 lines
7.1 KiB
TypeScript
/**
|
|
* Example 03 — Explicit Task Pipeline with Dependencies
|
|
*
|
|
* Demonstrates how to define tasks with explicit dependency chains
|
|
* (design → implement → test → review) using runTasks(). The TaskQueue
|
|
* automatically blocks downstream tasks until their dependencies complete.
|
|
*
|
|
* Run:
|
|
* npx tsx examples/03-task-pipeline.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'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Agents
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const designer: AgentConfig = {
|
|
name: 'designer',
|
|
model: 'claude-sonnet-4-6',
|
|
systemPrompt: `You are a software designer. Your output is always a concise technical spec
|
|
in markdown. Focus on interfaces, data shapes, and file structure. Be brief.`,
|
|
tools: ['file_write'],
|
|
maxTurns: 4,
|
|
}
|
|
|
|
const implementer: AgentConfig = {
|
|
name: 'implementer',
|
|
model: 'claude-sonnet-4-6',
|
|
systemPrompt: `You are a TypeScript developer. Read the design spec written by the designer,
|
|
then implement it. Write all files to /tmp/pipeline-output/. Use the tools.`,
|
|
tools: ['bash', 'file_read', 'file_write'],
|
|
maxTurns: 10,
|
|
}
|
|
|
|
const tester: AgentConfig = {
|
|
name: 'tester',
|
|
model: 'claude-sonnet-4-6',
|
|
systemPrompt: `You are a QA engineer. Read the implemented files and run them to verify correctness.
|
|
Report: what passed, what failed, and any bugs found.`,
|
|
tools: ['bash', 'file_read', 'grep'],
|
|
maxTurns: 6,
|
|
}
|
|
|
|
const reviewer: AgentConfig = {
|
|
name: 'reviewer',
|
|
model: 'claude-sonnet-4-6',
|
|
systemPrompt: `You are a code reviewer. Read all files and produce a brief structured review.
|
|
Sections: Summary, Strengths, Issues (if any), Verdict (SHIP / NEEDS WORK).`,
|
|
tools: ['file_read', 'grep'],
|
|
maxTurns: 4,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Progress handler — shows dependency blocking/unblocking
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const taskTimes = new Map<string, number>()
|
|
|
|
function handleProgress(event: OrchestratorEvent): void {
|
|
const ts = new Date().toISOString().slice(11, 23)
|
|
|
|
switch (event.type) {
|
|
case 'task_start': {
|
|
taskTimes.set(event.task ?? '', Date.now())
|
|
const task = event.data as Task | undefined
|
|
console.log(`[${ts}] TASK READY "${task?.title ?? event.task}" (assignee: ${task?.assignee ?? 'any'})`)
|
|
break
|
|
}
|
|
case 'task_complete': {
|
|
const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
|
|
const task = event.data as Task | undefined
|
|
console.log(`[${ts}] TASK DONE "${task?.title ?? event.task}" in ${elapsed}ms`)
|
|
break
|
|
}
|
|
case 'agent_start':
|
|
console.log(`[${ts}] AGENT START ${event.agent}`)
|
|
break
|
|
case 'agent_complete':
|
|
console.log(`[${ts}] AGENT DONE ${event.agent}`)
|
|
break
|
|
case 'error': {
|
|
const task = event.data as Task | undefined
|
|
console.error(`[${ts}] ERROR ${event.agent ?? ''} task="${task?.title ?? event.task}"`)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Build the pipeline
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const orchestrator = new OpenMultiAgent({
|
|
defaultModel: 'claude-sonnet-4-6',
|
|
maxConcurrency: 2, // allow test + review to potentially run in parallel later
|
|
onProgress: handleProgress,
|
|
})
|
|
|
|
const team = orchestrator.createTeam('pipeline-team', {
|
|
name: 'pipeline-team',
|
|
agents: [designer, implementer, tester, reviewer],
|
|
sharedMemory: true,
|
|
})
|
|
|
|
// Task IDs — use stable strings so dependsOn can reference them
|
|
// (IDs will be generated by the framework; we capture the returned Task objects)
|
|
const SPEC_FILE = '/tmp/pipeline-output/design-spec.md'
|
|
|
|
const tasks: Array<{
|
|
title: string
|
|
description: string
|
|
assignee?: string
|
|
dependsOn?: string[]
|
|
}> = [
|
|
{
|
|
title: 'Design: URL shortener data model',
|
|
description: `Design a minimal in-memory URL shortener service.
|
|
Write a markdown spec to ${SPEC_FILE} covering:
|
|
- TypeScript interfaces for Url and ShortenRequest
|
|
- The shortening algorithm (hash approach is fine)
|
|
- API contract: POST /shorten, GET /:code
|
|
Keep the spec under 30 lines.`,
|
|
assignee: 'designer',
|
|
// no dependencies — this is the root task
|
|
},
|
|
{
|
|
title: 'Implement: URL shortener',
|
|
description: `Read the design spec at ${SPEC_FILE}.
|
|
Implement the URL shortener in /tmp/pipeline-output/src/:
|
|
- shortener.ts: core logic (shorten, resolve functions)
|
|
- server.ts: tiny HTTP server using Node's built-in http module (no Express)
|
|
- POST /shorten body: { url: string } → { code: string, short: string }
|
|
- GET /:code → redirect (301) or 404
|
|
- index.ts: entry point that starts the server on port 3002
|
|
No external dependencies beyond Node built-ins.`,
|
|
assignee: 'implementer',
|
|
dependsOn: ['Design: URL shortener data model'],
|
|
},
|
|
{
|
|
title: 'Test: URL shortener',
|
|
description: `Run the URL shortener implementation:
|
|
1. Start the server: node /tmp/pipeline-output/src/index.ts (or tsx)
|
|
2. POST a URL to shorten it using curl
|
|
3. Verify the GET redirect works
|
|
4. Report what passed and what (if anything) failed.
|
|
Kill the server after testing.`,
|
|
assignee: 'tester',
|
|
dependsOn: ['Implement: URL shortener'],
|
|
},
|
|
{
|
|
title: 'Review: URL shortener',
|
|
description: `Read all .ts files in /tmp/pipeline-output/src/ and the design spec.
|
|
Produce a structured code review with sections:
|
|
- Summary (2 sentences)
|
|
- Strengths (bullet list)
|
|
- Issues (bullet list, or "None" if clean)
|
|
- Verdict: SHIP or NEEDS WORK`,
|
|
assignee: 'reviewer',
|
|
dependsOn: ['Implement: URL shortener'], // runs in parallel with Test after Implement completes
|
|
},
|
|
]
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Run
|
|
// ---------------------------------------------------------------------------
|
|
|
|
console.log('Starting 4-stage task pipeline...\n')
|
|
console.log('Pipeline: design → implement → test + review (parallel)')
|
|
console.log('='.repeat(60))
|
|
|
|
const result = await orchestrator.runTasks(team, tasks)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Summary
|
|
// ---------------------------------------------------------------------------
|
|
|
|
console.log('\n' + '='.repeat(60))
|
|
console.log('Pipeline complete.\n')
|
|
console.log(`Overall success: ${result.success}`)
|
|
console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
|
|
|
|
console.log('\nPer-agent summary:')
|
|
for (const [name, r] of result.agentResults) {
|
|
const icon = r.success ? 'OK ' : 'FAIL'
|
|
const toolCount = r.toolCalls.map(c => c.toolName).join(', ')
|
|
console.log(` [${icon}] ${name.padEnd(14)} tools used: ${toolCount || '(none)'}`)
|
|
}
|
|
|
|
// Print the reviewer's verdict
|
|
const review = result.agentResults.get('reviewer')
|
|
if (review?.success) {
|
|
console.log('\nCode review:')
|
|
console.log('─'.repeat(60))
|
|
console.log(review.output)
|
|
console.log('─'.repeat(60))
|
|
}
|