# TRANSCRIPT STORAGE — GOAL DOC

**Status:** SCOPED · Phase 1 SHIPPED v9.10.442 · Phases 2-4 NOT BUILT
**Owner:** Ryan (nastermodx)

> Owner ask, verbatim: *"when I say don't need transcripts is when user voice talks with AIs
> we don't need to see the voice script, but it can be in the server placed on the user's
> account, their cloud storage. and then when calling an AI, the AI retrieves the transcript
> with that user on their cloud storage account in the area it finds it and then it
> continues... so transcripts still happen for memory yes, but not needing to see a
> transcript under the transcript channels, as that is Discord stuff that we are slowly
> getting off from."*

**Transcripts are not being deleted.** They are the agent's memory of a person. What changes
is where they live and whether they are surfaced.

---

## 1. What `transcripts.db` actually is — read this before planning a move

**It is not a transcript store.** It is a shared multi-subsystem SQLite file at
`tools/oracle-discord/transcripts.db`. **Five modules open it read-write:**

| Module | Tables |
|---|---|
| `shared/transcript-memory.mjs` | `transcript_fts`, `user_profile_memories`, `message_meta`, `entity_facts` |
| `shared/epistemic-vault.mjs` | `epistemic_cells` |
| `shared/lattice-mempalace.mjs` | `user_profiles`, `channel_profiles` |
| `shared/user-warehouse.mjs` | `user_facts`, `user_fingerprints` |
| `shared/kai-dream.mjs` | (opens the same path) |

**Anyone who says "move transcripts.db" is moving four other subsystems with it** — the
epistemic vault, the mempalace user/channel profiles, and the user-warehouse fact store.
This is the single biggest risk in the whole task.

The **dashboard server only ever opens it READ-ONLY** (`command-center-server.mjs:472`,
`{ readonly:true, fileMustExist:true }`), and every reader is guarded — if the file vanishes
the dashboard degrades, it does not crash. That read/write split is the clean seam.

### Confirmed dependencies

- **Leo's per-person memory — REAL.** `bots/leo.mjs:2363` imports `recallProfileMemories`,
  `getRecentContext`, `getEntityProfile`. Same in `native-bot.mjs`, `openjarvis.mjs`,
  `gemini-live-bridge.mjs`, `kai-dream.mjs`, `social-scoring.mjs`.
- **`mergedThread()` — REAL but owner-only.** `command-center-server.mjs:4162` merges the DB
  half **only when `isOwnerCaller`** (the v9.10.365 isolation fix). Guests already get the
  JSON half only, and the whole merge is inside a best-effort `try/catch`.
- **Rust lattice ingest — REAL but conditional.** `transcript-memory.mjs:688`
  `offloadToEngine()` POSTs to `/api/bulk-ingest`. **Its only caller is `runRetention()`**
  (`:880`) — transcript text reaches the lattice *only as rows are about to be pruned*, not
  on ingest. Fails soft; retention defers deletion when the engine is unreachable.
- **~12 dashboard read endpoints**, including `/api/transcripts` and the Discord-429 fallback.

### Denied — do NOT plan around these

- **`person-recognition.mjs` does NOT read transcripts.** `recentMemories: []` — the join is
  Phase 3, unbuilt (`:150`). It touches `user_facts` only, indirectly.
- **No distillation pipeline reads it.** `distill_from_bitnet.py` has zero matches for
  `transcript` or `.db`.

### Voice transcripts are already per-user scoped

`oracle-live-voice.mjs:399` — `` const _thread = `oracle-os-voice:${_speakerId}` ``, and the
v9.10.365 comment records that this line *used to* write every caller's speech into the
owner's record. **The per-user requirement is already met for voice.** Unidentified callers
land in a neutral bucket, never the owner's.

---

## 2. "Their cloud storage" — what that concretely is today

Investigated rather than assumed. The honest answer is **the thing the owner is describing
does not exist yet**, and the nearest two candidates each have a disqualifying property.

| Candidate | Real? | Per-user? | Location | Verdict |
|---|---|---|---|---|
| `/api/storage/*` → `shared/drive-storage.mjs` | **Yes, wired** (8 call sites) | Folder-path isolation `KAI-Server/users/<userId>/` | **Google Drive** | **Blocked — see below** |
| `shared/workspace-vault.mjs` + `/api/vault/*` | **Yes, wired**, tests pass | Yes, genuinely | Local disk `state/workspace_vault_blobs/` | **Blocked — see below** |
| `state/<subsystem>/<userId>.json` | Yes | Yes | Local disk | Works, but it is not "cloud" |
| Per-user Google **Drive** credentials | **NO — do not exist** | — | — | Scaffolding gap |

