Scopes and clearance
Two independent axes. Scope is where a memory applies. Clearance is how sensitive it is. A principal needs both to read something.
Scope keys
Section titled “Scope keys”Flat level:id strings:
org:acme repo:payments service:checkout env:productionModel flat, because only two relationships compute
Section titled “Model flat, because only two relationships compute”The limit. v1 computes exactly two: an exact match, and org:* covering everything.
Anything else fails closed — treated as not covered.
| Do | Do not |
|---|---|
repo:payments | org:acme/team:core/repo:payments |
service:checkout | repo:payments/service:checkout |
org:* for organisation-wide policy | invent team:* and expect it to fan out |
What still holds. Both computable cases are exact and enforced, and failing closed fails in the safe direction: a mis-modelled scope costs you recall, never isolation. You see it as memory that does not surface, never as memory appearing where it should not.
What to do about it. If something genuinely applies in two places, write it at org:* or write
it twice. Both are honest. A fictional hierarchy is not, and it will quietly return nothing.
Choosing a scope when writing
Section titled “Choosing a scope when writing”Ask: where would this be wrong?
| Memory | Scope |
|---|---|
“billing/reconcile.py needs fixtures regenerated” | repo:payments |
| “All migrations need a rollback plan” | org:* |
| “Checkout’s p99 budget is 300ms” | service:checkout |
| “Do not deploy on Fridays” | org:* |
Too narrow and it never surfaces where it is needed. Too broad and it is noise everywhere else —
org:* for a repository-specific quirk pollutes every other repository’s context.
Clearance
Section titled “Clearance”| Sensitivity | Typical use |
|---|---|
public | Safe anywhere |
internal | Default for engineering memory |
restricted | Limited need-to-know |
secret | Tightly held |
A principal cleared to internal never sees restricted, even inside its own scope. Enforced at
admission on every read.
Give agents the lowest clearance that works. Most engineering memory is internal, and an agent
that never needs restricted should not be able to read it — that is what limits the blast radius of
a prompt injection.
Narrowing at request time
Section titled “Narrowing at request time”One process acting for several agents can narrow per call with an actor block, so each sees only
its slice:
vd.recall("deploy history", actor={"scope_key": "service:checkout"})Narrowing is enforced by rejection, not by clamping:
- a narrower
scope_keywithin authority binds the visible set - a
scope_keyoutside authority is aforbiddenfault - a
clearancethat would widen is rejected, not quietly reduced
Clamping would look friendlier and hide a bug. A caller asking for more than it may have is either broken or being manipulated, and a smaller answer with no explanation tells you neither.
actor is optional. Omitted, it defaults to the verified principal.
Debugging “why did I get nothing?”
Section titled “Debugging “why did I get nothing?””An empty result is legitimate, so work down this list:
- Scope mismatch. Written at
repo:payments, queried atservice:checkout? Exact match only. - Clearance. Is the memory above your principal’s clearance?
- Status.
deprecatedmemory is excluded from current results. - Narrowing. Did an
actorblock narrow further than you meant? - It really is empty. Nothing was written yet.
You cannot tell 2 from 5 by counting results, and that is deliberate — being able to would leak the existence of restricted memory to anyone who could compare counts.