Permissions and trust
A run can do only what three things allow together: the agent it runs as, the agent’s role, and the loop. This page says how they combine, what each built-in tool needs, why plutonium.io refuses some combinations (the Rule of Two), and what marking a source trusted changes. It describes the rules as they work today. A rule that is still coming is labelled Coming.
Permissions
A permission (a capability in a loop’s record) is one kind of thing a run may do. plutonium.io defines the list, and it is closed: a loop, a model or a tool cannot add one. The console names each one in words; a loop’s record names it by the word in the first column.
Each permission also carries up to three marks, which the Rule of Two below adds up:
- Outside content — it can bring text that plutonium.io did not write into the model’s context: a web page, a search result, what a stranger types.
- Your data — it can read or change your account’s data or secrets.
- Acts outside — it can cause an effect outside plutonium.io that the person did not ask for turn by turn. Sending you a notification is not this; sending mail to somebody else is.
| Permission | What it lets a run do | Marks |
|---|---|---|
read_web | Read any page on the web | Outside content |
call_endpoint | Call an endpoint you declared | Outside content |
read_workspace | Read your files | Outside content, Your data |
write_workspace | Write to your files | Your data |
read_records | Read your collections | Outside content, Your data |
write_records | Write to your collections | Your data |
search_indexes | Search your indexes and read their documents | Outside content, Your data |
notify | Send you a notification | — |
install_automations | Install an automation that runs on its own | Your data |
install_loops | Install a loop | Your data |
install_tools | Install a tool your loops can call | Your data |
install_agents | Install an agent your loops can run as | Your data |
install_pages | Draft a page your members can read | Your data |
read_connection | Read from a service you have connected | Outside content, Your data |
write_connection | Change things in a service you have connected | Your data, Acts outside |
send_external | Send things outside plutonium.io | Acts outside |
serve_records | Serve your collections to an outside caller with an API key | Your data, Acts outside |
spawn_tasks | Start further runs | — |
run_code | Run code | Acts outside |
read_own_results | Read back results it already received | Outside content |
rehearse_claim | Try an automation once on the live site, saving nothing | Outside content |
client_navigate | Open a page of your app for the person it talks to | Outside content |
client_act | Act in your app as the person it talks to, once they confirm each use | Outside content, Acts outside |
The install permissions carry no Acts outside mark because every install tool asks a person first, each time (see the table of tools below).
Three layers decide what a run may call
- The agent’s tools. An agent lists the tools it knows how to use. A tool that is not on the list is not something a run can call at all.
- The role’s permissions. An agent has a default role, a named set of permissions —
one that plutonium.io defines, or one an owner of your account defines from the permissions
above (Agents lists the platform’s and says how to define your own).
The role is the outer bound, in every run: a tool runs only when the role holds every
permission the tool needs, and neither a loop nor a card can add one the role does not allow.
A tool that needs one is left out of the run, and a call to it is refused in words that name
the role and the permission — to give an agent more, change its role on the Agents page.
Four permissions are the loop’s, not a role’s:
spawn_tasks,client_navigate,client_actandcall_endpointname something only a loop has — its other agents, the page that hosts it, its own endpoint tools — so no role holds them, the loop’scapabilitiesgive them to its runs, and a run outside a loop never holds them. In your own run on the console, a card asks you only what the role leaves to you: which site, when a role allows reading the web a host at a time, and which connection, when it allows using one. While a card waits, the run’s activity line for it quotes what the agent said it was about to do, in its own words; the card itself states only what the platform computed. A published loop keeps the role as it was when you published. A role’s limits travel with it into a loop’s run. A role can hold a permission only within limits —web_readerreads two job sites and writes files underdocs/only. A grant in a loop’s run gives back no more than those limits, and agrantableentry that names wider ones is narrowed to them. A path outside a write limit is refused and nobody is asked. A site outside the role’s list is refused too, unless the loop’sgrantabledeclares awidenfor that permission: then the person that entry names is asked about that one site. - The loop’s
capabilities. A loop’s record lists the permissions its runs may hold — the ceiling. A loop only narrows the role: a run holds what the role allows and the ceiling lists (plus the four loop permissions above, when the ceiling lists them). A run never holds a permission outside the ceiling, whatever the role grants, and a tool that needs one is left out of the run — unless the loop’sgrantablelets the run ask for that permission (below), which it can only when the agent’s role allows it.
An example. The librarian role holds write_records, and records_put needs
write_records. A loop whose capabilities lists read_records but not write_records, and
whose grantable does not name it, runs a librarian agent without records_put: the role allows
it, the ceiling does not, so the run cannot write a record and nothing offers to.
When a run calls a tool anyway, the refusal tells the model which layer stopped it:
- Not one of this agent’s tools — no grant can add a tool to an agent, so the model does something else.
- Not allowed by this agent’s role, or held back by the loop — the refusal says which, and
names the permission. No grant can change it inside the run: the owner changes the agent’s
role on the Agents page, or the loop’s
capabilities. - Needs a permission this run does not hold — only a person can grant one. In a loop’s run
this is a permission the role allows and the loop’s
capabilitieshold back, or one of the four loop permissions (inside thecapabilities, the published version grants that one at the call). If the loop’sgrantablenames that permission, the run asks: a card asks the person in the conversation (approve: user), parks on your Runs queue (approve: operator), or waits for your callback (approve: external). If the loop’sgrantabledoes not name it, nobody is asked. An entry ingrantablegrants nothing until somebody says yes, and publish refuses an entry that no agent of the loop could use, or that no agent’s role allows.
What each built-in tool needs
A loop’s own tools say what they need in their requires
(Tools). These are the built-in tools. A tool marked asks first always
asks a person before each call, even when the run holds what it needs.
| Tool | Needs | Asks first |
|---|---|---|
ask_user | notify | |
request_inputs | notify | |
notify_user | notify | |
escalate | notify | |
finish | notify | |
read_file | read_workspace | |
list_dir | read_workspace | |
write_file | write_workspace | |
records_query | read_records | |
records_put | write_records | |
search | search_indexes | |
read_document | search_indexes | |
read_result | read_own_results | |
web_fetch | read_web | |
web_fetch_source | read_web | |
web_extract | read_web | |
rehearse_claim | rehearse_claim, read_web | |
github_search_issues | read_connection | |
spawn_tasks | spawn_tasks | |
read_loop | install_loops | |
propose_loop | install_loops | asks first |
propose_tool | install_tools | asks first |
propose_agent | install_agents | asks first |
propose_page | install_pages | asks first |
propose_claim | install_automations | asks first |
escalate and finish are on no role’s list: plutonium.io offers them to a run itself. No
built-in tool needs call_endpoint, write_connection, run_code, serve_records,
client_navigate or client_act; only a loop’s own tools reach those.
The Rule of Two
No agent may hold all three marks at once. An agent that reads outside content, touches your data and can act outside is one that a web page or a stranger’s message can steer into doing something with your data that you never asked for. Any two of the marks are allowed; all three are not.
plutonium.io adds up the marks of everything an agent can hold — its role’s permissions, the
loop’s own tools, and anything the loop lets it ask for. A loop that declares any delivery (the
embed, the SDK, the API) is public: what its users type is outside content too, so every agent
of such a loop already carries that mark.
Where the rule applies:
- Publish refuses a version in which one agent could hold all three, and names the agent, the
tool and the marks: for example, “tool
send_reporttakes agentanalystto all three trust properties …”. Agrantableentry that could only be answered by breaking the rule is refused too, because nobody could ever say yes to it. - In a loop’s run, the ask is refused before anybody sees a card. The model is told that an action with external effects belongs to a separate agent that reads nothing.
- In a person’s own run on the console, the card that asks for a permission warns and names which permission brings which mark; the person may still approve it. Once that run holds all three, every action with an outside effect waits for you on a card that shows what it will send, before it runs. No card asks when you turned on yolo, or when the loop’s published version answers its permissions itself. A visitor’s run never gets this card: there the ask is refused, as above.
- Approving an automation warns in the same words and does not refuse.
The two usual fixes:
- Split the work across two agents. One agent reads and decides; a second agent, which reads
nothing, sends, given what it needs as arguments. The
outbound_writerrole (send_externalandnotify) exists for that second agent. - Narrow the role or the loop. Remove the permission that brings the third mark from the
loop’s
capabilities, or from a tool’srequires.
Trusted and untrusted
Some things a run reads can be marked trusted — content you vouch for. Untrusted is always the default.
- A loop’s sources (
sources[].trusted). A file you upload is stored with that mark. When a run reads it,read_filereports the mark, and a file with no mark counts as untrusted. The model’s instructions label each source trusted or untrusted and tell it to treat an untrusted source as data, never as instructions. - A tool’s answers (
output.trustedon a loop’s own tool). An untrusted answer is read first by a separate model that has no tools, and only what that reader extracts reaches the run’s model. A trusted answer reaches the model directly. A client tool’s answer is never trusted, and a version that LoopAssistant proposes cannot make a tool’s answers trusted: only you can. - An index (off by default). An owner of the account marks it on the index’s page: press Mark trusted…, read what the mark changes, and confirm. A member sees the mark and cannot change it, and the account’s audit log records each change with its old and new value. A search result from an index that is not trusted is marked untrusted.
- The run journal ends each untrusted answer’s line with [untrusted].
What the index marks change in the Rule of Two. Searching an index adds outside content unless the index is trusted, and adds your data unless it is public. A site crawl is public, because the crawl sends no login; uploaded files and a collection’s records never are. So searching a trusted crawl adds neither mark. The loop’s publish review names each index’s marks and what a search of it adds. Publish checks the marks as they are and the version keeps them: marking an index trusted counts from the loop’s next publish, and marking it untrusted counts at once. See What an index lets a loop do.
What the trusted mark does not change yet. read_workspace and read_records always count as
outside content and your data, whatever their sources say.