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.
PermissionWhat it lets a run doMarks
read_webRead any page on the webOutside content
call_endpointCall an endpoint you declaredOutside content
read_workspaceRead your filesOutside content, Your data
write_workspaceWrite to your filesYour data
read_recordsRead your collectionsOutside content, Your data
write_recordsWrite to your collectionsYour data
search_indexesSearch your indexes and read their documentsOutside content, Your data
notifySend you a notification—
install_automationsInstall an automation that runs on its ownYour data
install_loopsInstall a loopYour data
install_toolsInstall a tool your loops can callYour data
install_agentsInstall an agent your loops can run asYour data
install_pagesDraft a page your members can readYour data
read_connectionRead from a service you have connectedOutside content, Your data
write_connectionChange things in a service you have connectedYour data, Acts outside
send_externalSend things outside plutonium.ioActs outside
serve_recordsServe your collections to an outside caller with an API keyYour data, Acts outside
spawn_tasksStart further runs—
run_codeRun codeActs outside
read_own_resultsRead back results it already receivedOutside content
rehearse_claimTry an automation once on the live site, saving nothingOutside content
client_navigateOpen a page of your app for the person it talks toOutside content
client_actAct in your app as the person it talks to, once they confirm each useOutside 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

  1. 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.
  2. 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_act and call_endpoint name something only a loop has — its other agents, the page that hosts it, its own endpoint tools — so no role holds them, the loop’s capabilities give 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_reader reads two job sites and writes files under docs/ only. A grant in a loop’s run gives back no more than those limits, and a grantable entry 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’s grantable declares a widen for that permission: then the person that entry names is asked about that one site.
  3. 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’s grantable lets 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 capabilities hold back, or one of the four loop permissions (inside the capabilities, the published version grants that one at the call). If the loop’s grantable names 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’s grantable does not name it, nobody is asked. An entry in grantable grants 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.

ToolNeedsAsks first
ask_usernotify
request_inputsnotify
notify_usernotify
escalatenotify
finishnotify
read_fileread_workspace
list_dirread_workspace
write_filewrite_workspace
records_queryread_records
records_putwrite_records
searchsearch_indexes
read_documentsearch_indexes
read_resultread_own_results
web_fetchread_web
web_fetch_sourceread_web
web_extractread_web
rehearse_claimrehearse_claim, read_web
github_search_issuesread_connection
spawn_tasksspawn_tasks
read_loopinstall_loops
propose_loopinstall_loopsasks first
propose_toolinstall_toolsasks first
propose_agentinstall_agentsasks first
propose_pageinstall_pagesasks first
propose_claiminstall_automationsasks 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_report takes agent analyst to all three trust properties …”. A grantable entry 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:

  1. 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_writer role (send_external and notify) exists for that second agent.
  2. Narrow the role or the loop. Remove the permission that brings the third mark from the loop’s capabilities, or from a tool’s requires.

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_file reports 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.trusted on 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.