Files
open-multi-agent/packages/core/README.md
T
Jack Chen e36cd1f4c0 feat(core): add authoritative run store and execution leases (#587)
A checkpoint records what a run can resume from, not who is allowed to
advance it. Two processes can restore the same snapshot and both continue
it, and a worker that stalled past its own liveness can wake up and
overwrite the state a replacement has since written.

Add an opt-in `runStore`: one authoritative, versioned record per logical
run holding its lifecycle status, execution lease, and a monotonic fencing
token.

- `RunRecord` keeps `version` for optimistic concurrency and `fencingToken`
  for ownership, so a worker holding an old token stays detectable across
  any number of unrelated writes. Legal transitions are enumerated and
  terminal statuses have no outgoing edge.
- `RunStore` is three methods plus an explicit `atomicity` declaration.
  Lease expiry, fencing, transition legality, and command idempotency live
  above the seam in `RunLedger`, so a Postgres or Redis backend reproduces
  storage semantics rather than a state machine.
- `MemoryStoreRunStore` adapts any `MemoryStore` with compare-and-set and
  defaults to `atomicity: 'process'`. `FileStore` remains the sequential
  restart reference store and is not presented as a lease backend.
- The orchestrator acquires the lease before it dispatches, fences every
  checkpoint write with its token, stops the run at the dispatch gate when
  the lease is lost, and records the terminal transition. A worker that
  lost its lease writes no terminal status and never reports success.
- `restore()` is the resume command: it makes a suspended record eligible
  and takes ownership before reconciling the approval ledger.

Ownership and lifecycle writes fail closed rather than inheriting the
best-effort semantics of checkpoint and telemetry writes. With no run store
configured, existing single-process checkpoint and restore behavior is
unchanged.

Ships a reusable `RunStore` conformance suite covering create races,
version-matched compare-and-set, lease expiry, takeover, stale-writer
rejection, and terminal closure, so an out-of-repository backend can prove
the same contract.

Closes #481
2026-09-07 02:35:32 +08:00

26 KiB


Open Multi-Agent

Describe the goal, not the graph.
A self-organizing team of agents that runs in your environment, pauses for approval on consequential actions, and leaves a verifiable record of every run.

npm version Node.js version CI Supply chain audit MIT License codecov

Website · Docs · npm · Discussions

English · 中文


@open-multi-agent/core is the OMA orchestration runtime for TypeScript backends. Give it one agent, an explicit task graph, or a dynamic workflow that the coordinator generates from a goal at runtime.

The runtime schedules dependencies, runs independent work in parallel, shares context across agents, and returns an inspectable result. For product positioning and known users, see the project overview.

Contents

Quick Start · Execution Modes · Scheduling · Capabilities · Architecture · Examples · Providers · Production · Documentation

Quick Start

Requires Node.js 20 or newer. For production, use a currently maintained Node.js LTS release. Node.js 20 is upstream-EOL and retained only as a migration compatibility window; OMA will remove it in the next major release, no earlier than 2026-10-31. Scaffold and run a starter in one command:

npm create oma-app@latest my-oma

In an interactive terminal, the scaffolder selects a starter and Cloud/Ollama runtime, installs dependencies, then runs a deterministic demo and produces an offline dashboard. The demo uses scripted model responses, needs no API key, and makes no model request; OMA orchestration still runs locally for real. Pass --no-install to generate files only, or --no-run to install without starting the demo.

To add OMA to an existing backend:

npm install @open-multi-agent/core
import { OpenMultiAgent, type AgentConfig } from '@open-multi-agent/core'

const model = process.env.OMA_MODEL ?? 'gpt-5.4'

const agents: AgentConfig[] = [
  { name: 'researcher', model, systemPrompt: 'Find the relevant facts.' },
  { name: 'analyst', model, systemPrompt: 'Compare evidence and identify tradeoffs.' },
]

const orchestrator = new OpenMultiAgent({
  defaultProvider: 'openai',
  defaultModel: model,
})

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

const result = await orchestrator.runTeam(team, 'Compare three approaches and recommend one.')
console.log(result.agentResults.get('coordinator')?.output)
Pause consequential tool calls for approval
import { FileStore, OpenMultiAgent } from '@open-multi-agent/core'

// Your keys and your endpoint: a hosted provider, or a local server through baseURL.
const oma = new OpenMultiAgent({
  defaultProvider: 'openai',
  defaultModel: 'gpt-5.4',
  // Consequential tool calls (file writes, shell) pause for a human decision.
  onToolCall: ({ consequential }) => (consequential ? { action: 'suspend' } : { action: 'allow' }),
})

const team = oma.createTeam('ops', {
  name: 'ops',
  agents: [{ name: 'operator', systemPrompt: 'Reconcile overdue invoices.', toolPreset: 'readwrite' }],
})

// The coordinator plans the task DAG from the goal; the checkpoint store keeps the run durable.
const result = await oma.runTeam(team, 'Find overdue invoices and draft the reminders.', {
  checkpoint: { store: new FileStore('./.oma/run.json') },
})

// result.status?.code === 'suspended' until a reviewer decides result.pendingApprovals,
// each bound to a hash of exactly what the reviewer was shown.

Set OPENAI_API_KEY for this example. For other hosted or local models, see Providers.

Execution Modes

Mode Method When to use Example
Single agent runAgent() One agent, one prompt basics/single-agent
Auto-orchestrated team runTeam() Give a goal, let the coordinator plan and execute basics/team-collaboration
Explicit pipeline runTasks() You define the task graph and assignments basics/task-pipeline

Use planOnly to inspect a generated task graph before execution, then createPlanArtifact() and runFromPlan() to replay it. runConsensus() adds a proposer→judge verification loop when one answer needs extra scrutiny.

Structured single-agent input

Agent.run(), Agent.stream(), and OpenMultiAgent.runAgent() keep the string form above and also accept a complete LLMMessage[], for caller-owned conversation history or blocks such as base64 images. Structured input is validated and defensively copied, and process and ACP backends stay string-only: they reject structured arguments rather than discarding history or images. See structured agent input for copy, hook, and external-backend semantics, or run basics/structured-input.

Execution routing

runTeam() uses the deterministic router by default and makes no extra model call. executionRouting: { strategy: 'hybrid' } keeps deterministic Team decisions and sends only Single candidates to a one-call, no-tool TaskProfiler; results then expose routingDecision and semanticRoutingAssessment. The Profiler falls back to the Coordinator adapter and then the orchestrator's default provider, so it can make a provider call even when every worker has its own adapter. See execution routing for that provider boundary and the full policy precedence; model routing selects models inside the chosen topology.

Declared governance roles

When an application must enforce named independent roles, declare that governance intent instead of relying on wording in the goal:

const governed = await orchestrator.runTeam(team, 'Review the evidence and assess the risk.', {
  governanceIntent: 'required',
  requiredRoles: ['researcher', 'analyst'],
  requiredOrder: ['researcher', 'analyst'],
})

if (governed.governanceConclusion !== 'satisfied') {
  throw new Error('Required governance was not satisfied by the executed topology.')
}

The topology comes only from these structured fields, so equivalent goals in different languages produce the same roles and order. governanceConclusion comes from the structured execution receipt rather than from role names or approval wording in the model answer, so governance-sensitive applications must check it separately from success. See declared governance roles.

Scheduling

Set schedulingStrategy on OpenMultiAgent to choose how unassigned tasks are mapped to agents. The setting applies to coordinator-generated runTeam() plans and explicit or restored task queues. Tasks with an explicit assignee keep that assignment.

Task DAG execution is event-driven: a downstream task starts as soon as its dependencies are satisfied, without waiting for unrelated tasks from the same ready set, and dependency outputs reach dependents as task-scoped results and validated structured handoffs.

const orchestrator = new OpenMultiAgent({
  schedulingStrategy: 'composite',
  schedulingWeights: { fit: 0.7, load: 0.3 },
})
Strategy Assignment behavior Recommended when
dependency-first (default) Assigns tasks that unblock the most downstream work first, rotating eligible agents The task graph has meaningful dependencies
round-robin Distributes tasks in queue order across eligible agents Agents are interchangeable
least-busy Chooses the eligible agent with the fewest active or newly assigned tasks Task duration varies and load balance matters
capability-match Filters explicit task requirements, then prefers declared capability tags before legacy keyword affinity Tasks or agents declare differentiated requirements/capabilities
composite Ranks tasks by blocked dependents, then maximizes fit and available capacity across eligible agents Criticality, capability fit, and current load should influence one decision

Agents may declare description, capabilities, costTier, and latencyClass, and tasks may add hard requires constraints; every strategy fails before worker execution when they cannot be satisfied. Weight semantics, load normalization, strictAssignees, and the NO_ELIGIBLE_AGENT and INVALID_ASSIGNEE failure modes are covered in task scheduling and dispatch.

Capabilities

Capability What you get
Dynamic orchestration Runtime goal decomposition, dependency-aware scheduling, parallel branches, configurable assignment, task-scoped results and handoffs, opt-in team context for workers (revealCoordinator), and final synthesis.
Models and reasoning Mix built-in, OpenAI-compatible, AI SDK, or local models; map one thinking config to each provider's reasoning setting, route phases separately, and preserve reasoning only when explicitly enabled.
Tools and handoffs Built-in tools are default-deny; custom tools, MCP, and guarded delegate_to_agent handoffs are opt-in, and consequential tools on undeclared runs are flagged for confirmation.
Controlled outputs Send text or structured single-agent input, stream per agent, validate results with Zod, approve or durably suspend plans, task rounds, dispatches, and tool calls, rewrite messages/prompts or post-process results with beforeRun / afterRun, and cancel with AbortSignal.
Evaluation Version EvalSets, run reference scorers, gate CI with offline reports, persist results, or sample production runs on a best-effort path.
Memory and recovery Shared memory is pluggable; checkpoints resume interrupted runs without repeating completed tasks.
Observability Stable run identity, traces, execution receipts, redaction, TraceStore, and the offline DAG/Waterfall Viewer are available without a hosted service.
External agents ACP and process backends let coding CLIs participate while OMA keeps scheduling, memory, and budgets; the per-call tool gate, filesystem sandbox, and LLM egress policy do not cover them.

Architecture

goal or explicit tasks
         |
         v
Coordinator -> Task DAG -> Scheduler -> AgentPool
                    |                       |-- LLM adapters
                    |                       `-- tools / external backends
                    |
                    |-- SharedMemory / checkpoints
                    |-- TraceRecord -> TraceStore / Run Viewer / OTel
                    `-- results -> evaluation (offline / sampled, observe-only)

The coordinator plans once by default; the scheduler owns execution order. Applications can opt into append-only adaptive recovery when task outcomes need to revise the unstarted part of the graph. Agents share results through memory, while checkpoints and traces form separate recovery and observability paths. Evaluation observes completed results and never changes them. Detailed contracts live in the linked subsystem guides below.

Examples

Start with one example that matches the behavior you need:

Goal Example
Send image blocks and caller-owned history basics/structured-input
See coordinator planning basics/team-collaboration
Build an explicit DAG cookbook/contract-review-dag
Observe event-driven DAG dispatch patterns/event-driven-dag
Validate structured output patterns/structured-output
Delegate between agents patterns/agent-handoff
Replay a frozen plan patterns/plan-replay
Suspend and resume an approval patterns/durable-approval
Embed OMA in a backend integrations/express-customer-support
Export an offline trace viewer integrations/observability-v2/run-viewer

The example index lists every runnable example across basics, cookbook workflows, patterns, providers, and integrations.

Providers

Change provider, model, and credentials; the agent shape stays the same.

Route Use
Built in Anthropic, OpenAI, Azure OpenAI, Copilot, Grok, DeepSeek, Doubao, Hunyuan, MiniMax, MiMo, Qiniu
Optional peers Gemini (@google/genai) and Bedrock (@aws-sdk/client-bedrock-runtime)
OpenAI-compatible Set provider: 'openai' + baseURL for Ollama, vLLM, LM Studio, OpenRouter, Groq, Mistral, Kimi, Qwen, or Zhipu
AI SDK Use AISdkAdapter with ai and your selected @ai-sdk/* provider (AI SDK 7 needs Node.js 22+)

Optional integrations load only when used: core directly installs only @anthropic-ai/sdk, openai, and zod; other SDKs are lazy-loading opt-in peers, and OpenTelemetry lives entirely in @open-multi-agent/otel. Dependency changes are weighed on demonstrated value plus security, size, maintenance, and compatibility cost, not a fixed count.

See Providers, framework-owned LLM egress policy, Self-hosting and data residency, and Tool configuration for credentials, models, the AI SDK bridge, reasoning settings, MCP, local endpoints, self-hosted deployment, and the exact network-enforcement boundary.

Provider sponsors

Paid sponsors supporting open-multi-agent. Sponsorship does not affect technical decisions or model recommendations.

  • Atlas Cloud: Full-modal AI inference platform giving one API for video, image, and LLM across 300+ curated models. $5 credit vouchers for OMA users, first come first served. See the Atlas Cloud setup guide.

Production

Goal Configure
Bound work maxTurns, timeoutMs, callTimeoutMs, contextStrategy, loopDetection
Control spend maxTokenBudget; maxCostBudget + application-owned estimateCost
Limit tools tools / toolPreset, cwd / defaultCwd, tool-output caps
Recover Task retries, checkpointing, restore(), and opt-in adaptive plan repair
Own a run across workers Opt-in runStore: one execution lease per run, fenced checkpoint writes, durable lifecycle
Review work planOnly, inline approval callbacks, or durable approval gates; your application owns the approval surface and transport
Observe Trace sinks, TraceStore, execution receipts, Run Viewer, or the optional OTel adapter

Budget checks run at turn and task boundaries, so a run can overshoot by up to one model turn; they are not a cent-exact stop. estimateCost receives each call's token usage plus the agent, effective model, provider, phase, and taskId, and your application owns the price table. Budgets and limits covers every ceiling, where it is checked, and what happens when one trips.

Built-in tools are default-deny, and every model-visible tool result is sent to your model provider, so grant read and exec access deliberately. Tools may keep application-owned data separate while returning text, image, or file content through modelOutput; see the tool configuration guide. Filesystem tools stay within the configured cwd; granted bash is not sandboxed. Its execution target can be replaced through a ShellExecutor, while the default LocalShellExecutor preserves host execution and is not a security boundary. Secrets are redacted from traces, shell output, and Viewer payloads by default, but result messages and checkpoints have their own persistence boundary.

Observability

Core already provides run identity, trace sinks, execution receipts, queryable in-memory/file stores, and an offline Run Viewer. These cover local debugging, audit artifacts, and post-run analysis without OpenTelemetry.

@open-multi-agent/otel is an optional enterprise integration for teams that already operate a centralized OpenTelemetry stack. It converts OMA traces into standard OTel spans so multi-agent runs can join company-wide monitoring, alerting, and incident workflows. The application owns the provider and its lifecycle; telemetry failures never change the run result.

See the observability guide and the migration guide.

OMA Run Viewer replaying a real multi-agent run: task DAG and span waterfall views with per-task status, assignee, tokens, and tool calls

The offline Run Viewer replaying a real run from the trace store: task DAG, span waterfall, and per-task evidence, with no hosted service involved.

Run store and execution leases

A checkpoint says what a run can resume from; it does not say who is allowed to resume it, so two workers can load the same snapshot and both advance it. The opt-in runStore adds the missing authority: one authoritative record per run holding its lifecycle status, an execution lease, and a monotonic fencing token. A worker acquires the lease before it dispatches anything, every checkpoint write is fenced with its token, and a run taken over by another worker stops instead of overwriting the new owner's state. Suspended runs stop depending on a live process, and an operator can cancel or resume one from outside the worker. Off by default, unchanged behavior when off, and documented in the run store guide.

Run journal

When a long run goes wrong, the record usually missing is what each agent actually saw at the moment it was asked. The opt-in run journal keeps it: every message and tool result as an appended event, plus the exact block a context strategy put in place of the turns it dropped, so a finished run can be read back instead of reconstructed by guesswork. verifyRun() then checks offline that every block the model saw is reproducible from the log rather than trusting the log's own account of itself, which establishes order and lineage rather than tamper-evidence, and restore() can resume from the last appended event instead of the last snapshot. It is off by default, costs nothing when off, and is documented in the run journal guide.

Documentation

Area Guides
Build agents Providers, structured input, tools, sandbox and shell, MCP, context
Run reliably Evaluation, evaluation in CI, checkpoint & resume, run store and leases, durable approvals, adaptive recovery, execution routing, model routing, consensus, errors
Control workflows Coordinator, plan preview & replay, shared memory, hooks and callbacks, streaming, budgets and limits, external agents
Operate Observability, Run Viewer, CLI, production checklist, glossary, production examples

Contributing

Issues and PRs are welcome. For production examples, follow the acceptance criteria; for code changes, see the contribution guide.

Contributors

Per-contributor credits by area are in CONTRIBUTORS.md.

License

MIT

Maintained by YuanASI (Shenzhen YuanASI Technology Co., Ltd.).