rvm · capability-governed context
ruv:// names memories, resources, and skills across tenants and agents.
Holding the name gets you exactly zero authority — the check happens somewhere else,
and it happens first.
One spelling per name. No percent-encoding, no fragments, no credentials, no port — so policy scopes, witness records, signatures, and caches can never disagree about whether two URIs are the same URI.
01 · Introduction
Nearly every system you have used decides access the same way: you name a thing, the system works out who you are, and then it decides. Your authority is ambient — it hangs in the air around your identity and applies to everything you can name. Unix permissions work this way. So do database row filters, S3 bucket policies, and almost every RBAC layer ever shipped.
Ambient authority has a failure mode with a name, and it is nearly sixty years old: the confused deputy. A component that holds more authority than its caller gets talked into spending that authority on the caller's behalf. The deputy is not compromised and not buggy in any local sense — it simply cannot tell which of its powers the request was entitled to, because the request only carried a name, and names are free to write down.
This gets sharply worse with agents. An agent is a deputy by construction: it holds tools, credentials, and memory, and it takes instructions from text it just retrieved — text that may have been authored by someone else's user. If naming a resource is sufficient to reach it, then any string that reaches the model is a potential instruction to reach it.
Parsing a URI grants no rights. Resolving a skill does not execute it.
So ruv:// splits the two things that ambient systems fuse. The URI is
inert — a pure name, canonical to a single spelling, useless on its own. The right
to act on it arrives separately, as a capability handle that was deliberately handed over,
can be narrowed on the way, and can be revoked. An untrusted request carries only that
handle, an operation, and the canonical URI. It has no actor field and no timestamp,
because those are precisely the fields a caller would want to forge; the actor is bound
immutably when the runtime is constructed, and there is no setter.
The practical difference is that a prompt-injected agent that talks itself into naming another tenant's memory gets nothing. Not a filtered empty result — nothing, from a backend that was never consulted.
| Traditional approach | Its failure mode | What ruv:// does |
|---|---|---|
| Bearer token in the URL | The URL is the credential — it leaks through logs, referrers, screenshots, support tickets | Handles never appear in the URI or in ordinary logs |
| Check the ACL after resolving | Existence leaks through timing, cache state, and errors that differ by cause | Authorization precedes the resolver; four causes converge on one external shape |
WHERE tenant_id = ? |
One forgotten clause is a breach, and every query still traverses everyone's data | Separate physical index per exact scope — isolation is a different file, not a filter |
| Path-prefix multi-tenancy | /project silently captures /project-archive |
Segment-wise matching; text prefixes are never used |
| Role-based access | Authority follows identity, so it is hard to attenuate and delegation is all-or-nothing | Attenuation is the default; no binding ever grants delegation rights |
| Soft-delete a row | The bytes remain, and "deleted" is a flag anything can ignore | Destroy the per-object key; the ciphertext becomes noise for everyone, including you |
None of this is novel cryptography — capability systems date to the 1960s and the ideas here are older than most of the stacks they replace. What is new is the pressure: an agent that reads untrusted text and holds real tools is the confused deputy the literature warned about, running in production, at scale, by design.
| Component | Accepted values | Why it is closed |
|---|---|---|
subject-kind | agent · user · service · team | A closed set cannot be widened by a config edit |
collection | memory · resources · skills | Three kinds of thing, three retention stories |
view | abstract · overview · content | Disclosure tiers, separately grantable |
rev | sha256: + exactly 64 lowercase hex | An immutable citation to a byte stream |
| Query | absent · rev · view · canonical rev&view | Four forms, so ordering can't fork the name |
The limits are equally deliberate: 2,048 bytes for the whole URI, 253 for the authority, 63 per identifier, 128 per path segment, 32 segments, 1,024 bytes of path. A name that cannot be unbounded cannot be used to exhaust the thing that stores it.
02 · The mechanism
This is the whole design in one arrow. The resolver and the vector index are never consulted on an unauthorized request — not consulted and filtered, not consulted at all. A denied request stops at the boundary and produces a witness record, and the storage layer never learns the name was asked for.
03 · Isolation
The tempting way to build multi-tenant retrieval is one vector index with a tenant field, filtered after the search returns. That is a post-filter, and it means every query traverses a graph built from every tenant's data — latency, result quality, and memory pressure all become functions of objects the caller was never authorized to enumerate.
A detail worth stealing regardless of whether you use rvm: the scope is matched on
path segments, never on string prefix. …/project and
…/project-archive are siblings, not parent and child, and no amount of clever
naming makes one reachable from the other's grant.
04 · Disclosure
The same object can be exposed at three depths. These are capability-scoped representations, not interchangeable filenames — a grant can permit the abstract while denying the content, and an adapter must never silently broaden a grant to a larger representation.
05 · Silence
An error message is a side channel. If "forbidden" and "not found" differ, an unauthorized caller can enumerate your namespace by reading the difference. So four distinct internal outcomes converge on one external shape — unless the caller holds explicit audit authority, in which case they get the real reason.
06 · Practical applications
Give each agent its own subject scope. Two agents on the same host, in the same process, searching the same collection name, never traverse each other's vectors — the isolation is a different file, not a filter clause.
ruv://context.example/acme/agent/researcher/memory
Publish a skill once at team scope, then hand out grants that narrow it. A grant can permit resolve-and-read while denying execute, and no binding ever confers the right to delegate onward.
ruv://context.example/acme/team/platform/skills/web-search
When an agent cites what it read, it cites a pinned revision, not a mutable alias. The bytes under that revision are never overwritten, so a transcript stays checkable after the alias has moved on six times.
…/memory/project-orion?rev=sha256:2f9c…&view=content
Every object gets a fresh AES-256-GCM data key bound to its tenant and its pinned URI. Forgetting destroys the key rather than hunting down copies, and the durable purge outbox means a crash mid-erase resumes rather than silently stopping.
forget → key destroyed → ciphertext is noise
Route on ?view=abstract. A planner deciding which of forty memories is worth
opening pays for forty abstracts, not forty full documents — and never holds authority
for the content it decided against.
…/memory/project-orion?view=abstract
Expose the resolver through MCP or HTTP and every authorized call leaves a witness observation stamped by a runtime-owned clock. The audit trail is not something the caller can influence, because the caller never supplies the timestamp.
MCP · TLS HTTP · certificate-validating CLI
07 · Exotic
Some of these are load-bearing ideas wearing an odd hat; a couple are genuinely strange. All of them fall out of properties the namespace already guarantees.
view tiers of the same object. Neither cohort can reach the other's
representation, and the provenance binding means you can prove afterward which bytes each
cohort actually saw.
08 · Usage guide
Build the URI through the strict parser. A forgiving URL library will happily accept
%2f, a trailing slash, an uppercase tenant, or a .. segment — all
of which are different names that would resolve to the same object, which is precisely the
ambiguity the canonical contract exists to prevent.
# canonical — the only accepted v1 spelling
ruv://context.example/acme/agent/researcher/memory/project-orion
# all rejected by RuvUri, none of them equivalent
ruv://context.example/ACME/agent/researcher/memory InvalidTenant
ruv://context.example/acme/agent/researcher/memory/ trailing slash
ruv://context.example/acme/agent/researcher/memory/.. dot segment
ruv://context.example/acme/agent/researcher/memory//x empty segment
ruv://user:pw@context.example/acme/… CredentialsNotAllowed
ruv://context.example:8443/acme/… PortNotAllowed
Capabilities are declared out of band in the artifact's metadata, across fifteen classes. Two rules do most of the work, and both fail closed:
| Capability classes | |
|---|---|
Memory · Filesystem · Network · Model · Mcp | data and reach |
Process · Clock · Randomness · Gpu | execution and entropy |
Sensor · Display · Audio · Clipboard | the human's machine |
PersistentState · InterAgentMessaging | durability and reach between agents |
Bytes register under a pinned revision and are never overwritten. The alias is a separate, mutable pointer that advances only by compare-and-swap — so two writers racing produce one winner and one explicit failure, never a lost update. In a distributed alias store, that CAS has to be genuinely linearizable; a last-write-wins store silently reintroduces the race.
put …/skills/web-search?rev=sha256:2f9c… immutable, pinned required
advance …/skills/web-search from=sha256:1a04… CAS on the current revision
to=sha256:2f9c…
→ alias now points at 2f9c…; 1a04… still resolves forever
Put and Execute.The bundled local key manager requires an explicit development opt-in. Production embeds its own KMS and purge providers.
09 · Boundaries
The reference crate establishes namespace, integrity, capability, and evidence boundaries. The hosted service adds encrypted persistence, cryptographic erasure, exact-scope indexes, durable outboxes, and a transactional receipt drainer. Everything below is outside that line, and saying so is part of the design:
ruv:// does not define how bytes move between hosts.A capability system that overclaims is worse than one that underclaims, because the overclaim is what gets quoted in the design review six months later.