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.
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 olderexecute:group:<group>form. Prefer the semantic form. - Subcommand groups are their own objects:
ravi bases rows addneedsmutate:bases.rows:addorexecute:group:bases_rows.execute:group:basesdoes not cover it.
Agent ceilings
Set the ceiling withravi agents permissions. Run it without a profile to see the current one:
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
Abootstrap agent holds:
- every tool the provider offers (
use:tool:*,use:toolgroup:*), including file read and write tools; - the CLI groups
sessions,tasks,specs,skills,selfanddoctor; - Bases reads through views and charts:
ravi bases list|show,ravi bases views list|show|queryandravi bases charts list|show|data(read:bases:list,read:bases.views:query,read:bases.charts:dataand the like). The Console still decides what each view shows, and in a turn written by someone else it reads as that person through theirravi 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 asmutate:bases.rows:add;ravi bases rows approve|rejectis in no default; - the executables
bun,cat,date,find,git,head,ls,nl,pwd,ravi,rg,sed,sort,tail,uniq,wcandxargs; read_own_contacts:system:*.
What a turn may do
The turn’s authority depends on who sent the message:Admin tags on contacts
Three contact tags grantadmin: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.
admin system:* (contact-policy:contact:<id>:admin-tag).
The tag does two things:
- It makes the contact an approver on every channel where its identity is linked.
- It gives the contact admin inside governed chats.
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
--apply this prints a plan (permission allow plan) and the exact apply command. Nothing is written.3
Apply and check
deny; when the last grant in a chat is gone, the chat is no longer governed:
--to contact:<id>requires a scope:--chat,--chat-tagor--force. Without one the command refuses (CHAT_SCOPE_REQUIRED).--chat-tag <tag>covers every chat carrying that tag. Tag a chat withravi tags attach <tag> --chat <chat-id>.--forcegrants the profile in every chat by adding the profile tag to the contact. Like the admin tags, it counts only while the contact isallowed. 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,--applyadds them, which can raise abootstrapagent to admin. Leave--agentout 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: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 aravi 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 taggedpermission-ownercan 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 alloworresolve. - 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 sendhas no chat to ask in, so the request is denied at once. Terminal turns work with the agent’s ceiling only. Achat-onlyagent’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,perlorrubyas the last command (code read from stdin); - inline code:
python -c,python3 -c,node -e,perl -e,ruby -e,php -r.
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 topicravi.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:
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.