·15 min read

Open Multi-Agent: A TypeScript-native multi-agent framework (MIT) that decomposes goals into runtime task DAGs

A TypeScript-native multi-agent framework that decomposes goals into runtime task DAGs — runs fully offline on quantized models via Ollama with just 4-8GB VRAM.

The Problem

Every multi-agent framework on the market makes the same implicit bet: that you want to wire up your agent graph by hand. LangGraph JS makes you declare every node and edge upfront. Mastra has you chain workflows manually. CrewAI is Python-only and assumes you’re comfortable with its agent-tool-task triad. They all assume you know the shape of your problem before you start coding.

But real-world agentic workflows are not static. A code review pipeline might need three agents one day and seven the next. A research task might branch into parallel subtasks you didn’t anticipate. A customer support flow might need to escalate to a specialist only when certain conditions arise. Hand-wiring a graph for every permutation is not scalable — it is maintenance debt disguised as architecture.

The deeper problem: none of these frameworks were designed to run offline. They assume cloud API access, which means they fail in air-gapped environments, on laptops without internet, and in compliance zones where data cannot leave the premises.

Dimension Graph-First Frameworks (LangGraph JS, Mastra) Goal-Driven Frameworks (Open Multi-Agent)
Graph definition You declare nodes/edges upfront Coordinator builds the DAG at runtime from a goal
Parallelism You wire parallel branches manually Automatic — independent tasks run concurrently
Model flexibility Single provider per pipeline Mix providers in one team (Anthropic + Ollama + OpenAI)
Offline support Cloud-dependent Full offline via Ollama, vLLM, LM Studio
Runtime dependencies 10+ (LangChain ecosystem) 3 (@anthropic-ai/sdk, openai, zod)
Checkpoint/resume Manual state management Built-in per-task snapshots over any MemoryStore
Human-in-the-loop Custom implementation Plan approval gates + per-round approval hooks
Observability External tools Built-in progress events, trace spans, HTML dashboard

Why this matters: The gap between “I have a goal” and “I have a working multi-agent pipeline” should not require you to enumerate every edge in a graph. Open Multi-Agent collapses that gap by making the coordinator responsible for decomposition, scheduling, and synthesis — you provide the team and the goal, and the framework handles the rest. This is not a competing approach to graph-first frameworks — it is a complementary one that covers the cases where the problem shape is not known in advance.

The Investigation

The team behind Open Multi-Agent (launched April 1, 2026, MIT license, 6,400+ GitHub stars) spent months investigating why existing multi-agent frameworks fail in production. The answer is not agent quality — it is orchestration rigidity.

Finding 1: Static graphs do not survive production.

Every hand-wired agent graph assumes the problem decomposition is known at design time. In practice, the coordinator’s first decomposition pass reveals dependencies you did not anticipate. A research task that looks like a linear pipeline (“search, then summarize, then write”) becomes a branching DAG when the search agent discovers conflicting sources that need parallel verification. Static graphs force you to either over-approximate (wasting tokens on unnecessary agents) or under-approximate (missing critical parallel branches).

What this means: A framework that builds the DAG at runtime — after seeing the actual goal — adapts to the problem rather than forcing the problem to fit the graph. Open Multi-Agent’s coordinator agent receives the goal, agent names, and a single instruction: “decompose this into a JSON task array.” The output is a dependency graph that reflects the actual work needed, not the work you predicted.

Finding 2: Provider lock-in is unnecessary overhead.

Most frameworks couple tightly to one LLM provider. LangGraph JS is OpenAI-first. Mastra defaults to Anthropic. CrewAI is Python-bound. This means your multi-agent pipeline inherits a single provider’s failure modes — rate limits, outages, pricing changes, model deprecations.

Open Multi-Agent ships with 13 built-in providers (Anthropic, OpenAI, Azure OpenAI, GitHub Copilot, xAI Grok, DeepSeek, Doubao, Hunyuan, MiniMax, MiMo, Qiniu) plus any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, llama.cpp, OpenRouter, Groq, Mistral, Moonshot, Qwen, Zhipu). Google Gemini and AWS Bedrock are opt-in peer dependencies. The Vercel AI SDK bridge adds 60+ more models. And critically: you can mix providers in a single team — the coordinator can use Claude to plan while a Llama 3.1 model via Ollama runs leaf tasks.

What this means: Provider diversity is not a nice-to-have — it is a production requirement. When Anthropic has an outage, your pipeline should not go down with it. When you need to run offline, you should not need to rewrite your agent configs. Open Multi-Agent’s provider model makes this a configuration change, not an architecture change.

Finding 3: Three runtime dependencies is a feature, not a limitation.