**Two findings that change the shape of this task:**

1. **There is no per-user Google Drive credential anywhere in this codebase.**
   `/api/integrations/google/*` is real per-user OAuth with an on-disk token store — but its
   scopes are `openid email profile` only, **no Drive scope**. And `drive-storage.mjs` never
   imports it; it reads `GDRIVE_*` env vars for **one shared service account**. The two
   Google systems are disconnected. "The user's own cloud storage" would require adding
   Drive scope to the per-user OAuth and re-pointing `drive-storage` at per-user tokens.
   That is a real piece of work, not a config change.

2. **Google Drive storage is OFF by default and has no offline path.** `isDriveEnabled()`
   requires three env vars; without them every `/api/storage/*` call returns 503 today.
   There is no local cache and no fallback — `downloadFile` is a live `drive.files.get`.

### The blocker that matters most: an AI cannot read a zero-knowledge vault

`workspace-vault.mjs` is per-user, local, and properly designed — **the client holds the
keys and the server stores ciphertext only.** That is exactly why it cannot be the memory
store: **an agent running server-side has no key and cannot decrypt it.** Using the vault for
transcripts would mean either giving the server the plaintext key (destroying the vault's
entire security property) or the AI never being able to read the memory. Neither is
acceptable. This must not be papered over.

### Honest risk statement, as requested

> **Moving agent memory to Google Drive makes every conversation depend on an external
> service being reachable.** Today, everything an agent uses for memory mid-conversation
> (`transcript_fts`, `entity_facts`, `user_facts`) is local SQLite and survives a network
> outage. Drive has no local mirror. **An AI that cannot reach its memory mid-call is a worse
> failure than one with local memory** — it would either stall or, worse, continue while
> having silently forgotten the person.
>
> **Therefore any move MUST be local-first**: local SQLite stays the read path an agent uses
> live, and cloud is a *replica* for durability and portability, not the primary. If the
> owner wants the file genuinely off the server, the fallback has to be an explicit, visible
> degraded state ("I can't reach my memory of you right now"), never silent amnesia.

---

## 3. Phased plan

**Phase 1 — hide the Discord-era surface. ✅ SHIPPED v9.10.442.**
Removed `'transcript'` from the channel rail `order` array (`oracle.html`). The rail section
is gone; channels, writes and memory are all untouched. Guests never saw it anyway.
Deliberately did NOT touch `#transcript` (the shared message pane for channels AND DMs) or
`view-transcripts` (the Messages page) — three different things share that word and only the
rail group is safe.

**Phase 2 — stop voice transcripts polluting the channel list. NOT BUILT.**
Because the per-caller thread string is used as `channel_id`, every voice caller creates a
discovered "channel" in `dynamicChannels()` (`command-center-server.mjs:559`). Filter
`oracle-os-voice:*` out of channel discovery. Small, self-contained, no data migration.

**Phase 3 — per-user export/replica. NOT BUILT.**
Add a per-user transcript export keyed by `currentContentUser().id`, written to that user's
storage area, on a schedule or on call-end. **Local SQLite remains the live read path.** This
is the first phase that touches data, so it inherits the history-migration discipline:
backups first, counts reconciled, verified through the real read path, idempotent, reversible.
Needs an EXACT `GUEST_API_EXACT` entry if it adds a route.

**Phase 4 — retrieval-on-call. NOT BUILT.**
The agent pulls that user's transcript from their storage at call start and continues from
it. Only worth building after Phase 3 proves the replica is trustworthy, and only with the
local-first fallback above.

**Prerequisite for Phases 3-4, owner decision required:** which backend? Enabling the shared
Drive account (3 env vars) is quick but is *the operator's* Drive, not the user's. Genuine
per-user Drive means adding Drive scope to the existing per-user OAuth — more work, and it
matches what the owner actually described.

## 4. Constraints

- Never delete transcripts — they are the agents' memory.
- `transcripts.db` is shared by five subsystems; a move is a five-subsystem migration.
- Local-first always. Cloud is a replica, not the live read path.
- Per-user isolation is non-negotiable (v9.10.365) — one user's transcript must never be
  readable by another.
- The zero-knowledge vault cannot hold AI-readable memory. Do not try.
