Skip to content

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.

Flat level:id strings:

org:acme repo:payments service:checkout env:production

Model 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.

DoDo not
repo:paymentsorg:acme/team:core/repo:payments
service:checkoutrepo:payments/service:checkout
org:* for organisation-wide policyinvent 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.

Ask: where would this be wrong?

MemoryScope
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.

SensitivityTypical use
publicSafe anywhere
internalDefault for engineering memory
restrictedLimited need-to-know
secretTightly 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.

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_key within authority binds the visible set
  • a scope_key outside authority is a forbidden fault
  • a clearance that 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.

An empty result is legitimate, so work down this list:

  1. Scope mismatch. Written at repo:payments, queried at service:checkout? Exact match only.
  2. Clearance. Is the memory above your principal’s clearance?
  3. Status. deprecated memory is excluded from current results.
  4. Narrowing. Did an actor block narrow further than you meant?
  5. 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.