Persome

Local-first Personal Model Runtime for macOS

View the Project on GitHub Persome-ai/persome-core

Runtime internals

Secrets and configuration

Runtime secrets live in <PERSOME_ROOT>/env (~/.persome/env by default), mode 0600. persome start loads this file before daemonization. Business code reads the resulting environment variables:

ANTHROPIC_API_KEY
ANTHROPIC_BASE_URL
OPENAI_API_KEY
OPENAI_BASE_URL
PERSOME_SCREENSHOT_KEY

config.toml contains behavior and model names, never API keys. PERSOME_ROOT redirects the entire runtime for tests or isolated profiles. install.sh generates the machine-local screenshot key automatically and preserves it across reinstalls; it is not a provider credential.

Daemon lifecycle

Direct CLI lifecycle:

persome start
persome status
persome stop

start double-forks and writes <PERSOME_ROOT>/.pid. The HTTP/MCP server defaults to 127.0.0.1:8742; the same loopback app serves /model and Chat REST routes. persome chat is the bundled interactive client.

Optional launchd ownership:

persome launchagent install
persome launchagent status
persome launchagent uninstall

The LaunchAgent label is com.persome.runtime; logs go to <PERSOME_ROOT>/logs/launchd.{out,err}.log. Product consumers may manage this lifecycle themselves, but product-specific labels, ports, and data roots do not belong in core.

Data root

src/persome/paths.py is authoritative.

Path Purpose
env provider secrets plus the generated screenshot-encryption key
config.toml runtime configuration
.pid direct-daemon PID
capture-buffer/ bounded AX/OCR records
memory/ durable Markdown memory
index.db SQLite WAL model/index
model-build.lock cross-process build lock
session-model.lock cross-process terminal-session finalization lock
model-build.json last build manifest
exports/ owner-only snapshots
backup/ optional SQLite snapshots
logs/ component logs

OCR is off by default. When enabled it uses a child worker process managed by capture/ocr_subprocess.py; a native Paddle crash does not take down the daemon. The parent OCR submission thread only coordinates local inference and SQLite backfill.

New code must use paths.py; tests use a temporary PERSOME_ROOT and must never inspect the real store.

Recovery

If direct persome start reports an existing daemon but health is unavailable:

cat ~/.persome/.pid
kill -0 "$(cat ~/.persome/.pid)" 2>/dev/null && echo alive || echo stale
rm ~/.persome/.pid
persome start
curl -s http://127.0.0.1:8742/health

For launchd:

launchctl print "gui/$(id -u)/com.persome.runtime"
persome launchagent status
tail -f ~/.persome/logs/launchd.err.log

The SQLite store uses WAL mode. Integrity checks and rebuild commands are documented in troubleshooting.md.