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:
- 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.
- 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 key | Controls | Ships |
|---|---|---|
nova-budget-enforcement | Budget-policy enforcement + alerts | enforce:false, alert:true |
nova-invocation-policy | Who may invoke (per-source defaults + master switch) | enforce:false |
nova-memory-retention | Stale-memory flagging TTL | ttlDays:90, enabled:true |
nova-proactivity | The schedule_follow_up tool grant | enabled:false |
nova-slack-flow-channel-allowlist | Outbound Slack channels agents may post to | code 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
approverslist, 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).#randomis deliberately absent. - Email / Linear / webhook: domain / team-id / host matching respectively.
logandweboutputs 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-index → nova-users/{email}), so gating is by member, not raw external id.
A flow's trigger can carry an invokerPolicy:
| Mode | Who may invoke |
|---|---|
open | Anyone |
members | Resolves to an active member of the roster |
allowlist | Explicit 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:
| Field | Values |
|---|---|
scope | company · agent · flow (+ optional scopeId) |
metric | cost_cents · tokens |
window | daily · weekly · monthly |
limit | the cap |
warnPercent | alert threshold |
hardStop | block 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
Proactivity (Scheduled Follow-ups)
How an agent schedules its own future follow-up. A one-shot, same-thread continuation via the schedule_follow_up tool. Channel-agnostic, deterministically pinned to the originating thread, gated by an editable config flag.
Sandboxes
Sandboxes are isolated execution environments where agents can browse, run shell commands, or drive a virtual desktop without risking the host.