Skip to main content
By the end of this page you will know every check between a message and the work an agent does. You will also know what Ravi records, how to add your own authority, and which limits exist today.
This page describes ravi.bot@next. Command counts and defaults come from that branch.

How it fits together

The checks on this page run on the machine, in the daemon or the CLI. The Console checks only the calls made to it, such as member roles, page access and connector approvals.
  1. Entry. Instance policies and routes decide who reaches an agent.
  2. Ceiling. A runtime profile and a list of capabilities cap what an agent may do.
  3. Turn. Each turn gets its own authority, from the ceiling and from who sent the message.
  4. Commands. Every ravi command declares its risk. Commands with outside or irreversible effects wait for --execute.
  5. Record. Denials are stored and published, and each command publishes an audit event.

Who can reach an agent

Entry policies decide who reaches an agent. Each instance has one policy for direct messages and one for groups, and a route can override them.
  • The policy comes from the matching route’s --policy first, then the instance, then open.
  • An explicit route admits its group under every group policy, closed included. Any pattern other than * is explicit.
  • ravi instances pending approve on a chat creates that route. On a contact, it sets the contact to allowed.
  • A sender who gets in without a contact receives text replies only. The turn has no tools and no commands.
Both policies default to open. ravi instances connect rewrites the instance record each time it runs, so the policies go back to open. Set pairing and allowlist again after every connect.
Members of your organization see an agent in the directory only after its operator lists it (ravi agents listing <agent> on --execute), and reach it from another machine only after the operator opens it with ravi agents requests <agent> on --execute. Those turns run as the person who asked, with no capabilities. See Machines. Channel details, such as how Slack differs from WhatsApp, are in Instances and routing.

What an agent may do

Each agent has a ceiling: a runtime profile plus an optional list of capabilities. Each turn gets its own authority under that ceiling. The authority of a turn depends on who sent it: A chat becomes governed as soon as one contact holds a grant scoped to it. Set a narrow ceiling, grant each person what they need in the chat, and check the result:
Console access calls made from a chat, such as ravi pages share, ravi invites and ravi access request, act for the person who wrote. The Console resolves that person through their ravi link. A turn that cannot be attributed is refused and never falls back to your login. Other Console writes made in a chat, such as ravi pages ship and ravi bases rows, are credited to the person who wrote when they have linked their account with ravi link and are an active member of your organization. The Console then acts with the lesser of their role and yours, and records both of you in the audit log. Without that link the write is refused, and Ravi starts the link for them: on WhatsApp the agent asks them to message it in private (it never starts the conversation), and Ravi answers that message with their approval link. Once they approve, their private chat reaches the agent even if it had no route. Reads still use your login. See Permissions for capabilities, the bootstrap floor, governed chats and admin tags.

Every command declares its risk

Every ravi command declares a kind (read or mutate), a resource, an action and a risk. A command without that declaration is denied, whoever runs it. The counts below are from 18 October 2026 and grow as commands are added. The kind, resource and action form the capability the command checks, such as mutate:agents:permissions. The older execute:group:<group> form also works, except on a few commands that need an exact resource. To read any command’s declaration:
The access block shows kind, resource, action, risk and requiresConfirmation.

Exit codes and the write brake

157 commands carry the write brake. Without --execute they print the plan and exit 3. Some brake only when the change adds authority: ravi agents permissions applies a reduction at once.
Shell commands get one more check. Shells, string execution, command substitution and inline code for python, node, perl, ruby and php are always blocked, under every profile. bun -e is not blocked. See The Bash check.

Approvals

When a turn lacks a capability, Ravi can ask a trusted person in the chat. Today only the Pi and Grok providers ask for capabilities. On every provider, a ravi command outside the ceiling is refused without asking.
  • Who can approve: a person whose chat identity links to a contact or agent. Its global capabilities must include the requested one, or admin:system:*. Plans and specs need admin:system:*, and chat-scoped grants do not count.
  • Slack: the Approve and Reject buttons. Reactions and replies are ignored.
  • WhatsApp and other channels: react with 👍 or ❤️ to approve. Any other reaction, or a reply to the request, rejects it.
  • Timeout: after 5 minutes without a decision, the request is denied.
  • Scope: an approval covers the current runtime context only. For lasting access, use ravi permissions allow or ravi permissions resolve.
Ravi never asks in three cases, and denies at once:
  • The turn has no chat, such as ravi sessions send from your terminal.
  • The sender has no contact. The request would go back to that same sender, who could approve it.
  • The agent is chat-only and the request is for a tool, a CLI group or an executable.
Connector writes, such as ravi gmail send --execute, follow a separate path. The account owner approves on a Console page, and the command runs again with --approval <id>. See Connect your accounts.

Credentials

