@nightowlsdev/core
EngineMastraThe engine core: define agents, skills, tools and swarms, then run them, the @mastra-backed brain behind every Night Owls host.
What it does
@nightowlsdev/core is the runtime heart of Night Owls. You declare tools (defineTool), bundle them into skills (defineSkill), declare agents with a personality and skill set (defineAgent), and compose them into a runnable swarm (defineSwarm); the SwarmEngine then executes runs and durable resumes, emitting a typed SwarmEvent stream. It also bundles the cross-cutting machinery hosts configure on a swarm: cost/budget governance (CostGovernor, per-delegate budgets, price tables), the Swift/Genius cheap-model tier router, telemetry composition, run-scoped secrets, an in-memory storage/container floor, and the decision-hook substrate (re-exported from @nightowlsdev/hooks so hosts wire SwarmConfig.hooks from one import). It depends on @mastra/* internally but is built so its public .d.ts stays engine-vendor-free (the engine wall), adapters and UI packages only ever see Night Owls types.
Install
pnpm add @nightowlsdev/coreKey exports
- defineTool
- defineSkill
- defineAgent
- defineSwarm
- defineRule
- defineWorkflow
- SwarmEngine
- engine.listGrantHolders() (FR-043: who currently holds a capability)
- CostGovernor
- resolveTier
- customTelemetry
- RowCache
- InMemoryStorage
- composeSystemPrompt
- HookDispatcher / createHookDispatcher / defineHook (re-exported from hooks)
- deny / ask / ALLOW / ALLOW_TOOL / DEFAULT_READ_ONLY_TOOLS (re-exported from hooks)
- hook types: HookDecision, SwarmHooks, PreGenerationHook, PreToolCallHook, GuardMutationHook, ToolApprovalPolicy (re-exported from hooks)
- ev / isEvent
Usage
import { defineTool, defineSkill, defineAgent, defineRule, defineWorkflow, defineBundle, defineSwarm, SwarmEngine } from "@nightowlsdev/core";
import { z } from "zod";
const greet = defineTool({
name: "greet",
description: "Greet a person by name.",
inputSchema: z.object({ name: z.string() }),
outputSchema: z.object({ greeting: z.string() }),
execute: async ({ name }) => ({ greeting: `Hello, ${name}!` }),
});
const helper = defineAgent({
slug: "helper",
role: "orchestrator",
personality: "A friendly assistant.",
capabilities: [],
skills: [defineSkill(greet)],
delegates: [],
modelId: "tier:",
});
// Rules: `advise` is injected into the prompt; `enforce` gates a tool call (deny / ask the human).
const citeSources = defineRule({
id: "cite-sources",
statement: "Always cite your sources.",
when: {},
level: "advise",
});
// Workflows. An `advisory` procedure is injected as a suggested play (the LLM may deviate); a `strict`
// procedure is executed step-by-step by the engine step-driver and run by name via RunInput.workflow.
const intake = defineWorkflow({
name: "intake",
compliance: "strict",
description: "Triage the request, then greet the user by name.",
steps: [
{ id: "triage", agent: "helper", instruction: "classify the request", next: "greet" },
{ id: "greet", agent: "helper", instruction: "greet the user" },
],
});
const swarm = defineSwarm({ agents: [helper], rules: [citeSources], workflows: [intake] });
const engine = new SwarmEngine(swarm);
// Free-form turn (default): engine.run({ message: "hi" }, ctx)
// Run the strict workflow deterministically: engine.run({ message: "hi", workflow: "intake" }, ctx)
// Reuse a whole crew (agents + rules + workflows + connector grants) across projects as a
// capability bundle, closure-validated at author time. Full guide → /docs/capability-bundles
const studio = defineBundle({ slug: "content-studio", agents: [helper] });What it provides
core is the whole framework in one package: the authoring primitives (defineTool / defineSkill / defineAgent / defineRule / defineWorkflow / defineBundle / defineSwarm), the SwarmEngine that runs a swarm, and the governance plane every deployment relies on — a fail-closed tool gate, cost caps, a secrets boundary, telemetry hooks, and a durable suspend/resume contract. You describe a crew as data; core turns it into a governed, observable run loop that emits a typed SwarmEvent stream.
When to use it
- You are building any Night Owls deployment — core is the one required package; everything else is optional and plugs into it.
- You want multi-agent delegation, strict or advisory workflows, and per-tool human-in-the-loop approval out of one governed loop.
- You need the run to be observable (a typed event stream) and governable (cost ceilings, a tool gate, secret redaction) without wiring those yourself.
When not to
- You only need a single raw model call with no tools, governance, or events — call the model SDK directly; core would be overhead.
- You want the run loop to execute on a remote runtime (Bedrock/A2A, a Vercel Eve app) — you still use core, but you swap the engine for an adapter engine, which relaxes the governance core would otherwise enforce.
Alternatives
- The raw AI SDK / a provider SDKA one-shot generation with no tool-calling, no approval, no cost ceiling, and no need for a resumable transcript. You lose the governance plane and the swappable engine.
- Mastra directlyYou want to couple to Mastra's own types and don't need the engine wall, the governance plane, or the durable HITL contract. core's default engine IS Mastra, exposed through Night Owls types — so going through core costs no Mastra capability while keeping the loop swappable.
Strengths
- One dependency, whole framework: primitives + engine + governance in a single package with a stable, engine-vendor-free public API.
- Fail-closed by construction — the tool gate, cost caps, and secrets boundary default to safe, so a misconfiguration blocks rather than leaks.
- Swappable engine: the same swarm runs on Mastra (default), the AI SDK, @openai/agents, or an adapter, behind one capabilities descriptor the UI gates on.
- Durable HITL: a run can suspend on an approval/question and resume later, with the segment's spend and state carried across the boundary.
Limits & trade-offs
- Multi-agent delegation, strict workflows, and semantic-recall memory are Mastra-engine features today; the other native engines run single-agent in v1.
- The full durable resume story needs a durable backend (Trigger v4 / Vercel Workflow) + a cross-process StorageAdapter; in-memory storage is not durable across a restart.
- It is a framework, not a one-liner: the value shows once you have tools, governance, or more than one agent — a trivial single call is faster without it.
How it works
defineSwarm collects your agents, rules, workflows, and options into a plain data description; new SwarmEngine(swarm) builds a run loop from it. engine.run(input, ctx) streams SwarmEvents as the loop advances — messages, tool calls, questions, usage, status. Every side-effecting tool passes the fail-closed gate (deny / ask-the-human / allow), a governor meters cost against the caps and can stop or park the run, and secrets never reach the model or the event log. On a human approval or a budget-cap ask, the loop suspends: core persists a snapshot (the next generation index + per-run state + the segment budget) and emits a swarm.question, and a later resume re-enters the loop under the admitted context.
Examples
A governed single-agent run
Define one tool + one agent, build the engine, and stream the run's events.
import { defineTool, defineAgent, defineSkill, defineSwarm, SwarmEngine } from "@nightowlsdev/core";
import { z } from "zod";
const lookup = defineTool({
name: "lookup_order",
description: "Look up an order by id.",
inputSchema: z.object({ id: z.string() }),
outputSchema: z.object({ status: z.string() }),
execute: async ({ id }) => ({ status: `order ${id}: shipped` }),
});
const support = defineAgent({
slug: "support",
role: "orchestrator",
personality: "A concise support agent.",
skills: [defineSkill(lookup)],
modelId: "tier:",
});
const engine = new SwarmEngine(defineSwarm({ agents: [support] }));
for await (const ev of engine.run({ message: "where is order 42?" }, ctx)) {
if (ev.type === "swarm.message") console.log(ev.data.delta ?? ev.data.text);
}Gate a side-effecting tool behind a human, and cap cost
A rule enforces approval on a tool; cost.maxCostUsd stops the run if it overspends.
import { defineRule, defineSwarm, SwarmEngine } from "@nightowlsdev/core";
const approveRefunds = defineRule({
id: "approve-refunds",
statement: "A refund must be approved by a human.",
when: { tool: "issue_refund" },
level: "enforce",
action: "ask",
});
const engine = new SwarmEngine(
defineSwarm({ agents: [support], rules: [approveRefunds], cost: { maxSteps: 20, maxCostUsd: 1 } }),
);
// A refund tool-call now suspends the run with a swarm.question; resume with the human's answer.Doing the parts it doesn't support
- Persisting runs / crews across restartscore keeps the run loop; durable state lives in a StorageAdapter you inject. Use @nightowlsdev/storage-supabase (or storage-local for dev) so suspend/resume snapshots and the agent catalog survive a process restart.
- Running the loop in the background / durablycore runs in-process. Wrap it with @nightowlsdev/runner-background (Trigger v4) or runner-nextjs to enqueue runs, park on questions across process death, and resume from the durable snapshot.
- A different run loopPass defineSwarm({ engine }) an engine package (engine-ai-sdk, engine-openai-agents, or an adapter). The swarm description is unchanged; only the loop swaps, gated by the engine's capabilities descriptor.
Related
- hooks — The tool-approval / pre-generation hook types the governance plane composes.
- runner-background — Run the core loop durably in the background (Trigger v4 tasks + wait tokens).
- storage-supabase — The durable StorageAdapter that makes suspend/resume survive a restart.
- react — The client hooks + components that render a core run's event stream.
- capability-bundles — Package a whole crew (agents + rules + workflows + grants) for reuse.