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.
| Field | Meaning |
|---|---|
kind | One of the ten kinds below. Determines how config is read and where it is enforced. |
scope | Org-wide, or narrowed to a team, a role, a user, or a single agent. See the scope grammar. |
config | The rule itself. The shape is per kind, documented below. |
enabled | Turn a policy off without deleting it β the config and its audit trail survive. |
schema_version | The 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.
| Scope | Applies to | Specificity |
|---|---|---|
{} | 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
- Deny wins. A deny at any applicable scope blocks the item, however specific a competing allow is. A narrow allow can never punch a hole in a broad deny.
- Most-specific wins among allows. If both an org and a team policy define an allow list, the team's list is the operative one for that team's members.
- DLP is the deliberate exception: rules union across scopes rather than overriding, because a broader scope must not be able to strip a narrower scope's protections. A rule redefined under the same id at a more specific scope wins for that id.
When no policy of a kind exists
Defaults are deliberate: open where openness is safe, closed where it isn't.
| Kind | With nothing configured |
|---|---|
dlp | A shipped baseline: US SSNs and credit-card numbers are redacted. |
tool_allowlist | Open β every tool the agent declares is callable. |
model_allowlist | Open β any configured model may be declared. |
approval | The baseline still applies: every write parks for approval. |
budget | No spend limit. |
autonomy | Floor is autonomous β the agent's own declaration stands uncapped. |
browser_egress | Closed. browser_use can reach nothing at all. |
wakeup | Instance defaults: 30-day horizon, 20 pending per user, 5 per agent. |
injection_screen | Off (none) β nothing is screened. |
conditional_access | Unconstrained β agent tokens mint without extra conditions. |
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"]
}
]
}
| Action | Effect |
|---|---|
redact | Replace each match with replacement (default [REDACTED]) and record the hit. |
flag | Record the hit; leave the text intact. Useful for measuring before enforcing. |
block | Record the hit and refuse β the text does not cross the boundary. |
Builtin matchers ship for the common sensitive classes:
| Builtin | Matches |
|---|---|
ssn_us | US Social Security numbers. |
credit_card | 13β19 digit card numbers, Luhn-validated to cut false positives. |
iban | International bank account numbers. |
email | Email addresses. |
phone_e164 | E.164 phone numbers. |
aws_key | AWS access key ids. |
private_key_block | PEM 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:
- Model input and model output, on every run.
- Tool arguments and tool results, at the gateway choke point β so data read back from an external system is screened before it re-enters the model.
- Text extracted by
browser_use, voice transcripts before they reach the model, and content written to a canvas document.
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"]
}
- Patterns are globs.
*allows everything; a bare id likecode_executionmatches exactly. - MCP servers are matched under the id
mcp:<slug>, so one deny removes a whole server's tool surface. - A denied MCP server is hidden β its tools never enter the run's tool registry, so the model doesn't attempt them and collect a wall of errors.
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": []
}
- Globs again:
claude-*covers a whole model family. - Matched against the model the agent declares, which may be a tier alias (
default,cheap,strong) rather than a concrete id β if your agents use tiers, allow the tier names too.
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"
}
| Condition | Effect |
|---|---|
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. |
approver | Who 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.
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"
}
| Field | Meaning |
|---|---|
period | day or month β the window spend is summed over. |
soft_usd | Warning threshold. The run proceeds; the owner is notified once per period. |
hard_usd | Stop threshold. |
on_hard | block 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:
- Chat β sending a message returns
402 budget.hard_stopbefore a run row is even created. - Non-interactive paths (automations, channels, inbound A2A) β the run fails with
budget_exceededrather than silently spending. - The gateway β a tool call is refused with 402 before the upstream request is made.
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" }
| Level | Baseline write behaviour |
|---|---|
supervised | Every write parks for approval. |
trusted | Ordinary writes run; destructive operations still park. |
autonomous | Writes 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"]
}
- Hostname globs. Allow and deny lists union across applicable scopes.
- With no policy at all the allow list is empty, and
browser_usefails closed withbrowser.egress_blocked.
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
}
| Cap | Bounds | Default |
|---|---|---|
max_horizon_days | How far ahead a wake-up may be scheduled. | 30 |
max_pending_per_user | Outstanding wake-ups across all of a user's agents. | 20 |
max_pending_per_agent | Outstanding 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" }
| Action | Effect |
|---|---|
none | Off. The default when no policy exists. |
warn | Keep the content; annotate and audit the detection. |
strip | Redact each offending span, keep the rest, audit. |
block | Replace 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.
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"
}
| Field | Refuses the token when⦠|
|---|---|
deny | Set true β a hard stop for that scope. |
allowed_hours | The current UTC hour falls outside the window. |
allowed_cidrs | The caller's address is outside every listed range. |
risk_max | The 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
- Scope narrowly first. Prove a rule on one team or one agent before making it org-wide.
- Disable, don't delete while investigating β you keep the config and the history.
- Prefer allow lists to deny lists for models and tools. A deny list needs updating every time something new appears; an allow list fails safe.
- Use
flagandwarnas a measurement phase for DLP and injection screening before switching to enforcement. - Remember the interaction order: explicit approval policies beat autonomy, deny beats allow, and the most specific allow wins the rest.