The run loop is swappable. Which engine you pick is a question about where the loop runs, not a feature checklist.
Six engines ship today, in two kinds. A native engine runs the agent loop in your process, so it reuses the full Night Owls governance plane verbatim. An adapter fronts a loop that runs on a remote product you don't own — which is genuinely useful, but means the pre-generation veto, the tool gate, and cost caps cannot reach inside it. That is not a maturity gap to be closed; it is where the loop physically executes. This guide is how to choose, and where durability actually comes from.
The one axis that decides everything: where the loop runs
Governance in Night Owls is enforced around the generation call — a cost cap checked before the next step, a fail-closed tool gate that can refuse a call, a pre-generation veto, secret injection scoped to the run. All of that only works if the loop is in your process, where the framework sits between the model and the tools. A native engine is exactly that: the loop runs in-process, so the governance plane wraps it unchanged. An adapter hands the loop to a remote runtime (an A2A endpoint, a deployed Trigger chat.agent Session, a Vercel Eve session); the framework can relay a request and observe results, but it cannot step between the remote model and the remote tools. Reduced governance on an adapter is structural, not a second-class implementation.
"chat.agent has fewer features than Mastra" is true, and it is supposed to be. An adapter is not a weaker port of the native engine that will one day catch up. It is a bridge to a loop running somewhere the governance plane cannot follow. If you need the full plane, run a native engine — and if you also need durability, bundle a native engine into the background runner (step 04). If you specifically need to front an existing remote agent product, an adapter is the right tool, and its descriptor states in code precisely what it cannot enforce so nothing is hidden.
The capability matrix — every engine, honestly
This is the same descriptor data the home page and the Studio UI read, rendered here so you can choose from one table. Every engine publishes an honest capability descriptor; the UI gates on it, so a surface an adapter can't support is disabled rather than silently degraded.
| engine | run loop | multi-agent | governance | events | durable resume |
|---|---|---|---|---|---|
| Native, 3 engines · the loop runs in your process; the full governance plane is reused verbatim: fail-closed tool gate, cost caps, secrets, telemetry | |||||
| engine-mastra default | Mastra agent loop, in process | full delegation + workflows | full plane | tier 3 | yes, with semantic recall |
| engine-ai-sdk | governed AI SDK streamText loop | single-agent in v1 | full plane; approvals map to native needsApproval | tier 3 | yes |
| engine-openai-agents | governed @openai/agents run() | single-agent in v1; handoffs become delegation in v1.1 | full plane, OTel | tier 3 | yes |
| Adapter, 3 engines · the loop runs on a remote product, so our pre-generation veto, tool gate, and cost caps cannot reach it. The descriptor says so, and the UI gates on it. | |||||
| engine-a2a | any A2A v0.3-wire endpoint: Bedrock AgentCore, Azure AI Foundry, Google ADK | on the remote | relayed, reduced | tier 1 | on the remote |
| engine-trigger-chat ⚠ experimental | a deployed Trigger chat.agent session (NOT a GA Trigger primitive) | on the remote | relayed, reduced | tier 2 | parks on Trigger's runtime (for GA durability use runner-background) |
| engine-eve | a deployed Vercel Eve NDJSON session | on the remote | relayed, reduced | tier 2 | parks on Vercel Workflows |
All six are on npm now. Native means the loop executes in your process with the full Night Owls governance plane. Adapter means the loop fronts a remote runtime with reduced governance and coarser events; each descriptor states exactly what it cannot enforce.
How to choose
Answer two questions in order — do I need the full governance plane? and does the loop already run somewhere else? — and the engine falls out.
| If you want… | Pick | Why |
|---|---|---|
| The default: full governance, interactive, least config | engine-mastra (omit engine) | In-process loop, full plane, tier-3 events, durable resume with semantic recall. This is the baseline every other row is measured against. |
| The AI SDK loop, still fully governed in-process | engine-ai-sdk | Native: streamText under the full plane; approvals map to the SDK's needsApproval. Single-agent in v1. |
| An OpenAI Agents SDK loop, fully governed in-process | engine-openai-agents | Native, full plane. Single-agent in v1; the SDK's native handoffs become delegation in v1.1. |
| A run that parks on a human for days and survives a restart, with full governance | runner-background + a native engine | Durability is a runner, not an engine. A native engine bundled into the durable runner keeps the whole plane while it parks on Trigger's durable primitives. See step 04. |
| To front an existing remote agent (Bedrock AgentCore, Azure AI Foundry, Google ADK) | engine-a2a | Adapter: any A2A v0.3-wire endpoint appears as a Night Owls agent. Governance is relayed and reduced because the loop runs on the remote — by design. |
| To front a deployed Trigger chat.agent Session you already run | engine-trigger-chat experimental | Adapter, tier-2. For a durable run you own, use runner-background instead (step 04) — that is the first-class Trigger-durability path. |
| To front a deployed Vercel Eve NDJSON session | engine-eve | Adapter, tier-2; durability parks on Vercel Workflows on the remote. Reduced governance, by design. |
import { defineSwarm } from "@nightowlsdev/core";
// 1) The default. Omit `engine` entirely and you get the built-in Mastra engine, byte-identical —
// full delegation, workflows, the whole governance plane, tier-3 events, durable resume.
const swarm = defineSwarm({ /* … model, agents, tools … */ });
// 2) A different NATIVE loop, same full governance in-process. You still own the loop, so cost caps,
// the fail-closed tool gate, the pre-generation veto, and secrets all still apply.
import { aiSdkEngine } from "@nightowlsdev/engine-ai-sdk";
const onAiSdk = defineSwarm({ engine: aiSdkEngine({ /* … */ }), /* … */ });
// 3) An ADAPTER — you are fronting a loop that runs on a remote product. The descriptor says, in code,
// exactly what governance it cannot enforce, and the Studio UI gates on that descriptor.
import { a2aEngine } from "@nightowlsdev/engine-a2a";
const overRemote = defineSwarm({ engine: a2aEngine({ endpoint: "https://…/a2a" }), /* … */ });Durability is a separate axis — and the governed path is a native engine
The most common confusion in the matrix is reading the durable resume column as an engine ranking. It isn't. "Can this run park on a human for days and survive the process dying?" is answered by the runner, not the engine. The interactive runner streams the loop to the browser and dies with the request; the background runner (@nightowlsdev/runner-background) parks a run with zero compute and resumes it later — and crucially, it parks a native engine, so you keep the entire governance plane while durable.
// "I want a run that can park on a human for days and survive a restart" is a DIFFERENT question
// from "which engine". Durability is not an engine feature — it is a RUNNER. The first-class,
// fully-governed durable path is a NATIVE engine bundled into @nightowlsdev/runner-background:
import { createBackgroundRunner, createTriggerBackend } from "@nightowlsdev/runner-background";
import { SwarmEngine } from "@nightowlsdev/core"; // the native Mastra engine
const runner = createBackgroundRunner({
engine: new SwarmEngine({ /* … */ }), // ← native ⇒ FULL governance, even while durable
storage, // cross-process Postgres snapshot to resume from
backend: createTriggerBackend(swarmRun), // Trigger v4 durable tasks + wait tokens (GA)
});
// This is the answer to "I need Trigger durability." It is NOT @nightowlsdev/engine-trigger-chat.
// engine-trigger-chat is an ADAPTER that fronts a deployed Trigger chat.agent Session (reduced
// governance, the loop runs on Trigger). runner-background parks a NATIVE loop on Trigger's durable
// PRIMITIVES (tasks + wait tokens) — you keep the whole governance plane AND get durability.So "I need Trigger durability" and "I use engine-trigger-chat" are different decisions. runner-background rides Trigger v4's durable primitives (tasks + wait tokens) and parks a native loop — full governance, durable, GA. engine-trigger-chat is an adapter that fronts Trigger's chat.agent Sessions product — reduced governance, because that loop runs on Trigger. Reach for the adapter only when the Session is the thing you already have and want to expose; reach for the background runner when you want to own a durable, governed run.
You give up nothing by going through the engine wall
The engine wall
Only @nightowlsdev/core imports the Mastra engine, and no package re-exports a @mastra/* type in its public .d.ts. The boundary is linted in CI. You only ever see Night Owls types, which is exactly what keeps the engine swappable.
You give up nothing by going through the wall
The default engine IS Mastra: omit engine and you get it, byte-identical. Everything Mastra exposes, full delegation and workflows, the full governance plane (fail-closed tool gate, cost caps, secrets, telemetry), tier-3 event granularity, and durable resume with semantic recall, is reachable through Night Owls' own SwarmContext / SwarmEvent / Runner / StorageAdapter types, with zero @mastra/* in any public .d.ts. Delegation, workflows, and semantic recall are Mastra-engine features today; the other native engines are honest that they run single-agent in v1.
What durable means here
durable: true needs a backend, Trigger.dev v4 or Vercel Workflow, wire exactly one, plus storage that persists suspend and resume snapshots, such as storage-supabase. The in-memory store is not durable across a restart, and we will not pretend otherwise.
Where to go next
The durable background runner
The first-class governed-and-durable path in full: the three-verb backend seam over Trigger v4, Vercel Workflow, and in-memory, and the stuck-run reaper.
Approval modes
One of the governance surfaces a native engine enforces and an adapter can only relay: the tool risk vector and the manual/auto/permissive gate.
Audit every LLM call
Every generation the framework runs is metered and can be recorded opt-in — and the same page states exactly what an adapter's remote loop can and cannot report.
Building on Night Owls? See the source on GitHub.