# Conclave (/docs) **Conclave is your team's confidential memory.** Every meeting your team has — captured, searchable, and reasoned over — and 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. The system is three cooperating products behind one boundary: * **Conclave** — the intelligence + control plane. The TEE where content lives (transcripts, summaries, insights, knowledge base, audio). * **Capture** — meeting capture + acoustic diarization. Joins online meetings or streams a room mic. **Identity-free** by design. * **VFTE** — the voice-identity + consent layer. Turns audio into voiceprints, recognizes speakers *scoped and consented*.
The load-bearing idea. Access is not privacy. Logins decide who gets in; only encryption + a TEE decide what the vendor can read. Conclave's reads are operator-blind and attested — verify, don't trust.
## Start here [#start-here] ## The tiers [#the-tiers] ## Agent-friendly by construction [#agent-friendly-by-construction] Every page is plain MDX you can read straight from the repo. The site also serves [`/llms.txt`](/llms.txt) (an index of every page) and [`/llms-full.txt`](/llms-full.txt) (the entire corpus as one plain-text document), and each page has a **Copy as Markdown** button. Point your agent at any of these. > **One rule for this repo:** we document only what is *implemented*. Planned work lives on the > [Roadmap](/docs/engineering/roadmap), derived from the live GitHub issue tracker and clearly > labelled as not-yet-built. See [Status](/docs/engineering/status) for what actually ships. # Build workflow (/docs/contributing/build-workflow) How work actually lands in the Conclave codebase. It's deliberately conservative — the trust story means a broken merge is expensive. ## One branch (and worktree) per task [#one-branch-and-worktree-per-task] * Work on a single `feat/` branch per task, per repo — usually in a dedicated **git worktree** so several tasks proceed in parallel without stepping on each other. * The plan for a task lives in `task-plan/TASK--.md`. Build the flagged master list first, deepen one task at a time, then implement. ## The merge gate [#the-merge-gate] A change reaches `main` only when **all** of these hold: 1. **Full test suite is green.** Every commit is test-gated; no merge on red. 2. **An independent mutation audit passed** — a *separate* agent breaks each load-bearing behavior and confirms a test goes red, so the tests actually defend the behavior. 3. **Explicit human approval.** Plan approval is not implementation approval; a green build is not a merge. Wait for the go-ahead. ## Pushes are human-run [#pushes-are-human-run] Many features are **merged to a local `main` but not pushed** — that's why [Status](/docs/engineering/status) distinguishes *built · merged local* from *shipped*. Don't assume `main` is on origin; `git fetch` before reasoning about ahead/behind. ## Deploys [#deploys] Never re-provision a CVM (it wipes the sealed volume). Deploy in place with `phala deploy --cvm-id`. Bump the **literal** image tag every time. Full recipe: [Deployment](/docs/engineering/deployment) and `DEPLOYMENT-GUIDE.md`. ## When writing docs here [#when-writing-docs-here] Match [Writing docs](/docs/contributing/doc-style): document only what's implemented, badge status honestly, cite the source, keep `npm run build` green. # Writing docs (/docs/contributing/doc-style) The bar for this site is *minimalist but important info*. A person or an agent should be able to scan a page in under a minute and come away with the real thing, not filler. ## The one rule [#the-one-rule] **Document only what is implemented.** Two ground truths: * **Shipped** → [Status](/docs/engineering/status) + `task-plan/MASTER-TASK-LIST.md` upstream. * **Planned** → the live GitHub issues in `conclavehq/{Conclave,Capture,VFTEE,Mobile}`. These belong on the [Roadmap](/docs/engineering/roadmap), clearly labelled *not yet built* — never in a feature page as if they exist. If you can't confirm a capability is built, it doesn't go in a feature page. ## Status badges [#status-badges] Every feature gets one, stated plainly in prose or a table cell: * **Live in prod** — deployed and running on the Phala CVMs. * **Built · merged local** — merged to a local `main`, not yet pushed/deployed. Say *built*, not *shipped*. * **Built · not merged** — exists on a feature branch, not merged. Surface known open bugs where they matter (e.g. the web login loop) rather than hiding them. ## Voice [#voice] * Dense and concrete. Prefer a table or a tight list to a paragraph. * Cite the source. Link the canonical upstream doc (`FEATURE-SPEC.md`, `DEPLOYMENT-GUIDE.md`, a `TASK-*.md`) so a reader can verify and go deeper. * Machine identifiers — CVM ids, env vars, endpoints, file paths — in `monospace`. * No marketing adjectives in engineering pages. No invented numbers. Flag assumptions as assumptions (the GTM pages label pricing/market sizing as *the model, not validated*). * No emoji in headings, no decorative gradients, no stock imagery. ## Mechanics [#mechanics] * Pages are MDX under `content/docs/`. Frontmatter needs `title` and `description`. * Sidebar order and tabs live in each folder's `meta.json` (`"root": true` makes a tab). * The trust-accent green is reserved for trust/attestation cues — use the `trust-note` block sparingly, not as general emphasis. * Run `npm run build` before you commit; it must stay green. # For agents (/docs/contributing/for-agents) This site is built to be consumed by agents as much as by people. If you were pointed here to understand Conclave or to contribute, read this first. ## Fastest way to load the whole thing [#fastest-way-to-load-the-whole-thing] * **`/llms.txt`** — a compact index of every page with links. * **`/llms-full.txt`** — the entire documentation corpus as one plain-text document. Fetch this when you want everything in one shot. * **`/llms.mdx/docs//content.md`** — any single page as clean Markdown. Every page also has a **Copy as Markdown** button in its header. * The raw source is plain MDX under `content/docs/` — read it directly from the repo. ## Reading order [#reading-order] 1. **`index`** and **`product/overview`** — the three products (Conclave / Capture / VFTE) and the boundary between them. 2. **`product/trust-model`** + **`product/concepts/*`** — operator-blindness, voiceprints, the consent plane, workspaces, diarization, the knowledge graph. 3. **`engineering/architecture`** + **`engineering/data-flow`** — topology and how a meeting flows end to end. 4. **`engineering/status`** (what's built) then **`engineering/roadmap`** (what's not). 5. Dive into **`product/features/*`** or **`engineering/reference/*`** for the specific surface you're touching. ## The source-of-truth map [#the-source-of-truth-map] These docs summarize and link to the canonical specs, which live in the upstream `shape-rotator-all/` monorepo. When a status claim matters, verify against the source: | Topic | Canonical source | | --------------------------------- | ------------------------------------------------------------------- | | Every feature, all three products | `FEATURE-SPEC.md` | | Trust / scoping / consent grammar | `discoveries/WORKSPACES-AND-VFT-GRAMMAR.md`, `FPM/CONSENT-PLANE.md` | | Architecture & data flow | `CONCLAVE-CAPTURE-ARCHITECTURE.md`, `DIARIZATION-MIGRATION.md` | | Deployment, CVMs, gotchas | `DEPLOYMENT-GUIDE.md`, `ENVIRONMENTS.md` | | What's built (status) | `task-plan/MASTER-TASK-LIST.md` | | What's planned (backlog) | live GitHub issues in `conclavehq/{Conclave,Capture,VFTEE,Mobile}` | ## Rules if you write here [#rules-if-you-write-here] * **Only document what is implemented.** Ground truth for "shipped" is [Status](/docs/engineering/status) + `MASTER-TASK-LIST.md`; ground truth for "planned" is the live GitHub issue tracker. Never dress an open issue up as a shipped feature. * Attach an honest **status badge** to every feature: `Live in prod` · `Built · merged local` · `Built · not merged`. Say **built**, not **shipped**, when it's local-only. * Surface known open bugs where relevant (e.g. the web login loop) — don't hide them. * Match the house style in [Writing docs](/docs/contributing/doc-style): dense, cited, minimalist, no filler. * Keep `npm run build` green. # Architecture (/docs/engineering/architecture) The product is a **three-repo system** plus satellite clients. The clean boundary: **Capture = who spoke when + what they said · VFTE = who that person is · Conclave = orchestration + persistence + intelligence.** See [The three products](/docs/product/overview) for the product view; this page is the engineering topology. ## Components [#components] | Component | Repo / dir | GitHub | Role | State | | -------------------- | ------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------- | | **Conclave** | `conclave-shape-rotator` | `conclavehq/Conclave` | Control plane + dispatcher; the **only** persistence (audio + transcripts + KB); enrichment; VFTE orchestration | Stateful (in TEE) | | **Capture** | `capture` | `conclavehq/Capture` | Stateless capture + acoustic diarization (diart + DiariZen) + ASR call. Emits `{start,end,local_speaker}` | Stateless | | **VFTE / FPM** | `FPM` | `conclavehq/VFTEE` | Identity-only voiceprint layer + consent plane + sealed store | Stateful (sealed) | | **diart** | inside `capture` | — | Live real-time diarization, CPU-pinned | ephemeral | | **DiariZen worker** | inside `capture` | — | Authoritative GPU batch post-pass, stateless/swappable | Stateless | | **Veritas recorder** | `veritas-recorder` | `conclavehq/Veritas` | Standalone attested iOS/Mac recorder (satellite, not in the meeting pipeline) | on-device | | **Shape-OS client** | `os-ws-passthrough` | — | Electron desktop app Conclave contributes into | client | > Naming: "VFTE" and "FPM" are the **same service** (dirs/env still prefixed `fpm`/`FPM_`; GitHub > repo is `VFTEE`). "capture" was extracted from the dissolved **Recato** product. ## Tech stack [#tech-stack] * **Conclave** — Python **FastAPI** (uvicorn), one **SQLite** file (relational + FTS5 + `sqlite-vec`), SQLAlchemy 2 + Alembic, **Redis** (durable job queue + live-segment stream), LangChain at ingest, `dstack-sdk` for TDX, Supabase auth. Frontend: **Next.js 16** in `frontend/`. LLM: RedPill TEE / NEAR AI; ASR: NEAR Whisper. * **Capture** — mixed: diarization service = Python FastAPI; recato-bot = Node/TypeScript (CDP-driven headless browser); runtime-api orchestrator. Engines: diart (torch 2.2.2, CPU) + DiariZen (torch 2.1.1 cu121, GPU). * **VFTE / FPM** — Python FastAPI, **ONNX Runtime** CPU embedder (CAM++), SQLite sealed store, `dstack-sdk`, Supabase JWT verification. Frontend: Next.js consent dashboard. No torch in prod. ## How they connect (prod) [#how-they-connect-prod] ``` browser / OS recorder ──wss──▶ capture-diart (TLS-passthrough ingress) │ publishes segments ▼ Redis stream ──▶ Conclave (persist audio + transcript) │ dispatch post-pass job ▼ DiariZen GPU worker (claims over :443) │ authoritative spans ▼ VFTE /v1/identify-spans (names, scoped + consented) ▼ Conclave enrich → KB → view ``` Online meetings follow the same Redis wire format via a **recato-bot** dispatched through the runtime-api. Full flows on [Data flow](/docs/engineering/data-flow). *Source: `CONCLAVE-CAPTURE-ARCHITECTURE.md`, per-repo READMEs.* # Data flow (/docs/engineering/data-flow) There are four flows. All of them keep capture identity-free and all naming consent-gated. ## Online (live) [#online-live] ``` Conclave dispatch → recato-bot joins Meet/Teams/Zoom (structural diarization) → Redis transcription_segments → Conclave TEE (persist) → VFTE identify(ws) → provisional tags shown live ``` ## In-person (live) [#in-person-live] ``` browser / OS mic → capture-diart WS (diart live + per-span NEAR Whisper ASR) → Redis transcription_segments → Conclave live-view (SSE at /recording/[id]) ``` ## Post-meeting (authoritative) [#post-meeting-authoritative] ``` Conclave finalize → durable diarize job (Redis diarize_jobs stream) → GPU DiariZen worker claims over HTTP, fetches audio by reference (GET /api/diarize/audio/{uid}) → authoritative {start,end,local_speaker} → POST /api/diarize/result → VFTE /v1/identify-spans(ws, audio, spans) → reconcile (authoritative overwrite of raw_diarization) → merge_by_timestamp → [speaker] text transcript → enrich → KB index/extract ``` ## Manual tag [#manual-tag] ``` Conclave UI → (a) resolved_speakers overlay (instant, raw stays immutable) → (b) VFTE consent-gated feedback (self-tag auto-confirms; else PENDING until the subject confirms) ``` ## The segment contract [#the-segment-contract] The split between diarization and identity is exact — `segment_dict`: | Field | Owner | | ------------------------------------------------- | ----------- | | `start`, `end`, `local_speaker` | **Capture** | | `voiceprint_id`, `name`, `decision`, `confidence` | **VFTE** | A **second** meeting *recognizes* the same voices — the voiceprint count does not grow. The durable job queue (`#16`) makes finalize crash-safe: restarting Conclave, the worker, or the GPU box loses no jobs (Redis XAUTOCLAIM + dead-letter). *Source: `DIARIZATION-MIGRATION.md`, `FEATURE-SPEC.md` §1.* # Deployment (/docs/engineering/deployment) Production runs on **Phala dstack TEE (Intel TDX)**. The authoritative, always-current reference is `DEPLOYMENT-GUIDE.md` in the monorepo — this page is the orientation; go there for exact ids, tokens, and rebuild recipes.
Golden rule. Never re-provision a CVM — it wipes the sealed volume. Always deploy in place with phala deploy --cvm-id \.
## Topology [#topology] | Component | Where | Ports / URL | | ---------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | **Conclave CVM** (`conclave`) | Phala CVM | `:8000` API · `:8087` diart WS · `:443` ingress. Co-locates conclave-api + **Redis** + **capture-diart** + **capture-ingress** | | **FPM / VFTE CVM** (`fpm-backend`) | Phala CVM | `:8085` (reached only by Conclave) | | **DiariZen GPU worker** | External GPU box (RTX 3090) | Dials **out** to Conclave `/api/diarize/jobs/claim` over cloudflared | | **Conclave dashboard** | Vercel (Root Dir `frontend`) | `*.vercel.app` | | **FPM consent dashboard** | Vercel | `vftee.vercel.app` | | **Auth** | Supabase | verifies "Connect to Conclave" JWTs | ## In-person WS routing (Option A) [#in-person-ws-routing-option-a] Long-lived recording WebSockets were dying at the Phala gateway (\~2–4 min). The cure: ``` browser ──wss://capture.conclavehq.org──▶ Phala gateway (raw SNI TLS-passthrough) ──▶ in-CVM HAProxy (dstack-ingress) terminates LE cert, TIMEOUT_CLIENT/SERVER=3600s ──▶ capture-diart ``` ## Images & deploy flow [#images--deploy-flow] * Docker Hub: `prakharojha/conclave`, `prakharojha/capture-diarize-diart`, `prakharojha/fpm-backend`. Build `--platform linux/amd64`. * **Deploy:** `edit deploy-tonight.env → scripts/stage-deploy.sh → build + push image (bump the LITERAL tag) → phala deploy --cvm-id → warm diart → smoke test`. Alembic auto-applies on boot. * `IN_TEE=true` in prod (real TDX quotes via `/var/run/dstack.sock`); `false` locally. ## Sizing [#sizing] DiariZen ≈ 885 MB VRAM at batch=1 (flat across clip length) + 4–8 GB RAM; \~15 concurrent workers per GPU. diart ≈ 1 GB RAM on CPU. **DiariZen weights are CC-BY-NC** — must be swapped before commercial use. > Read [Gotchas](/docs/engineering/gotchas) before touching prod. Full deploy status and the current > hardening pass: [Status](/docs/engineering/status) and [P0: diarization](/docs/engineering/p0-diarization). *Source: `DEPLOYMENT-GUIDE.md`, `BUILD-LOG-diarization-deployment.md`.* # Gotchas (/docs/engineering/gotchas) A curated subset of the deployment gotchas catalog. The full, numbered list (**G1–G31**) with exact fixes lives in `DEPLOYMENT-GUIDE.md` — this page is the "read before you touch prod" shortlist. ## Deploy mechanics [#deploy-mechanics] * **G16 — image tags must be LITERAL** in `docker-compose.yml`. dstack does **not** expand `${IMAGE_TAG}`; a variable tag means it silently won't re-pull. Bump the literal tag every deploy. * **dstack ignores compose `${:-default}` defaults** — flags like `CONCLAVE_INPERSON_VIA_CAPTURE` must be set **explicitly**, or they read as false (this silently disabled the DiariZen post-pass). * **Never re-provision a CVM** — it wipes the sealed volume. Deploy in place by `--cvm-id`. ## In-person WebSockets [#in-person-websockets] * **G17 — the Phala gateway kills long-lived WS** at \~2–4 min (close code 1006). The fix is the Option-A **TLS-passthrough ingress** (`wss://capture.conclavehq.org` → in-CVM HAProxy, `TIMEOUT_*=3600s`) plus client reconnect. See [Deployment](/docs/engineering/deployment). * On macOS the **Docker VM drops long WS** too — run diart natively for local in-person testing; Linux CVMs are unaffected. ## Diarization / ASR chain [#diarization--asr-chain] * **G24–G26** — the post-pass didn't fire because the enqueue flag was false, the GPU worker was on a stale URL, and reconcile no-op'd when VFTE found no voiceprints. All three had to be fixed together for the authoritative pass to run end to end. * **ASR** — force `language=en` + `temperature=0` on NEAR Whisper (auto-detect garbles English); `whisper-1` 404s → use `openai/whisper-large-v3`. ## Auth (open) [#auth-open] * **G27–G31 — web login loop.** Google sign-in reaches the dashboard then bounces to `/login` (both apps), worse on sign-out → sign-in. A cluster of Vercel-rewrite bugs; several fixes shipped (Bearer token, client-set cookie, `no-store`) but it **still reproduces (G31)**. Tracked at [Conclave#13](https://github.com/conclavehq/Conclave/issues/13); currently the top blocker for web sign-in. See [Status](/docs/engineering/status). *Source: `DEPLOYMENT-GUIDE.md` (G1–G31), `task-plan/MASTER-TASK-LIST.md`.* # Local development (/docs/engineering/local-development) Local dev runs the whole stack via docker-compose, from the `shape-rotator-all` monorepo root. ## Environment switching [#environment-switching] * **Single source of truth per world:** `environments/matrix.local.env` and `matrix.prod.env` (gitignored; `.example` templates versioned). * The **`envctl`** script (`render / show / check / gen-tokens`) reads the matrix and **upserts** derived keys into the three backend `.env` files (`conclave-shape-rotator/.env`, `capture/.env`, `FPM/.env`), preserving each product's own keys.
The #1 local-stack footgun. There are two URL classes: INTERNAL\_\* resolves on the docker network ([http://fpm-backend:8085](http://fpm-backend:8085)); BROWSER\_\* must resolve from the host browser ([http://localhost:8085](http://localhost:8085)). Conflating them is the most common breakage.
## Bringing up the stack [#bringing-up-the-stack] ```bash # from shape-rotator-all/ docker compose -f docker-compose.local.yml up # migrated / authoritative-finalizer topology: docker compose -f docker-compose.local.yml -f docker-compose.migrated.yml up ``` Services in the local stack: | Service | Port | Notes | | ------------------------------------- | ----------- | -------------------- | | `conclave-api` | 8000 | FastAPI + SQLite | | `conclave-web` | 3001 | `next dev` | | `capture-runtime-api` / `redis` / bot | 8090 / 6379 | online-bot path | | `fpm-backend` | 8085 | identity | | `fpm-web` | 3002 | consent dashboard | | `capture-diarize-diart` | 8087 | in-person live diart | | `fpm-diarizen` | — | profile-gated | > Cloud endpoints — Supabase auth, NEAR Whisper ASR, RedPill LLM — are **the same in both worlds**. > Local isolation comes from a separate **workspace** (`local-ws` vs `demo-ws`) + separate DB volumes, > not separate cloud services. In the migrated topology DiariZen runs on a **remote GPU box** reached > by SSH tunnel (`scripts/diarize-tunnel.sh`, `localhost:8086`). ## compose files [#compose-files] `docker-compose.local.yml` (full stack) · `.migrated.yml` (authoritative finalizer override) · `.diarize-e2e.yml` / `.identify-e2e.yml` (E2E harnesses) · `.audio-upload*.yml` (upload onboarding). *Source: `ENVIRONMENTS.md`, `DEPLOY-LOCAL.md`.* # P0: diarization (/docs/engineering/p0-diarization) The top engineering priority is **diarization latency & reliability** ([Capture#1](https://github.com/conclavehq/Capture/issues/1)) — getting the in-person path to real-time (RTF \< 1), the post-pass onto a licensing-clean engine, and identity resilient over long meetings. It's orchestrated as **four tracks** in `task-plan/p0-diarization/`. ## The tracks [#the-tracks] | Track | Goal | State | | -------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | **0 — eval loop / analytics** | Shared eval instrument + a test CVM as the merge base | Band 1 built (`feat/analytics-cockpit`, not merged); test-CVM provisioning in flight | | **A — replace DiariZen model** | Own WavLM-Base+ diarizer (licensing-clean, competitive DER) | ✅ validated + GO (DER \~0.201 vs 0.216, parity VRAM); prod-port pending Track 0 | | **B — VFTEE 600s async identity** | Durable identify job for long meetings (`#52`) | ✅ lifted onto `feat/identity-async-lift` + validated; cross-CVM E2E pending a test FPM CVM | | **C — live path (diart RTF \< 1 + ASR/VAD)** | Real-time diart + ASR-fallback | ⛔ blocked on the test Conclave CVM; ASR+VAD sub-design TBD | Only **Track C** functionally depends on Track 0. ## Why it matters [#why-it-matters] * **diart is the live engine, DiariZen is post-pass** — this is a firm product call. The live path must reach **RTF \< 1**; it was compounding (\~RTF 1.15, lag growing 20s → 60s over 5 min). The cheap levers: raise `step` 0.5 → 1.0, ONNX/int8, drop-to-latest cap — all measured *on the CVM*, not the laptop (threads were backwards: OMP=2 on 4 vCPU). * **DiariZen weights are CC-BY-NC**, so Track A's clean model is required before commercial use. * **Long meetings** exceeded VFTE's identify window; Track B makes identity a durable, resumable job. ## Also open [#also-open] * **DiariZen GPU worker persistence** — `@reboot` + repoint-on-deploy + a health signal (it silently rots), tracked as a follow-up ([Capture area](https://github.com/conclavehq/Capture/issues/1)). * **parallel ASR** — the \~20s baseline lag is serial NEAR Whisper; a small concurrent pool helps. *Source: `task-plan/p0-diarization/STATUS.md`, `README.md`, `DIART-RTF-FIX-PLAN.md`.* # Repositories (/docs/engineering/repos) The code lives across several GitHub repos under the `conclavehq` org, mirrored as working dirs in the `shape-rotator-all` monorepo used for local development. | GitHub repo | Local dir | Owns | | --------------------- | ------------------------- | --------------------------------------------------- | | `conclavehq/Conclave` | `conclave-shape-rotator/` | Intelligence + control plane, persistence, frontend | | `conclavehq/Capture` | `capture/` | Meeting capture + diarization (identity-free) | | `conclavehq/VFTEE` | `FPM/` | Voice identity + consent plane | | `conclavehq/Mobile` | `conclave-mobile/` | React Native / Expo app | | `conclavehq/Veritas` | `veritas-recorder/` | Standalone attested recorder (satellite) | | `conclavehq/Sync` | — | The umbrella / capture engine lineage | ## What each repo contains [#what-each-repo-contains] * **Conclave** — FastAPI backend (`api/`, `connectors/`, `infra/`, `transcripts/`, `storage/`), Alembic migrations (`alembic/`), and the Next.js dashboard in `frontend/`. * **Capture** — the diarization service (`services/diarization/`), the Node recato-bot (`services/recato-bot/`), and the runtime-api orchestrator. Dockerfiles `.diart` / `.gpu`. * **VFTE / FPM** — the identity service (`fpm/`), the sealed SQLite store, the Ed25519 signers, and the Next.js consent dashboard. > Boundary reminder: **only Conclave persists content.** Capture holds nothing; VFTE holds > voiceprints + consent edges but never audio or transcript. See > [Architecture](/docs/engineering/architecture). ## Branch & merge conventions [#branch--merge-conventions] Work happens on one `feat/` 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`.*