For agents
Sensitivity contract
How knowledge gaps move from internal (hash-only) to redacted to external — and what each tier authorizes.
The knowledge_gaps table is privacy-safe by construction. This page is the contract you must follow before letting any gap-derived text leave the brain. Read it before you call brain_gap. Read it again before you escalate one.
The default
Section titled “The default”When you call brain_gap, the row is created with:
query_hash— SHA-256 of the original query. Always populated.query_text— NULL.sensitivity—internal.approved_by— NULL.approved_at— NULL.
Database CHECK constraints enforce this:
CHECK (query_text IS NULL OR (approved_by IS NOT NULL AND approved_at IS NOT NULL))CHECK (sensitivity = 'internal' OR (approved_by IS NOT NULL AND approved_at IS NOT NULL))There is no code path that stores raw text without an approval pair. None.
The three tiers
Section titled “The three tiers”internal
Section titled “internal”Hash-only. Default. No raw query stored.
Authorizes — keeping a record that some query failed, dedup of repeated failures, internal counters and stats. Nothing leaves the brain.
Use when — the gap is part of normal retrieval bookkeeping. No external research needed.
redacted
Section titled “redacted”Sanitized variant stored. Original text not retained.
Authorizes — sharing the redacted form with an external system (e.g. an enrich skill that fetches reference material), keeping a human-readable record without storing PII.
Use when — you need to communicate the kind of gap externally without disclosing the original wording. The user (or a policy workflow) provided a sanitized variant.
Required to set — approved_by, approved_at, and redacted_query populated.
external
Section titled “external”Raw query stored. May be sent to external services.
Authorizes — external API calls with the raw query (e.g. asking a third-party search service for context).
Use when — the user has explicitly approved the original query for external research, and the gap can’t be resolved with redaction.
Required to set — approved_by, approved_at, and query_text populated.
How escalation works
Section titled “How escalation works”You, the agent, do not unilaterally escalate. Escalation is an approval workflow, not a tool call. Specifically:
- You detect the gap and call
brain_gap. The gap is nowinternal. - If retrieval fails repeatedly and the user wants to escalate, the user (or a policy workflow) approves.
- The approval populates
approved_byandapproved_at. It also sets:- For
redacted:redacted_query, leavingquery_textNULL. - For
external:query_textwith the original query.
- For
- Only after the row carries an approval pair may an external call proceed.
There is no brain_gap_approve MCP tool yet. Approval is currently effected by the user (or a policy workflow) updating the row directly, or via a future tool. Don’t fake approvals.
What you must not do
Section titled “What you must not do”- Don’t write
query_textfrom your own decision. That CHECK constraint will reject the write; even if it didn’t, doing so violates the contract. - Don’t escalate
sensitivityfrominternalwithout an approval pair. Same. - Don’t send the raw query to an external service while sensitivity is
internalorredacted. The raw query has not been authorized. - Don’t infer approval from “the user seemed to want me to.” An approval is structured:
approved_byandapproved_atset in the row. If they’re not set, you do not have approval.
What you may do without escalation
Section titled “What you may do without escalation”- Log every weak query as a gap (hash-only).
- Aggregate hashes (count distinct, dedup, see whether you’ve hit the same gap twice).
- Surface the gap count to the user (
brain_gapsreturns counts; raw text is hidden). - Suggest escalation, with an explanation of which tier is appropriate and why.
Example flows
Section titled “Example flows”Pure internal
Section titled “Pure internal”brain_query "compliance contact for Belgium" → no strong resultsbrain_gap query="compliance contact for Belgium" context="quarterly review" → row created, sensitivity=internal, query_text=NULLbrain_gaps resolved=false → 1 unresolved gap (hash + context only)User decides not to escalate. Nothing leaves the brain.
Redacted escalation
Section titled “Redacted escalation”gap created sensitivity=internal, hash=Huser approves redaction with redacted_query="who covers EU compliance?" → sets approved_by, approved_at, sensitivity=redacted, redacted_query=...enrich skill fetches a reference list using the redacted variantbrain_raw stores the responsebrain_put extracts facts into a new pagegap resolved by linking resolved_by_slug=concepts/eu-complianceOriginal wording never leaves the brain.
External escalation
Section titled “External escalation”gap created sensitivity=internal, hash=Huser approves with query_text="Who is our partner contact at Acme?" → sets approved_by, approved_at, sensitivity=external, query_text populatedresearch skill calls an external API with the raw queryfindings ingested as a new pagegap resolvedUsed sparingly. The raw query is now in the brain and may have been transmitted; both states require explicit consent.
Logging
Section titled “Logging”The MCP server logs brain_gap calls with the hash, not the text — matching the table policy. If you redirect logs to a SIEM or off-machine destination, no raw query text is in them by default.
When sensitivity is external and query_text is populated, log behavior does not change at the server level. If you write your own audit log, you must respect the same defaults.
Why this is structured this way
Section titled “Why this is structured this way”The blast radius of “agent leaked a sensitive query to a third-party API” is large and irreversible. The contract makes it impossible by construction in the default case, and explicit-and-auditable in the exceptional case. This is the price of letting agents act on the brain.
If you find yourself wishing the contract were looser, you’re probably the wrong abstraction layer for the work. Either escalate to the user, or accept the gap.