Skip to main content
By the end of this page you will know what an agent is allowed to do, who can drive it from a chat, how to give one person more power in one group, how approvals work, and how to find out why something was denied.
This page describes ravi.bot@next. In @latest (3.260927.5) there is no explicit-only profile, no governed chats (--chat, --chat-tag, --force), and no ravi permissions deny or list.

How it fits together

  • Every permission is a capability string.
  • Each agent has a ceiling: a runtime profile plus an optional list of explicit capabilities.
  • Each turn gets its own authority, computed from the ceiling, who sent the message and which chat it came from.
  • When a turn lacks a capability, Ravi can ask a trusted person in the chat to approve it for that turn (on the Pi and Grok providers; see Approvals).
  • Some shell commands are always blocked, whatever the profile.
  • Every denial is published on ravi.audit.denied.
If you only want the owner setup (agent at full-access, yourself as owner contact), follow Make yourself the owner. This page explains what those steps do.

Capabilities

A capability has three parts:
  • The only wildcard is a trailing * on the object id: dev-*, *.
  • A CLI command accepts its semantic form (<read|mutate>:<resource>:<action>, the action may be *) or the older execute:group:<group> form. Prefer the semantic form.
  • Subcommand groups are their own objects: ravi bases rows add needs mutate:bases.rows:add or execute:group:bases_rows. execute:group:bases does not cover it.

Agent ceilings

Set the ceiling with ravi agents permissions. Run it without a profile to see the current one:
Dry-run rule. The command writes immediately when the change keeps or reduces authority. It stops with a dry-run (exit 3, nothing written) only when the change expands authority: moving to full-access, adding a capability not already covered by its explicit list, or leaving chat-only or explicit-only (including none from those). Re-run with --execute. --capabilities replaces the list. Repeat the capabilities the agent already has. The seeded main agent starts with one explicit capability, view:agent:*. --clear-capabilities drops the list and keeps the profile. What the ceiling covers. The runtime’s built-in tools (Read, Edit, Bash, WebFetch and the rest), shell commands and ravi commands. Tools from MCP servers that the runtime loads from its own configuration, such as a Claude .mcp.json in the agent’s cwd, are not checked against it: they run without the ceiling and without approval, from any chat. Add only MCP servers you would let the agent use freely. When changes apply. From the agent’s next turn. Ravi revokes the agent’s live permission contexts when you change the ceiling, and the Bash check of a running turn reads the new ceiling at its next command. You do not need to restart the daemon or reset sessions. See what an agent holds right now:

The bootstrap floor

A bootstrap agent holds:
  • every tool the provider offers (use:tool:*, use:toolgroup:*), including file read and write tools;
  • the CLI groups sessions, tasks, specs, skills, self and doctor;
  • Bases reads through views and charts: ravi bases list|show, ravi bases views list|show|query and ravi bases charts list|show|data (read:bases:list, read:bases.views:query, read:bases.charts:data and the like). The Console still decides what each view shows, and in a turn written by someone else it reads as that person through their ravi link; without a link it refuses reads too (ACTOR_NOT_LINKED). Direct row reads (ravi bases rows query, ravi bases aggregate) and every Bases write need an explicit capability, such as mutate:bases.rows:add; ravi bases rows approve|reject is in no default;
  • the executables bun, cat, date, find, git, head, ls, nl, pwd, ravi, rg, sed, sort, tail, uniq, wc and xargs;
  • read_own_contacts:system:*.
The bootstrap floor is a convenience floor, not a sandbox. bun -e runs any code, and find -exec and xargs run other programs; the Bash check only sees bun, find and xargs. The provider’s file read and write tools are allowed too. For agents that talk to untrusted people, use chat-only or explicit-only.

What a turn may do

The turn’s authority depends on who sent the message:
With a full-access agent, anyone who reaches it and resolves to a contact can drive it with admin rights, unless the chat is governed. Contact tags do not restrict anyone. Keep strangers out with entry policies (pairing DMs, allowlist groups, explicit routes; see Instances and routing), and govern group chats that include other people.

Admin tags on contacts

Three contact tags grant admin:system:*: permission-admin, permission-owner and permission-superadmin. They count only while the contact’s status is allowed and the contact has not opted out.
The second command prints admin system:* (contact-policy:contact:<id>:admin-tag). The tag does two things:
  1. It makes the contact an approver on every channel where its identity is linked.
  2. It gives the contact admin inside governed chats.
It does not raise the agent’s ceiling, and it does not limit anyone else.

Governed chats

A chat becomes governed as soon as any contact holds a grant scoped to it. In a governed chat, each sender gets the agent’s ceiling intersected with their own grants; members without a grant (and without an admin tag or a --force profile) get no tools. Threads inherit their chat.
1

Find the chat id

