Skip to content

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:

cedar
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

ActionControls
invoke_toolWhether an agent can call a specific tool or any tool in a group
delegate_taskWhether one agent can hand off work to another
send_messageWhether one agent can message another
receive_signalWhether an agent can be triggered by an inbound message (signal matching). See Signals.
receive_messageWhether an agent can read and reply to an inbound message on a channel. See Channels.
readSandbox: which paths the agent can read
writeSandbox: which paths the agent can write
connectSandbox: which network destinations the agent can reach
bindSandbox: 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:
    • enabledBool
    • model_groupString
    • toolsSet<String> of tool IDs the agent has access to. Use principal.tools.contains("x"), containsAll([...]), or containsAny([...]) 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_idString

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>"
  • 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_idString. The space-binding instance that produced the inbound (e.g., space-personal-gmail). Distinguishes multiple instances of the same channel kind.
    • senderContact. The sender entity. For known senders, the real directory contact. For unknown external senders, a synthesized contact with id unresolved:<address>. For self-source, a synthesized contact with id equal to the user.
    • userUser. The receiving user.

Policy::Contact

A user-owned contact, populated from the directory. Parent: User.

  • ID format: Policy::Contact::"<contact_id>"
  • Attributes:
    • addressString. The address used for the inbound currently being authorized (e.g., +15551234). Per-evaluation.
    • addressesSet<String> of all known addresses for this contact.
    • nameString. 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 optional context.is_task: Bool.
  • receive_signal and receive_message accept context.paired_addresses: Set<String> — the operator's addresses paired on this inbound's channel binding. Use context.paired_addresses.contains(resource.sender.address) to gate on self-source.

Common patterns

Restrict a tool group to a specific agent.

cedar
permit(
  principal == Policy::Agent::"developer",
  action == Policy::Action::"invoke_tool",
  resource in Policy::ToolGroup::"cli"
);

Block CLI access for a junior agent.

cedar
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.

cedar
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.

cedar
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.

cedar
permit(
  principal,
  action == Policy::Action::"receive_message",
  resource
) when { context.paired_addresses.contains(resource.sender.address) };

Block a specific sender across all signals.

cedar
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:

FormResolves 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:

LayerSourceEditable
ManagedServer config (e.g., sandbox.default_network_access)No, change via deployment config
SystemBuilt-in defaults shipped with FronaYes, with care
UserPolicies you writeYes

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 agent and policy groups (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 forbid policies 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 permit policies.

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 a receive_message permit 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 cli tool group for the Junior agent"
  • "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 forbid rules as you find behaviors you want to lock down.
  • Use forbid ... unless for allowlists. It reads better than enumerating every permit.
  • 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 validate action of manage_policy catches 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_message and receive_signal gate inbound traffic.
  • Signals. How receive_signal policies interact with the matcher.