The credential broker keeps provider secrets out of Ravi’s database. Ravi stores the connection and a reference, and the secret stays in macOS Keychain or in Vault.
  • Each ravi credentials command checks its own capability, such as mutate:credentials.broker:exec. No profile below full-access holds these by default.
  • Adding a connection, removing one and running the broker are CLI-only. A ravi command from an agent turn is refused for them.
  • broker exec is braked. With --execute it resolves the secret inside the process and never prints it.
  • Native Slack reads its tokens from the broker when its channel names a connection. Its secret holds appToken and botToken, as JSON or as env lines.
policies explain prints the policy the broker declares for an action. Using a connection needs use:credential:<provider>:<connection> and execute:<provider>:<action>. Every action except a short list of reads, such as auth.check and users.list, is marked as needing approval.
Today the broker declares this policy but does not enforce it when it resolves a secret. The audit row records the approval as not_requested. Treat the command capabilities and the CLI-only rule as the real boundary. They do not stop an agent that can run any code, such as a bootstrap agent through bun -e.

Keys for apps

Apps, scripts and external CLIs call Ravi with a context key (rctx_...). A key is a lease of named capabilities, and it never holds more than the context that issued it.

Isolated CLIs never run locally

A process that carries RAVI_CONTEXT_KEY sends every command to the daemon’s gateway. It uses RAVI_GATEWAY_URL when set, otherwise the daemon’s socket at ~/.ravi/cli-gateway.sock. The daemon that owns the key authorizes the command. If no gateway is reachable, the command fails with GATEWAY_REQUIRED. It never falls back to running locally against another database. CLI-only commands are refused in this mode.

The HTTP gateway

  • The gateway binds to 127.0.0.1 by default. The daemon refuses to start on any other host unless RAVI_GATEWAY_NETWORK_AUTHORIZED=1 is set.
  • Superadmin commands are refused through the gateway unless RAVI_SDK_GATEWAY_ALLOW_SUPERADMIN=1 is set.
  • CLI-only commands are not exposed through the gateway at all.
See SDKs and HTTP gateway to turn it on and issue keys.

Plug in your own authority

The external-authority provider lets an outside issuer cap what agents may do. The issuer signs an assertion, and Ravi verifies it locally, with no network call. Any doubt is a denial. How it combines. Ravi asks every provider in the chain, and any denial wins. With external-authority added, a request must pass the agent’s ceiling and match the assertion. The provider never grants a capability on its own. What it covers. Checks that carry the agent’s identity: every ravi command an agent runs, and tool and shell requests on Pi and Grok. Your own terminal commands stay with operator-control, which is always in the chain. What it does not cover yet. Outside governed chats, Claude Code and Codex check built-in tools and shell commands against the agent’s ceiling directly. The assertion does not apply to those checks.
1

Write and sign the assertion

sub is the subject, such as agent:<id>. iat and exp are seconds since the epoch, and exp is required. In a scope entry, * matches any value of that whole field.To sign, remove sig and serialize the rest as JSON with keys sorted at every level and no spaces. Sign those bytes with an Ed25519 private key and put the base64 signature in sig.A matching entry with "requiresApproval": true answers “needs approval” instead of “allow”. A ravi command has no way to ask, so it is refused.
2

Point Ravi at the assertion and the public key

The public key is an inline PEM or a path to a PEM file. Ravi refuses a key file that its group or other users can write. Keep both files out of reach of the agents.Changing a setting under permissions. needs admin:system:*. You hold it at your terminal. Inside an agent turn, only an agent that holds it, such as a full-access agent, passes.
3

Add the provider to the chain

Keep context-capabilities in the list. Without it, the assertion alone decides the checks it covers, and the agents’ ceilings stop applying there. Most tool and shell checks of Claude Code and Codex are then denied.
4

Check the chain

The authorization providers line must list external-authority. Ravi reads the setting and the assertion file on every check, so no restart is needed. To rotate, replace the assertion file.
Every one of these cases is a denial, recorded like any other: To turn it off, remove the setting. The command is braked:

Audit

Denials are stored and published, and each command publishes an audit event. Apps can watch the audit topics live over the SDK.
An app with a key holding view:system:audit can stream every ravi.audit.> event from GET /api/v1/_stream/audit, or with audit() in the SDKs.
  • ravi.audit.> and ravi._cli.cli.> are kept in the RAVI_EVENTS JetStream stream for 7 days, up to 512 MB. It is a replay buffer, not an archive.
  • Events on ravi.<sessionKey>.cli.* topics are published live only. The denials table is the lasting record.
  • Ravi has no sync that sends the denials table or the audit topics to the Console. See What leaves your machines for what does reach it.
  • RAVI_SUPPRESS_AUDIT_EVENTS=1 in a process’s environment, the daemon or the CLI, stops that process from publishing audit events. Denial rows are still written. Keep it out of ~/.ravi/.env.

Known limits

These limits exist today. Plan around them.

Not built yet

Report a vulnerability

Report security issues privately through a GitHub security advisory. If GitHub does not offer the form, open an issue that asks for a private security contact, without any details. Please do not put details in a public issue.