Every action an agent takes (calling a tool, delegating to another agent, reading a file, opening a network connection, receiving a message) goes through the policy engine. Policies are written in Cedar, a small expressive language designed for authorization. The same engine controls tool access and the sandbox, so there's one place to express what an agent is allowed to do.
How policies work
A policy is a permit or forbid statement. It names a principal (who is acting), an action (what they're trying to do), and a resource (what they're acting on), with optional when/unless conditions:
permit(
principal,
action == Policy::Action::"read",
resource == Policy::Path::"/data"
);
forbid(
principal == Policy::Agent::"junior",
action == Policy::Action::"invoke_tool",
resource in Policy::ToolGroup::"cli"
);Frona evaluates every relevant statement: a request is allowed if at least one permit matches and no forbid does. forbid always wins.
What policies control
| Action | Controls |
|---|---|
invoke_tool | Whether an agent can call a specific tool or any tool in a group |
delegate_task | Whether one agent can hand off work to another |
send_message | Whether one agent can message another |
receive_signal | Whether an agent can be triggered by an inbound message (signal matching). See Signals. |
receive_message | Whether an agent can read and reply to an inbound message on a channel. See Channels. |
read | Sandbox: which paths the agent can read |
write | Sandbox: which paths the agent can write |
connect | Sandbox: which network destinations the agent can reach |
bind | Sandbox: which ports the agent can listen on |
The sandbox actions (read, write, connect, bind) replace the per-agent sandbox toggles you may remember from older versions. The sandbox UI now reads its rules from policies. See Sandbox for the runtime side.
Principals and resources
Each entity has an ID format and may have attributes you can read in when/unless conditions.
Policy::User
The signed-in human. Owner of agents and resources.
- ID format:
Policy::User::"<user_id>" - Attributes: none
Policy::Agent
An AI agent that can perform actions. Parent: User.
- ID format:
Policy::Agent::"<agent_id>" - Attributes:
enabled—Boolmodel_group—Stringtools—Set<String>of tool IDs the agent has access to. Useprincipal.tools.contains("x"),containsAll([...]), orcontainsAny([...])in conditions.
Policy::Mcp
An installed MCP server. Sandbox principal: subject to read, write, connect, and bind actions like agents. Parent: User.
- ID format:
Policy::Mcp::"<server_slug>" - Attributes: none
Policy::App
An agent-published web application running in a sandbox. Sandbox principal. Parent: User.
- ID format:
Policy::App::"<app_id>" - Attributes: none
Policy::Tool
A specific tool identified by its tool ID. Parent: ToolGroup.
- ID format:
Policy::Tool::"<tool_id>" - Attributes:
provider_id—String
Policy::ToolGroup
A group of related tools (e.g., browser, search, cli, task). Use with the in operator to match all tools in the group.
- ID format:
Policy::ToolGroup::"<group>" - Attributes: none
Policy::Path
A filesystem path or virtual path for sandbox read/write rules. Virtual paths resolve to host paths at sandbox apply-time.
- ID format:
- Absolute:
Policy::Path::"/data" - User namespace:
Policy::Path::"user://<username>/<rel>" - Agent namespace:
Policy::Path::"agent://<agent_id>/<rel>"
- Absolute:
- Attributes: none
Policy::NetworkDestination
A network destination for sandbox connect/bind rules.
- ID format: any of:
"hostname"—gmail.com"hostname:port"—api.example.com:443"<ipv4>"—1.2.3.4"<ipv6>"—::1"[<ipv6>]:<port>"—[::1]:443"<cidr>"—10.0.0.0/8"<cidr>!<ports>"—10.0.0.0/8!443
- Attributes: none
Policy::Channel
A platform/channel kind (e.g., sms, telegram, email). Used as the resource for channel-level rules and as the parent of MessageSource.
- ID format:
Policy::Channel::"<kind>" - Attributes: none
Policy::MessageSource
An external source from which a message arrived. Parent: Channel (so you can target a whole channel kind via in).
- ID format:
Policy::MessageSource::"<connector_id>:<address>" - Attributes:
connector_id—String. The space-binding instance that produced the inbound (e.g.,space-personal-gmail). Distinguishes multiple instances of the same channel kind.sender—Contact. The sender entity. For known senders, the real directory contact. For unknown external senders, a synthesized contact with idunresolved:<address>. For self-source, a synthesized contact with id equal to the user.user—User. The receiving user.
Policy::Contact
A user-owned contact, populated from the directory. Parent: User.
- ID format:
Policy::Contact::"<contact_id>" - Attributes:
address—String. The address used for the inbound currently being authorized (e.g.,+15551234). Per-evaluation.addresses—Set<String>of all known addresses for this contact.name—String. Display name (empty for unresolved senders).
Action context
Some actions read additional context attributes alongside the principal/resource:
- All sandbox actions (
read,write,connect,bind) and tool actions (invoke_tool,delegate_task,send_message) accept an optionalcontext.is_task: Bool. receive_signalandreceive_messageacceptcontext.paired_addresses: Set<String>— the operator's addresses paired on this inbound's channel binding. Usecontext.paired_addresses.contains(resource.sender.address)to gate on self-source.
Common patterns
Restrict a tool group to a specific agent.
permit(
principal == Policy::Agent::"developer",
action == Policy::Action::"invoke_tool",
resource in Policy::ToolGroup::"cli"
);Block CLI access for a junior agent.
forbid(
principal == Policy::Agent::"junior",
action == Policy::Action::"invoke_tool",
resource in Policy::ToolGroup::"cli"
);Allow file reads only when the agent has the browser tool.
permit(
principal,
action == Policy::Action::"read",
resource == Policy::Path::"/browser-profiles"
) when { principal.tools.contains("browser") };Restrict an agent's network access to a few destinations.
forbid(
principal == Policy::Agent::"restricted",
action == Policy::Action::"connect",
resource
) unless {
resource == Policy::NetworkDestination::"api.internal:443"
|| resource == Policy::NetworkDestination::"gmail.com:443"
};Allow inbound messages only from paired addresses on a channel.
permit(
principal,
action == Policy::Action::"receive_message",
resource
) when { context.paired_addresses.contains(resource.sender.address) };Block a specific sender across all signals.
forbid(
principal,
action == Policy::Action::"receive_signal",
resource
) when { resource.sender.address == "spam@example.com" };Virtual paths
Sandbox paths can use virtual schemes that resolve to host paths at runtime:
| Form | Resolves to |
|---|---|
Policy::Path::"/abs/path" | The absolute filesystem path |
Policy::Path::"user://<username>/<rel>" | The user's file storage subtree |
Policy::Path::"agent://<agent_id>/<rel>" | An agent's workspace subtree |
You can only reference user://<your-username> and agent://<your-own-agent> paths. Cross-user and cross-agent references are rejected at write time.
Policy layers
Policies come from three places:
| Layer | Source | Editable |
|---|---|---|
| Managed | Server config (e.g., sandbox.default_network_access) | No, change via deployment config |
| System | Built-in defaults shipped with Frona | Yes, with care |
| User | Policies you write | Yes |
All three are merged when a request is evaluated. Managed policies show up read-only in the UI and tool output; if you need to change them, edit the config file.
Built-in defaults
Out of the box, Frona ships these system policies:
- All agents can use any tool except
agentandpolicygroups (those are reserved for the system agent). - Agents can delegate only to agents whose tools are a subset of their own.
- Agents can send messages to other agents.
- Agents can receive signals from any source. Operators add
forbidpolicies for sensitive sources. - Inbound messages on a channel only match if the sender is one of the channel's paired addresses. Operators open up specific contacts or sources with additional
permitpolicies.
This means a fresh install behaves sensibly without writing a single policy. You add policies as you tighten things down.
Managing policies
You can manage policies two ways.
From settings pages
There's no single "Policies" page. Instead, the relevant policies surface inside the settings of whatever they govern:
- Agent settings show a checklist of allowed tool groups (Tools section) and the agent's read/write/connect/bind rules (Sandbox section). Editing them rewrites the underlying policies.
- MCP server settings show the server's sandbox rules (read/write paths, network destinations, port binds) with the same UI as agents.
- App settings show the app's sandbox rules (read/write paths, network destinations, port binds) with the same UI as agents.
- Channel settings expose inbound rules: which senders are allowed to message the channel's agent (
receive_message) and which sources can fire signals (receive_signal). Pairing addresses on a channel is the same as adding areceive_messagepermit for that address.
Toggling rows in these UIs is the right path for the common cases. For anything more nuanced (per-tool conditionals, cross-principal rules, allowlists with exceptions), use the chat flow below.
From chat
Ask the system agent to manage policies for you:
- "Show me the current policies"
- "Add a policy that blocks the
clitool group for theJunioragent" - "Validate this policy text and tell me what's wrong"
- "What's the schema? I want to write a policy by hand."
The agent uses the manage_policy tool, which has schema, create, update, delete, list, and validate actions. Validation parses the policy and warns about unknown entities. Managed policies (those backed by server config) are read-only and the agent will tell you which config key to change instead.
How tools surface in agent settings
In the agent settings UI, the Tools section shows a projection of the policies as a checklist: each row is a tool group with an "enabled" toggle. Toggling a row writes a small per-agent policy that allows or denies that group. Behind the scenes it's still Cedar; the toggle is a shortcut for the common case.
If you write more nuanced policies (e.g., "allow cli only when the agent has deploy and auth tools"), the toggle UI shows the row as managed-by-policy and links you to the full policy editor.
Tips
- Start permissive, tighten as you learn. The default system policies are a sensible starting point. Add
forbidrules as you find behaviors you want to lock down. - Use
forbid ... unlessfor allowlists. It reads better than enumerating everypermit. - Reference tool groups, not individual tools, when you can. Group rules survive tool renames and additions.
- Don't fight the layers. If you find yourself overriding a system policy in every user policy, change the system policy or the managed config instead.
- Validate before saving. The
validateaction ofmanage_policycatches typos in entity names that would otherwise silently never match.
Next steps
- Sandbox. How sandbox rules are enforced at runtime.
- Creating & Configuring Agents. Where the per-agent tool projection lives.
- Channels. How
receive_messageandreceive_signalgate inbound traffic. - Signals. How
receive_signalpolicies interact with the matcher.