Nova Admin Docs
Concepts

Governance & Safety

How Nova Admin keeps autonomous agents safe. Covers HITL approvals, the outbound allowlist, invocation gating, budget enforcement, and memory retention. Most controls ship observe-first behind editable config flags.

Agents in Nova Admin act under the org's identity with real tools (send mail, post to Slack, move money). Governance is the layer that bounds what they can do, who can invoke them, where they can send output, and how much they can spend.

Two principles run through all of it:

  1. Deterministic gates, not prompt instructions. Where it matters (outbound destinations, spend, invocation), the control is a hard code check; an agent cannot prompt its way past it.
  2. Observe-first rollout. New gates ship off / non-blocking behind an editable config flag: they compute and log the decision so you can watch real traffic, then you flip enforcement on.

Editable config

Most governance knobs live in Firestore under config/{key}, read via readEditableConfig(key, compiledDefault):

  • Deep-merged over a compiled-in default (the default is the schema), ~60s cache, fail-soft (any read error → the compiled default; a bad doc never takes down a run).
  • Edit the doc → live in ~60s, no deploy.
Config keyControlsShips
nova-budget-enforcementBudget-policy enforcement + alertsenforce:false, alert:true
nova-invocation-policyWho may invoke (per-source defaults + master switch)enforce:false
nova-memory-retentionStale-memory flagging TTLttlDays:90, enabled:true
nova-proactivityThe schedule_follow_up tool grantenabled:false
nova-slack-flow-channel-allowlistOutbound Slack channels agents may post tocode default + editable

HITL approvals

Every tool carries a risk level: safe, moderate, or destructive. A destructive tool requires approval by default; moderate can be set to require approval via tool policy; safe runs automatically.

When an agent calls a tool that requires approval, the run pauses (control-flow, not a busy wait), posts a Block Kit approval card to the channel, and resumes only when an authorized approver clicks Approve (or edits the args). Deny cancels the call. See flows for the pause/resume mechanics.

  • Approvers come from the flow node's approvers list, or fall back to the active admin members in the member roster.
  • Sticky approve ("always for this tool, this run") and an edit-args modal are supported.
  • Approvals are audited (who/what/when).

Outbound allowlist

The deterministic outbound guard is a hard, fail-closed check at the central output chokepoint: before an agent delivers anything on any channel, the destination is validated against an allowlist.

  • Slack: only allowlisted channel ids/names (config/nova-slack-flow-channel-allowlist). An unset/empty channel is blocked (fail-closed). #random is deliberately absent.
  • Email / Linear / webhook: domain / team-id / host matching respectively.
  • log and web outputs are always allowed (no external addressable destination).

This exists because letting an agent choose a destination at runtime is excessive agency: a past incident once posted to the wrong channel. The guard makes destination selection deterministic; an agent cannot post somewhere it wasn't pinned to. Proactive follow-ups re-assert this guard before posting.

Invocation gating

Controls who may trigger an agent run. External identities (Slack, Linear, email) are mapped to a canonical org member via the identity index (identity-indexnova-users/{email}), so gating is by member, not raw external id.

A flow's trigger can carry an invokerPolicy:

ModeWho may invoke
openAnyone
membersResolves to an active member of the roster
allowlistExplicit canonical/external ids

Defaults are resolved per source via config/nova-invocation-policy: human sources (slack/linear/email/sms) default to members; system sources (api/web/scheduled/webhook) default to open. The master enforce flag ships false: the gate computes and logs the decision but does not block, so you can confirm members are linked before turning it on. Approval authorization uses the same roster (active admin members).

Budget enforcement

A budget policy caps spend over a window. Policies live in budget-policies with:

FieldValues
scopecompany · agent · flow (+ optional scopeId)
metriccost_cents · tokens
windowdaily · weekly · monthly
limitthe cap
warnPercentalert threshold
hardStopblock when over limit

Spend is accumulated in a window-keyed, transactional ledger (budget-spend/{policyId}__{windowKey}) after each LLM round; the policy is evaluated before/within a run. The warnPercent crossing emits a single idempotent alert per window.

Gated by config/nova-budget-enforcement { enforce, alert }. enforce:false (default) = observe-first: the ledger fills, ratios compute, alerts fire, but a hardStop over-limit is logged, not blocked. Flip enforce:true once the ledger looks right. See observability for usage/spend views and service tiers for the cost axis.

Memory retention

Agent memories are not silently deleted. A daily-maintenance task flags memories older than the retention TTL (config/nova-memory-retention { ttlDays:90, enabled:true }) by setting flaggedStale. It never deletes. Flagged memories surface in Agents → Memory with a Stale filter, where an admin can Keep (clear the flag) or Delete (approve-to-delete). ttlDays<=0 or enabled:false disables flagging.

See also

  • Proactivity, agent self-scheduled follow-ups (same-thread, allowlist-guarded)
  • Flows, where approval pause/resume lives
  • Agents, the roster, tool allowlists, memory
  • Observability, runs, usage, spend

On this page