Knowledge layer
Learns across your customers. Never leaks them.
Scoped memory answers “what happened with customer A.” The knowledge layer answers “what have we learned across customers” — objection patterns, segment behaviors, playbooks that work, failure modes to avoid. 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 behavior of recall. It is arguably Verity's most differentiated capability — no OSS memory system (Mem0, Zep, Letta, Cognee) does cross-customer learning at all.
The knowledge layer publishes de-identified generalizations (“healthcare deals need DPA redlines”) that any scoped agent can read — and it's safe because each one is positively verified entity-free: past a deterministic de-identification gate, supported by ≥3 distinct customers, and human-approved (off by default). It is the one thing that safely crosses scopes.
Everything below is the depth behind that sentence — who generalizes, the gates, the merge cascade, and worked lifecycles.The idea: an entity-free knowledge item
A knowledge item is a semantic memory whose subject is a category, never an entity. It carries no customer names, no quoted spans, no identifying amounts — only the generalization and the categories it applies to:
knowledge_item {
statement: "Healthcare-segment customers consistently require DPA
redlines before security review; budget ~2 extra weeks."
categories: ["industry:healthcare", "objection:dpa", "stage:security_review"]
support: { distinct_entities: 3, writers: 2, first_seen, last_reinforced }
status: candidate | quarantined | eligible | published | invalidated | rejected
support_tier: emerging | established | extensive // bucketed; what agents see
visibility: broad (org principal) once published
lineage: → supporting L2 facts → L0 episodes // NEVER in the recall payload
valid_from/to: bi-temporal like everything else — knowledge gets superseded
}Knowledge is bi-temporal like every other memory in Verity: a generalization can be superseded (“post-repricing, this objection stopped appearing”). It ranks on similarity × confidence with a slow decay driven by last_reinforced — knowledge that stops being reinforced ages out of the top-k long before it is invalidated.
Episodic → semantic consolidation
The knowledge layer is a consolidation pipeline: it promotes scoped, episodic memory (what one agent saw with one customer) into org-level, semantic memory (a category-level pattern). It is async and sleep-time — it never runs on any hot path, never on a read.
Who generalizes: three roles, one honest asymmetry
An agent inside a scoped session sees one customer's interactions. It can notice something, but it structurally cannot know it is a pattern — it has no view across customers. So the roles split, and the split is the load-bearing safety property:
- Agents — hypothesis generators & reinforcement voters not generalizers
- A scoped agent calls
memory_propose_learning. From one session this typically carriesn=1evidence — an expected-weak signal, unpublishable alone by construction (k-support). When many agents each propose something similar from their own single-customer view, the worker merges the similar proposals, adding each new entity and writer to the existing candidate's evidence. Many agents each seeing one interaction collectively assemble k-support — without any of them ever seeing across scopes. - The consolidation worker — the actual generalizer trusted server plane
- The worker runs in the trusted server plane, like a connector. It is not an agent: it has no conversational output channel and never talks to a customer. It has legitimate cross-scope read access, and that is safe precisely because its only outflow is a candidate that must survive the de-identification gate. Contextual integrity is about where information flows; this component's only flow is gated. It clusters similar hypotheses across entities, drafts category-level candidates, and accrues support.
- Humans (or configured policy) — publish the final gate
- The review queue is the final gate. A candidate that crosses k-support becomes
eligible, not published; a reviewer approves, edits, or rejects. Auto-publish is a per-tenant opt-in, off by default.
The publish gates
Between a proposal and a published, org-wide knowledge item stand four deterministic gates. They are enforced in code and covered by tests — a false generalization is structurally harder to publish than a real one.
- 1 · De-identification gate deterministic, not vibes
- The candidate statement is screened against the L1-derived lexicon of entity names, aliases, and domains; quoted-span detection against the source episodes; and identifying-value checks (amounts and dates that match restricted-class facts). Any hit is rejected back to scoped memory. An LLM may have written the candidate; a deterministic gate decides whether it can leave its scope.
GET /v1/admin/knowledge/{id}exposes the gate result (passed + reason) on the review surface. - 2 · k-distinct-entity support default k = 3
- Published only when supported by evidence from ≥ k distinct entities (default
K_SUPPORT_MIN = 3, per-tenant configurable).k=2is explicitly refused as a default: with two supporting customers, either one can subtract their own interaction and infer the other's (membership inference). Support must also span ≥ 2 distinct writers or include Tier-1 evidence — one agent repeating itself across k entities must not self-promote (the poisoning path). - 3 · Category-size floor deterministic
- Every category a statement references must contain enough entities in L1 that the statement cannot deanonymize a single customer. “Our aerospace customers negotiate hard” deanonymizes perfectly when there is one aerospace customer, regardless of k.
- 4 · Human review the final gate
- Gate-passing candidates land in the review queue as
eligible. A human (or an explicitly configured policy) calls the publish endpoint. Auto-publish thresholds are configurable but OFF by default; publishing is what grants broad visibility and mints the retrievable carve-out chunk.
The §7g retrieval carve-out — and why it's safe
Verity's zero-tag rule normally excludes untagged content from an entity-bound scope, because a missing tag might mean unclassified sensitive content (“untagged therefore almost nowhere”). A published knowledge item is the one principled exception:
status: published. So an agent in a session scoped to customer A retrieves exactly two things: (a) content tagged within its entity scope, and (b) published knowledge items matching the query — and nothing else.The enforcement detail is what makes this trustworthy: the carve-out keys on the item's verified status, stamped into the index payload as kind: knowledge at publish time — never on the absence of tags. Candidates, eligible, and quarantined items stay invisible outside audit scopes. The scope-soundness fuzzer has dedicated cases: a quarantined item surfacing in any non-audit scope, or any tagged chunk sneaking through the carve-out, fails the build. The full treatment is on the permissions page.
The merge cascade
Merging is the only way k-support is ever reached: two agents on two different customers propose “the same” generalization in different words, and the worker must recognize them as the same and combine their evidence. This is where precision is won or lost.
So the merge is a three-stage cascade. Each stage can only reduce what merges; none can force a merge past the human gate:
- Canonicalize (deterministic). The extractor emits a canonical, lowercased, article-stripped form of each statement. Two byte-identical canonical forms merge with no model call at all — the fast path. This also fixes L2
(subject, relation)supersession alignment by emitting a controlled-vocabulary predicate. - Stage 1 — the blocker. A cheap bi-encoder (MiniLM-class) finds existing candidates above a low cosine floor (
TAU_BLOCK = 0.45) that also share ≥ 1 category. It is allowed high recall / low precision on purpose: its only job is to bound how many comparisons reach the judge (capped at 8 candidates). - Stage 2 — the LLM judge. The LLM already in the worker answers “is this the same generalization?” per candidate, strict and precision-tuned, ties/uncertain → NO. A yes returns
{merge_into, judge_reason}; the reason is stored on the merge record (auditable, reversible). This is canonical to the “the worker generalizes” design: the component with cross-scope read makes the semantic call, and its only outflow is still gated. - Stage 3 — the human gate. Unchanged. Merging only accrues candidate support; crossing k makes the item
eligible; nothing auto-publishes.
The old bare-cosine auto-merge (a single 0.85 threshold on the write path) has been removed: the server no longer decides a semantic merge on cosine alone, because a false merge fabricates cross-customer support. The stored statement embedding now feeds the blocker's candidate-set query, not an auto-merge.
Measured precision/recall — the honest operating point
You cannot tune what you cannot measure, and “trust us, we lowered a threshold” fails a security review. The merge decision is a benchmark metric (Scoped Recall Benchmark metric #6) over a labeled eval set of statement pairs — 206 pairs: 94 positives, 90 hard negatives, 22 easy negatives — where the hard negatives are statements that are topically close but genuinely distinct (“requires a DPA before security review” vs “requires a SOC 2 report before security review”). Encoder: all-MiniLM-L6-v2 (384-d).
| operating point | precision | recall | false-merge rate |
|---|---|---|---|
| shipped threshold 0.85 (cosine-only baseline) | 1.0000 | 0.0000 | 0.0000 |
| lowest threshold holding precision ≥ 0.99 (0.73) | 1.0000 | 0.1064 | 0.0000 |
| threshold 0.50 (for reference — below the contract) | 0.8052 | 0.6596 | 0.1339 |
Acceptability controls
Auto-derived, cross-customer knowledge is the most trust-sensitive thing Verity does. These controls are the operator- and buyer-facing statement of what is never automatic. Everything here is enforced in code and covered by tests.
- Publishing is never automatic on the read path
- Merging only accrues candidate support. Crossing k-support (≥ 3 distinct entities, default) makes an item
eligible— a status betweencandidateandpublished— not published. Nothing an agent reads is ever the trigger for a publish.POST /v1/knowledge/{id}/publish(admin bearer) stays the human gate; it re-enforces k-support + corroboration + de-id before it mints the carve-out chunk. - Auto-publish is opt-in, default OFF
- A per-tenant setting (
knowledge_auto_publish, absent = OFF). With the default posture, an eligible item waits for a human or an explicitly configured policy. Even when ON, promotion runs through the same publish gate on the async/admin path — still never on the read path — and if no default audience is configured the item is heldeligiblerather than published to an unknown audience (fail-safe). The OSS build ships OFF. - Kill switch —
VERITY_KNOWLEDGE_AUTO_MERGE - Default ON. Set to
0to disable the worker-judged merge leg entirely: the server then ignores worker-suppliedmerge_into, and only the deterministic canonical-exact fast path can merge. Consolidation degrades to assisted / human-clustered — candidates queue for human review — never a silent judged merge. The canonical-exact merge and every gate keep working under the kill switch. - Support tiers — buckets, never exact counts
- Published and eligible items carry a bucketed
support_tierderived from the distinct-entity count. A consuming agent sees only the tier on akind=knowledgerecall hit, so it can weight the knowledge without a false precision. Exactdistinct_entitiescounts are admin-only.tier distinct entities emerging3–4 established5–9 extensive10+ - Reject-with-memory
POST /v1/admin/knowledge/{id}/rejectlets a reviewer refuse a candidate or eligible item. Rejection is remembered: status becomesrejectedand the samecanonical_statementwill not resurface as a fresh candidate — the propose path returns the remembered rejected row unchanged. Rejecting a published item is refused: retraction ismemory.forget's job, which runs the k-support recount and auto-invalidation cascade.
A lifecycle, end to end
The whole loop, from a scoped agent's hypothesis to what a different, later agent retrieves. Every command matches a real route or tool. Assume a running server, $VERITY, $TENANT, and $VERITY_ADMIN_TOKEN.
The propose call
A scoped agent proposes what it noticed. Over MCP this is the memory_propose_learning tool; over REST it is POST /v1/knowledge. The statement is about a category, never a customer — a statement carrying a known entity identifier is quarantined, not published.
MCP tool call — memory_propose_learning
{
"tool": "memory_propose_learning",
"arguments": {
"scope_handle": "<acme-scoped handle from memory_open_scope>",
"statement": "Healthcare-segment customers require DPA redlines before security review; budget ~2 extra weeks.",
"categories": ["industry:healthcare", "objection:dpa"],
"evidence": ["ep-8f1c…"] // supporting episode ids; attribution read server-side
}
}
// → { "status": "candidate", "knowledge_id": "k-3f1…" } — visible only in auditFrom one scoped session this is n=1: a candidate, not a publish, and unpublishable alone. A similar proposal from a different customer's agent is what accrues support — the worker's cascade decides whether the two are the same generalization and, if so, merges their evidence.
propose → eligible → review → publish
Once support crosses k across distinct entities and writers, the item becomes eligible and appears in the admin review queue. A human inspects the evidence and publishes.
# the review queue (admin) — status, admin-exact distinct_entities, bucketed tier, merge reason
curl -s "$VERITY/v1/knowledge?tenant_id=$TENANT&status=eligible" \
-H "authorization: Bearer $VERITY_ADMIN_TOKEN" \
| jq '.items[] | {statement, status, support_tier, distinct_entities, merge_reason}'
# full detail for one item — adds the de-identification gate result (passed + reason)
curl -s "$VERITY/v1/admin/knowledge/$ID?tenant_id=$TENANT" \
-H "authorization: Bearer $VERITY_ADMIN_TOKEN"
# publish — the human gate; k_min is clamped to ≥3 server-side, de-id re-checked
curl -s -X POST "$VERITY/v1/knowledge/$ID/publish" \
-H "authorization: Bearer $VERITY_ADMIN_TOKEN" \
-d '{"tenant_id":"'"$TENANT"'","visibility":[1],"k_min":3}'
# or refuse it — rejection is REMEMBERED; the same canonical statement won't resurface
curl -s -X POST "$VERITY/v1/admin/knowledge/$ID/reject" \
-H "authorization: Bearer $VERITY_ADMIN_TOKEN" \
-d '{"tenant_id":"'"$TENANT"'","reason":"too broad; not actually DPA-specific"}'The read-only /ui “Knowledge review” panel lists candidates, eligible, and published items with a status badge, support tier, merge reason, and evidence count — a pure inspector; approve and reject are documented API actions, never buttons on the page. The consolidation worker itself drives /v1/admin/consolidation/lease → /merge-candidates (the blocker) → /complete (with the judge's {merge_into, judge_reason}).
What a consuming agent sees
A completely different agent — scoped to a new healthcare customer, months later — recalls the published lesson via the carve-out, alongside its own account's tagged content, and nothing else. It sees the bucketed support tier, never an exact customer count.
curl -s -X POST "$VERITY/v1/recall" \
-d "{\"scope_handle\":\"$NEWCO_SCOPE\",\"text\":\"what slows down healthcare security review?\"}" \
| jq '.[] | {kind, content, support_tier, acl_provenance}'
// → {
// "kind": "knowledge",
// "content": "Healthcare-segment customers consistently require DPA redlines before security review…",
// "support_tier": "emerging", // 3–4 distinct entities — a bucket, never an exact count
// "acl_provenance": "admin-assigned"
// }status: published and kind: knowledge. The lineage back to the three original customers exists for audit and retraction — but it is never in this payload, and the count the agent sees is a bucket. What those three customers' interactions were never crossed streams; what was learned across them is available here.Honest limits
The site's credibility is a selling point, so the limits are stated as plainly as the capabilities:
- The de-identification gate is deterministic against known identifiers. A sufficiently unusual pattern can still be identifying in ways no lexicon catches (the small-category problem generalizes). The mitigations are the category floor, bucketed support, human review before publish, and — for tenants that need it — keeping auto-publish off permanently.
- We say “engineered de-identification with auditable gates,” never “differential privacy.” We do not add calibrated noise, and we will not claim what we don't do.
- Precision is engineered, not proven. We say “≥99% precision on our eval set with human approval before publish,” never “cannot produce a wrong generalization.” The human gate is the real backstop; the cascade makes the reviewer's job rare and easy, it does not replace them.
- The LLM judge's recall lift is not yet measured. The published recall number (~30% at ≥99% precision) is the deterministic cosine judge. The cascade is designed to do better; we will publish that number when we have run it, and not before.