Governor
Policy, budgets, and outbound decisions. Governor is the subsystem that lets an agent reason about should I, not just can I.
Tables
| Table | Purpose |
|---|---|
governor.policies | Named rule sets (allow-lists, spend caps, tool gates). |
governor.decisions | Append-only log of (agent_id, policy, action, verdict, reason). |
governor.budgets | Per-agent per-window spend counters. |
Example policies
- Domain allow-list. Outbound model calls must target a domain in
governor.policies(key='model_domains'). Everything else returnsverdict='deny'withreason='domain_not_allowed'. - Daily spend cap.
governor.check_budget(agent_id, cents)rejects once the rolling window is exhausted. - Tool gate. Dangerous tools (e.g. code execution) require an explicit policy row per agent.
Routes
| Method | Path | Purpose |
|---|---|---|
| POST | /policy/put | Create or replace a policy. |
| POST | /policy/list | Enumerate an agent's policies. |
| POST | /check | Ask for a verdict on a proposed action. |
| POST | /budget/charge | Debit a budget after a successful action. |
| POST | /decisions/list | Read the decisions log. |
Quotas and billing
Quota enforcement lives in the same layer. Every metered callable wraps a
Firestore transaction via requireQuota(ownerUid, agentId, action, units)
that atomically (a) reads the owner's current plan from
subscriptions/{ownerUid}, (b) reads the month-to-date counter at
agents/{agentId}/usage/{YYYY-MM}, and (c) increments or rejects with
resource-exhausted. Because check and debit share the transaction, there
is no read-then-write race.
Counters are credited back (credit(agentId, action, units)) when the
surrounding operation throws after the debit — so a Firestore write that
fails doesn't count against the user's quota.
Plans map to per-action limits in functions/src/lib/plans.ts:
| Action | Free | Builder | Scale |
|---|---|---|---|
postbox.send | 100/mo | 100,000/mo | ∞ |
mesh.issueCert | 50/mo | 50,000/mo | ∞ |
scheduler.createJob | 5 live | 500 live | ∞ |
artifacts.grant | 25/mo | 25,000/mo | ∞ |
tools.invoke | 100/mo | 100,000/mo | ∞ |
The subscriptions/{ownerUid} doc is hydrated by the RevenueCat webhook
(functions/src/billing/webhook.ts). ACTIVE_EVENTS toggle proActive;
expiresAtMs is honored so a lapsed subscription quietly drops back to
the free plan on the next quota check without requiring a re-webhook.
Why "decisions" is its own log
Governor writes audit.events rows like everything else, but it also
keeps its own high-volume governor.decisions table for cheap, indexed
reads. Audit stays authoritative for security; decisions stay fast for
dashboards.