` branch (often a dedicated git worktree) per task per repo. A change
lands on `main` only after a green test gate, an independent mutation audit, and explicit approval —
and pushes are human-run. Many features are therefore **merged locally but not yet pushed**; the
[Status](/docs/engineering/status) board tracks which. Full workflow:
[Build workflow](/docs/contributing/build-workflow).
*Source: per-repo READMEs, `task-plan/MASTER-TASK-LIST.md`.*
# Roadmap (/docs/engineering/roadmap)
Everything here is **planned or in-flight — not shipped.** It's derived directly from the open
GitHub issues under `conclavehq`. For what's actually built, see [Status](/docs/engineering/status).
Two numbering systems. These GitHub issue numbers are separate from the
task-plan numbers on the Status board. A few epics here (e.g. Conclave #1, #2) are partially
delivered under task-plan items already marked built — the issue stays open to track the
remaining scope. When in doubt, the Status board is the source of truth for "done".
## Active / P0 [#active--p0]
* **Diarization latency & reliability** — [Capture#1](https://github.com/conclavehq/Capture/issues/1). RTF \< 1, ASR fallback, DiariZen pipeline, VFT long-meeting limit. The current workstream: [P0: diarization](/docs/engineering/p0-diarization).
* **Web login loop** — [Conclave#13](https://github.com/conclavehq/Conclave/issues/13). Blocks web sign-in on both apps. See [Gotchas](/docs/engineering/gotchas).
* **Speaker-labelling → voice-consent flow (epic)** — [Conclave#1](https://github.com/conclavehq/Conclave/issues/1). Partially delivered (`#3` hear-clips is built); tracks the remaining flow.
## Planned — P1 [#planned--p1]
| Issue | Summary |
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Conclave#2](https://github.com/conclavehq/Conclave/issues/2) | Team workspaces + roles (partly delivered as task `#32`) |
| [Conclave#3](https://github.com/conclavehq/Conclave/issues/3) | Transcript versioning (git-history-style viewer) |
| [Conclave#4](https://github.com/conclavehq/Conclave/issues/4) | Action items: per-item "save to memory" (good first issue) |
| [Conclave#5](https://github.com/conclavehq/Conclave/issues/5) | Merge the live co-pilot hotkey (built on `conclave-copilot`) |
| [Conclave#6](https://github.com/conclavehq/Conclave/issues/6) | Showcase private notes |
| [Conclave#7](https://github.com/conclavehq/Conclave/issues/7) · [#8](https://github.com/conclavehq/Conclave/issues/8) | Audit + integrate a shared Conclave workspace into Shape OS |
| [Conclave#12](https://github.com/conclavehq/Conclave/issues/12) | Attribution accountability (voice-ID ≠ attesting the transcript) |
| [Conclave#14](https://github.com/conclavehq/Conclave/issues/14) | Audio-file upload → onboarding (in-flight; built on a branch) |
| [Conclave#19](https://github.com/conclavehq/Conclave/issues/19) | Conclave **MCP server** + scoped agent tokens (agent read+write surface) |
## Backlog — P2 [#backlog--p2]
| Issue | Summary |
| --------------------------------------------------------------- | -------------------------------------------------------- |
| [Conclave#9](https://github.com/conclavehq/Conclave/issues/9) | Browser capture resilience (tab-switch / backgrounding) |
| [Conclave#10](https://github.com/conclavehq/Conclave/issues/10) | Meeting templates (per meeting-type presets) |
| [Conclave#11](https://github.com/conclavehq/Conclave/issues/11) | Live co-pilot overlay → native Mac app |
| [Conclave#15](https://github.com/conclavehq/Conclave/issues/15) | Personal memory (RLHF feedback loop) |
| [Conclave#16](https://github.com/conclavehq/Conclave/issues/16) | When is Conclave-web "done"? (product-boundary decision) |
| [Conclave#17](https://github.com/conclavehq/Conclave/issues/17) | Post-meeting meeting-type tagging + clustering |
| [Conclave#18](https://github.com/conclavehq/Conclave/issues/18) | Unified retrieval service (one retrieval path) |
| [Capture#2](https://github.com/conclavehq/Capture/issues/2) | Bot-path acoustic identity (voiceprint over Meet) |
| [Capture#3](https://github.com/conclavehq/Capture/issues/3) | Multi-phone capture (0.2) |
| [Capture#4](https://github.com/conclavehq/Capture/issues/4) | Voice recreation from fingerprints (experiment) |
| [Capture#5](https://github.com/conclavehq/Capture/issues/5) | Per-workspace bot allocation / no duplicate bot |
*Source: live `gh issue list -R conclavehq/{Conclave,Capture,VFTEE,Mobile} --state open`. VFTEE and Mobile currently have no open issues.*
# Status (/docs/engineering/status)
This is the single honest board of **what exists.** Planned/not-yet-built work is on the
[Roadmap](/docs/engineering/roadmap). Badges:
* **Live in prod** — deployed on the Phala CVMs.
* **Built · merged** — merged to `main` (some local/unpushed — built, not "shipped").
* **Built · not merged** — exists on a feature branch.
> Ground truth is `task-plan/MASTER-TASK-LIST.md` + the live GitHub issues. Re-verify before trusting
> a status here. Reflects the monorepo as of mid-July 2026.
## Deployed [#deployed]
The three services are deployed to Phala CVMs (Conclave `conclave`, FPM `fpm-backend`) with the
Vercel dashboards and the external DiariZen GPU worker. The platform is **up but under active
hardening**.
There are **two P0s**, distinct: the top *engineering workstream* is
[diarization latency & reliability](/docs/engineering/p0-diarization) (`Capture#1`); the top *bug*
blocking web sign-in is the login loop (`Conclave#13`) below. If you can only unblock one thing for
users right now, it's the login loop.
Known blocker. The
web login loop
(
Conclave#13, open, P0) currently
bounces Google sign-in back to
/login on both apps. Fixes shipped; still reproduces.
## Built & merged [#built--merged]
| Area | Items |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Identity / consent | deletion proof (`#1`), export/import (`#4`), hear-clip + transparency (`#3`/`#3c`), permission scoping (`#2`), retire `identify_allowed` (`#35`) |
| Editor | vocab + autocomplete (`#6`), word-typing (`#7`), transcript-refine inline editor (`#9`), heal-on-open (`#13`) |
| Insights | calendar-intent grounding (`#11`), in-person agenda (`#12`), LLM title (`#40`) |
| Capture UX | live page (`#14`), turn coalescing (`#37`), origin/time/title badges (`#38`/`#39`/`#40`), click-to-seek (`#41`), delete + lifecycle (`#42`) |
| Sharing / data-rights | granular scopes (`#31`), tag→email (`#15`), data-dump + T\&C (`#18`), Shape-OS contribute Arm 1 (`#20`) |
| Audio | audio-in-TEE + player (`#30`) |
| Infra | durable jobs queue + Option-A HTTP claim (`#16`), shared Supabase login (`#41` out-of-band) |
| Workspaces | multi-member team workspaces + roles + owner-private (`#32`, merged local/unpushed) |
## Built · not merged [#built--not-merged]
| Item | Where |
| ------------------------------------------- | ---------------------------------- |
| Audio-file upload → onboarding (`#52`) | `conclave-audio-upload-onboarding` |
| Async identity (VFTEE 600s) (`#52` Track B) | `feat/identity-async-lift` |
| Live co-pilot hotkey search (`#47`) | `conclave-copilot` |
| React Native mobile app (`#22`) | `conclavehq/Mobile` |
| Own WavLM diarization model (P0 Track A) | validated, prod-port pending |
## Deprioritized / skipped [#deprioritized--skipped]
* Preset insight prompts (`#10`) — design-locked, deprioritized.
*Source: `task-plan/MASTER-TASK-LIST.md`, `FEATURE-SPEC.md` §10.*
# For developers (/docs/flows/for-developers)
The same system from the engineering side. Each flow links to the reference page that covers it in
depth. Green steps are trust / consent boundaries.
## The meeting spine [#the-meeting-spine]
The end-to-end path every meeting takes. Full detail: [Data flow](/docs/engineering/data-flow).
## Durable jobs & the GPU worker [#durable-jobs--the-gpu-worker]
Finalize is crash-safe — the post-pass runs as a durable job an off-CVM GPU worker claims over
`:443`. See [Deployment](/docs/engineering/deployment) and [Capture features](/docs/product/features/capture).
## VFTE identity [#vfte-identity]
How identity attaches to anonymous spans — enroll, match, consent-gate, resolve at display. See
[VFTE features](/docs/product/features/vfte) and [the consent plane](/docs/product/concepts/consent-plane).
## Sharing & permissions [#sharing--permissions]
How a share resolves to exactly which artifacts a viewer receives. See
[Data model](/docs/engineering/reference/data-model) for the columns and gates.
## Deploy [#deploy]
The in-place deploy to the Phala CVMs. Full recipe + gotchas: [Deployment](/docs/engineering/deployment).
***
For the product-side view of these journeys, see [For people](/docs/flows/for-people).
# For people (/docs/flows/for-people)
Six journeys that cover most of what you'll do. Each one reads left → right; green steps are the
**trust / consent** moments.
## Get started [#get-started]
Sign in and you land in your own private space — nothing is shared until you say so.
## Record an in-person meeting [#record-an-in-person-meeting]
The flagship path: your browser mic streams a live transcript, and insights are ready when you stop.
## Name who spoke — with consent [#name-who-spoke--with-consent]
Naming a speaker is never silent. The person is *proposed*, not labeled, until they confirm — and
they can hear the exact clip that named them first.
## Own your voice [#own-your-voice]
Your voiceprint is yours. You're recognized only in workspaces you agreed to, and you can go
anonymous, pause, or be forgotten — with a signed, verifiable receipt.
## Ask your team's memory [#ask-your-teams-memory]
Instead of digging through transcripts, ask a question in plain language and get an answer pulled
from every meeting — always cited.
## Share a meeting [#share-a-meeting]
Sharing is granular: pick exactly which parts a recipient sees.
***
Want the same journeys from the engineering side? See [For developers](/docs/flows/for-developers).
# User flows (/docs/flows)
This section maps the important journeys through Conclave as simple **left → right step maps**.
Two views of the same system:
## How to read them [#how-to-read-them]
Each flow is a row of steps you follow in order. A **green step** marks a
**trust or consent** moment — where encryption, attestation, or the subject's own choice governs
what happens.
Every flow here is implemented. Where a step depends on the subject's consent or
the enclave, it's marked green — that's the load-bearing part, not decoration.
> One caveat: the very first step of *Get started* — web sign-in — currently has a known login-loop
> issue (see [Status](/docs/engineering/status)). Everything after it works.
# The three products (/docs/product/overview)
Conclave is not one service — it's **three cooperating products behind one trust boundary**. Get
this mental model and the rest of the docs fall into place.
| Product | Repo | Role | Holds |
| ------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| **Conclave** | `conclave-shape-rotator` | The **intelligence + control plane**. The TEE where content lives. Owns UI, auth, workspaces, sharing, jobs. | All meeting content + the `user ↔ workspace` graph |
| **Capture** | `capture` (ex-Recato) | The **meeting-capture + diarization** microservice. Joins online meetings or streams a room mic; does acoustic diarization + ASR. **Identity-free.** | Nothing persistent (stateless; audio by reference) |
| **VFTE** | `FPM` | The **voice-identity + consent** layer. Turns audio into voiceprints, recognizes speakers *scoped + consented*, owns the consent plane. **Product-agnostic.** | Voiceprints + consent edges (never audio or content) |
## The data flow, in one line [#the-data-flow-in-one-line]
```
meeting → Capture (diarize: who spoke when, anonymously) → Conclave (finalize)
→ VFTE (recognize who, scoped + consented) → Conclave (enrich: summary / insights / KB)
→ user views / edits / shares · subject is notified + controls their voice
```
## Two graphs, one boundary [#two-graphs-one-boundary]
The reason this split matters: **Conclave owns `user ↔ workspace`; VFTE owns `voiceprint ↔ scope`.**
They meet at exactly one opaque mapping — `settings.fpm_workspace_for(workspace_id)` — a scope id
that VFTE attaches **no meaning** to. That opacity is what keeps VFTE a general identity layer any
app can hook into, and it's why capture emitting `{start, end, local_speaker}` and nothing else is
the *load-bearing boundary* of the whole system.
Why three products, not one. Separating capture (who spoke) from identity (who
that is) from intelligence (what it means) is what lets consent live at the microphone and lets
the operator stay blind to content. Collapse them and the privacy story collapses too.
## Where to go next [#where-to-go-next]
* **[Why Conclave](/docs/product/why-conclave)** — the positioning and the wedge.
* **[Trust model](/docs/product/trust-model)** — operator-blindness and attestation.
* **[Concepts](/docs/product/concepts/voiceprints)** — voiceprints, consent, workspaces, diarization, the knowledge graph.
* **Features** — the shipped catalog for [Conclave](/docs/product/features/conclave), [Capture](/docs/product/features/capture), [VFTE](/docs/product/features/vfte).
*Source: `FEATURE-SPEC.md` §0, `discoveries/WORKSPACES-AND-VFT-GRAMMAR.md`.*
# Trust model (/docs/product/trust-model)
Conclave's central claim is **operator-blindness**: the people who run the service cannot read your
data. This page is how that claim is actually enforced.
## The posture [#the-posture]
* **LLM only at ingest.** The one place an LLM sees content is enrichment at ingest time. The
**read path is pure SQL + local embeddings** — no model call, no egress. Ask (RAG) is the single
deliberate exception, and it runs inside the TEE.
* **Raw transcript is never serialized** across the API boundary unless the caller is authorized;
raw diarization is **write-once**.
* **Telemetry is kill-switched in code** (LangChain/LangSmith disabled before env is even read).
* **No external database** — a single SQLite file lives inside the TEE.
* **Audio is AES-256 encrypted at rest**; **voiceprints are AES-256 sealed**; deletions are
**Ed25519-signed** and offline-verifiable.
## The enclave: TEE + TDX attestation [#the-enclave-tee--tdx-attestation]
All three services run in **Phala dstack CVMs on Intel TDX**. Each exposes
`GET /attestation?nonce=` returning a TDX quote that binds the running code (a measured enclave) to
its keys. Keys are **TEE-sealed** via dstack — they exist only inside a correctly-measured enclave.
Verify, don't trust. Operator-blindness isn't a promise in a policy
document — it's an attestation you can check. The quote proves which code is running
before you decide to trust it with audio.
## Consent is separate from access [#consent-is-separate-from-access]
Three distinct gates — do not conflate them:
* **Workspace membership** — a login/key. Decides who can reach a workspace.
* **Sharing** — the host's per-artifact call on their own data (`transcript` / `insights` / `audio`).
* **Voice consent** — the *subject's* control over being recognized (`allow` / `anonymous` /
`denied`), owned by [the consent plane](/docs/product/concepts/consent-plane).
Being a *member* of a workspace (has a key) is not the same as being *recognized* in it (has a
voiceprint on file). Consent governs only the second.
## Operator-blindness levels [#operator-blindness-levels]
The design names a maturity ladder (full detail in `PERMISSION-LAYER.md`):
* **Today** — LLM-at-ingest-only, sealed keys, attested reads, identity-free capture.
* **Level 2 (deferred)** — content keyed to *non-operator* keys + attested, with a per-binding
real-time consent callback as the upgrade path. This is the stated end-state moat, not yet built.
*Source: `FEATURE-SPEC.md` §1 & §9, `PERMISSION-LAYER.md`, `discoveries/WORKSPACES-AND-VFT-GRAMMAR.md`.*
# VFTE as a service (/docs/product/vftee-as-a-service)
VFTE is built as the identity + consent layer for Conclave, but it was deliberately kept
**product-agnostic** so it can be rented as infrastructure. The pitch: **a voice you own.**
## For the person [#for-the-person]
Enroll your voice once, own it, and be recognized **only in workspaces you agreed to** — with a real
consent plane:
* **Claim** your voice (bind it to your email).
* **Pause** enrollment or **stay anonymous** per scope.
* **Be forgotten** — hard delete with a signed, offline-verifiable receipt.
* **See a full usage ledger** — every recognition recorded and notified.
A participant can say *"don't record me"* or *"redact my name"* and it's honored automatically —
only possible because consent lives at the microphone.
## For the app [#for-the-app]
Any product hooks in by passing its own **scope + host + consumer token**. VFTE attaches no meaning
to the scope id, so there's no client-specific code:
* Opaque `workspace_id`, pluggable `consumer` string in the ledger.
* Generic, fail-closed M2M scopes (`enroll | identify | voiceprints | …`) — not product names.
* One-way knowledge channel, neutral branding.
Think of it as **identity + consent for voice** the way Stripe is for payments or Auth0 is for
login. Conclave and Capture are simply tenant #1 and #2.
## Status [#status]
The identity engine, consent plane, deletion proofs, and export/import are **built and merged** in
VFTE (see [VFTE features](/docs/product/features/vfte)). "VFTE as a metered external product" is a
**direction**, not a shipped billing surface — treat the standalone-API framing as the thesis, not a
live offering.
*Source: `Crazy Research/VFT-AS-A-SERVICE-USECASES.md`, `FEATURE-SPEC.md` §4.11.*
# Why Conclave (/docs/product/why-conclave)
## The one-liner [#the-one-liner]
> **Conclave is your team's confidential memory — every meeting, searchable, and even we can't read it.**
Longer: every meeting your team has — captured, searchable, and reasoned over — built so that even
the people who run it can't read your data. Most meeting AI makes you trade intelligence for
control. Conclave removes that trade.
Instead of digging through transcripts, you **ask your team's memory in plain language** and get an
answer pulled from *every* meeting, not just the last one — and **every answer is cited** (quote,
speaker, meeting). The differentiator: that intelligence runs inside a TEE on data the operator
cannot read, and it is **attested** — verifiable, not "trust us".
## The empty quadrant [#the-empty-quadrant]
Position everything on two axes: *does it remember across the whole team?* × *can the operator read
it?*
* **Notetakers** (Otter, Fireflies, Granola) — single-meeting, operator-readable.
* **Team knowledge** (Glean, Notion AI) — team-wide, operator-readable.
* **Personal memory** (Rewind, Limitless, mem) — cross-time but personal, operator-readable.
Conclave sits alone in the top-right: **team memory that is operator-blind.**
## The moat, stated plainly [#the-moat-stated-plainly]
Access is not privacy. Logins and tokens decide who gets in; only
encryption + a TEE decide what the vendor can read. And confidentiality is
end-to-end or it's a lie — which is why Conclave owns capture and enforces consent at the
microphone. Operator-blindness is attested: verify, don't trust.
Two lines are meant to land verbatim: **"even we can't read it"** and **"access is not privacy —
confidentiality is end-to-end or it's a lie."**
## VFTE — a voice you own [#vfte--a-voice-you-own]
The identity layer is sold as its own idea: **a voice you own.** Enroll once, own it, and be
recognized only in workspaces you agreed to — with a real consent plane (claim / pause / be
forgotten / full usage ledger). A participant can say *"don't record me"* or *"redact my name"* and
it's honored automatically — only possible because consent lives at capture. See
[VFTE as a service](/docs/product/vftee-as-a-service).
## Where it's going [#where-its-going]
We start where confidentiality is **mandatory** — legal, health, finance, privacy-native teams —
then expand to every team handling something sensitive (IP, deal terms, customer data). As breaches
and AI-privacy law compound, **confidential-by-default** becomes the expectation for everyone, and
Conclave is the memory layer underneath it. The go-to-market detail lives in
[Business](/docs/product/business/market).
*Source: `pitching/Conclave-Pitch-Script.md`, `Crazy Research/VFTE-PITCH-RESEARCH.md`.*
# API reference (/docs/engineering/reference/api)
The load-bearing endpoints per service. Auth is Supabase JWT (Conclave), scoped M2M tokens (VFTE),
and Bearer tokens (Capture). This is a map, not an exhaustive OpenAPI dump — see each repo for the
full surface.
## Conclave [#conclave]
| Endpoint | Purpose |
| -------------------------------------------------------------- | ------------------------------------------------------ |
| `POST /api/meetings/invite-bot` | Launch a Meet/Teams/Zoom bot |
| `POST /api/workspaces/{id}/transcripts` | Transcript upload |
| `POST /api/workspaces/{id}/record` | Audio-file / in-person record ingest |
| `GET /api/meetings/{id}/live` · `/live-view` | Live SSE stream + view |
| `POST /search` · `POST /ask` | Hybrid search · Ask (RAG, the only read-path LLM call) |
| `GET /api/transcripts/sessions/{id}/transcript.txt` | Speaker-turn transcript download |
| `GET /api/transcripts/sessions/{id}/audio?start=&end=` | Decrypt-on-read audio (Range-enabled) clip |
| `POST /api/diarize/jobs/claim` · `/{id}/heartbeat` · `/result` | Option-A GPU worker claim transport |
| `GET /api/diarize/audio/{id}` | Audio-by-reference (service-token gated) |
| `GET /attestation?nonce=` | TDX quote |
## Capture [#capture]
| Endpoint | Purpose |
| ---------------------------------- | ---------------------------------------------- |
| `POST /v1/diarize` | Batch diarize (heartbeat NDJSON) |
| `WS /v1/diarize/stream` | Streaming diarize |
| `WS /v1/inperson/stream` | In-person live path (diart + ASR) |
| `GET /health` · `GET /attestation` | Health · TDX quote (quote-only, no sealed key) |
## VFTE / FPM [#vfte--fpm]
| Endpoint | Purpose |
| --------------------------------------------------- | ------------------------------------------------------- |
| `POST /v1/enroll` | Create/strengthen a voiceprint |
| `POST /v1/identify` · `POST /v1/identify-spans` | Identify a clip · pre-diarized spans (flagship) |
| `POST /v1/propose` · `/v1/confirm` · `/v1/deny` | Consent proposal lifecycle |
| `GET /v1/me/pending` · `GET /v1/me/recognitions` | Subject inboxes |
| `POST /v1/me/voiceprints/{ws}/{vid}/forget` | Erasure (+ signed receipt) |
| `GET .../export` · `POST /v1/me/voiceprints/import` | Signed export / import |
| `POST /v1/recognitions` | Record + notify a consented recognition |
| `GET /v1/deletion-receipt-key` | Ed25519 public key for offline verification |
| `POST /auth/v1/exchange-token` | Verify Conclave JWT → mint `fpm_session` (shared login) |
*Source: `FEATURE-SPEC.md` §2.x/§3.7/§4, per-repo READMEs.*
# Configuration (/docs/engineering/reference/config)
The variables that actually change behavior. Full sets live in each repo's `.env.example` and are
rendered from `environments/matrix.*.env` by `envctl` — see
[Local development](/docs/engineering/local-development).
## Cross-cutting [#cross-cutting]
| Var | Meaning |
| --------------------------- | ----------------------------------------------------------------------------------- |
| `IN_TEE` | `true` in prod (real TDX quotes via `/var/run/dstack.sock`); `false` locally (stub) |
| `INTERNAL_*` vs `BROWSER_*` | docker-network URL vs host-browser URL — do not conflate |
## Conclave [#conclave]
| Var | Meaning |
| ------------------------------- | -------------------------------------------------------------------------------------------- |
| `CONCLAVE_INPERSON_VIA_CAPTURE` | Route in-person through capture-diart (**set explicitly** — dstack ignores compose defaults) |
| `CONCLAVE_DIARIZE_URL` | Where to send the authoritative post-pass |
| `CONCLAVE_ADMIN_EMAILS` | In-enclave admin allowlist |
| `CONCLAVE_DEV_LOGIN` | Enables `/api/auth/v1/dev-login` locally |
| LLM / ASR backend | RedPill TEE / NEAR AI; ASR = NEAR Whisper (`whisper-large-v3`) |
## Capture [#capture]
| Var | Meaning |
| ------------------------------------------------------------ | ------------------------------------------------ |
| `CAPTURE_DIARIZER` | `diart` \| `diarizen` \| `remote` \| `mock` |
| `CAPTURE_DIARIZER_URL` | Remote GPU diarizer endpoint (torch-free caller) |
| `CAPTURE_AUDIO_FETCH_TOKEN` · `CAPTURE_DIARIZE_RESULT_TOKEN` | Job-queue service tokens |
| `HF_HUB_OFFLINE` | `1` — offline models, no runtime egress |
## VFTE / FPM [#vfte--fpm]
| Var | Meaning |
| --------------------------------- | ------------------------------------------------------------------------- |
| `FPM_DB_KEY` | Master seal key (TEE-sealed → env → dev keyfile) |
| `FPM_AUTH_TOKENS` | Scoped, fail-closed M2M tokens (per-endpoint + optional workspace filter) |
| `fpm_workspace_for(workspace_id)` | The opaque scope-id mapping (must match on identify **and** tag) |
> Local workspaces: Conclave uses `local-ws`; VFTE voiceprints are scoped to `local-ws` (not the raw
> `ws_xxx`) — a common gotcha when wiping test voiceprints.
*Source: `ENVIRONMENTS.md`, per-repo `.env.example`, `DEPLOYMENT-GUIDE.md`.*
# Data model (/docs/engineering/reference/data-model)
Conclave persists everything in **one SQLite file inside the TEE** (relational + FTS5 + `sqlite-vec`
vectors), managed by SQLAlchemy 2 + Alembic. Alembic auto-applies on boot. VFTE keeps its own sealed
SQLite voiceprint store.
## Key tables (conceptual) [#key-tables-conceptual]
* **`transcript_sessions`** — the meeting: `owner_user_id`, `recorder_user_id`, `owner_only`,
`created_at`, `metadata` (source/platform/origin), `enrichment_state`.
* **`raw_diarization`** — write-once span-level truth `{start, end, local_speaker}`. Never mutated;
edits and identity live in overlays.
* **`transcript_v2`** + **`vocab`** — the editable, span-annotated doc (draft → approved) + personal
vocabulary.
* **`live_segments`** — append-only live stream for the SSE view.
* **`resolved_speakers`** — the identity overlay `{label: {voiceprint_id, name, confidence}}`.
* **`chunks`** + FTS5 + embeddings; **entities / facts / obligations** — bi-temporal
(`valid_to` / `superseded_by`).
* **`workspaces` / `workspace_members` / `workspace_invites` / `meeting_workspace_shares`** — the
membership + sharing graph.
* **`magic_links`**, **audio-store** metadata, **feedback**, **T\&C** acceptance.
## Load-bearing migrations [#load-bearing-migrations]
| Migration | What it introduced |
| ------------- | ------------------------------------------------------------------------------------------- |
| `0002` | workspaces / members / shares / magic\_links |
| `0007` | entities / facts / obligations |
| `0012 → 0024` | share scope enum → per-artifact `{transcript, insights, audio}` flags |
| `0013` | retention |
| `0016` | live\_segments |
| `0018` | transcript\_v2 + vocab |
| `0022` | audio-store |
| `0023` | in-person agenda |
| `0025` | user T\&C |
| `0027` | workspace\_invites + meeting\_workspace\_shares + `recorder_user_id` / `owner_only` (`#32`) |
> The **raw / derived** split is load-bearing: `raw_diarization` is immutable and never serialized
> unless authorized; everything else (overlays, v2, insights, KB) is derived and re-derivable.
## Worked example: the sharing scopes (how to add one) [#worked-example-the-sharing-scopes-how-to-add-one]
Sharing is per-artifact **booleans**, not an enum (that was the `0012 → 0024` change). Concretely:
* **Columns:** `share_transcript`, `share_insights`, `share_audio` on the share record.
* **Model:** `ShareConfig` in `infra/workspaces.py` is the single place a share's booleans are built.
* **Enforcement gates:** `can_see_transcript` / `can_see_insights` / `can_see_audio` in
`api/transcripts_routes.py` decide what a viewer's response includes (insights are independently
withholdable).
To add a new scope (e.g. "share attachments"): add the boolean column via a new Alembic migration
(mirror `0024`), extend `ShareConfig`, add a `can_see_attachments` gate alongside the others, and
surface it in the share UI. This is the `#31` pattern end to end.
*Source: `FEATURE-SPEC.md` §2.6 & §2.12, `conclave-shape-rotator/alembic/`, `infra/workspaces.py`, `api/transcripts_routes.py`.*
# Legal beachhead (/docs/product/business/beachhead-legal)
The **primary beachhead is Legal.** It's where every part of the Conclave thesis is a hard
requirement rather than a preference.
## Why Legal converts first [#why-legal-converts-first]
* **Privilege & work-product.** Meeting content is often privileged; a vendor that can read it is a
privilege problem. Operator-blindness maps directly onto the duty.
* **Two-party consent.** Recording law in many states requires all-party consent — exactly what the
[consent plane](/docs/product/concepts/consent-plane) enforces at the microphone.
* **BIPA / voiceprints.** Biometric-privacy law makes voiceprint-without-consent a live liability.
Conclave's consent-gated voiceprint + signed deletion receipts are a direct answer.
## The wedge [#the-wedge]
The outreach playbook targets **small and mid-size litigation firms** — buyers who feel privilege,
work-product, two-party-consent, and BIPA acutely, and who can adopt without an enterprise
procurement cycle. The concrete lead personas and buyer language live in
`pitching/conclave_legal_lead_personas.md`.
## The tailwind [#the-tailwind]
The incumbents validate the thesis by being sued on exactly this axis — see
[Competitive landscape](/docs/product/business/competitive). "Consent-gated voiceprint + BIPA" is
the specific wedge into Legal.
*Source: `pitching/conclave_legal_lead_personas.md`, `Crazy Research/VFTE-PITCH-RESEARCH.md`.*
# Competitive landscape (/docs/product/business/competitive)
Everyone in this space is readable by their operator. That's the gap Conclave is built into.
## The map [#the-map]
| Category | Examples | Team memory? | Operator-blind? |
| --------------- | ------------------------- | -------------------- | --------------- |
| Notetakers | Otter, Fireflies, Granola | No (single meeting) | No |
| Team knowledge | Glean, Notion AI | Yes | No |
| Personal memory | Rewind, Limitless, mem | Cross-time, personal | No |
| **Conclave** | — | **Yes** | **Yes** |
Conclave is alone in the top-right: **team memory that's operator-blind.** See
[Why Conclave](/docs/product/why-conclave) for the quadrant argument.
## The legal tailwind [#the-legal-tailwind]
The thesis isn't hypothetical — incumbents are being sued on exactly the voiceprint-and-consent axis
Conclave is designed around:
* ***In re Otter.AI Privacy Litigation*** — 5:25-cv-06911.
* ***Cruz v. Fireflies*** — 3:25-cv-03399.
Every such suit strengthens the "consent-gated, operator-blind" position and the
[Legal beachhead](/docs/product/business/beachhead-legal).
## Why it isn't trivially copied [#why-it-isnt-trivially-copied]
Operator-blindness is **end-to-end or it's a lie** — it requires owning capture, enforcing consent at
the microphone, and attesting the enclave. A readable-by-default incumbent can't retrofit that
without rebuilding their trust architecture and giving up the operator-side data access their own
products depend on.
*Source: `Crazy Research/VFTE-PITCH-RESEARCH.md`, `pitching/Conclave-Pitch-Script.md`.*
# Market (/docs/product/business/market)
Read this as the model, not as proof. The seat price and penetration below are
assumptions — they are not validated by paying customers. Treat every number here as a
sizing hypothesis.
## The beachhead [#the-beachhead]
Conclave starts where confidentiality is **mandatory** — teams for whom "the vendor can't read it"
is a requirement, not a nice-to-have:
> We start where confidentiality is mandatory — legal, health, finance, privacy-native teams.
## Sizing (the model) [#sizing-the-model]
The serviceable market is framed as **\~2.85M US regulated knowledge workers**:
| Segment | Approx. workers |
| -------------------------- | --------------- |
| Lawyers | \~1.32M |
| Behavioral / mental health | \~1.0M |
| Financial advisors | \~311K |
| HR | \~222K |
At an assumed **\~$600 / seat / year**, that's a **\~$1.7B US beachhead (SAM)**. Near-term SOM is
modeled at **\~$20–50M ARR** from **under 2% of seats** — reached via privacy-native startups and
regulated boutiques, where a partner can put it on a card without a procurement gauntlet.
## The arc [#the-arc]
The vision doesn't stop at regulated verticals:
> It expands to every team handling something sensitive — IP, deal terms, customer data. And the
> arc is that as breaches and AI-privacy law compound, confidential-by-default becomes the
> expectation for everyone — and Conclave is the memory layer underneath it.
See [Monetization](/docs/product/business/monetization) for the pricing logic and
[Legal beachhead](/docs/product/business/beachhead-legal) for the first wedge.
*Source: `pitching/Conclave-Market-Monetization-Memo.md`.*
# Monetization (/docs/product/business/monetization)
The model, not proof. Pricing and penetration here are assumptions, not validated
by paying customers. Frame them as the hypothesis.
## The pricing logic [#the-pricing-logic]
Conclave is priced against **liability avoided**, not against notetaker commodity pricing. For a
regulated team, the relevant comparison isn't "$10/seat like Otter" — it's the cost of the breach or
consent violation the product is designed to prevent.
* Assumed list: **\~$600–1,200 / seat / year**.
* Against the downside, that's a rounding error.
## The liability math [#the-liability-math]
* **Average healthcare data breach: \~$9.77M.**
* **Largest BIPA settlement: $650M** (Facebook, biometric/voiceprint consent).
A tool that keeps voiceprints consented and keeps content operator-blind is cheap insurance against
numbers like these — which is the entire pricing argument.
## Why the wedge converts [#why-the-wedge-converts]
The near-term motion targets buyers who can say yes without procurement: privacy-native startups and
regulated boutiques where **a partner swipes a card**. That's what makes a modeled **\~$20–50M ARR
from under 2% of seats** plausible on paper — small penetration of a compliance-driven market, not a
land grab.
See [Market](/docs/product/business/market) for sizing and
[Competitive landscape](/docs/product/business/competitive) for why incumbents can't simply
copy the posture.
*Source: `pitching/Conclave-Market-Monetization-Memo.md`.*
# The consent plane (/docs/product/concepts/consent-plane)
The **consent plane** is VFTE's single source of truth for one question: *may this person's voice be
recognized, and may their name be shown?* It is separate from workspace membership and from sharing.
## The controls [#the-controls]
* **`subject_consent`** (per edge) — the core naming decision:
* `allow` — "say my name."
* `anonymous` — "know me, but hide my name" (recognized, not labeled).
* `denied` — "don't recognize me at all here."
* **`id_visibility`** — `adder-only` vs `scope-wide`: who gets to see that it's you.
* **`enroll_allowed`** — pause enrollment entirely (stop new voiceprints forming).
* **Forget / erasure** — hard-delete a voiceprint; an append-only ledger row + signed receipt remain.
The old flat `identify_allowed` boolean was folded into `subject_consent` (task `#2`) and its alias
retired (`#35`).
## Propose → Confirm → Deny [#propose--confirm--deny]
Recognition **never** silently names someone:
1. A host tags a speaker → `POST /v1/propose` → the match goes **pending**.
2. The subject confirms or denies from their inbox (`GET /v1/me/pending`). **Self-tagging
auto-confirms.**
3. The proposal can carry a `clip_ref` so the subject can **play the clip that named them** — via a
short-lived **Ed25519-signed capability**, so VFTE itself never streams the bytes (`#3`).
## Recognition transparency [#recognition-transparency]
Even a *consented* auto-recognition is **recorded and notified every time** (`POST /v1/recognitions`
→ the subject's `GET /v1/me/recognitions` inbox), with metadata and controls — but **no transcript
access**. Recognized-but-still-told (`#3c`).
Why this can exist at all. Honoring "don't recognize me" or "redact my name"
automatically is only possible because consent lives at capture, not bolted on after the
fact. That is the point of owning the microphone.
## Member ≠ recognized [#member--recognized]
A crisp distinction the whole model rests on: being a **member** of a workspace (you hold a key) is
not the same as being **recognized** in it (your voiceprint is on file). The consent plane governs
only the second. See [Workspaces](/docs/product/concepts/workspaces).
*Source: `FEATURE-SPEC.md` §4.3 & §2.5, `FPM/CONSENT-PLANE.md`, `discoveries/WORKSPACES-AND-VFT-GRAMMAR.md`.*
# Diarization (/docs/product/concepts/diarization)
**Diarization** answers *who spoke when* — as anonymous speaker labels (`Speaker 1`, `Speaker 2`),
never names. Naming is a separate, consented step ([voiceprints](/docs/product/concepts/voiceprints)).
This is the load-bearing boundary: **Capture emits `{start, end, local_speaker}` and nothing else.**
## Two engines [#two-engines]
| Engine | When | Where | Accuracy (DER) | Notes |
| ------------ | --------------------------- | ----- | -------------- | -------------------------------------------------------------- |
| **diart** | Live, streaming | CPU | \~0.190 | pyannote streaming; drives the on-screen live preview |
| **DiariZen** | Post-meeting, authoritative | GPU | \~0.088 | WavLM + VBx; overwrites the live labels with the accurate pass |
The live path shows a diart preview immediately; after the meeting, the **authoritative DiariZen
post-pass** re-diarizes for accuracy, then hands spans to VFTE for identity.
## How a meeting resolves [#how-a-meeting-resolves]
```
in-person mic → diart (live) + NEAR Whisper ASR per span → live preview
finalize → DiariZen post-pass (authoritative spans)
→ VFTE identify-spans (scoped + consented names)
→ merge by timestamp → [speaker] text transcript → enrich
```
## The headline is the merged transcript [#the-headline-is-the-merged-transcript]
Diarization quality is judged by the **merged `[speaker] text` transcript**, not DER in isolation:
Whisper (what was said) runs alongside the diarizer (who said it), and the two are merged by
timestamp. A great DER with a garbled merge is still a bad transcript.
## Licensing & production reality [#licensing--production-reality]
DiariZen's **weights are CC-BY-NC** — fine for evaluation, but a clean engine is required before
commercial use, which is why replacing the post-pass model is an active workstream. The live path
(diart) is the firm choice for real-time; DiariZen is post-pass only. Current latency/reliability
work is tracked as [P0: diarization](/docs/engineering/p0-diarization).
*Source: `FEATURE-SPEC.md` §3.2, `DIARIZATION-MIGRATION.md`.*
# Knowledge graph & search (/docs/product/concepts/knowledge-graph)
Conclave doesn't just store transcripts — it builds a **knowledge base** across meetings so you can
ask a question and get a cited answer pulled from everything, not just the last call. All of this is
built at **ingest**; the read path stays operator-blind.
## What gets extracted [#what-gets-extracted]
* **Entities** — per-chunk LLM extraction of people, technologies, and affiliations (*derived*
types), stored **bi-temporally** (`valid_to` / `superseded_by`) so the graph has history.
* **Entity resolution** — lexical-first, definition-driven merge (tuned to avoid "black-hole"
over-merges).
* **Obligations & facts** — structured action items and facts, also bi-temporal.
* **Chunking** — turn-aware (never splits mid-turn) with context headers, plus FTS5 + embeddings.
## Hybrid search [#hybrid-search]
Retrieval runs two lanes and fuses them:
* **FTS5 BM25** (lexical) ∥ **local nomic embeddings** (768-d, 256-d Matryoshka ANN via
`sqlite-vec`).
* Fused with **Reciprocal Rank Fusion (RRF)**, then **visibility-filtered**.
It's **pure SQL + local embeddings** — no model call, no egress. That's what makes search
operator-blind. Surfaced at `POST /search`.
## Ask (RAG) — the only read-path LLM call [#ask-rag--the-only-read-path-llm-call]
`POST /ask` synthesizes an answer from retrieved contexts and is the **single deliberate exception**
to "no LLM on reads." Every answer is **cited** (quote, speaker, meeting). It's optional (returns 404
when disabled) and runs inside the TEE.
Cited or it didn't happen. Ask never free-associates — it answers from
retrieved, visibility-scoped context and shows its sources, so an answer can always be traced back
to the meeting it came from.
The graph, per-entity pages, obligations, and open-questions rollups are all browsable in the app.
*Source: `FEATURE-SPEC.md` §2.4 & §6, `conclave-graph-synthesis/`, `ENTITY-CANON.md`.*
# Voiceprints (/docs/product/concepts/voiceprints)
A **voiceprint** is how VFTE recognizes a speaker across meetings without ever storing their audio
or their words.
## What it is [#what-it-is]
* A **CAM++ 512-dimension** voice embedding, computed by a pure-numpy fbank front-end feeding a
CAM++ ONNX model (CPU — no GPU, no torch in production).
* A voiceprint holds **≤ 20 exemplars** with eviction, a recomputed centroid, k-means
sub-centroids, and a quality score. Its id is **deterministic**.
* Stored **AES-256-CBC + HMAC-SHA256 sealed** at rest (MAGIC + IV + HMAC blob; HKDF-derived
enc/mac keys; master key TEE-sealed via dstack).
VFTE holds voiceprints and consent edges — **never audio, never transcript content.**
## Enrollment → identification [#enrollment--identification]
* **Enroll** (`POST /v1/enroll`) — labeled audio + identity (email) + workspace → embedding →
create-or-strengthen the voiceprint. Gated by consent (`enroll_allowed`) and a quality threshold.
* **Identify** (`POST /v1/identify-spans`, the flagship) — pre-diarized spans are matched, votes are
locked across the session, and spans are retro-relabeled. Decision tiers:
**MATCH (≥ 0.45) · AMBIGUOUS (top-2 within 0.10) · LOW · UNKNOWN**, with sigmoid-calibrated
confidence. `live` mode is read-only; `offline` mode may write.
The **first** meeting enrolls a new voice; **later** meetings recognize it — so the voiceprint count
does not grow as the same people keep meeting.
## You own it [#you-own-it]
Because the id is deterministic and the store is portable, a voiceprint is a thing a person can
actually control:
* **Forget / erasure** — hard-delete, leaving only an append-only ledger row + a signed receipt.
* **Deletion proof** — an **Ed25519-signed, offline-verifiable receipt** (`#1`, merged in VFTE).
* **Export / import** — signed export of the exemplars and re-import elsewhere (`#4`, merged),
cross-model blocked.
See [the consent plane](/docs/product/concepts/consent-plane) for who is allowed to be recognized,
and [VFTE features](/docs/product/features/vfte) for the full surface.
*Source: `FEATURE-SPEC.md` §4.1–4.8.*
# Workspaces (/docs/product/concepts/workspaces)
A **workspace** is the trust boundary of the product: where content persists (inside the TEE) and
the unit that membership, sharing, and voice-scope all hang off.
## Two graphs, one boundary [#two-graphs-one-boundary]
* **Conclave** owns the `user ↔ workspace` graph — who is a member, who owns a meeting, what's shared.
* **VFTE** owns the `voiceprint ↔ scope` graph — which voiceprints may be recognized in which scope.
They meet at exactly one point: `settings.fpm_workspace_for(workspace_id)` — an **opaque** scope id
that VFTE treats as meaningless. Neither side trusts the other's internals; **both sides gate.** That
opacity is precisely what keeps VFTE a general identity layer any product can reuse.
## Membership and ownership [#membership-and-ownership]
* Workspaces are N:N (`workspace_members`). A solo **Personal** workspace is auto-provisioned on
login (non-invitable).
* Team workspaces are **invite-gated**: an owner invites by email → invite → accept
(magic-link / auto-on-signup) → member row. Owner-only management; the last owner can't be removed.
* Meetings are **owner-private by default** — bare membership does *not* expose a meeting. The owner
opts in via *share-to-workspace* (covers future members) or *share-to-member*.
* The meeting owner is the **recorder** (stamped at capture), so share / edit / retention / delete /
rename light up for whoever actually recorded.
*(This is `#32`, built and merged locally; see [Status](/docs/engineering/status).)*
## Scopes are more than workspaces [#scopes-are-more-than-workspaces]
VFTE scopes can be `workspace` · `person` · `meeting`, with membership **edges** carrying
`{added_by, id_visibility, subject_consent}`. The candidate set for identification is
host-dependent, **promotion re-triggers consent**, and existing voiceprints were migrated onto this
edge model (`#2`).
ELI5. Your login gets you into a workspace. Your voiceprint
being recognized there is a separate, consented thing. You can be a member who is never recognized,
or be recognized as a guest who has no login at all.
*Source: `FEATURE-SPEC.md` §2.9 & §4.7, `discoveries/WORKSPACES-AND-VFT-GRAMMAR.md`, `FPM/docs/vft-scoping-model.md`.*
# Capture features (/docs/product/features/capture)
The **Capture** plane joins meetings and produces *who spoke when + what they said* — with **no
identity**. It is stateless and holds nothing persistent. Everything here is **built and merged**.
## Two capture paths [#two-capture-paths]
* **Online bot (recato-bot)** — a headless browser / Zoom-SDK bot joins Meet / Teams / Zoom and
captures **per-participant** audio (structural diarization, no ML), with Silero VAD, a confirmation
buffer, hallucination filtering, per-speaker language lock, DOM speaker names, and authenticated
warmed accounts.
* **In-person single-mic** (the flagship) — `WS /v1/inperson/stream` → **diart** live acoustic
diarization → **NEAR Whisper** ASR per span → Redis publish, while buffering the recording for the
authoritative post-pass.
A **runtime-api** orchestrates bot containers (idle auto-stop, crash reconciliation, exit webhooks
with retry, per-user concurrency, hot-reload profiles).
## Diarization engines [#diarization-engines]
| Engine | Role | Placement |
| ------------ | ------------------------------------ | --------------------------------- |
| **diart** | Live streaming preview | CPU |
| **DiariZen** | Authoritative post-pass | GPU (stateless, swappable by URL) |
| **remote** | torch-free caller → external GPU box | — |
| **mock** | deterministic test double | — |
Selected via `CAPTURE_DIARIZER`; lazy import keeps the core torch-free. DiariZen uses 15-minute
**windowing** with an ephemeral CAM++ **linker** that stitches windows in memory and persists
nothing. See [Diarization](/docs/product/concepts/diarization).
## The identity seam (load-bearing) [#the-identity-seam-load-bearing]
Capture emits {'{'}start, end, local\_speaker{'}'} only — no
voiceprint id, no name, no store import. This is test-locked against identity leaks
(diarize\_only.py). The ephemeral linker reuses CAM++ math but keeps nothing. Identity
is a separate, consented step in VFTE.
## Durable jobs & orchestration [#durable-jobs--orchestration]
* **Durable queue** (`#16`) — Redis-Streams `diarize_jobs` with XAUTOCLAIM reclaim + dead-letter.
* **Option-A HTTP-claim** — an off-CVM GPU worker claims via `:443` only (claim / heartbeat /
result), **no Redis or SSH on the box**, fetching **audio by reference** (by token, not through
Redis).
* **Meeting-end** — upload recording → meeting-completed webhook → fire-and-forget authoritative
post-pass. `store_audio=false` skips the upload but keeps the transcript.
## API & TEE [#api--tee]
`POST /v1/diarize` · `WS /v1/diarize/stream` · `WS /v1/inperson/stream` · `GET /health` ·
`GET /attestation`. Bearer-token auth (constant-time), `?token=` for browsers. TDX attestation is
**quote-only, no sealed key** — the box holds nothing. Offline models, no runtime egress.
*Source: `FEATURE-SPEC.md` §3, `capture/README.md`.*
# Conclave features (/docs/product/features/conclave)
This is the shipped feature catalog for the **Conclave** plane. Everything here is **built and
merged to `main`** unless noted. Status vocabulary and the full board are on
[Status](/docs/engineering/status); the platform is deployed but under active hardening (see
[P0: diarization](/docs/engineering/p0-diarization)) with one known blocker — the **web login loop**
([Conclave#13](https://github.com/conclavehq/Conclave/issues/13), open).
## Capture / ingest surfaces [#capture--ingest-surfaces]
| Feature | What it does | Status |
| ------------------------------ | ------------------------------------------------------------------------------- | ------------------ |
| In-person recording | Browser mic → live diart + ASR → finalize → DiariZen + VFTE | merged |
| Google Meet / Teams / Zoom bot | Paste URL → bot joins → streaming transcript; concurrency caps, warmed accounts | merged |
| Transcript upload | Paste/file text or JSON (Otter/VoxTerm/generic) → parse → enrich | merged |
| Audio-file upload (`#52`) | Voice-memo picker → record pipeline → **Uploaded** badge | built · not merged |
| Calendar auto-record | Scheduler launches a bot \~15 min before toggled events | merged |
| Live segment streaming | Capture → Redis → SSE to `/recording/[id]` | merged |
## Transcript editor & refinement [#transcript-editor--refinement]
V2 **inline word-level editor** over a span-annotated `transcript_v2` doc (separate from immutable
`raw_diarization`), with a **draft → approved** workflow. Includes speaker tagging (name/email →
voiceprint), **personal vocabulary + autocomplete**, spaCy **NER pre-typing**, **approve → re-derive**
(re-runs enrichment from the corrected text), re-edit-after-approve, and **heal-on-open** (`#13`) —
when a name is confirmed later, the summary regenerates on next open. *All merged.*
## Insights & grounding [#insights--grounding]
One LLM call at ingest fills **summary + signals** (action items, decisions, open questions).
**Meeting-intent grounding** compiles a freeform focus + calendar description + in-person agenda
(`#12`) into `` that steers the summary, with an **"Agenda-grounded" badge**. A short
**LLM title** (`#40`) and per-workspace **obligations / open-questions** rollups round it out.
*All merged.*
## Knowledge base [#knowledge-base]
Bi-temporal **entity / obligation / fact** extraction, definition-driven **entity resolution**,
**hybrid BM25 + embedding search** (`POST /search`, RRF), **Ask / RAG** (`POST /ask`, the only
read-path LLM call, always cited), a D3 **entity graph**, and per-entity pages. See
[Knowledge graph & search](/docs/product/concepts/knowledge-graph). *Merged.*
## Sharing, trust & data-rights [#sharing-trust--data-rights]
| Feature | What it does | Status |
| ------------------------------------ | ------------------------------------------------------------------------------------------------ | ------ |
| Granular sharing (`#31`) | Per-share **`{transcript, insights, audio}`** booleans; insights independently withholdable | merged |
| Magic-link shares | Single-use email link → public resolve → session | merged |
| Transcript `.txt` download (`#52`) | Speaker-turn transcript as `text/plain` for authorized viewers | merged |
| Tag → email transcript (`#15`) | Tagging can also create the share + send the link in one step | merged |
| Data-dump + T\&C (`#18`) | Owner-scoped ZIP export + blocking first-login T\&C gate | merged |
| Retention | Per-meeting + account default; scheduled sweep purges raw, keeps summary + KB | merged |
| Contribute to Shape OS (`#20` Arm 1) | Host-approved v2 → Shape OS anonymous inbox, opt-in | merged |
## Audio (TEE-encrypted) [#audio-tee-encrypted]
**AES-256 at rest** (`#30`, encrypt-on-write, dstack-sealed key), decrypt-on-read **player** with
**per-segment clips** and **click-to-seek** (`#41`), a store-audio toggle, and service-token-gated
**audio-by-reference** fetch for workers. *Merged.*
## Auth, workspaces & lifecycle [#auth-workspaces--lifecycle]
Supabase OTP + OAuth with an auto-provisioned **Personal** workspace; **multi-member team
workspaces** with invites, roles, and owner-private meetings (`#32`, built · merged local);
**meeting origin** (`#38`) / **time-of-day** (`#39`) / **title** (`#40`) surfacing;
**speaker-turn coalescing** (`#37`); and a real **delete + processing/failed lifecycle** (`#42`).
*Source: `FEATURE-SPEC.md` §2, `task-plan/MASTER-TASK-LIST.md`.*
# VFTE features (/docs/product/features/vfte)
**VFTE** (a.k.a. FPM) is the voice-identity + consent layer. It turns audio into voiceprints,
recognizes speakers *scoped and consented*, and owns the consent plane. It holds voiceprints and
consent edges — **never audio or transcript content**. Everything here is **built and merged**.
> VFTE no longer diarizes — the old `/v1/diarize` was stripped; diarization lives entirely in
> [Capture](/docs/product/features/capture).
## Enrollment & identification [#enrollment--identification]
* **Enroll** (`POST /v1/enroll`) — labeled audio + email + workspace → CAM++ 512-d embedding →
create-or-strengthen a voiceprint (deterministic id, ≤ 20 exemplars with eviction, k-means
sub-centroids, quality gate, `enroll_allowed` gate).
* **Identify** — `POST /v1/identify` (single clip) and the flagship `POST /v1/identify-spans`
(pre-diarized spans, vote-locked, retro-relabel, mint-anonymous). Tiers **MATCH / AMBIGUOUS / LOW
/ UNKNOWN** with calibrated confidence; `live` (read-only) vs `offline` (write).
See [Voiceprints](/docs/product/concepts/voiceprints) for the mechanics.
## Consent plane [#consent-plane]
The full grammar — `subject_consent {allow, anonymous, denied}`, `id_visibility`, `enroll_allowed`,
propose/confirm/deny, forget/erasure, and recognition transparency (`#3c`) — is documented at
[The consent plane](/docs/product/concepts/consent-plane). Permission scoping (`#2`, merged)
replaced flat scoping with scopes + membership edges.
## Ownership & cryptography [#ownership--cryptography]
| Feature | What it does | Status |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------ |
| Deletion proof (`#1`) | **Ed25519-signed**, offline-verifiable deletion receipts; pubkey at `GET /v1/deletion-receipt-key`, attestation-bound | merged |
| Export / import (`#4`) | Signed voiceprint export + re-import (create-or-merge); cross-model blocked | merged |
| Hear-clip capability (`#3`) | Short-lived **Ed25519-signed** capability lets a subject play the clip that named them; VFTE never streams bytes | merged |
| At-rest crypto | AES-256-CBC + HMAC-SHA256; master key TEE-sealed → env → dev keyfile | merged |
| Auth / tokens | Scoped, fail-closed M2M tokens + session auth (OAuth / dev-login); per-token rate limits | merged |
## Dashboard & product-agnostic design [#dashboard--product-agnostic-design]
A Next.js consent dashboard (voiceprint cards: stay-anonymous / pause-enroll / forget + usage
ledger; pending inbox with clip player; recognitions inbox; export/import bar). VFTE stays
**product-agnostic** by design: an opaque `workspace_id`, a pluggable `consumer` string, generic M2M
scopes, and neutral branding — so **any app** hooks in by passing its own scope + host + token. This
is the basis for [VFTE as a service](/docs/product/vftee-as-a-service).
*Source: `FEATURE-SPEC.md` §4, `FPM/README.md`.*