The chat must already exist in Ravi’s chat ledger, so send one message there first.
--chat accepts the canonical chat id, or the platform id (for example a Slack C... id) when it matches only one chat. Inside an agent turn, --chat current means the turn’s chat.
2

Plan the grant

Without --apply this prints a plan (permission allow plan) and the exact apply command. Nothing is written.
3

Apply and check

Grant other members their own profiles the same way, with only the capabilities they need. Revoke with deny; when the last grant in a chat is gone, the chat is no longer governed:
Use a profile name other than owner for chat grants. The profile name becomes a tag with a permission- prefix, so allow owner targets permission-owner. If you created that tag with ravi contacts tag, it is a plain user tag and ravi permissions allow owner ... fails with Command failed unexpectedly.
Notes on the flags:
  • --to contact:<id> requires a scope: --chat, --chat-tag or --force. Without one the command refuses (CHAT_SCOPE_REQUIRED).
  • --chat-tag <tag> covers every chat carrying that tag. Tag a chat with ravi tags attach <tag> --chat <chat-id>.
  • --force grants the profile in every chat by adding the profile tag to the contact. Like the admin tags, it counts only while the contact is allowed. Use it only when you mean “everywhere”.
  • --agent <id> makes sure the agent’s ceiling includes the profile’s capabilities. If they are not in the agent’s explicit list, --apply adds them, which can raise a bootstrap agent to admin. Leave --agent out if you do not want the ceiling touched.
  • Chat-scoped grants work for any contact that is not blocked or opted out. Global tags need status allowed.

Recurring access for an agent

To give an agent one more capability for good, create a named profile and apply it to the agent:
This creates the provider-owned profile permission-cron-ops and adds its capability to the agent’s ceiling. Like deny and resolve, allow writes only with --apply (not --execute); without it, it prints the plan and exits 0.

Inspecting

check evaluates the context it runs in, so it is meant for agents inside a turn. From your own shell it reports denied with a note to use materialize. --local-operator evaluates the request as the local operator (you, at the terminal). All ravi permissions subcommands accept --json.

Approvals

When a turn needs a capability it does not hold, Ravi can post an approval request in the chat the turn came from. Only agents on the Pi and Grok providers ask. On Claude and Codex, a shell command outside the ceiling is refused at once, and on every provider a ravi command group outside it is refused without asking.
  • Who can approve: a contact (or agent) that the clicker’s or reactor’s identity is linked to, and whose global capabilities include the requested capability or admin:system:*. An owner tagged permission-owner can approve anything. Chat-scoped grants do not make anyone an approver. Clicks or reactions from anyone else are ignored.
  • Slack: the card goes privately to the people who can approve (an ephemeral message in the chat, or their DM with the app when they are not in it), and they use its Approve or Reject button. When nobody linked to the workspace can approve, no card is sent and the request is refused at once. Reactions and replies are ignored.
  • WhatsApp and other channels: react with 👍 or ❤️ to approve. Any other reaction, or a reply to the request message, rejects it.
  • Timeout: after 5 minutes without a decision, the request is denied.
  • Scope: an approved capability lasts for that turn only. For recurring access, use ravi permissions allow or resolve.
  • Shell commands: an approved executable runs for the rest of that turn. To let the agent run it every time, add it to the agent’s ceiling.
  • No approvals in the terminal. A turn started with ravi sessions send has no chat to ask in, so the request is denied at once. Terminal turns work with the agent’s ceiling only. A chat-only agent’s tool requests are denied without asking.

The Bash check

Before every Bash command, Ravi checks it against the turn’s capabilities. These are always blocked, under every profile, and cannot be approved:
  • shells and string execution: bash, sh, zsh, dash, ksh, csh, fish, tcsh, eval, exec, source, .;
  • command substitution ($(...), backticks), process substitution and here-documents;
  • piping into a shell, or into python, python3, node, perl or ruby as the last command (code read from stdin);
  • inline code: python -c, python3 -c, node -e, perl -e, ruby -e, php -r.
Below full-access, two more rules apply: redirecting output to a file needs use:tool:Write (the bootstrap floor includes it), and commands that set RAVI_* environment variables are refused.

Denials and audit

Every denial is recorded and published on the NATS topic ravi.audit.denied. The payload includes type, agentId, denied, reason and dedupeKey, plus command, context, blockType and denialId when they apply. Watch them live, or route them to an agent:
Turn a denial into a fix with its denialId:
resolve proposes a profile that holds the missing capability. For an ordinary denial it adds the profile to the agent’s ceiling. For a denial in a governed chat it grants the profile to that contact in that chat only, and makes sure the agent’s ceiling includes it. --profile and --capabilities override the suggestion, and --chat, --chat-tag or --force override the scope. See Triggers and Events for more on topics.