Grants
A grant is permission for one capability, optionally one record, that expires on its own.
It is the whole of the model. A read that aims nowhere the agent chooses is free; everything that changes anything costs a grant, and so does a read aimed at a destination the agent names — a host, a URL, a port, a connection. There is no flag that stands in for one. A grant is decided when you need it, for as little as you need, and then stops being true without anybody remembering to revoke it.
Issuing one
rta grant allow kv.get db-password --agent claude --ttl 30mThat reads as: allow the agent claude to call kv.get, but only on the key db-password, for thirty minutes.
| Part | What it means |
|---|---|
<target> | A capability ID (kv.get) or a plugin name (kv, covering all of it, destructive capabilities included) |
[scope] | One record — a key, a table, a bucket — or, ending in /, a folder of them. Omit to cover the whole capability |
--ttl | How long: 30s, 15m, 2h. Default 15m, maximum 24h |
--agent | Narrow to one named agent — the name rta mcp serve --as uses |
--profile | Narrow to one configured connection (staging, not production) |
--max-uses | Expire after this many successful calls |
--rate | Bound how fast, as calls/window — 10/1h |
--note | Why, shown by grant list |
Leaving the scope out of a grant on a capability that reveals a stored value, such as kv.get, covers every record it reaches, so the receipt says so while the clock is running: note: kv.get reveals the stored value itself, and with no record named it does so for every record it reaches — name one to allow only that record. A plugin name counts the capabilities of that kind it carries.
A whole environment at once is rta grant issue --profile staging --agent claude: one grant per capability per connection, reads unless you say more, under one passphrase. Roles says what it covers and what it leaves out.
Only a person at a terminal can issue one. An agent that could grant itself access would be no gate at all, so there is no MCP tool for this and no flag that makes one.
The four bounds, and what each is for
They compose. A grant stops at whichever is reached first.
rta grant allow kv.get deploy-key --agent claude --ttl 5m --max-uses 1--ttlbounds time. The one you always get, because it is the only bound that keeps working when you forget.scopebounds reach.rta grant allow kv.getallows the whole store.rta grant allow kv.get db-passwordallows one key. Reach for the second unless you mean the first. A record ending in/is a folder:rta grant allow kv.get prod/allows every key underprod/, those written later included, and never one that climbs back out —prod/../staging/db-password, or any spelling a server decodes into that,%2e%2eand..%2famong them. Name such a record whole if you mean it; a folder never sweeps one in. A record is compared as the call spells it, white space included: a grant ondb-passworddoes not coverdb-passwordordb-passwordfollowed by a no-break space, which a store reads as another key and a web server as another path, so a call naming one asks for its own consent. An audit whose record is a host, a domain or a namespace refuses one with white space around it, since no such name has any, rather than audit the name without it:audit web,audit mailand theaudit kubechecks alike.audit webrefuses a host holding a character that draws as nothing as well, wherever it stands, since the request would drop it before the name resolves and audit the name without it all the same, and a host in anything but ASCII, typed or percent-encoded, which the request folds into another name the same way: a fullwidth digit into its digit, an ideographic full stop into a dot. An internationalised name goes in itsxn--form, the name the request resolves.grant allow,grant renewandgrant revoketake a record exactly as you give it, so the command a refusal offers for such a call, quoted for your shell, grants that call's record and not the one it looks like; a record that is only white space names nothing and is refused, by every way a grant is issued, and a refusal an agent gets for a call naming one offers no command for it. It is shown the way it is compared: a record with white space in it, or with a character that draws as nothing, is shown quoted as Go quotes a string, each such character written as the escape of its code point, wherever you answer for one:agent pending,agent show, the answer andgrant listalike. A record shown as it is never begins with a quotation mark, so the two are never shown alike.agent logshows the records each call named the same way, in a record column, and the agent record keeps them exactly as the call spelled them. The arguments shown beside a record, inagent log, in the would-do column ofagent pendingand onagent show, are kept as a model may read them, without the zero-width, direction and tag characters, so an argument can read as the bare record where the record beside it is shown padded: the record is the one to answer for.--max-usesbounds quantity.--max-uses 1is the shape for a value that should be read exactly once — a deploy key, a one-time token. When a grant a server was running an agent's calls on has used up its uses or run out its time, the agent's next call is refused asthe grant for kv.get ended (1 of 1 use), orendedafter the clock, so it learns it was allowed and is not any longer, and the record carries the same words; the code is stillcore.grant.required. A call that never had a grant, or one a restarted server has no memory of, saysno active grant.--ratebounds speed.--rate 10/1hallows ten calls in any hour and tells the agent when to come back. A session that has gone wrong slows to something you can notice, rather than draining at machine speed.
The last one is worth dwelling on. Time and quantity both fail the same way: an agent in a loop exhausts them immediately and correctly. A rate limit is the only one of the four that turns a runaway into something a human can catch while it is happening.
Naming the agent
rta grant allow pg.query --agent claude --profile staging --ttl 1hA grant is for one agent, and there is no grant for every client: consent given while pairing with one editor should not silently follow the agent running in a CI container. Leave --agent out and rta fills the name in when this machine knows exactly one agent — one that has connected, or that already holds a grant — asks which when it knows several (grant.whichagent), and refuses when it knows none (grant.noagent), since a grant for nobody would be a row grant list shows and the gate ignores.
The name comes from rta mcp serve --as <name>, which rta mcp install sets for you. Both halves name the same thing on purpose.
An empty agent on a grant is not a wildcard — it matches a server started without --as, and nothing else. Matching is exact in both directions, which is the same rule --profile already used.
Seeing and taking back
rta grant list # what is allowed right now
rta grant renew kv.get db-password # push out the deadline
rta grant revoke kv.get db-password # take it back now
rta grant revoke kv # or all of it
rta grant revoke kv.get --agent claude --exact # the one grant naming no record, on the base connectionEach selector narrows, and one left out matches every grant: grant revoke kv.get takes back the grant on every record, every connection and every agent, and a plugin name everything inside it. An environment's name is every connection in it: --profile staging takes staging/analytics with it, and --profile staging/analytics names that connection alone. --exact makes the selectors name one grant — a record, --profile or --agent left out means the grant naming none, and a plugin name its grant on the whole plugin alone — which is the only way to name the grant on no record, or on the base connection, without taking its neighbours with it. grant renew takes it too. In the TUI, x and n on a row of grant list act on that row's grant alone, as --exact, and x on a roster read with --server does so on that server: one older than the switch would read it as absent, so it is asked first and refused rather than trusted. n is not offered on such a roster, since grant renew takes no --server: a renewal of that server's grants is made there.
grant revoke lists each grant it took back with the grant allow line that would issue the same one again, for the time and the uses it had left, note included, and when the grants taken back are exactly what one grant issue --profile call issues it names that call once, with the class, window, bound and note it was given; a set that is less than that, or was issued by hand, gets the lines alone. grant renew moves a deadline out and never in: a renew that would shorten a grant says nothing renewed and leaves it. Allowing a whole plugin, as rta grant allow kv, says how many of its capabilities need a grant and which of them destroy something, because that short command is the one place a grant is broad without looking it. rta grant list --agent claude is one agent's grants.
grant list is one line per grant at a terminal: the target, the record, the agent, and what remains of the time and of the uses. A connection, a role, an origin of command and a plugin that was replaced each get a column the day one grant has it, and not before. A pipe, -o json or --detail shows every field, the deadline as an exact time and your --note among them, and --detail adds the plugin build each grant is bound to: the short digest of the plugin's artifact, or built in. A grant whose plugin has been replaced since it was issued is marked (replaced) on every listing of this machine's grants, and one whose plugin rta no longer loads (not loaded), each with a warning beside the rows saying what fixes it. A roster read with --server is judged by that server, against the plugins that answer there, which this machine cannot see, and carries the same marks and warnings; a server on an older rta sends no verdict, and each of its grants is marked (unknown). It is the answer to "what can an agent do right now", and it is the one screen worth checking before you walk away from a machine with a server running.
Revoking takes back what grants gave — it does not touch the ungated read tools an agent's token still opens. When the need is "this agent makes no call of any kind until I say so", that is a lock: rta lock add <name>, effective on its next call, no restart.
Bound to a task, not to a process
The server is per-session by construction, so the question of how long an agent's reach lasts is answered by the permissions, and those have their own clocks rather than the process's:
| Bound to a task by | What it does |
|---|---|
rta grant allow … --ttl 30m | Consent that expires on its own, whatever the server does |
rta grant allow … --max-uses 5 | Consent that runs out by use rather than by clock |
rta use staging | While it is on, every other environment is refused whatever grants exist, as Profiles explains |
rta grant revoke --all | The end of the task, without touching the client |
Restarting the server changes none of it. That is deliberate: a deadline that ended when a process did would be a deadline your editor could reset by crashing.
What a grant does not do
- It does not survive the plugin it names being replaced. A grant on a plugin's capability records that plugin's artifact digest, so swapping the binary under the same name stops it covering anything. Built-ins have no separate artifact and carry no digest. Upgrading or rebuilding a plugin is such a replacement:
grant listmarks every grant standing on the old build(replaced)andrta doctorwarns of them, because an agent refused under one is told only what an ungranted call is told. Issue it again after the upgrade withrta grant allow;grant renewmoves the deadline and never rebinds a grant. - It does not widen a path root. Path confinement is checked separately, on every path argument.
- It does not survive a ceiling. If a
.rta-policy.yamlsaysmaxTTL: 15m, a--ttl 2hgrant is clamped to 15m and told so. - It does not widen what a profile reaches. A profile entry that states
only:holds every call made through it to that list, whatever the grant says, and the grant is on the connection as fenced: editing the list re-aims it, and the grant is asked for again. - It does not authorize a profile you have not configured.
--profile stagingmatches the connection namedstaging, exactly. - It does not make an unusable profile usable. A profile that
rta profile listmarksinvalid— a stale pin after a plugin rebuild, a key nothing reads — is refused on every call, whatever grants exist, and the agent is told only that the call needs a grant, so that its names cannot be read off the refusal. The grant is issued all the same, and its receipt carries a note with the reason and the fix, because you are the one who can act on it. - It does not blur instances. When an environment holds several connections to one plugin —
pgandpg/analytics— a grant names exactly one (--profile staging/analytics), and asking for the bare name is refused with the list rather than resolved into a consent you did not give. Each grant pins the instance it was issued against, so re-aiming that one connection revokes exactly that grant.
Related
- Roles, consent and the guard — a day of grants under one word, answering a parked call, the file's seal, and a passphrase in front of issuance
- Team policy — a ceiling nobody on the team can raise
- Profiles — what
--profileis naming
Next
The record — what agents actually did with what you granted.