Patter(...) writes under the platform default location unless you pass persist=False (force-off) or override the path. The default was flipped from off→on because the dashboard’s hydrate path needs on-disk records to survive process restarts.
The same on-disk layout also backs the local dashboard’s call history: when persistence is enabled, phone.serve() rebuilds the in-memory dashboard from disk on startup so call history survives process restarts without an external database.
Enable
You can turn persistence on either in code (recommended for application defaults) or via an environment variable (recommended for ops overrides). Both reach the same on-disk layout — see the Configuration reference for the full precedence rules.In code
persist value | Behaviour |
|---|---|
omitted / None (default) | Reads PATTER_LOG_DIR if set; otherwise falls back to the platform default location. On by default since 0.6.2. |
False | Force-off, even if PATTER_LOG_DIR is set. |
True | Platform default location (see below). |
"<path>" | Use the supplied path (~ expanded). |
Via env var
SetPATTER_LOG_DIR before starting the server:
auto (and for persist=True):
- macOS:
~/Library/Application Support/patter - Linux:
$XDG_DATA_HOME/patter(falls back to~/.local/share/patter) - Windows:
%LOCALAPPDATA%\patter
persist is unset and the env var is unset, Patter falls back to the platform default path (persistence is on by default since 0.6.2). When persist is set explicitly, the env var is ignored. Pass persist=False to force the logger to be a no-op.
Layout
metadata.json is written atomically (tmp file + rename) so a reader never sees a half-written file. JSONL files are append-only.
Metadata schema
Phone redaction
Caller / callee numbers inmetadata.json default to full (raw E.164) since 0.6.2 so the dashboard’s reveal toggle has something to reconstruct from. The on-disk path under ~/Library/Application Support/patter/ (macOS) / XDG data dir (Linux) / %LOCALAPPDATA%\patter (Windows) is user-private. Override via:
PATTER_LOG_REDACT_PHONE=mask for setups that ship logs off-host.
transcript.jsonl is not redacted — it can contain customer PII spoken during the call. Gate access to the log root and/or wire up your own redaction pipeline before exporting.
Retention
Old day directories are cleaned up automatically. The sweep runs on ~2% of calls (sampled; no daemon) so a long-running server doesn’t accumulate indefinitely.Dashboard hydration
When persistence is enabled,phone.serve() calls MetricsStore.hydrate(log_root) once at startup so the local dashboard repopulates its 500-call ring buffer from the on-disk envelopes before the first new call lands. There’s nothing to wire up — it just happens.
If you instantiate the store yourself (custom dashboard host, separate process), call hydrate() directly:
hydrate() is idempotent: call_ids already in the store are skipped, and unparseable records are logged at debug level rather than aborting. The on-disk JSON/JSONL files are the source of truth — the in-memory store is a cache on top.
Reading a call
Safety guarantees
- File-write errors never raise into the call path — a full disk or a permissions hiccup logs a warning and the call continues uninterrupted.
- Writes happen on a background thread (
asyncio.to_thread) so they never block the audio pipeline. - When persistence is disabled (
persist=False, orpersistunset andPATTER_LOG_DIRunset),CallLogger.enabledisFalseand every method returns immediately.
Interop
Patter session reports include per-turn STT/LLM/TTS spans, tool invocations, latency metrics, and barge-in events.transcript.jsonl rows slot into OpenTelemetry gen_ai.* turn spans. The TypeScript SDK writes the same schema, so a multi-runtime deployment produces a single coherent directory tree.
