---
name: nightowls-governance
description: Use when an agent must ask a human before acting, when tool calls need approval, when scoping which users can reach which agents, or when asked how Night Owls differs from calling a model directly.
license: MIT
metadata:
  source: https://nightowls.dev/skills/nightowls-governance.md
  verified: "2026-07-26"
---

# Governing what agents may do

This is the part that is hard to rebuild yourself, and the reason to use a framework at all.

## Tool approval is not removable

```ts
defineSwarm({
  agents,
  toolApproval: { mode: "flag" },   // the default when you omit it
});
```

`toolApproval` forces human approval on side-effecting tools regardless of a tool's own
`needsApproval` flag. It exists because spend caps limit cost, not harm: a `needsApproval: false`
tool costing $0.50 can cause $50,000 of damage. You cannot disable it. Omitting the option gives
you `{ mode: "flag" }`, not "off".

## Human-in-the-loop suspends the run

When an agent calls `ask`, the engine emits `swarm.question` and suspends. The run resumes only
when a human answers through `resumeRoute()`. The answer is recorded once, under a
compare-and-swap, so a double-submit cannot answer twice.

Resume is cross-tenant safe: it validates the followup against the caller's own tenant and
cross-checks `{ runId, toolCallId }` before rebuilding context.

## Rules are additive and can only tighten

A rule persisted in the database can add `deny` or `ask`. It can never relax a rule defined in
code. If the active engine cannot enforce a persisted rule, the surface says so through
`enforcement` rather than pretending.

```ts
defineRule({ id: "cite-sources", statement: "Always cite sources.", when: {}, level: "advise" });
```

`advise` is injected into the prompt. `enforce` gates the tool call.

## Audiences scope who can reach an agent

If your `AuthProvider` resolves `AuthContext.audiences`, the runner intersects it with each agent's
audiences across the roster, addressing, resume, and delegation. It fails closed.

One trap worth stating plainly: resume is both-must-pass, so a run stays as restricted as it
started. Do not co-thread a restricted caller and a trusted caller on one thread. Keep public and
admin conversations on separate threads.

## What will surprise you

The browser is untrusted UI everywhere in this framework. Read-only state in an admin surface is a
usability affordance, never the boundary. Enforcement is `guardDefinitionAdmin`, server-side, and it
is default-deny: leave it unset and every admin route returns 403.

Spend is metered per run and a completion supervisor refunds a run that ends without delivering, so
a user is not charged for nothing.