The published package @open-multi-agent/core pulls exactly three runtime dependencies: @anthropic-ai/sdk, openai, and zod. Everything else — Gemini, Bedrock, MCP, Vercel AI SDK — is an opt-in peer dependency. This is not an accident. The team deliberately avoided the “batteries-included” approach that makes LangChain’s ecosystem a 10+ dependency install.

The tradeoff is real: you install extras when you need them. But the benefit is a lean core that installs in under 10 seconds, has minimal attack surface, and does not force you to carry dependencies you will never use.

The Solution

Open Multi-Agent is a TypeScript-native multi-agent orchestration framework built around a single insight: give the framework a goal, and let it figure out the graph. The coordinator agent decomposes the goal into a task DAG at runtime, the scheduler assigns tasks to agents, the AgentPool parallelizes independent work, and the coordinator runs a final synthesis pass to produce the result.

┌─────────────────────────────────────────────────────────────┐
│                    OpenMultiAgent                           │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                    runTeam(team, goal)                 │  │
│  │                                                       │  │
│  │  1. Coordinator decomposes goal → JSON task array     │  │
│  │     (title, description, assignee, dependsOn)         │  │
│  │                                                       │  │
│  │  2. TaskQueue builds dependency graph                 │  │
│  │     ┌──────┐    ┌──────┐    ┌──────┐                 │  │
│  │     │Task A│    │Task B│    │Task C│                 │  │
│  │     └──┬───┘    └──┬───┘    └──────┘                 │  │
│  │        │           │                                 │  │
│  │        ▼           ▼                                 │  │
│  │     ┌──────────────────┐                             │  │
│  │     │    Task D        │  ← depends on A, B          │  │
│  │     └──────────────────┘                             │  │
│  │                                                       │  │
│  │  3. Scheduler assigns tasks to agents                 │  │
│  │     (dependency-first / round-robin / least-busy       │  │
│  │      / capability-match)                              │  │
│  │                                                       │  │
│  │  4. AgentPool executes in parallel (maxConcurrency=5)  │  │
│  │     ┌────────┐  ┌────────┐  ┌────────┐              │  │
│  │     │Agent A │  │Agent B │  │Agent C │              │  │
│  │     └────────┘  └────────┘  └────────┘              │  │
│  │           │            │                             │  │
│  │           ▼            ▼                             │  │
│  │     ┌──────────────────────┐                        │  │
│  │     │   Shared Memory      │                        │  │
│  │     │   (KV store)         │                        │  │
│  │     └──────────────────────┘                        │  │
│  │                                                       │  │
│  │  5. Coordinator synthesis pass → final result         │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                           │
│  Observability: onProgress │ onTrace │ HTML dashboard     │
│  Lifecycle:    planOnly │ onPlanReady │ onApproval        │
│  Resilience:   checkpoint │ retry │ AbortSignal           │
└─────────────────────────────────────────────────────────────┘

Here’s what each piece does:

  • OpenMultiAgent (Orchestrator) — top-level entry point. Exposes createTeam(), runTeam(), runTasks(), runAgent(), runConsensus(), getStatus(). Holds default provider, model, and tool configuration.
  • Coordinator Agent — a temporary agent created per run and discarded afterward. Receives the goal, agent names, and a single instruction: decompose the goal into a JSON task array with title, description, assignee, and dependsOn. Costs one extra LLM call. If the coordinator returns unusable JSON, the framework falls back to one task per agent with the original goal — a degraded run, not an exception.
  • TaskQueue — manages the dependency graph. Title-based dependsOn references resolve to real task IDs. A task becomes “ready” only after all its dependencies complete. Failed tasks cascade to dependents as blocked; unaffected branches continue.
  • Scheduler — assigns ready tasks to agents. Four strategies: dependency-first (default), round-robin, least-busy, capability-match (scores agent name and system prompt against the task).
  • AgentPool — semaphore-based parallel execution. Independent tasks run concurrently up to maxConcurrency (default 5). Dependent tasks wait, then dispatch once their inputs complete.
  • Shared Memory — default in-process KV store. Each task’s output is written to shared memory after completion. Downstream agents read from this store. Swappable for Redis, Postgres, or any custom MemoryStore implementation.
  • ToolRegistry — holds defineTool() definitions, 6 built-in tools (bash, file_read, file_write, file_edit, grep, glob), plus the opt-in delegate_to_agent handoff. All built-in tools are default-deny — an agent only gets tools explicitly listed in its config.
  • Lifecycle HooksbeforeRun/afterRun rewrite or reject prompts; onPlanReady gates the entire plan before any agent runs; onApproval gates between task rounds; AbortSignal cancels a run in flight.

Production-Grade Code Walkthrough

Here is a complete, production-ready setup that mixes cloud and local models in a single team:

