Skip to main content

Documentation Index

Fetch the complete documentation index at: https://patter-06b046ce-feat-observability-otel-attrs-0-6-1.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Patter can persist every call to a directory tree on disk so you can replay transcripts, audit tool calls, and track latency/cost trends without running a hosted dashboard. Logging is opt-in and off by default — nothing is written unless you ask for it. 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

from getpatter import Patter, Twilio

# Platform default location
phone = Patter(carrier=Twilio(), phone_number="+15555550100", persist=True)

# Explicit path
phone = Patter(carrier=Twilio(), phone_number="+15555550100", persist="/var/log/patter")
persist valueBehaviour
omitted / None (default)Falls back to PATTER_LOG_DIR; off when env is also unset (backward-compatible).
FalseForce-off, even if PATTER_LOG_DIR is set.
TruePlatform default location (see below).
"<path>"Use the supplied path (~ expanded).

Via env var

Set PATTER_LOG_DIR before starting the server:
# Explicit path
export PATTER_LOG_DIR=/var/log/patter

# Or use the platform-idiomatic default directory
export PATTER_LOG_DIR=auto
Platform defaults for auto (and for persist=True):
  • macOS: ~/Library/Application Support/patter
  • Linux: $XDG_DATA_HOME/patter (falls back to ~/.local/share/patter)
  • Windows: %LOCALAPPDATA%\patter
When persist is unset and the env var is unset, the logger is a no-op — no directories are created, no files are written. When persist is set explicitly, the env var is ignored.

Layout

<PATTER_LOG_DIR>/calls/YYYY/MM/DD/<call_id>/
├── metadata.json     # envelope; written at call start, updated at call end
├── transcript.jsonl  # one JSON object per turn (role/text/ts/latency/cost)
└── events.jsonl      # operational events (tool_call, barge_in, error, ...)
metadata.json is written atomically (tmp file + rename) so a reader never sees a half-written file. JSONL files are append-only.

Metadata schema

{
  "schema_version": "1.0",
  "call_id": "CA9a2b...",
  "trace_id": null,
  "started_at": "2026-04-23T18:02:12.413Z",
  "ended_at":   "2026-04-23T18:03:47.892Z",
  "duration_ms": 95479,
  "status": "completed",
  "caller": "***4567",
  "callee": "***7890",
  "telephony_provider": "twilio",
  "provider_mode": "openai_realtime",
  "agent": { "provider": "openai_realtime", "voice": "nova" },
  "turns": 8,
  "cost":    { "total": 0.1234, "stt": 0.01, "tts": 0.02, "llm": 0.08 },
  "latency": { "p50_ms": 412, "p95_ms": 870, "p99_ms": 1240 },
  "error": null
}

Phone redaction

Caller / callee numbers in metadata.json are masked by default (last 4 digits). Change via:
# Mask last 4 digits (default): "***4567"
export PATTER_LOG_REDACT_PHONE=mask

# Store the full E.164 number (disables redaction)
export PATTER_LOG_REDACT_PHONE=full

# Replace with a sha256 prefix for correlation without storing the number
export PATTER_LOG_REDACT_PHONE=hash_only
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.
# Default: 30 days
export PATTER_LOG_RETENTION_DAYS=30

# Disable cleanup (keep forever)
export PATTER_LOG_RETENTION_DAYS=0
Retention defaults to 30 days. Set PATTER_LOG_RETENTION_DAYS=0 if you need to keep call history indefinitely (the default is intentionally conservative to avoid runaway disk use). Phone redaction defaults to last-4 masking; review the Phone redaction section before storing customer numbers.

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:
from getpatter.dashboard.store import MetricsStore

store = MetricsStore()
restored = store.hydrate("/var/log/patter")
print(f"Restored {restored} call(s) from disk")
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

import json
from pathlib import Path

call_dir = Path("/var/log/patter/calls/2026/04/23/CA9a2b...")

metadata = json.loads((call_dir / "metadata.json").read_text())
print(f"{metadata['call_id']}: {metadata['duration_ms']}ms, ${metadata['cost']['total']:.4f}")

with (call_dir / "transcript.jsonl").open() as fh:
    for line in fh:
        turn = json.loads(line)
        print(f"[{turn['ts']}] {turn['role']}: {turn['text']}")

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, or persist unset and PATTER_LOG_DIR unset), CallLogger.enabled is False and 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.