#!/usr/bin/env node /** * Dependency ordering for a multi-agent task list, in plain JavaScript. * * Defines four tasks with `dependsOn` lists, checks the list for cycles and * missing references, prints the rounds in which the tasks become ready * (tasks in one round do not depend on each other), and then makes one direct * OpenAI chat completion call as a connectivity check. * * It does not run AgentOS's workflow engine and calls no AgentOS API. For * workflows that execute, see examples/workflow-dsl.mjs (workflow() on * GraphRuntime) and examples/agency-graph.mjs (agency() with dependsOn). * * Usage: * OPENAI_API_KEY=sk-... node examples/multi-agent-workflow.mjs * * Or with .env: * node --env-file=.env examples/multi-agent-workflow.mjs */ import { readFileSync } from 'fs'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; // Load .env manually if OPENAI_API_KEY not set const __dirname = dirname(fileURLToPath(import.meta.url)); if (!process.env.OPENAI_API_KEY) { try { const envFile = readFileSync(resolve(__dirname, '../.env'), 'utf-8'); for (const line of envFile.split('\n')) { const match = line.match(/^(\w+)=(.+)$/); if (match) process.env[match[1]] = match[2]; } } catch { /* no .env file */ } } if (!process.env.OPENAI_API_KEY) { console.error('โŒ OPENAI_API_KEY not set. Pass via env or .env file.'); process.exit(1); } console.log('๐Ÿงช Multi-Agent Task Ordering'); console.log('=' .repeat(60)); // โ”€โ”€โ”€ Step 1: Check the dependency graph (no LLM needed) โ”€โ”€โ”€ console.log('\n๐Ÿ“‹ Step 1: Checking the task dependency graph...\n'); // Define the task list const workflowDefinition = { id: 'test-market-analysis', name: 'Market Analysis Pipeline', version: '1.0.0', tasks: [ // PARALLEL: no dependencies { id: 'research', name: 'Competitor Research', description: 'Research top 3 competitors in the AI agent framework space', dependsOn: [], executor: { type: 'gmi', roleId: 'researcher', instructions: 'Research competitor pricing, features, and market positioning for AI agent frameworks like LangChain, CrewAI, and AutoGen.' }, }, { id: 'data-collection', name: 'Market Data Collection', description: 'Collect market size and growth data for the AI agent market', dependsOn: [], executor: { type: 'gmi', roleId: 'analyst', instructions: 'Analyze the current AI agent market size, growth trends, and key segments.' }, }, // SEQUENTIAL: depends on both parallel tasks { id: 'synthesis', name: 'Strategy Synthesis', description: 'Synthesize research and data into a pricing strategy', dependsOn: ['research', 'data-collection'], executor: { type: 'gmi', roleId: 'strategist', instructions: 'Using the competitor research and market data, create a pricing strategy recommendation.' }, }, // SEQUENTIAL: depends on synthesis { id: 'report', name: 'Final Report', description: 'Write the final market analysis report', dependsOn: ['synthesis'], executor: { type: 'gmi', roleId: 'writer', instructions: 'Write a concise executive summary report based on the strategy synthesis.' }, }, ], }; // Validate DAG structure console.log('\n Task dependency graph:'); for (const task of workflowDefinition.tasks) { const deps = task.dependsOn.length > 0 ? task.dependsOn.join(', ') : '(none โ€” runs immediately)'; console.log(` ${task.id} โ†’ depends on: ${deps}`); } // Check parallel detection const parallelTasks = workflowDefinition.tasks.filter(t => t.dependsOn.length === 0); const sequentialTasks = workflowDefinition.tasks.filter(t => t.dependsOn.length > 0); console.log(`\n โœ“ ${parallelTasks.length} tasks run in PARALLEL: ${parallelTasks.map(t => t.id).join(', ')}`); console.log(` โœ“ ${sequentialTasks.length} tasks run SEQUENTIALLY:`); for (const t of sequentialTasks) { console.log(` ${t.id} waits for: ${t.dependsOn.join(' + ')}`); } // Validate no cycles function hasCycles(tasks) { const graph = new Map(tasks.map(t => [t.id, t.dependsOn || []])); const visited = new Set(); const inStack = new Set(); function dfs(nodeId) { if (inStack.has(nodeId)) return true; if (visited.has(nodeId)) return false; visited.add(nodeId); inStack.add(nodeId); for (const dep of graph.get(nodeId) || []) { if (dfs(dep)) return true; } inStack.delete(nodeId); return false; } for (const [id] of graph) { if (dfs(id)) return true; } return false; } const cycleResult = hasCycles(workflowDefinition.tasks); console.log(`\n ${cycleResult ? 'โŒ' : 'โœ“'} Cycle detection: ${cycleResult ? 'CYCLES FOUND' : 'No cycles (valid DAG)'}`); // Validate all dependencies reference existing tasks const taskIds = new Set(workflowDefinition.tasks.map(t => t.id)); let allDepsValid = true; for (const task of workflowDefinition.tasks) { for (const dep of task.dependsOn) { if (!taskIds.has(dep)) { console.log(` โŒ Task "${task.id}" depends on "${dep}" which doesn't exist`); allDepsValid = false; } } } if (allDepsValid) console.log(' โœ“ All dependency references valid'); // โ”€โ”€โ”€ Step 2: Simulate execution ordering โ”€โ”€โ”€ console.log('\n๐Ÿ“‹ Step 2: Simulating execution order...\n'); const completed = new Set(); const timeline = []; let round = 0; while (completed.size < workflowDefinition.tasks.length) { round++; const ready = workflowDefinition.tasks.filter(t => !completed.has(t.id) && t.dependsOn.every(dep => completed.has(dep)) ); if (ready.length === 0) { console.log(' โŒ Deadlock detected โ€” no tasks ready but not all complete'); break; } const readyIds = ready.map(t => t.id); const isParallel = ready.length > 1; timeline.push({ round, tasks: readyIds, parallel: isParallel }); console.log(` Round ${round}: ${isParallel ? 'โšก PARALLEL' : 'โžก๏ธ SEQUENTIAL'} โ†’ [${readyIds.join(', ')}]`); for (const t of ready) { completed.add(t.id); } } console.log(`\n โœ“ All ${workflowDefinition.tasks.length} tasks scheduled in ${round} rounds`); console.log(' โœ“ Execution order is valid'); // โ”€โ”€โ”€ Step 3: One direct OpenAI API call โ”€โ”€โ”€ console.log('\n๐Ÿ“‹ Step 3: Calling the OpenAI API directly...\n'); let apiCallSucceeded = false; try { // A direct HTTP call, not an AgentOS call const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: 'You are a market research analyst. Be concise (2-3 sentences max).' }, { role: 'user', content: 'What are the top 3 AI agent frameworks in 2026 and their key differentiators?' }, ], max_tokens: 200, }), }); const data = await response.json(); if (data.choices?.[0]?.message?.content) { apiCallSucceeded = true; console.log(' โœ“ OpenAI API call successful'); console.log(` โœ“ Model: ${data.model}`); console.log(` โœ“ Tokens: ${data.usage?.total_tokens}`); console.log(`\n Response: "${data.choices[0].message.content.slice(0, 200)}..."`); } else { console.log(' โŒ Unexpected response:', JSON.stringify(data).slice(0, 200)); } } catch (e) { console.log(' โŒ API call failed:', e.message); } // โ”€โ”€โ”€ Summary โ”€โ”€โ”€ const graphValid = !cycleResult && allDepsValid; console.log('\n' + '=' .repeat(60)); console.log(graphValid && apiCallSucceeded ? 'โœ… Complete' : 'โŒ Complete with failures'); console.log(''); console.log(` DAG validation: ${graphValid ? 'โœ“ No cycles, all deps valid' : 'โŒ see above'}`); console.log(` Parallel tasks: ${parallelTasks.length} (${parallelTasks.map(t => t.id).join(', ')})`); console.log(` Sequential tasks: ${sequentialTasks.length} (${sequentialTasks.map(t => t.id).join(', ')})`); console.log(` Execution rounds: ${round}`); console.log(` OpenAI API call: ${apiCallSucceeded ? 'โœ“ succeeded' : 'โŒ failed'}`); console.log(''); if (!graphValid || !apiCallSucceeded) process.exitCode = 1;