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. This page describes what actually happens today — not what we intend to build.
What stays local
Your ~/.dotperson/corpus.db stays on your machine. Nothing sweeps it wholesale to a server.
Two things do leave, and both are things you switch on:
- Browser-extension captures. Each surface you enable in the popup POSTs its captured turns to the relay’s
/v1/observations. That is how a conversation you had on one machine reaches another. - IDE observer sweeps. Once you’ve signed in, the daemon’s observe stage POSTs observations from your editors to the same endpoint.
The relay’s other jobs are auth, GitHub App brokerage and synthesis.
Which corpus events are mirrored
The corpus-sync push (personkit corpus sync, and the daemon’s sync stage) only sends events whose allow_relay_mirror flag is set. That flag is decided by the harvester that wrote the event, at write time:
| Written by | allow_relay_mirror |
|---|---|
| Git harvester (commits, repo state) | true |
| Claude plans harvester | true |
| Claude sessions harvester | true |
| Reading-list harvester | true |
| Config-snapshot harvester | false — configs can carry partial secrets pre-redaction |
| Screenshot harvester | false |
ChatGPT archive import (personkit import chatgpt) | false |
| MCP bridge entries | caller-specified; false unless the caller opts in |
So config snapshots, screenshots and anything you import from a ChatGPT archive are never pushed to the relay, while your git, plan, session and reading-list events are.
There is no per-kind override today. The dashboard privacy screen is where that control will live, and it is being wired — until it ships, the table above is the behaviour. If you want a harvester’s events to stay local, disable that harvester.
Where browser captures live
Browser-extension thread_turn observations carry metadata.{role, conversation_id, external_message_id, url}. The relay fans each captured turn into:
persona_observations— the signal queue the synthesis worker reads to propose persona updates.corpus_threads+corpus_messages— the conversation store, keyed by a deterministic UUIDv5 thread id so concurrent writes from different devices converge without coordination.
Both of those are relay-side. Browser captures are not written to your local messages table today — mirroring them back down to the local corpus is being built. Until it ships, personkit corpus messages shows your editor and harvester activity, and your browser captures are readable through the dashboard.
You can turn capture off per surface in the popup, or pause every surface at once with the master kill switch.
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.
For cloud captures you can add your own patterns in the extension popup’s “Privacy controls” textarea — those apply in-browser, before the turn ever leaves the page. The dashboard’s extra-redaction-patterns field stores what you enter but is not yet read by the harvest path, so treat the popup as the one that takes effect today.
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.dbas a whole — only events flagged mirrorable by their harvester (the table above) are pushed, and it is never swept wholesale. - Your Ed25519 secret key — generated locally, never transmitted. It sits in
~/.dotperson/protected by file permissions; it is not encrypted at rest, so the security of your machine’s user account is what protects it. - Anything in a thread captured with the extension while you’re signed out — the capture queue holds it in the browser with no relay path until you sign back in.
- Anything no harvester covers — we don’t ingest your email, browser history, or filesystem unless you wire a harvester for it.