Skip to content

Sanctum CRM

Jocasta routing deal and relationship state into one memory vault, with a partition down the middle

A partner sits down at the fund laptop and opens the deal timeline. In the same haus, on the same Mac, lives every text you have sent your spouse, every note from your kid’s school, the angel cheque you would rather nobody priced. The partner sees none of it. That gap — full archive for you, work graph for them — is the entire job of Sanctum CRM.

Jocasta keeps the archive. This page is about the wall she runs down the middle of it.

Every conversation that lands on the Mac — mail, iMessage, SMS, Signal, WhatsApp, Telegram, LinkedIn — is ingested into one local DuckDB store. Affinity deal flow and a personal angel ledger sit beside it. The Archives UI is a thin, read-only window on that store: if the UI burns down, zero bytes of intelligence burn with it.

What matters for a human operator is not the pipeline diagram. It is who can see what.

SurfaceWhoWhat they see
PersonalYouFull timeline, family threads, Angel & LP
WorkPartners / fund sessionWork-graph only — Affinity people, deal domains, firm addresses

There is no LLM reading your texts to decide “sounds professional.” Classification is who the counterparty is, not what they said.

  • Work = already on the work graph (Affinity contact, deal website domain, @triptyq.vc).
  • Personal = household roster (instance family + Firewalla/screen-time devices) plus phones and emails that show up in family threads.
  • Unknown = treated as personal for partners. Fail-closed.

Personal always wins when both match (spouse at a portfolio company stays personal).

ingest (hourly) ──▶ workspace.duckdb ──▶ v_timeline (you)
├── contact_silo + work_allowed_handles
└──▶ v_timeline_work (partners)
workspace-work.duckdb (export)
RoleUIData
YouArchives :3346~/.openclaw/workspace/workspace.duckdb
PartnersArchives :3347 (CRM_SESSION=work)Prefer workspace-work.duckdb — personal rows never copied

Both ports require a token. Each session mints its own 0600 file on first start — ~/.sanctum/secrets/jocasta-crm-personal.token for :3346, …-work.token for :3347 — and they are not interchangeable: the work token on the personal port is a 401, and so is the reverse. A request without one gets a 401 carrying WWW-Authenticate: Basic, so a browser prompts and you paste the token as the password with an empty username.

Both ports also bind the tailnet, which is deliberate — the archive is reachable from the MBP. It is only safe because of the paragraph above. It was not always: until 2026-08-11 :3346 answered /api/angel with the full Angel & LP ledger to anything that could reach the port, and :3347 had never started, so the only surface answering was the personal one. Auth landed first, then the partner port was turned on; the other order would have published a second open door.

The partner export is physical isolation, not a filter you hope nobody bypasses. Raw channel tables, the full timeline, and Angel & LP are simply not in that file — /api/angel on :3347 is a 403 even with a valid work token.

One correction worth carrying, because it is the more common shape of this mistake: the export’s row filter was never the leak. Its contacts table was built with SELECT * over a 30-column view, so business rows arrived carrying home addresses, birthdays and personal notes. Rows were right; columns were not. It now ships a six-column allowlist — name, email, company, title, type, Affinity ID — and sanctum-crm doctor fails critical if a future SELECT * widens it again.

Terminal window
# Health of the dual surface
sanctum-crm status
sanctum-crm silo-status
sanctum-crm doctor # must be all-critical green before a partner session
# Your full archive
sanctum-crm who "François Rioux"
sanctum-crm timeline "demute"
sanctum-crm needs-reply
# Partner-safe (same commands, work graph only)
sanctum-crm timeline "demute" --work
CRM_SESSION=work sanctum-crm needs-reply
# After Affinity sync or family roster changes
sanctum-crm silo-seed
sanctum-crm work-export # → workspace-work.duckdb

Affinity sync re-seeds the work graph automatically on success. Family identity is reloaded from instance.yaml and Firewalla-backed devices.yaml on every seed — you do not hand-maintain household members in a side file unless someone is missing.

ChannelHowTable
EmailMail.app / Graphemail_history
iMessage / SMSSigned FDA reader → dumpimessage_history
SignalDesktop SQLCipher + Keychainsignal_history
WhatsAppChatStorage.sqlite (busy-timeout hardened)whatsapp_history
TelegramMTProto sessiontelegram_history
LinkedInVoyager / harvestlinkedin_history
DealsAffinity v2 → v_dealdealflow
Angel & LPCurated ledgerangel_investments (you only)

Unified view: v_timeline. Partner view: v_timeline_work. Cross-channel “ball in your court”: v_needs_reply / v_needs_reply_work.

  1. sanctum-crm doctor — all critical checks green
  2. sanctum-crm work-export — fresh partner DB
  3. curl -sf -u ":$(cat ~/.sanctum/secrets/jocasta-crm-work.token)" http://127.0.0.1:3347/ — confirm the partner surface answers 200 before you seat anyone in front of it. It runs under KeepAlive, so it should already be up; if it is not, launchctl kickstart -k gui/$(id -u)/com.sanctum.jocasta-crm-work.
  4. Hand partners the :3347 token, or only workspace-work.duckdb
  5. Never share workspace.duckdb, the personal Archives on :3346, or the :3346 token

Both ports listen on the tailnet as well as loopback, so “it is only on my machine” is not one of the guarantees. The token is.

Work access is audited at ~/.openclaw/logs/crm-work-access.log.

The wall is physical, not aspirational. A partner can read every deal in the room and never learn the family was ever in the building — which is how one Mac in a Quebec haus holds both a fund and a household without either one leaking into the other.