# Understanding OMA before planning anything else **Objective set by the person, 2026-11-09:** understand `@open-multi-agent/core` fully, then build a plan against it. Not a rewrite. Not a second engine. ## What OMA is, first facts - Package: `@open-multi-agent/core`, version 1.23.0, installed at `/home/sam/paseo/harness/phase2/node_modules/@open-multi-agent/core`. - Its own description: "Self-hosted TypeScript agent runtime with durable approvals and verifiable run records." - It ships a CLI, `oma`, with `run`, `task`, `dashboard`, `eval run`, `eval gate`, `provider`. - It exports, among others, `observability`, `observability/file`, `acp`, `process`, `mcp`, `ai-sdk`, `classifiers`, `shell`, `eval`, `eval/file`. ## Why this document exists The harness grew a second implementation of things OMA already provides: an approval path, a viewer, a task starter, a card renderer. That is why faults arrived in threes and why the same question had two answers. This document records what OMA actually does, with evidence, so that the next plan puts each job in one place. ## Rules for the work that follows 1. The harness is the cockpit: pages, panes, notes, project files, the decision. 2. OMA is the engine: coordinator, task graph, approvals, run records, the graph. 3. Paperclip is inspiration, not a blueprint: approvals as objects, blockers as edges, a plan attached to each task, every action tied to its run. 4. Nothing is deleted until its replacement has run a real task and the result can be shown. ## Findings (to be filled from the research: CLI, team/tasks schemas, approval API, eval gate, trace store, process backend, upstream docs) Read from the installed package (v1.23.0) on 2026-11-09. Evidence is `phase2/node_modules/@open-multi-agent/core/dist/...`. ### The CLI (`dist/cli/oma.js`) - `oma run --goal --team [--orchestrator ] [--coordinator ]` - `oma task --file [--team ]` - `oma dashboard --trace-store --run-id [--output ]` - `oma eval run --set --target [--scorers ] [--repeats n] [--concurrency n] [--tags a,b] [--report json|markdown|junit]... [--out dir] [--meta k=v]... [--gate ] [--baseline ]` - `oma eval gate --report --gate [--baseline ]` - `oma provider [list | template ]` - Exit codes: `0` success, `1` run/eval gate failed, `2` usage/validation, `3` internal. - `team.json` may be a `TeamConfig` or `{ team, orchestrator }`. `tasks.json` is `{ team, tasks, orchestrator? }`. ### Task shape (`types.d.ts:1341-1377`, `RunTaskSpec`) `title`, `description`, `assignee?`, `dependsOn?: string[]`, `memoryScope?: 'dependencies' | 'all'`, `dependencyPayload?: 'output' | 'structured' | 'both'`, `metadata?`, `maxRetries?`, `retryDelayMs?`, `retryBackoff?`, `role?`, `priority?: low|normal|high|critical`, `requires?` (requiredTools, requiredCapabilities, requiredBackend, requiredProvider), and **`verify?: ConsensusVerifyOptions`**. `memoryScope: 'dependencies'` and `dependencyPayload` mean OMA itself decides what a task sees of its predecessors — we were assembling that by hand. ### Verification per task — `ConsensusVerifyOptions` (`types.d.ts:1883-1898`) `judges: AgentConfig[]`, `mode: 'refute' | 'lens'`, `quorum` (default ceil(n/2)), `maxRounds` (default 2), `verdictSchema` (zod; a schema failure counts as dissent), `onDissent: 'revise' | 'reject' | 'keep'`, `judgePrompt`. Result: `verdict: 'accepted' | 'rejected'`, plus `dissent[]` and `rounds`. That is a review gate already built into the runtime, per task. Our hand-written `gate`/`ocr` path overlaps it and is not aware of it. ### Approvals — durable, scoped, exact (`types.d.ts:318-390`, `approval/durable.d.ts`) - `ApprovalScope = 'plan' | 'task_round' | 'task_dispatch' | 'tool_call'`. - `ApprovalRequest` is immutable and **content-bound**: `id`, `runId`, `scope`, `boundary`, `requestHash` (SHA-256 of scope+boundary+content), `content`. - `PlanApprovalContent` carries the tasks (`TaskSnapshot[]`) and `continuation: 'execute' | 'plan_only'` — the same plan can either resume execution or be returned for a person to read. - `ApprovalDecisionRecord`: `decision: 'approved' | 'rejected'`, `reviewer{id,displayName}`, `decidedAt`, bound to the request hash. - `DurableApprovalLedger` stores rows in a `MemoryStore` under `__oma_approval__/`, with atomic decision writes and stable error codes (`APPROVAL_STALE_DECISION`, `APPROVAL_CONFLICT`, `APPROVAL_INTEGRITY_ERROR`). ### The gates the runtime itself calls (`types.d.ts:2194-2246`) - **`onPlanReady(tasks)`** — once, after the coordinator decomposes the goal, before anything runs. Return `true/allow` to proceed, `false/deny` to abort, or **`suspend`** to persist the exact plan for a later decision. - **`onTaskDispatch(task)`** — immediately before a ready task is dispatched. `deny` stops new dispatches; running tasks settle; the rest are marked `skipped`. - **`onApproval(completed, next)`** — between rounds (legacy executor; mutually exclusive with `onTaskDispatch`). - `requireConsequentialConfirmation` + `onToolCall` — gate consequential tool calls. - `PlanPatch` (`types.d.ts:1397`) — append-only repair of the not-yet-executed part of the graph: `addTasks`, `retargetPending`, `supersedePending`. The executed topology is immutable. ### Trace events (`types.d.ts:2017`) `agent_start | agent_complete | task_start | task_complete | task_skipped | task_retry | approval_pending | plan_revision | recovery_decision | budget_exceeded | message | warning | error`, in a `FileTraceStore`, exported per run by `oma dashboard`. ### What this changes 1. **The approval we hand-built is a weaker copy of `onPlanReady` + `suspend` + `DurableApprovalLedger`.** OMA's version is content-bound (a changed plan cannot be approved by a stale decision), scoped, and recorded with a reviewer identity. 2. **Per-task checks belong in `verify`, not in a side script.** A task can carry its own judges, quorum and dissent policy, and the result is `accepted | rejected` with the dissent text. 3. **Dependencies and what a task sees of them are OMA's job** (`dependsOn`, `memoryScope`, `dependencyPayload`), not ours to re-derive — which is why the re-plan lost them and nothing noticed. 4. **`oma task --file tasks.json` is the supported way to run a plan you already have**, which is what `bin/run-start` tried to be. 5. **Still ours to own:** the pages, the pane layout, the notes, the project files, the wording of the checks, and the decision surface — plus the graph view, where we add per-task state and the verdict on top of OMA's data rather than drawing a second graph from a different source. ### Questions the research did not settle - Which gates are available when the CLI is used, versus only through the library (`onPlanReady` is documented as `runTeam()` only). - How a `suspend` decision is later resumed from the CLI, as opposed to in-process. - Whether `verify` judges can run shell commands (our checks are commands), or only reason over the task output. ## Upstream documentation (researched 2026-11-09) - npm `@open-multi-agent/core@1.23.0`, published 2026-10-29. MIT. Author: Shenzhen YuanASI Technology. One npm maintainer. ~3,100 downloads in the last month. - Repository `github.com/open-multi-agent/open-multi-agent`, subdirectory `packages/core`, tag `v1.23.0`. - **There is real documentation**, not just a README: 40+ guides under `docs/`, an examples tree (`basics/`, `cookbook/`, `patterns/`, `providers/`, `integrations/`), and a docs site at open-multi-agent.com. - The pages that matter to us: `docs/cli.md` (team/tasks files), `docs/coordinator.md`, `docs/durable-approvals.md`, `docs/evaluation-ci.md`, `docs/observability.md`, `docs/run-viewer.md`, `docs/external-agents.md`, `docs/glossary.md`. ### Planning, from the docs `runTeam(team, goal)` is the only planning entry point. One coordinator model call produces a JSON array of task specs, validated against a Zod schema generated from the live roster, with one corrective retry. Caps: 200 tasks, 200 dependencies. An invalid plan **fails the run** (`COORDINATOR_PLAN_INVALID`, `INVALID_ASSIGNEE`, `INVALID_TASK_DEPENDENCIES`) and is never downgraded to a single agent. Ordering is then OMA's (`TaskQueue` + `Scheduler`); the coordinator does not re-engage mid-run. A deterministic router short-circuits simple goals to a single agent — that is why `mode: 'team'` is required for the split to happen at all. ### Approvals Four boundaries: `onPlanReady`, `onApproval`, `onTaskDispatch`, `onToolCall`; each may return `allow`, `deny` or `suspend`. Suspension needs checkpointing and a `MemoryStore` with atomic `compareAndSet`. Decisions are first-wins. The docs are explicit that the hash is **not** a cryptographic signature against storage administration. ### External agents `process` (command; stdin/argv in, stdout/exit code out) and `acp` (JSON-RPC over stdio; Gemini CLI, Claude Code, Codex). Important limit from the docs: the tool gate, filesystem sandbox and LLM egress policy **do not apply** to external agents. ### Verifiable records — with honest limits Three separate things: an execution receipt (pure function, no I/O), an opt-in run journal (`verifyRun()` checks sequence monotonicity and per-block hashes), and the trace store plus static HTML run viewer. The docs state plainly: no hash chain, no signature, no WORM storage — a writer with file access can rewrite an event and recompute its hash. ### Coding use Generic orchestration, with coding documented as a use case ("hybrid team": LLM planner → external coding CLI → LLM reviewer over the diff). Pattern examples include `multi-perspective-code-review`, `risk-gated-bash`, `external-agent-acp`. ### Gaps this research settled - `onApproval` and `onTaskDispatch` are **library-only**: function options cannot be passed through the CLI. So a CLI-driven run cannot use our gates; the coordinator must be our own small Node process calling the library. - Our earlier note about `oma eval gate --repo` is not in the upstream docs; only `oma eval run --gate `.