Local-first Personal Model Runtime for macOS
This guide covers the supported platform, permissions, local paths, lifecycle, inspection, correction, export, erasure, and uninstall behavior.
| Host | AX capture | PP-OCRv6 | Daemon, MCP, model |
|---|---|---|---|
| macOS 13+ Apple Silicon | yes | yes, bundled and local | supported |
| macOS 13+ Intel | yes | no compatible Paddle runtime | supported without OCR |
| Linux | no | no | offline tests and development only |
| Windows | no | no | unsupported |
The installer uses Python 3.11-3.13. Xcode Command Line Tools are required to compile the Swift AX helpers.
| Permission | Required | Purpose | Effect when disabled |
|---|---|---|---|
| Accessibility | yes for daemon-mode live collection | lets the source-versioned mac-ax-helper and optional mac-ax-watcher read focused AX structure and events |
daemon remains healthy but produces no useful live AX captures |
| Screen Recording | yes when screenshots or effective OCR are enabled | supplies focused-window pixels to local OCR and encrypted screenshot retention | AX collection continues; pixel features are unavailable |
| Full Disk Access | no | not used | no effect |
| Automation / Apple Events | no | not used by the Runtime | no effect |
Accessibility belongs to the native executables that actually call AX, not to
the terminal or Python daemon that launches them. Onboarding requests and probes
mac-ax-helper first and, when event_driven=true, mac-ax-watcher separately.
Screen Recording is checked by the Runtime process that captures pixels. If the
Runtime lifecycle or helper source changes, rerun persome onboard and let it
name any new principal explicitly.
Interactive install.sh runs persome onboard. Standard daemon mode presents
separate native dialogs for the helper and watcher Accessibility grants, then
Screen Recording when the configured pixel policy requires it. It starts the
final lifecycle owner, verifies its isolated OCR worker when enabled, and writes
one fresh capture through that daemon’s runner. The authenticated /permissions
endpoint invokes the actual helper/watcher probes and the Runtime’s Screen
Recording preflight. HTTP-disabled daemon mode publishes the same generation’s
owner-only state receipt; trusted ingest proves its authenticated runner and
does not claim daemon-owned macOS permissions. Intel without local OCR and
explicit OCR/pixel opt-out report their actual remaining AX/pixel capabilities.
Updates preserve paused/locked privacy state without forcing a frame. The first
OCR load can take up to two minutes; repeated runs publish progress and normally
reuse the worker.
[capture].ocr_policy makes intent durable: auto is a fresh/unconfigured
state, while enabled and disabled are explicit choices. persome onboard
without --tier preserves the current choice; persome onboard --tier tiny or
persome ocr setup enables OCR, and persome ocr disable records the opt-out.
The final notification is non-blocking.
Each compiled AX helper lives at an immutable
native/<source-digest>/<helper-name> path. Same-version reinstalls reuse the
exact files and grants. A helper-source change produces a new path and requires
an explicit new Accessibility grant; update rollback returns to the old wheel
and old helper path.
PERSOME_ROOT overrides the default ~/.persome root.
| Path under the root | Contents |
|---|---|
config.toml |
non-secret Runtime configuration |
env |
provider credentials, screenshot key, and local API bearer; mode 0600 |
capture-buffer/ |
AX text and optional encrypted screenshot payloads |
memory/ |
readable Markdown memory |
index.db |
FTS, canonical evomem nodes, relations, sessions, and geometry |
chat-history/, skills/ |
legacy Chat-era data from older releases; purged by persome clean all (stale Chat-era logs/chat.log* files are removed at startup) |
exports/ |
redacted model exports by default; mode 0600 |
backup/ |
SQLite safety snapshots containing personal model state |
logs/ |
daemon and launchd logs; may contain operational context |
model-build.json |
latest build manifest and degraded-stage report |
.integrity-recovery.pending.json |
crash-resumable full-database quarantine/replay journal |
.integrity-config-recovery.pending.json |
config-quarantine intent retained until database authority is reconciled |
.pid, .runtime-state.json |
compatibility PID plus owner-only generation, phase, permission, OCR-worker, and capture/privacy receipt |
.daemon.lock |
lifetime single-Runtime lock inherited across background forks |
.launchagent-owner |
durable intent that launchd owns Runtime lifecycle |
.update.lock, .update-state.json |
exclusive update lock and crash-recovery phase metadata |
native/<source-digest>/ |
immutable machine-local AX binaries; code, not personal data |
venv.replacement.update, venv.previous.committed.*, venv.failed.update.* |
candidate, retained-old, and cleanup environments during a transaction; code, not personal data |
venv/ |
dedicated installer environment; code, not personal data |
Treat the whole root as sensitive. Backups and exports are copies of personal
data, not harmless metadata. Persome enforces 0700 on the root/data
directories and 0600 on sensitive files, repairs legacy modes once after an
upgrade, and gives its LaunchAgent umask 0077.
When the Runtime is stopped, startup validates index.db before opening it.
If only a derived search index is malformed, Persome rebuilds captures_fts
from authoritative captures rows and entries from Markdown/evo_nodes. It
also handles older SQLite builds that collapse both damaged FTS projections
into a generic malformed-database error, while verifying every regular table
before the narrow index reset. Core database damage is still quarantined as
index.db.corrupt.<timestamp> for inspection. Persome then restores the newest
structurally verified daily snapshot when one exists and reconciles the current
Markdown memory projection without making LLM calls. Without a usable snapshot,
it still rebuilds the retrieval projection and Point state from Markdown; the
recovery marker lists database-only components that could not be reconstructed.
In both cases the old model-build.json is invalidated, because Faces, Volumes,
and Root must be verified by a fresh persome model build rather than reported
as a completed build from stale metadata. Details and recovered row counts are
written to .integrity-recovery.json.
Before moving a damaged database, Persome atomically writes
.integrity-recovery.pending.json with the exact quarantine destination and
phase. If the process exits after quarantine, snapshot copy, or projection
replay, the next stopped-Runtime startup resumes that journal instead of
treating a missing index.db as a clean install. The journal is removed only
after a completed recovery marker is durable. Snapshot recovery remains
best-effort: the marker records its timestamp and warns that writes since that
snapshot may be absent. Retained capture-buffer JSON can be reconciled with
persome rebuild-captures-index --merge when the marker reports a pending
replay. Recovery mode preserves older snapshot-backed captures whose buffer
JSON has already aged out; the command without --merge remains an exact
buffer-only rebuild and deletes such rows.
Unreadable or semantically invalid versioned journals (missing fields, unknown
phases, or paths outside the canonical recovery namespace) are themselves moved
to a timestamped forensic quarantine before normal integrity checks continue;
they do not become permanent startup blockers.
Config replacement has its own earlier intent journal,
.integrity-config-recovery.pending.json. Persome writes it before moving a
corrupt config.toml, and keeps database replay authority unknown until the
database is verified or restored. A structurally complete evo_nodes table in
a snapshot is not by itself proof of evomem authority: it can be a lagging
shadow from a Markdown-authoritative runtime. Recovery compares the snapshot’s
last retrieval projection with both surviving sources before replay. If neither
source is uniquely provable, it preserves snapshot entries, snapshot nodes, and
current Markdown, writes write_authority = "unknown", and waits for the owner
to choose. The intent is cleared only after that choice has transactionally
rebuilt the retrieval projection; an evomem choice also reprojects canonical
nodes to Markdown. This prevents a crash-created default from silently choosing
either source.
The running daemon owns active SQLite writes, so persome status, MCP clients,
and a second persome start do not repair or quarantine an open database.
Runtime startup also fails closed on ambiguous process state. .daemon.lock
serializes concurrent starters for the daemon’s entire lifetime; .pid is
resolved against current-user ownership, executable/command, process start
time, and the generation in .runtime-state.json before any signal. Never
manually signal or delete state based only on the numeric PID. Stop the owning
Desktop app or LaunchAgent first when a live Persome process has an invalid
generation receipt.
persome onboard
persome llm status --check
persome ocr status --check
persome doctor
persome status
persome pause
persome resume
persome stop
persome onboard leaves the proved Runtime running. A following persome start
should report that it is already running; stopping and starting again is not
part of normal installation or update. Use persome start only when status says
the Runtime is safely stopped.
persome update
The updater fetches a fresh shallow copy of official main instead of editing a
user’s checkout. The installer builds a transaction-marked
venv.replacement.update while venv remains the working old installation.
After stopping either the background daemon or owner LaunchAgent, the updater
atomically exchanges those directories in one same-filesystem kernel operation,
restores the prior owner with the new executable, and runs mode-aware onboarding
against that final generation. Configuration, secrets, capture history, memory,
model state, logs, ocr_policy, and lifecycle intent under PERSOME_ROOT are
not replaced.
The exchanged old venv remains at venv.replacement.update until permission,
OCR-policy, health, owner, and capture/readiness proof all pass. Only then is it
renamed for post-commit cleanup. A failed proof or interruption exchanges the
directories back before restoring and proving the prior Runtime owner. A
transaction marker plus fsynced preparing/prepared/activated/committing
state makes crashes on either side of the exchange deterministic to recover.
INT, TERM, and HUP share that recovery path; further signals cannot interrupt
rollback. Concurrent updates are serialized by the owner-only root lock.
Invoking bash install.sh from a checkout when Persome is already installed
delegates to persome update --source <checkout> so manual reinstalls receive
the same stop, rollback, and post-install proof guarantees.
persome update --source /path/to/checkout is the explicit developer/offline
path. The supplied tree must have the complete Persome source layout; the
updater never pulls or rewrites it.
Use persome onboard for an end-to-end proof. Its capture request runs inside
the active daemon and binds permission, worker, privacy/mode receipt, generation,
and lifecycle ownership.
persome capture-once is a lower-level developer diagnostic. It constructs a
new capture provider and scheduler in the calling CLI, so it does not prove the
daemon’s event watcher, lifetime lock, generation, owner, privacy receipt, or
isolated OCR-worker readiness. It may also race a running capture scheduler.
Stop Persome before using it to isolate helper output; a successful path is not
an onboarding or release acceptance result.
The default active-session flush is five minutes. Timeline closure and model
processing add bounded local work, so the operational target for first useful
recall is at most ten minutes after valid AX capture and provider availability.
Without a hosted credential or keyless local endpoint, capture and BM25 recall
still work; semantic modeling reports a degraded state. Run
persome llm setup to change the active route, then restart the daemon.
Use these checks when memory does not appear:
persome doctor
persome status
persome timeline tick
persome model status
tail -F ~/.persome/logs/*.log
persome status
persome model status
persome delta-report
persome faces-report
persome root-report
persome contradictions
persome as-of --help
persome model open
The viewer and GET /model/graph are raw owner-local inspection surfaces.
Model export and MCP snapshot export redact by default.
persome correct --help
persome contradictions-resolve --help
persome entity-retype --help
Trusted agents can call MCP remember and correct_memory. Correction keeps
the prior state and receipts so the change remains auditable. Use erasure, not
correction, when history itself must be deleted.
persome model export
persome model export --out ~/Desktop/persome-model.json
Exports default to <PERSOME_ROOT>/exports/, are redacted, and use mode 0600.
--raw deliberately disables redaction and should never be attached to a
public issue or release.
Stop the daemon first so no writer can race the deletion.
persome stop
# Delete every file under memory/, FTS entries, canonical evomem, relations,
# Faces, Volumes, Root, exports, projections, backups, and recovery markers.
# Captures/timeline remain.
persome clean memory
# Delete all personal data, including captures, timeline/session state, model,
# legacy Chat-era history and skills, exports, backups, logs, and SQLite files.
# Keep config, env, and the installed venv.
persome clean all
Clean commands refuse to run while the daemon PID is live, even with --yes,
so a writer cannot retain an open SQLite handle or recreate data mid-erasure.
They require confirmation unless --yes is passed. clean captures
removes capture-buffer files and indexed capture rows, including capture rows
inside daily, unfinished, and integrity-quarantine SQLite copies. clean
timeline applies the same rule to timeline blocks. Explicit clean operations
use SQLite core and FTS5 secure deletion, VACUUM, WAL truncation, and journal
cleanup; unreadable recovery copies are removed rather than retaining a hidden
copy of data the user asked to erase. The FTS rebuild also clears historical
segment terms produced before secure-delete became the default.
Remove MCP client entries while the CLI still exists:
persome uninstall claude-code
persome uninstall codex
persome uninstall claude-desktop
persome uninstall opencode
Then remove the daemon, LaunchAgent, shim, and dedicated virtual environment:
bash uninstall.sh
This keeps personal data and configuration. Full erasure is explicit:
bash uninstall.sh --delete-data --yes
The script refuses / and the home directory as install roots, and only removes
the CLI shim when it points to the expected Persome virtualenv.
Before moving data, stop Persome and copy the entire root so SQLite WAL sidecars,
Markdown, encryption material, and exports stay together. Do not publish the
copy. The backup/ directory contains safety snapshots, but it is not a remote
backup service.
See troubleshooting, runtime internals, and security and privacy.