Governance

Policy reference

Every governance rule in Trellaris is a row in one table, resolved by one engine, and enforced at runtime β€” not in the UI, and not by convention. This page covers the policy model, then each of the ten policy kinds in detail: what it does, its exact configuration, how conflicts resolve, where it bites, and what happens when it isn't configured.

Anatomy of a policy

A policy is four fields. The kind selects the rule family, the scope says who it applies to, and the config carries the kind-specific settings.

FieldMeaning
kindOne of the ten kinds below. Determines how config is read and where it is enforced.
scopeOrg-wide, or narrowed to a team, a role, a user, or a single agent. See the scope grammar.
configThe rule itself. The shape is per kind, documented below.
enabledTurn a policy off without deleting it β€” the config and its audit trail survive.
schema_versionThe config shape version, so a kind's schema can evolve without rewriting existing rows.

A complete policy

{
  "kind": "budget",
  "scope": { "team_id": "8f2c…" },
  "config": {
    "period": "month",
    "soft_usd": 50,
    "hard_usd": 100,
    "on_hard": "block"
  },
  "enabled": true
}

Scope grammar

A scope is a small JSON object. Exactly one key narrows it; an empty object is org-wide. Each scope carries a specificity used to break ties.

ScopeApplies toSpecificity
{}Everyone and everything in the organization.1 β€” org
{"team_id": "…"}Members of that team.2 β€” team
{"role": "org_admin"}Everyone holding that role.3 β€” role
{"user_id": "…"}One user, across every agent they run.3 β€” user
{"agent_id": "…"}One agent, whoever runs it.4 β€” agent

How conflicts resolve

When no policy of a kind exists

Defaults are deliberate: open where openness is safe, closed where it isn't.

KindWith nothing configured
dlpA shipped baseline: US SSNs and credit-card numbers are redacted.
tool_allowlistOpen β€” every tool the agent declares is callable.
model_allowlistOpen β€” any configured model may be declared.
approvalThe baseline still applies: every write parks for approval.
budgetNo spend limit.
autonomyFloor is autonomous β€” the agent's own declaration stands uncapped.
browser_egressClosed. browser_use can reach nothing at all.
wakeupInstance defaults: 30-day horizon, 20 pending per user, 5 per agent.
injection_screenOff (none) β€” nothing is screened.
conditional_accessUnconstrained β€” agent tokens mint without extra conditions.
Propagation Policies are read from a short-lived per-organization snapshot, so a hot path never pays for a database round trip. Administrative writes invalidate that snapshot immediately; across processes, staleness is bounded to a few seconds.

dlp β€” data-loss prevention

An ordered list of rules applied to text as it crosses a boundary. Each rule matches by builtin id or a custom regular expression and takes one action.

config

{
  "rules": [
    {
      "id": "dlp.ssn_us",
      "name": "US Social Security Number",
      "matcher": { "type": "builtin", "builtin_id": "ssn_us" },
      "action": "redact",
      "applies_to": ["input", "output"],
      "replacement": "[REDACTED_SSN]"
    },
    {
      "id": "dlp.employee_id",
      "name": "Internal employee id",
      "matcher": { "type": "regex", "pattern": "EMP-[0-9]{6}" },
      "action": "flag",
      "applies_to": ["output"]
    }
  ]
}
ActionEffect
redactReplace each match with replacement (default [REDACTED]) and record the hit.
flagRecord the hit; leave the text intact. Useful for measuring before enforcing.
blockRecord the hit and refuse β€” the text does not cross the boundary.

Builtin matchers ship for the common sensitive classes:

BuiltinMatches
ssn_usUS Social Security numbers.
credit_card13–19 digit card numbers, Luhn-validated to cut false positives.
ibanInternational bank account numbers.
emailEmail addresses.
phone_e164E.164 phone numbers.
aws_keyAWS access key ids.
private_key_blockPEM private-key blocks, including the body.

applies_to picks the direction: input screens text before the model ever sees it; output screens generated text before it is delivered. Most rules want both.

Enforced at every boundary where text moves, not just the chat window:

Resolution: rules union across every applicable scope; a rule id redefined at a more specific scope replaces the broader definition for that id.

tool_allowlist β€” which tools may be called

Governs the tool plane: built-in tools, connector tools, and MCP servers from the Tools Library.

config

{
  "allow": ["*"],
  "deny": ["browser_use", "mcp:unvetted-server"]
}

Enforced at tool-registry assembly for every run, at MCP discovery, at the gateway (for external MCP clients as well as the executor), and during a version's tool-surface review at scan time.

model_allowlist β€” which models may be used

config

{
  "allow": ["claude-*", "default", "cheap", "strong"],
  "deny": []
}

Enforced at definition time: creating an agent or a new version with a disallowed model fails validation with a field-path error, so a disallowed model never reaches runtime in the first place.

approval β€” force a human decision

Additive on top of the platform's baseline (writes park). An approval policy can only tighten, and it always wins over autonomy.

config

{
  "when": {
    "tool_tags": ["write"],
    "connectors": ["servicenow"]
  },
  "approver": "team_admin"
}
ConditionEffect
when.tool_tags: ["write"]Any write-tagged call parks, at every autonomy level.
when.connectors: […]Any call to those named connectors parks, read or write.
when: {} (empty)Unconditional β€” every tool call in scope parks.
approverWho may decide: team_admin, org_admin, or a specific user id.