import { OpenMultiAgent, defineTool, type AgentConfig } from '@open-multi-agent/core'
import { z } from 'zod'

// Define a custom tool with Zod validation
const searchDocs = defineTool({
  name: 'search_documentation',
  description: 'Search internal documentation for a given topic.',
  inputSchema: z.object({
    query: z.string().min(1).max(200),
    maxResults: z.number().int().min(1).max(20).default(5),
  }),
  execute: async ({ query, maxResults }) => {
    // In production, this would hit an internal search API
    return { results: [`Found ${maxResults} results for "${query}"`] }
  },
})

// Mix cloud and local models in the same team
const agents: AgentConfig[] = [
  {
    name: 'architect',
    model: 'claude-sonnet-4-6',
    provider: 'anthropic',
    systemPrompt: `You design clean, maintainable API contracts.
Focus on type safety, error handling, and backward compatibility.`,
    tools: ['file_write'],
    maxTurns: 8,
  },
  {
    name: 'developer',
    model: 'claude-sonnet-4-6',
    provider: 'anthropic',
    systemPrompt: `You implement runnable TypeScript from architectural specs.
Write tests alongside implementation. Use the bash tool to run the test suite.`,
    tools: ['bash', 'file_read', 'file_write', 'file_edit'],
    maxTurns: 15,
    timeoutMs: 300_000,
  },
  {
    name: 'reviewer',
    model: 'llama3.1',
    provider: 'openai',
    baseURL: 'http://localhost:11434/v1',
    apiKey: 'ollama', // Ollama ignores the key but SDK requires it
    systemPrompt: `You review code for correctness, security, and style.
Use file_read and grep to inspect the codebase.`,
    tools: ['file_read', 'grep'],
    maxTurns: 4,
    timeoutMs: 120_000,
  },
]

const orchestrator = new OpenMultiAgent({
  defaultProvider: 'anthropic',
  defaultModel: 'claude-sonnet-4-6',
  onProgress: (event) => {
    console.log(`[${event.type}] ${event.task ?? event.agent ?? ''}`)
  },
  onTrace: (span) => {
    // Feed to OpenTelemetry or your tracing backend
    console.debug(`[trace] ${span.name}: ${span.duration}ms`)
  },
})

const team = orchestrator.createTeam('api-team', {
  name: 'api-team',
  agents,
  sharedMemory: true,
})

const result = await orchestrator.runTeam(
  team,
  'Create a REST API for a todo list with PostgreSQL persistence and OpenAPI documentation.',
)

console.log(`Tokens used: ${result.totalTokenUsage}`)
console.log(`Tasks: ${Object.keys(result.tasks).length}`)

Setup Instructions

# Scaffold a new project
npm create oma-app@latest

# Or add to an existing project
npm install @open-multi-agent/core

# For local models, install Ollama
# https://ollama.com/download
ollama pull llama3.1

# For Gemini support (optional)
npm install @google/genai

# For MCP support (optional)
npm install @modelcontextprotocol/sdk

# For Vercel AI SDK bridge (optional)
npm install ai @ai-sdk/openai

How to Use Effectively

1. Start with planOnly to inspect the DAG before execution

Before committing to a full run, preview the coordinator’s decomposition. This costs one LLM call and returns the task DAG without executing anything.

const preview = await orchestrator.runTeam(team, goal, { planOnly: true })
console.log(preview.tasks)
// {
//   "task_1": { title: "Design API contracts", assignee: "architect", dependsOn: [] },
//   "task_2": { title: "Implement endpoints", assignee: "developer", dependsOn: ["task_1"] },
//   "task_3": { title: "Review implementation", assignee: "reviewer", dependsOn: ["task_2"] },
// }

// Serialize the plan for replay
const plan = orchestrator.createPlanArtifact(preview)

2. Use model routing to optimize cost

Route expensive planning work to a flagship model and cheap leaf tasks to a local or budget model. First-match-wins semantics — order rules from most to least specific.

const result = await orchestrator.runTeam(team, goal, {
  modelRouting: {
    rules: [
      { match: { phase: 'coordinator' }, route: { model: 'claude-opus-4-7' } },
      { match: { phase: 'synthesis' },   route: { model: 'claude-opus-4-7' } },
      { match: { leaf: true },           route: { model: 'claude-haiku-4-5' } },
      { match: { taskRole: 'security' }, route: { model: 'claude-sonnet-4-6' } },
    ],
  },
})

3. Add human-in-the-loop gates for safety-critical pipelines

Use onPlanReady to review the entire plan before any agent runs, and onApproval to gate between task rounds. Return false from either to abort or skip remaining tasks.

