- Status: Accepted
- Date: 2026-05-03
- Affected:
search_memories_semantic,match_memories,backend/mcp/arcive-memory-mcp/,apps/web/app/api/chat/,apps/web/lib/search.ts
Context
memory_participants.person_id references people, which carries a
consent_status column (granted / pending / revoked). The
Caregiver role’s prompt promises to refuse to draw conclusions about
people whose consent is pending or revoked. Reviewer / Tutor /
Brainstorm have the same need — quoting someone who hasn’t consented is
problematic regardless of the role.
But the data owner — the user — should still see everything they recorded. They’re the source of truth; the gate is about what agents expose downstream, not what the user can read.
Options considered
Option A — Per-role enforcement in the system prompt only
- Pros: Zero schema work; LLM follows the rule.
- Cons: Prompt-only enforcement leaks. The model sees the un-consented data in retrieval and may still reference it. Not auditable.
Option B — Filter at the application layer (/api/chat)
- Pros: Centralized in the agent surface.
- Cons: Each agent surface (chat, voice, MCP) has to remember to filter. Easy to miss when a new surface ships. The MCP server exposes the same retrieval to external clients (V1.0); they wouldn’t inherit the filter.
Option C — Filter at retrieval, opt-in via parameter (chosen)
- Pros: One place enforces it. Every agent path passes
respect_consent: trueand inherits the gate. User UI passes the defaultfalseand sees everything they own. The MCP server tool always passestrue, so external clients get the gate too. - Cons: Requires passing the flag everywhere agent retrieval happens. Rest of the system reads the schema directly (e.g. delete cascades); those paths aren’t affected.
Option D — Two separate RPCs (…_for_owner and …_for_agent)
- Pros: Type-level distinction; impossible to forget the gate on the agent path because the function name says it.
- Cons: Duplication; two functions to keep in sync. The boolean parameter is a clearer one-knob design.
Decision
Add respect_consent boolean default false to
search_memories_semantic and match_memories. When true, exclude
memories with any participant whose consent_status <> 'granted'. When
false (the default), behave as before. The default ensures the user’s
own /today browsing is unaffected.
The MCP server’s search_memories tool always passes
respect_consent: true. /api/chat’s inline-fallback path also
passes true. lib/search.ts (user-facing search box) does not pass
it; default false applies.
Consequences
- Caregiver, Reviewer, Tutor, Brainstorm now share the same retrieval consent gate without per-role code.
- A memory with one un-consented participant disappears from agent retrieval entirely — there’s no partial redaction. Per-segment redaction is harder and not in scope for V0.3.
- The owner can grant consent in the UI (
people.consent_status) to bring a memory back into agent retrieval. - Voice service (Pipecat) inherits the gate when MCP wiring lands (next slice).
- compute-edges-step uses
match_memories. Edges currently get computed withoutrespect_consent. We could flip it on there too to avoid hint-by-edge, but for V0.3 the gate on retrieval is sufficient: edges are surfaced through Universe (owner-visible surface) only.
Notes
People rows are user-scoped; consent_status defaults to pending
for newly auto-created people (e.g. an “Unknown speaker” from
re-ID). That means re-ID’d people are gated from agent retrieval
until the owner explicitly grants consent — failure-closed by
default. The “Self” person is granted on signup.