@nightowlsdev/hooks
EngineThe @mastra-free decision-hook substrate: the plain types and dispatcher the platform's billing, approval, and mutation gates hang off.
What it does
@nightowlsdev/hooks is the deliberately tiny, @mastra-free substrate that carries Night Owls' decision-hook contracts AND the runtime that applies them, kept engine-vendor-free by design so @nightowlsdev/core can depend on it without leaking engine types into core's public API. It defines the declarative decision shapes (HookDecision allow/deny, the three-way ToolDecision allow/deny/ask) and the SwarmHooks bundle of optional decision hooks: preGeneration (per-model-launch gate, e.g. billing reserve), preToolCall (action-approval gate honoring a non-removable ToolApprovalPolicy of flag vs all-side-effecting), and guardDefinitionMutation (who may publish/rollback an agent definition). It ships real runtime code too, not just types: the HookDispatcher / createHookDispatcher that applies the bundle uniformly and fail-closed (a hook that throws becomes a deny, so safety/billing vetoes never silently allow on error), the defineHook identity helper, the decision constructors deny()/ask(), and the canonical constants ALLOW / ALLOW_TOOL / DEFAULT_READ_ONLY_TOOLS. Hosts normally configure these via SwarmConfig.hooks; the values and types are re-exported through @nightowlsdev/core so most consumers never import this package directly.
Install
pnpm add @nightowlsdev/hooksKey exports
- HookDispatcher / createHookDispatcher
- defineHook
- deny / ask / ALLOW / ALLOW_TOOL
- DEFAULT_READ_ONLY_TOOLS
- types: HookDecision, ToolDecision, SwarmHooks, PreGenerationHook, PreToolCallHook, GuardMutationHook, ToolApprovalPolicy
Usage
import { defineHook, deny, ask, ALLOW } from "@nightowlsdev/hooks";
import type { SwarmHooks } from "@nightowlsdev/hooks";
const hooks: SwarmHooks = {
// Gate every side-effecting tool behind a human decision.
preToolCall: defineHook(async ({ tool }) =>
tool.sideEffecting ? ask("Approve this action?") : ALLOW,
),
// Veto a model launch when out of budget.
preGeneration: defineHook(async ({ budgetRemaining }) =>
budgetRemaining > 0 ? ALLOW : deny("out of budget"),
),
};