const result = await orchestrator.runTeam(team, goal, {
  onPlanReady: async (plan) => {
    console.log(`Plan has ${Object.keys(plan.tasks).length} tasks`)
    // Return false to abort before any agent runs
    return true
  },
  onApproval: async (completed, remaining) => {
    console.log(`${completed.length} done, ${remaining.length} remaining`)
    // Return false to mark remaining tasks as skipped
    return true
  },
})

4. Use checkpoint/resume for long-running pipelines

Enable checkpointing to survive crashes and restarts. Snapshots are taken after each completed task. On resume, already-finished tasks are skipped.

const result = await orchestrator.runTeam(team, goal, {
  checkpoint: {
    store: new InMemoryStore(), // Or RedisStore, PostgresStore
    runId: 'api-team-run-2026-06-24',
  },
})

// On restart (after crash):
const restored = await orchestrator.restore(team, {
  store: new InMemoryStore(),
  runId: 'api-team-run-2026-06-24',
})

5. Use runTasks for deterministic pipelines when you know the graph

When the problem decomposition is well-understood, skip the coordinator and wire the DAG directly. Same queue, scheduler, and parallel execution — you own the graph.

const result = await orchestrator.runTasks(team, [
  {
    title: 'Research TypeScript decorator tradeoffs',
    description: 'Gather concrete pros and cons of TypeScript decorators vs. wrapper functions.',
    assignee: 'researcher',
  },
  {
    title: 'Write the explainer',
    description: 'Using the research notes, write a 500-word technical explainer.',
    assignee: 'writer',
    dependsOn: ['Research TypeScript decorator tradeoffs'],
  },
  {
    title: 'Review for accuracy',
    description: 'Check the explainer for technical accuracy and clarity.',
    assignee: 'reviewer',
    dependsOn: ['Write the explainer'],
  },
])

Use Cases

1. Automated Code Review Pipeline

A team of specialized reviewer agents analyzes every PR for correctness, security, style, and performance — in parallel, with results synthesized into a single review.

When you’d use this: Your team’s PR review process is bottlenecked on senior engineers who spend 30 minutes per PR reading diffs.

Why Open Multi-Agent fits: The coordinator decomposes the PR into parallel review tasks (security scan, style check, performance analysis, correctness audit), assigns each to a specialized agent, and synthesizes the results. The onApproval hook lets a human sign off before the review is posted. The modelRouting policy routes security reviews to a stronger model and style checks to a cheaper one.

2. Offline Research Assistant

A research team that runs entirely on local quantized models via Ollama, with no internet connection required.

When you’d use this: You are on a plane, in a compliance zone, or working from a location with unreliable internet. You need to research a topic, synthesize findings, and produce a report — all offline.

Why Open Multi-Agent fits: Set provider: 'openai' with baseURL: 'http://localhost:11434/v1' for every agent. The coordinator decomposes the research goal into parallel search queries, each agent runs on a local model (4-8GB VRAM is sufficient for 7B-13B parameter quantized models), and the synthesis pass produces the final report. The contextStrategy: { type: 'compact' } keeps token usage within the local model’s context window.

3. Customer Support Escalation Flow

A support triage system that routes customer inquiries through tier-1 agents, escalates to specialists when needed, and synthesizes a final response.

When you’d use this: Your support team handles 500+ tickets per day across multiple product areas, and manual triage is the bottleneck.

Why Open Multi-Agent fits: The coordinator decomposes the inquiry into diagnostic tasks (account lookup, error log analysis, documentation search), assigns them to parallel agents, and the synthesis pass produces a coherent response. The delegate_to_agent tool enables synchronous sub-agent delegation for deep-dive investigations. The onApproval hook gates escalation decisions.

4. Content Production Pipeline

A multi-agent content team that researches, writes, edits, and formats blog posts, documentation, or marketing copy.

When you’d use this: You produce 10+ pieces of content per week and need consistent quality, tone, and formatting across all outputs.

Why Open Multi-Agent fits: The coordinator decomposes the content brief into research, outline, draft, edit, and format tasks. Each agent has a specialized system prompt (researcher, writer, editor, formatter). The sharedMemory option lets the writer access research notes and the editor access the draft without manual output threading. The runTasks method provides a deterministic pipeline when the content workflow is well-understood.

5. Multi-Model Security Audit

A security audit pipeline that uses different models for different phases — a strong model for vulnerability detection, a local model for sensitive data scanning, and a budget model for dependency checking.

When you’d use this: You need to audit a codebase for security vulnerabilities, but sensitive data (API keys, PII) must not leave the local machine.

Why Open Multi-Agent fits: Mix providers in one team: the vulnerability scanner uses Claude via Anthropic, the sensitive data scanner uses Llama 3.1 via Ollama (data never leaves the machine), and the dependency checker uses Haiku via Anthropic. The modelRouting policy routes each task to the appropriate model based on taskRole. The filesystem sandbox (default .agent-workspace) limits file access scope.

