Skip to content

Data and Retention

William Koch edited this page Sep 30, 2026 · 16 revisions

Where Keryx keeps its files

Keryx follows the XDG directory layout on both Linux and macOS. It honors each XDG_*_HOME you set, but ignores one that's empty or a relative path. To see where each directory is on your machine, run keryx config path.

Directory What's in it
~/.config/keryx (KERYX_HOME) config.toml, secrets.toml, the PIN, and the Google client file
~/.local/share/keryx (DATA_DIR) tasks and their reports, call transcripts, the memory, sign-in tokens, and the plugins and your own tools (tools/)
~/.local/state/keryx (STATE_DIR) logs, the restart record, and the approval bridge's socket
~/.cache/keryx (CACHE_DIR) files that can be downloaded again

Only you can read these directories. Directories use mode 0700 and files use 0600, and keryx doctor checks both. ~/.config/keryx holds secrets.toml and the PIN, so keep it out of any dotfiles repository. Data isn't encrypted at rest, and by default nothing is deleted automatically.

What is stored

Path What's in it
KERYX_HOME/config.toml, secrets.toml your settings, and every key and token
KERYX_HOME/pin the phone PIN, written once — see Security Model
KERYX_HOME/google_client_secret.json your Google OAuth client, if you connected Google
DATA_DIR/calls/<session_id>.log the full transcript of a call: every line you said and every line the assistant said. A spoken PIN is written as [PIN]. The first line says whether the call began authorized, and a --- authorized line marks where the PIN was accepted during the call.
DATA_DIR/tasks.db every task: what you asked for, its status, and the spoken summary that came back
DATA_DIR/tasks/<id>.log, .md a task's progress, tool by tool, and the written report it produced
DATA_DIR/workspace/ where a task with no project runs when PROJECTS_ROOT doesn't exist, and whatever it leaves there
DATA_DIR/memory.md what Keryx carries between calls. It starts from keryx memory seed or keryx setup, if you wrote a first draft, and is rewritten after each call that gave the PIN.
DATA_DIR/projects/<name>.md project summaries that keryx setup drafted and you accepted, for repositories with no .keryx-brief.md of their own
DATA_DIR/tools/ your own voice tools (see Custom Tools), and the plugins you turned on. Each plugin is a one-line <name>.py and its settings in <name>.toml. After keryx plugins remove, the settings file is renamed <name>.toml.off. No secret is kept here: a plugin's token or admin key stays in secrets.toml, and the Gmail sign-in has its own file.
DATA_DIR/gmail_token.json, google/ the read-only Gmail sign-in, and the agents' Google sign-in
DATA_DIR/codex/ Codex's own login, when it signs in with CODEX_API_KEY
DATA_DIR/pin-failures.json wrong PINs across calls, and the lock they set — see Security Model
DATA_DIR/report_secret the key that signs /reports/{id} links, if REPORT_SECRET isn't set
STATE_DIR/logs/*.log the server log, the service manager's stdout/stderr, and the restart watchdog's output
STATE_DIR/restart.json, running-version restart bookkeeping
STATE_DIR/approvals.sock, approvals/ the approval bridge's socket, its pending marker, its kill switch, and its audit log

A PIN is never written down. Keyed digits aren't written anywhere: not to the log, not to the transcript, and not into the model's context. The realtime model hears a PIN you say, because that's how it gets checked. But every transcript line is redacted as it's written, and no log line carries the PIN. Redaction works whether the transcription came out as digits (1 2 3 4 5 6, 123-456) or as words (one two three…, with oh or zero for 0).

What leaves the machine

  • OpenAI Realtime API: call audio, the conversation, and the system prompt. The system prompt is sent again on every call, and carries memory.md and every project's brief. Once a PIN exists, they're sent from the greeting. With BRIEFING_BEFORE_PIN=false, they're sent only after the PIN.
  • OpenAI Responses API: the small factual questions that the voice model answers itself.
  • Anthropic, through the Claude Agent SDK: the task text, and whatever the task reads. When a task runs on Codex, these go to OpenAI instead.
  • Twilio: phone audio and caller ID. Texts go through Twilio too, if you turned them on with SMS_ENABLED=true.
  • Google: Gmail and Calendar work, through the Claude CLI's own connectors, or through workspace-mcp if you turned it on.

The memory and the briefs are bounded. A call reads 4,000 characters of memory. Each brief is capped at 1,500 characters, and all briefs together at 6,000. A brief that would go past the total is left out entirely, and the log names it. keryx memory seed tells you how many characters they add up to on this machine. Don't put anything in either that you wouldn't send to OpenAI.

The plugins send nothing until you turn them on:

  • send_to_slack sends to Slack, but only when you ask. So does a task's use of the Slack MCP server named by mcp_server in send_to_slack.toml.
  • check_email reads Gmail read-only and summarizes it with the claude CLI, so what it reads goes to Anthropic too.
  • check_billing reads the provider's billing API.
  • cluster_stats runs read-only Slurm commands on the hosts you named in cluster_stats.toml. It uses only an ssh ControlMaster that you already have open, or a guard script of your own, and never opens a connection itself.

A tool of your own reaches whatever its code reaches. Keryx doesn't send any telemetry or analytics of its own.

Retention

Nothing is deleted by default. keryx.log rotates, but transcripts, task rows, task logs, and task reports stay until you set up retention or delete them. Clear this data before you pass the machine on to someone else.

Two settings turn retention on, independently. 0, the default, means keep everything:

Setting What it prunes Default
TRANSCRIPT_RETENTION_DAYS calls/*.log older than this 0 — never
TASK_RETENTION_DAYS finished task rows, and their .log and .md, older than this 0 — never

Set them with keryx config set, for example keryx config set TRANSCRIPT_RETENTION_DAYS 30. Pruning runs once when keryx serve starts, so a new value takes effect on the next restart. To prune now instead:

uv run keryx forget --older-than 30       # asks first
uv run keryx forget --yes                 # everything eligible, no window
uv run keryx forget --transcripts-only --older-than 7

--older-than 0 really does mean everything, and none of it can be undone. You can't combine --transcripts-only and --tasks-only.

A finished task stays until Keryx has told you its result. Keryx records when it told you, and a task it hasn't told you about is never pruned. keryx forget reports how many tasks it kept for this reason. Keryx's own housekeeping tasks and canceled tasks follow the normal schedule.

Nothing prunes DATA_DIR/tools/. A plugin's settings stay until you delete the files, and keryx plugins remove keeps them aside for the next time you turn the plugin on.

memory.md works differently. keryx memory seed can write the first one, and a subagent rewrites it after every authorized call. When that subagent finishes, Keryx trims the file to a size limit, so it doesn't grow without limit, even though nothing prunes it. uv run keryx memory prints it. It's plain Markdown that you can edit by hand, and the next update keeps your edits.


For how secrets are stored, see Where secrets live in SECURITY.md.

Clone this wiki locally