Enforced at the executor, immediately before a tool call executes β€” the run parks at awaiting_approval with its loop state saved, and resumes to execute the approved call exactly once. Over the MCP gateway, where no human is present to answer, the call is refused rather than parked.

An agent flagged requires_approval parks everything That frontmatter flag is independent of policy and of autonomy: every tool call the agent makes needs a decision.

budget β€” cap spend

config

{
  "period": "month",
  "soft_usd": 50,
  "hard_usd": 100,
  "on_hard": "block"
}
FieldMeaning
periodday or month β€” the window spend is summed over.
soft_usdWarning threshold. The run proceeds; the owner is notified once per period.
hard_usdStop threshold.
on_hardblock refuses. require_approval is treated as block today.

Resolution is unusual and worth understanding: the most-specific budget wins outright, and the winning policy's scope also selects the spend basis. A team-scoped budget is measured against that team's combined spend, a user-scoped one against that user alone, an org-wide one against everything.

Enforced at three places, all of them before money is spent:

autonomy β€” how much runs unattended

An organization-side ceiling. The effective level for a run is min(what the agent declares, the policy floor), so an agent can never grant itself more autonomy than the organization permits.

config

{ "max_level": "supervised" }
LevelBaseline write behaviour
supervisedEvery write parks for approval.
trustedOrdinary writes run; destructive operations still park.
autonomousWrites run unattended.

Deny-wins here means the lowest max_level across applicable policies is the floor. Autonomy only relaxes the baseline β€” an explicit approval policy still parks the call.

Enforced at the executor's write-parking decision, and again at promotion: proposing an agent to a shared ring whose declared autonomy exceeds the floor is rejected with policy.autonomy_floor.

browser_egress β€” where the browser may go

The one allowlist that is default-deny. Driving a real browser is the widest-blast-radius capability on the platform, so it reaches nothing until someone says otherwise.

config

{
  "allow": ["*.internal.example.com", "docs.example.com"],
  "deny": ["admin.example.com"]
}

Enforced at the browser_use tool, before navigation. It composes with the rest: navigate and extract are reads, while click, type, submit, and download are writes that route through the approval gate, and extracted text is DLP-screened before the model sees it.

wakeup β€” bound agent self-scheduling

An agent can schedule its own follow-up with schedule_followup. These caps stop that from becoming an unbounded queue of future work.

config

{
  "max_horizon_days": 30,
  "max_pending_per_user": 20,
  "max_pending_per_agent": 5
}
CapBoundsDefault
max_horizon_daysHow far ahead a wake-up may be scheduled.30
max_pending_per_userOutstanding wake-ups across all of a user's agents.20
max_pending_per_agentOutstanding wake-ups for one agent.5

Resolution is per key: the most specific policy overrides only the keys it actually sets, so a per-agent policy can tighten one cap without restating the others.

Enforced at schedule_followup. A breach returns a structured tool error rather than failing the run β€” the model sees the refusal and can explain it to the user.

injection_screen β€” screen retrieved content

Content coming back from the outside world β€” tool results, fetched pages, knowledge chunks β€” is data, never instructions. This screen enforces that before the content re-enters the model.

config

{ "action": "warn" }
ActionEffect
noneOff. The default when no policy exists.
warnKeep the content; annotate and audit the detection.
stripRedact each offending span, keep the rest, audit.
blockReplace the whole result with a structured tool error, audit.

Detection looks for the classic markers β€” instruction overrides, "disregard the above", demands to reveal the system prompt, exfiltration phrasing, injected role changes, and comment-smuggled directives. The detector sits behind a swappable backend: a deterministic pattern matcher ships by default, and a real classifier is a configuration change rather than a code change.

Enforced at the tool boundary in the executor, covering tool results and knowledge alike. Every action taken is audit-logged.

Start at warn Run in warn long enough to see what your real corpus trips, then move to strip or block. Going straight to block on a large knowledge base tends to catch quoted security documentation.

conditional_access β€” constrain agent identity

An agent can hold its own workload identity, minted as a short-lived, audience-scoped token. This policy constrains when such a token may be issued at all.

config

{
  "deny": false,
  "allowed_hours": [8, 18],
  "allowed_cidrs": ["10.0.0.0/8"],
  "risk_max": "high"
}
FieldRefuses the token when…
denySet true β€” a hard stop for that scope.
allowed_hoursThe current UTC hour falls outside the window.
allowed_cidrsThe caller's address is outside every listed range.
risk_maxThe agent's computed risk tier is higher than this ceiling.

Resolution: the most specific applicable policy wins as a whole, not merged key by key. Enforced at token minting β€” the single point every agent-identity token passes through β€” alongside the identity's own lifecycle state, so a killed or paused agent gets nothing regardless of policy.

Managing policies

Policies live under Admin β†’ Policies, restricted to organization admins. Picking a kind prefills a valid starter config; the scope box takes the JSON from the grammar above. Policies can be enabled, disabled, or deleted, and every change is written to the audit log.

Practical advice