Concepts
Privacy
Local-first by default. What stays on your machine, what gets mirrored, and how to lock it down further.
aiperson is local-first. Your persona is in a git repo you own. Your corpus is a SQLite file on your laptop. We host nothing you didn’t ask us to.
What stays local
By default, everything. The relay’s role is auth, GitHub App brokerage, and synthesis — not a duplicate of your conversations.
Optional mirror
A subset of corpus events — those with allow_relay_mirror = true — can be pushed to the relay’s Postgres for cross-device sync. The per-kind default is:
| Kind | allow_relay_mirror default |
|---|---|
git_event, task_event, plan_event, repo_state_event | true |
claude_session (metadata only) | true |
chat_prompt, thread_turn, tool_call, tool_result | true |
reading_list_entry | true |
email_body, sms, notes_body | false |
calendar_event_description, chatgpt_message | false |
config_event | false |
screenshot | false |
voice_capture | false |
Override per-kind under /dashboard/privacy.
Messages mirror policy
Browser-extension thread_turn observations carry metadata.{role, conversation_id, external_message_id, url} and are mirrored by default — that’s what makes “your AI remembers across devices” a true statement. The relay fans each captured turn into:
persona_observations— signal queue the synthesis worker reads to propose persona updates.corpus_threads+corpus_messages— the full conversation mirror, keyed by a deterministic UUIDv5 thread id so concurrent writes from different devices converge without coordination.
You can flip mirroring off per-surface in the popup, or globally under /dashboard/privacy. The full message bodies are written to your local messages table regardless of the mirror flag, so capture works even when the relay is unreachable or you’ve turned mirroring off.
Dignity controls in the browser
On every supported cloud chat surface (21 of them today — ChatGPT, Claude, Gemini, Copilot, Grok, Meta, Perplexity, Mistral, HuggingChat, Poe, Pi, Lmarena, plus Kimi, DeepSeek, Qwen, Doubao, ChatGLM, Wenxin, Yi, Hailuo, SenseChat), four controls give you the driver’s seat:
- Master kill switch in the popup — pauses every surface instantly.
- Per-conversation skip — the in-page indicator (bottom-right, on every captured surface) is a one-click toggle for that one chat. Flips to ⏸️ until you flip it back.
- Regex redactor — user-defined patterns are applied before the turn leaves the page. Invalid regex is flagged in the popup and never reaches the capture path. Stacks with the relay-side secret-redaction patterns documented below.
- Per-surface health dots — green (≤5 min since last accepted turn), yellow (≤60 min), red (otherwise or on error). Visible in the popup AND on the dashboard Overview via the relay’s
/v1/me/capture-healthendpoint.
A 401 from the relay wipes the cached ID token and surfaces a “Sign in again” CTA — capture never re-queues behind a permanently-failing handshake.
Secret redaction
Every harvested payload passes through secret_redact() before any write. Patterns matched:
- Quoted env-style:
API_KEY="…" - Unquoted env-style:
API_KEY=… - Prefix tokens:
gh[pousr]_…,sk-…,xox[abprs]-…,github_pat_… - Authorization headers:
Authorization: Bearer … - PEM blocks:
-----BEGIN […]-----…-----END […]-----
Redaction applies to both event payloads and message contents. Add your own patterns under /dashboard/privacy, and (for cloud captures specifically) in the extension popup’s “Privacy controls” textarea — the latter applies in-browser, before the turn ever leaves the page.
Signed snapshots
The corpus::snapshot API produces signed JSONL exports (entities / relations / events / provenance + manifest signed by your Ed25519 key). The same key signs every .person.json commit. Round-trip verification proves byte-equal recovery.
Portable export
personkit export --format=markdown emits the whole corpus as Obsidian-ready .md files — one per thread, with YAML front-matter. You own these files outright; dotperson keeps no claim on them. Move them, version-control them, leave the product entirely — the data is yours.
Wiping
personkit corpus wipe— interactive wipe of the local corpus DB. Adds--keep-personato preserve credentials + persona cache,--dry-runto preview,--yesto skip the confirm.personkit corpus snapshot --out ~/archive— take a signed JSONL snapshot first if you might want the data back later. Round-trip withpersonkit corpus restore --from ~/archive.personkit uninstall [--yes]— full uninstall: tear down the daemon, remove plists, wipe~/.dotperson/./dashboard/privacy → Wipe corpus— best-effort relay-side revoke for events you’ve mirrored.
What dotperson cannot see
- Your local
~/.dotperson/corpus.db— it never leaves your machine unless you turned mirroring on for the specific kind. - Your Ed25519 secret key — generated locally, never transmitted.
- Anything in a thread captured with the extension while you’re signed out — the capture queue holds it locally with no relay path until you sign back in.
- Anything outside the kinds in the table above — we don’t ingest your email, browser history, or filesystem unless you wire a harvester for it.