Concepts
The mental model
Verity has a small number of load-bearing ideas. Once the memory layers, entity tags, scope handles, and the knowledge layer click, every route and tool in the rest of the docs reads as an application of them.
Architecture & deployment
Before the concepts, the shape of the system — because it is easy to over-read “the MCP server.” The MCP server is not Verity. It is a front door. Verity is one central, multi-tenant server; the adapters that agents connect through are thin, stateless, and cheap to multiply. Getting this picture right tells you where memory lives, where enforcement runs, and how to give two agents different reach.
verity-mcp configs pointed at the same server. The red box is the one component that stores data and makes decisions.One core server, many thin front doors
The core verity server (Rust + Postgres, plus optional SpiceDB, Qdrant, and MinIO) is the singular, stateful, security-critical component. All memory for all tenants lives here, and every scope decision is enforced here. It is multi-tenant — tenant is the first-position filter on every query — and there is one of it.
verity-mcp is a thin, stateless proxy: no data, no storage, no enforcement logic. Each instance is a small stdio shim that carries an identity from its launch config and forwards each MCP tool call to exactly one REST call against the core server. The analogy is a database client with a baked-in login: one database, many authenticated clients — the clients are not databases.
401 from the server; nothing is enforced in the shim.Running many agents with different reach
To give two agents different reach today, you run two verity-mcp configs — different identity env, both pointing at the same server. That is a config entry, not a second Verity:
# adapter for the support agent — scoped to support principals
VERITY_URL=https://verity.internal \
VERITY_TENANT_ID=acme \
VERITY_PRINCIPALS=user:support-bot,group:support \
VERITY_ACTOR_SUB=support-bot VERITY_ACTOR_AZP=agent:support \
verity-mcp
# adapter for the sales agent — different identity, SAME server
VERITY_URL=https://verity.internal \
VERITY_TENANT_ID=acme \
VERITY_PRINCIPALS=user:sales-bot,group:sales \
VERITY_ACTOR_SUB=sales-bot VERITY_ACTOR_AZP=agent:sales \
verity-mcpAn application that isn't an MCP client skips the shim entirely and speaks to the core server directly over REST or a framework SDK, passing its own token. Same server, same enforcement — a different door.
Identity: config today, per-request token tomorrow
This is the one place to be precise about built-vs-spec'd, because it is what makes the adapter multiplicity necessary in the first place.
- Built now today
- Identity is fixed per adapter config. A
verity-mcpinstance carries one identity from its env (VERITY_PRINCIPALS/VERITY_ACTOR_SUB/VERITY_ACTOR_AZP). The agent can narrow within that —open_scope'sentityandpurpose, and which handles it picks — but it can never widen past the configured principals, and it cannot set its identity at all. Identity is process configuration, never a tool argument. - Spec'd next not yet built
- The SPEC's identity plane (§6/§9) describes per-request identity from the authenticated caller's token — the
(user, agent, on-behalf-of)tuple from an MCP Enterprise-Managed Authorization / ID-JAG token. That would let oneverity-mcpserve many identities, resolving principals live via SpiceDB per request — collapsing the adapter multiplicity entirely. The REST/v1/scopesroute already accepts asubjectthat the server resolves through SpiceDB; the MCP shim does not use that path yet — it passes its configured principals.
So: per-request token identity is not yet built — today the shim uses configured principals, and multiplicity lives in the adapter layer. Once the token path lands, a single adapter can front many callers. The concepts below (scopes, handles, provenance) hold either way; only where identity enters changes.
The memory layers (L0–L3)
Everything written to Verity lands first in an immutable evidence log, then is derived forward into progressively more query-shaped tiers. No LLM ever touches the structured write path — derivation for facts is deterministic.
- L0 — evidence log
- Every write (a CDC event, a webhook payload, an agent observation, a document version) becomes an immutable episode. L0 powers audit, poison rollback, and crypto-shredding. It is never rewritten.
- L1 — canonical records & chunks
- Structured data becomes bi-temporal facts keyed by
(source, entity_id, field); unstructured text becomes searchable chunks. An “opportunity updated” event is a deterministic keyed upsert that retires the old value — supersession is structural, not ranked. - L2 — extracted facts
- An extraction worker turns episodes into
(subject, relation, value)facts, superseded through the same L1 bi-temporal machinery. The extractor is deterministic in tests, with an Anthropic seam for the general case. - L3 — entity briefs
- A materialized brief is the current state of one entity in a single call: newest memory + recent agent activity + staleness metadata. Briefs refresh lazily on read (debounced) and in batch; crucially, the items in a brief are always re-derived under the caller's scope, so a brief can never leak.
Bi-temporality
L1 facts carry valid_from and valid_to. When a value changes, the previous row is not deleted — its valid_to is closed and a new row opens. This gives you two things at once:
- Current truth by default. A point read returns the value that is valid now.
- History on demand. Ask for a value
as_ofa past instant and Verity reconstructs the state that was current then — and labels a superseded value as such.
$50,000 to $84,000 via a CDC update. A read at ?as_of= 30 seconds ago still returns 50000, marked superseded — while the current read returns 84000. The Scoped Recall Benchmark measures 0 stale citations across 200 reads after supersession.Entities & entity tags
An entity is the thing a memory is about — usually a customer account, a contact, a team, or a user, addressed as a namespaced string like account:acme-corp. Every chunk, fact, and action is stamped with the entities it concerns.
Verity is honest about which tags are guaranteed:
- Deterministic tags guaranteed
- Tags derived from provenance at write time — a chunk from a structured object, a transcript attached to the Acme opportunity, an observation written under an Acme-scoped handle. Plane-3 guarantees over these tags are architectural.
- Probabilistic tags measured
- Tags inferred from unstructured text by an NER/LLM pass. A missed tag would widen retrievability, so the tagger is tuned for recall over precision, is suggest-only by default (opt in with
VERITY_AUTO_TAG=1), and low-confidence content from sensitive sources is quarantined rather than indexed.
Scopes & scope handles
A scope is the answer to “who is asking, and about what?” A session mints a server-side MemoryScope handle — an HMAC-signed credential (the Typesense pattern) that binds three things for the whole session:
- principals — the materialized permission tokens this caller holds (a user plus its group closure);
- entity_scope — the entities reads are filtered to and writes may tag;
- max_confidentiality — the ceiling on how sensitive a retrieved item may be.
The permissions page covers this in depth — the crucial property is that a handle's filters cannot be widened by agent-supplied parameters. Cross-entity analytics requires opening a different, audited scope. Go to Permissions →
ACL provenance
Every fact and chunk carries exactly one ACL provenance tag recording how its visibility was derived. This is surfaced on every read, so trust in a filter is never buried in docs:
- mirrored
- Visibility came from a real source ACL API (Google Drive
permissions.list). The highest-fidelity tier. - approximated
- Visibility was derived from a proxy for the real ACL (team membership standing in for issue visibility). Honest about being a best-effort mapping.
- admin-assigned
- Visibility was set by an explicit policy at ingest time — a webhook's bound set, a CLI
--visibility, a framework sink'svisibility_policy. - quarantined
- No usable visibility could be derived. The item is held for review, never indexed permissively. This is the fail-closed default for anything ambiguous.
The knowledge layer
Scoped memory answers “what happened with customer A.” Knowledge items answer “what have we learned across customers” — objection patterns, segment behaviors, playbooks. The learning is valuable precisely because it crosses scopes, and dangerous for exactly the same reason. Verity's answer: generalization is a privilege earned through provable de-identification, never a default of recall.
A knowledge item is an entity-free semantic memory whose subject is a category, never an entity — e.g. “Healthcare-segment customers consistently require DPA redlines before security review.” It is promoted through an async, sleep-time pipeline that never touches a hot path:
The safety properties, all deterministic:
- De-identification gate. The candidate statement is screened against the L1-derived lexicon of entity names, aliases, and domains; quoted-span detection; and identifying-value checks (amounts/dates matching restricted facts). Any hit is rejected back to scoped memory.
- k-distinct-entity support. Published only with evidence from ≥ 3 distinct entities (
k=2is refused: either supporter could subtract their own interaction and learn the other's). Support must span ≥ 2 writers or include Tier-1 evidence, so one agent repeating itself cannot self-promote. - Category-size floor. Every referenced category must contain enough entities that the statement cannot deanonymize a single customer.
- The provenance firewall. Lineage back to supporting episodes exists (it powers invalidation and audit) but is never in a recall payload; support counts shown to agents are bucketed, not exact.
- Retraction cascade. Erase a supporting episode and a support recount runs; below
k, the item auto-invalidates.
The consolidation-merge decision itself is measured, and it is tuned precision-first: a false merge would fabricate cross-customer support, so precision dominates recall by design. On a 206-pair eval set, the lowest threshold that holds precision ≥ 0.99 is 0.73, buying recall 0.1064 (~30% of true paraphrase merges) at a false-merge rate of 0 — recall is a capability disclosure, precision is the guarantee. An LLM judge in the merge cascade is designed to lift that recall, but is not yet measured. The full mental model — the three roles, every gate, the merge cascade, and worked lifecycles — is on the knowledge layer page.
status: published, not on the absence of tags. See the carve-out in Permissions, or the carve-out on the knowledge page.Glossary
- principal / principal token
- A canonical identity (user, group, or agent) that a permission can name. Materialized to a small integer token per tenant for fast in-index filtering.
- scope handle
- The HMAC-signed session credential binding principals, entity scope, and confidentiality ceiling. The only credential Verity issues.
- episode
- One immutable L0 record of a write. Everything derives from episodes.
- fact
- A bi-temporal L1 record keyed by
(source, entity_id, field). - chunk
- A searchable unit of text with an embedding, a visibility set, entity tags, and a confidentiality class.
- brief
- A materialized L3 view of one entity's current state, always re-filtered to the caller's scope on read.
- action record
- An entry on the scoped activity timeline — what an agent did, when, with what outcome — so other agents can check before acting.
- confidentiality class
- One of
public/internal/confidential/restricted. Pricing, quotes, and negotiation terms land inrestricted. - knowledge item
- An entity-free, category-level generalization published past the de-identification gate.
- tenant
- The top-level isolation boundary; the first-position filter on every query.