session_id is special: it groups pushes into one source. It’s not a
filterable key; the ones you declare are.)
Now Sara messages again. You want her history, not Tom’s. Two steps.
Step 1: declare the key (once)
email field matters and should be indexed.
Everything already in the vault is backfilled in the background (the call
returns straight away with backfill: "queued", and the work scales with the
vault), and every future push indexes on arrival.
Where keys can live: the registry is an agent/team-vault feature. Agent
vaults declare via the SDK (above) or the vault’s Settings; team vaults via
Settings → Filterable metadata in the app. The personal vault has no
registry, so metadata still rides on personal pushes (like session_id),
it just isn’t filterable there.
Declare exact-value keys you’ll filter by: email, customer_id,
ticket, order_id, repo. Don’t declare topical labels (topic: infra); grouping by theme is what tags are for. Up to 16 keys
per vault, and a key is lowercase letters, digits, _ or -, up to 40
characters. Values must be text, numbers, or booleans (or arrays of them),
and reserved keys like session_id are refused with a 409.
Step 2: filter your search
Call this rule match-or-absent: a source without the key is never
excluded. The policy note isn’t about Sara, but it isn’t about anyone
else either, so it stays available when answering her. Only data that
contradicts the filter is dropped. (Case doesn’t matter; lists work on
both sides, up to 16 values per key; multiple keys combine.)
Adding metadata later
Metadata usually rides in at push time, but you can attach or fix it on an existing source too:Filtering examples
One person’s context (their data + everything unlabeled, never anyone else’s):Tags or metadata keys?
One question decides it: should a source that says nothing about this still show up? Yes → metadata key (forgiving, match-or-absent). No, only my curated set → tag (hard scope).Scoped pull_context
MCP agents get the same filter onpull_context, for team scopes, since
that’s where MCP-reachable declared keys live (teams declare them in the
team’s Settings; the personal vault has no registry).
scope here is the team’s vault id, never its name. Team names are
rejected outright, because two teams can share one; get the id from
userinfo, which lists every team you can reach with its vault_id.
- Single vault. Filters apply to one vault per call (a named team on
MCP, the addressed vault on the SDK); shared context isn’t searched while
a metadata filter is active, and the
coverage_notesays so. - Typos fail loud. Filtering on an undeclared key is a 400 on the SDK
(listing the declared keys); on MCP it’s
items: []with the reason incoverage_note, so read it before concluding “no memory”. - Combines with tags:
filters={"tags": ["launch"], "meta": {"email": ...}}= your curated set, minus anyone else’s data.
Best practices
- Canonicalize values before pushing. Matching is exact (trimmed,
case-insensitive), so
ORD-123never matches123. - Stamp the key consistently if exclusion matters: keyless sources
always stay in scope, which is a feature for shared notes and a foot-gun
for data you forgot to label.
update_source_metafixes stragglers. - Put metadata on the first push of a session. Later pushes in the
same
session_idextend the content, not the metadata. - Use arrays for multi-party sources.
"email": ["a@…", "b@…"]matches either. - Deleting a key is a light switch, not a shredder. Removing a declared key only drops the index; the metadata stays on every source, and re-declaring rebuilds it identically.
- Need only-matching results, keyless excluded too? That’s not a metadata filter; that’s a curated set. Use a tag.
- Storing names or emails in the content itself, not just metadata? See PII scrubbing to strip that at ingest.