Cheat Sheet

Aspect Detail
Package @open-multi-agent/core
License MIT
Language TypeScript (98.6%)
Runtime Node.js >= 18
Runtime deps 3 (@anthropic-ai/sdk, openai, zod)
Stars 6,400+
Latest release v1.8.0 (June 19, 2026)
Execution modes runAgent (single), runTeam (auto-orchestrated), runTasks (explicit pipeline), runConsensus (proposer-judge)
Built-in providers 13 (Anthropic, OpenAI, Azure, GitHub Copilot, Grok, DeepSeek, Doubao, Hunyuan, MiniMax, MiMo, Qiniu)
OpenAI-compatible Ollama, vLLM, LM Studio, llama.cpp, OpenRouter, Groq, Mistral, Moonshot, Qwen, Zhipu
Peer deps Gemini (@google/genai), Bedrock (@aws-sdk/client-bedrock-runtime), MCP (@modelcontextprotocol/sdk), Vercel AI SDK (ai, @ai-sdk/*)
Built-in tools 6: bash, file_read, file_write, file_edit, grep, glob (all default-deny)
Custom tools defineTool(name, description, inputSchema, execute) with Zod validation
MCP support connectMCPTools() for stdio-based MCP servers
Scheduling strategies dependency-first, round-robin, least-busy, capability-match
Max concurrency Default 5, configurable
Context strategies sliding-window, summarize, compact, custom
Checkpoint/resume Per-task snapshots over any MemoryStore
Shared memory In-process KV (default), Redis, Postgres, or custom MemoryStore
Observability onProgress events, onTrace spans, post-run HTML dashboard
Lifecycle hooks beforeRun, afterRun, onPlanReady, onApproval, AbortSignal
Production controls maxTurns, timeoutMs, maxToolOutputChars, maxRetries, retryBackoff, maxTokenBudget, loopDetection
Filesystem sandbox Default .agent-workspace, per-agent via cwd, disable with null
CLI oma binary for shell and CI usage
Scaffold npm create oma-app@latest
Homepage open-multi-agent.com
GitHub github.com/open-multi-agent/open-multi-agent

Vibe Coding Projects

Project 1: PR Review Bot

What it does: A GitHub Actions workflow that runs an OMA review team on every PR. The coordinator decomposes the diff into parallel review tasks (security, style, correctness, performance), each assigned to a specialized agent. Results are posted as a PR comment with a summary and per-category findings.

What you’ll learn: Setting up OMA in CI, using defineTool to create a fetch_pr_diff tool, configuring modelRouting to route security reviews to a stronger model, and integrating with GitHub’s API via the bash tool.

Effort: 2-3 hours. Scaffold with npm create oma-app@latest, add a GitHub Actions workflow, define the review team, and wire the onProgress events to update a PR status check.

Project 2: Offline Research Dashboard

What it does: A local-first research assistant that runs entirely on Ollama. Give it a research question, and it spawns parallel research agents (each with a different search strategy), synthesizes findings, and renders an HTML dashboard with the task DAG, per-agent outputs, and a final report.

What you’ll learn: Configuring OMA for offline use, mixing local models (Llama 3.1 for research, Mistral for synthesis), using contextStrategy: { type: 'compact' } to stay within local model context windows, and rendering the post-run dashboard with renderTeamRunDashboard().

Effort: 3-4 hours. Install Ollama, pull two models, define a research team with 3-4 agents, and build a simple Express server that accepts research questions and returns the dashboard HTML.

Project 3: Multi-Model Content Factory

What it does: A content production pipeline that researches, drafts, edits, and formats blog posts. The researcher uses Claude for deep analysis, the writer uses GPT-4o for creative prose, the editor uses a local model for grammar and style checks, and the formatter uses a budget model for markdown conversion. All in one team.

What you’ll learn: Mixing providers in a single team, using runTasks for a deterministic pipeline, implementing sharedMemory for cross-agent context, and using onApproval for human review gates between stages.

Effort: 4-5 hours. Set up API keys for 2-3 providers, define the content pipeline as a task DAG, add a web UI for submitting content briefs, and wire the onProgress events to a real-time status display.

Problems Solved Efficiently

Problem Type Why Open Multi-Agent Fits When to Look Elsewhere
Goal-driven decomposition Coordinator builds the DAG at runtime — you do not need to know the graph shape upfront Your pipeline is fully deterministic and never changes shape
Multi-provider teams Mix Anthropic + OpenAI + Ollama in one team with per-agent provider config You use a single provider and never plan to switch
Offline/air-gap operation Full offline support via Ollama, vLLM, LM Studio — no internet required You always have cloud access and do not need offline fallback
Human-in-the-loop pipelines Built-in onPlanReady and onApproval gates with no custom infrastructure You need complex multi-round human review workflows with custom UIs
Checkpoint/resume Per-task snapshots over any MemoryStore — survive crashes and restarts Your runs complete in under 30 seconds and never fail mid-way
Rapid prototyping One runTeam(team, goal) call replaces hours of graph wiring You need fine-grained control over every tool call and message
Cost-optimized routing modelRouting routes planning to flagship models, leaf tasks to budget models Your token costs are negligible and optimization is not a concern
Custom tool integration defineTool with Zod schemas, MCP server support, and filesystem sandbox You need no tools beyond basic LLM chat

Architectural Tradeoffs

What We Gained

  • Zero graph wiring. One runTeam(team, goal) call replaces hours of manual node/edge declaration. The coordinator handles decomposition, scheduling, and synthesis.
  • Runtime adaptability. The DAG is built per-run, adapting to each goal. A research task that discovers conflicting sources spawns parallel verification agents automatically.
  • Provider diversity. Mix any combination of 13+ built-in providers plus any OpenAI-compatible endpoint in a single team. No provider lock-in.
  • Offline-first. Full offline capability with local quantized models. 4-8GB VRAM is sufficient for 7B-13B parameter models via Ollama.
  • Lean core. Three runtime dependencies. Installs in under 10 seconds. Minimal attack surface.
  • Production controls. Timeouts, retries, token budgets, loop detection, filesystem sandboxing, and lifecycle hooks are built-in, not afterthoughts.

What We Sacrificed

  • Two extra LLM calls per run. The coordinator runs twice (decomposition + synthesis). For short goals (under 200 characters with no coordination directives), the framework short-circuits and skips both calls. But for complex goals, you pay for two extra LLM invocations.
  • Non-deterministic DAGs. The same goal can produce different task decompositions on different runs. For pipelines that need bit-for-bit reproducibility, use runTasks() with an explicit graph.
  • No built-in web UI. The post-run dashboard is HTML rendered from a completed run. There is no real-time web interface for monitoring active runs. You build that yourself.
  • Python ecosystem gap. This is TypeScript-native. If your stack is Python (CrewAI, AutoGen, LangGraph Python), OMA is not a drop-in replacement.
  • Smaller community. 6,400+ stars is respectable but not LangChain territory. Fewer community plugins, fewer tutorials, fewer Stack Overflow answers.

Real lesson from production: Mark Galyan runs OMA fully offline on quantized models with context compaction under tight VRAM. The key insight: “The coordinator’s decomposition pass is the most important LLM call in the entire run. If it produces a bad DAG, the whole run degrades. We learned to use planOnly to inspect the DAG before execution, and to provide explicit coordinator instructions when the goal is ambiguous.” This is not a framework limitation — it is a design constraint that rewards careful prompt engineering for the coordinator agent.

Course-Style Deep Dive

Under the Hood: How runTeam Turns a Goal into a Task DAG

When you call runTeam(team, goal), the following sequence executes:

  1. Short-circuit check. If the goal is 200 characters or fewer with no coordination directives, the framework picks the best-matching agent and runs it directly. No decomposition, no synthesis. Zero extra LLM calls.

  2. Coordinator decomposition. A temporary coordinator agent is created with the team’s agent names and system prompts. It receives a single instruction: “Decompose this goal into a JSON task array inside a code fence.” Each task has title, description, assignee, and dependsOn. The coordinator runs with a default maxTurns of 3 — enough to refine the plan but not so many that it spirals.

  3. Dependency graph construction. The JSON tasks load into a TaskQueue. Title-based dependsOn references resolve to real task IDs. A task becomes “ready” only after all its dependencies complete. Tasks with empty dependsOn are ready immediately. If the coordinator returns unusable JSON, the framework falls back to one task per agent with the original goal — a degraded run, not an exception.

  4. Scheduling. The Scheduler assigns ready tasks to agents. The default dependency-first strategy processes tasks in dependency order. capability-match scores each agent’s name and system prompt against the task description using embedding similarity.

  5. Parallel execution. The AgentPool runs ready tasks concurrently up to maxConcurrency (default 5). Each agent runs in its own conversation loop with its own tools, context strategy, and timeout. Task outputs are written to shared memory after completion.

  6. Cascade failure. Failed tasks are marked failed; their dependents become blocked. Unaffected branches continue to completion. One error does not tear down the whole graph.

  7. Coordinator synthesis. After the queue drains, the coordinator runs a second time, reading all task outputs from shared memory to produce the final answer. This result is accessible via agentResults.get('coordinator').

  8. Result. runTeam() resolves to a TeamRunResult containing agentResults (map keyed by agent name), totalTokenUsage (token count), and tasks (record list with statuses and metrics).

Advanced Pattern 1: Consensus with Verification

The runConsensus primitive implements a proposer-judge loop with a per-task verify hook and a budget invariant. Use this when you need validated outputs, not just generated ones.

import { OpenMultiAgent, defineTool } from '@open-multi-agent/core'
import { z } from 'zod'

const orchestrator = new OpenMultiAgent({ defaultModel: 'claude-sonnet-4-6' })

const proposer = {
  name: 'proposer',
  model: 'claude-sonnet-4-6',
  systemPrompt: 'Generate solutions that are correct, efficient, and well-documented.',
}

const judge = {
  name: 'judge',
  model: 'claude-opus-4-7',
  systemPrompt: 'Evaluate solutions for correctness, performance, and security. Be strict.',
}

const result = await orchestrator.runConsensus(proposer, judge, {
  goal: 'Design a rate-limiting middleware for Express that supports sliding window counters.',
  maxRounds: 3,
  verify: async (task, output) => {
    // Custom verification logic per task
    const hasImplementation = output.includes('function') || output.includes('class')
    const hasTests = output.includes('describe') || output.includes('test')
    return hasImplementation && hasTests
  },
})

Advanced Pattern 2: Custom Context Strategy for Long-Running Agents

When an agent runs for many turns (code review of a large PR, multi-file refactoring), the conversation history grows beyond the model’s context window. The built-in strategies handle this, but you can also supply a custom compressor.

import type { ContextStrategy } from '@open-multi-agent/core'

const prDiffCompressor: ContextStrategy = {
  type: 'custom',
  compress: (messages, estimatedTokens) => {
    // Keep system prompt and last 2 turns intact
    const systemMessages = messages.filter(m => m.role === 'system')
    const recentMessages = messages.slice(-2)
    // Compress large assistant responses to their first 200 chars
    const compressed = messages
      .filter(m => m.role !== 'system')
      .slice(0, -2)
      .map(m => {
        if (m.role === 'assistant' && typeof m.content === 'string' && m.content.length > 500) {
          return { ...m, content: m.content.slice(0, 200) + '...[compressed]' }
        }
        return m
      })
    return [...systemMessages, ...compressed, ...recentMessages]
  },
}

const agent = {
  name: 'pr-reviewer',
  model: 'claude-sonnet-4-6',
  systemPrompt: 'Review PR diffs for correctness and security.',
  contextStrategy: prDiffCompressor,
  maxTurns: 20,
}

Advanced Pattern 3: MCP Tool Integration

Connect any stdio-based MCP server to expose its tools to your agents. This lets you integrate with databases, APIs, and internal services without writing custom tool definitions.

import { OpenMultiAgent, connectMCPTools } from '@open-multi-agent/core'

const orchestrator = new OpenMultiAgent({ defaultModel: 'claude-sonnet-4-6' })

// Connect to a GitHub MCP server
const mcpTools = await connectMCPTools({
  command: 'npx',
  args: ['@modelcontextprotocol/server-github'],
  env: {
    GITHUB_TOKEN: process.env.GITHUB_TOKEN,
  },
})

const team = orchestrator.createTeam('github-team', {
  name: 'github-team',
  agents: [
    {
      name: 'github-agent',
      model: 'claude-sonnet-4-6',
      systemPrompt: 'You manage GitHub issues and PRs using the available tools.',
      customTools: mcpTools,
      maxTurns: 10,
    },
  ],
})

Production Considerations

  • Set maxTurns per agent. The default is generous. For leaf tasks (style review, formatting), 3-4 turns is sufficient. For complex tasks (multi-file refactoring), 15-20 turns may be needed. Unbounded agents burn tokens.
  • Configure timeoutMs for local models. Ollama models are slower than cloud APIs. Set timeoutMs: 120_000 (2 minutes) for local models to avoid premature timeouts.
  • Use maxToolOutputChars to cap tool output. Tool results can be large (file reads, bash output). Set a per-agent or per-tool limit to prevent context window overflow.
  • Enable compressToolResults. This replaces already-consumed tool results with a short marker before each new LLM call. Default threshold is 500 characters.
  • Use retryBackoff for transient failures. Set maxRetries: 3, retryDelayMs: 1000, and retryBackoff: 'exponential' to handle API rate limits and transient errors gracefully.
  • Set maxTokenBudget on the orchestrator. This caps total token consumption across the entire run. When the budget is exhausted, remaining tasks are marked skipped rather than consuming unbounded tokens.
  • Enable loopDetection. Agents can get stuck in loops. Set loopDetection: { onLoopDetected: 'terminate' } to automatically terminate looping agents.

The Results

Metric Before (Manual Graph Wiring) After (Open Multi-Agent)
Time to set up a new multi-agent pipeline 2-4 hours (design graph, wire nodes, test edges) 10 minutes (define agents, write goal)
Lines of orchestration code 200-500 (node/edge declarations, state management, error handling) 30-50 (agent configs + one runTeam call)
Provider migration effort 1-3 days (rewrite adapters, retest pipeline) 0 days (change provider field in config)
Offline capability None (cloud-dependent) Full (Ollama, vLLM, LM Studio)
Pipeline resilience Manual retry logic Built-in: retry, checkpoint/resume, cascade failure isolation
Observability Custom logging Built-in: onProgress, onTrace, HTML dashboard
Runtime dependencies 10+ (LangChain ecosystem) 3 (@anthropic-ai/sdk, openai, zod)
Human-in-the-loop Custom implementation Built-in: onPlanReady, onApproval

What this means for you: If you are building multi-agent pipelines in TypeScript, Open Multi-Agent eliminates the most expensive part of the workflow — graph design and wiring. The coordinator’s runtime decomposition means your pipeline adapts to each goal rather than forcing the goal to fit a predetermined graph. The provider-agnostic design means you are never locked into a single LLM vendor. The offline support means your pipelines work anywhere, including on a plane with a laptop running Ollama.

The tradeoffs are real: two extra LLM calls per run, non-deterministic DAGs, and a smaller ecosystem. But for the common case — you have a goal, a team of agents, and you want them to work together without hand-wiring a graph — Open Multi-Agent is the most efficient path from goal to result.

What to Watch Out For

  1. The coordinator’s decomposition quality determines everything. If the coordinator produces a bad DAG, the whole run degrades. Always use planOnly to inspect the DAG before execution, especially for complex goals. Provide explicit coordinator instructions when the goal is ambiguous: runTeam(team, goal, { coordinator: { instructions: 'Prefer fewer, larger tasks.' } }).

  2. Local models need generous timeouts. Ollama models are 5-10x slower than cloud APIs for the same task. Set timeoutMs: 120_000 for local models. If you see frequent timeouts, check your Ollama configuration — ensure the model is loaded and the server is responsive.

  3. Tool output can blow your context window. A single file_read on a 10,000-line file produces 10,000 tokens of output. Set maxToolOutputChars per agent and enable compressToolResults. For filesystem tools, the default sandbox (.agent-workspace) limits scope, but it does not limit output size.

  4. Shared memory is not a database. The default in-process KV store is ephemeral. If you need durable state across runs, swap in Redis or Postgres by implementing the MemoryStore interface. Do not rely on in-process memory for production pipelines that span multiple invocations.

  5. Model routing rules are first-match-wins. Order your rules from most to least specific. A broad rule like { match: { leaf: true } } placed before a specific rule like { match: { taskRole: 'security' } } will match security leaf tasks to the cheap model instead of the strong one.

  6. The coordinator’s maxTurns defaults to 3. For complex goals, the coordinator may need more turns to produce a good decomposition. Increase it: runTeam(team, goal, { coordinator: { maxTurns: 5 } }). But be aware that each extra turn costs an LLM call.

  7. API keys and tokens are redacted from traces by default. This is a security feature, not a bug. If you need raw tool output in traces for debugging, you must explicitly opt out. The redaction applies to onTrace spans, bash output, and the HTML dashboard.

Lesson learned from production (PR-Copilot): “We initially ran the coordinator with the same model as the worker agents. The coordinator kept producing overly granular DAGs with 15+ tasks for simple PRs. Switching the coordinator to a stronger model (Opus) and adding explicit instructions (‘Prefer 3-5 tasks maximum’) cut token usage by 40% and improved DAG quality significantly.”

Lesson learned from production (temodar-agent): “Running built-in tools inside Docker required explicit filesystem sandbox configuration. The default .agent-workspace path did not exist in the container. We set defaultCwd: '/workspace' on the orchestrator and mounted the target directory at that path. Without this, the file tools silently failed.”

Getting started advice: Do not start with a complex multi-provider, multi-agent pipeline. Start with two agents and one provider. Get runTeam working with a simple goal. Inspect the DAG with planOnly. Add a third agent. Add a second provider. Add custom tools. Add model routing. Add checkpointing. Each layer is independently testable, and each layer adds complexity only when you need it. The framework’s design rewards incremental adoption — you can use runAgent today, runTeam tomorrow, and runConsensus next week, all from the same OpenMultiAgent instance.


Next in the Open-Source AI Tools Mastery series: Dapr Agents

NL

Written by Nivant Labs Team

Engineer at Nivant Labs

Share this post