Skip to main content

Data Retention and Durability Policy

The policy of this installation: where each kind of data lives, how long Postgres keeps it, when it moves to the ClickHouse lake, how long the lake keeps it, and what protects it against a mistaken command or a failed disk.

TL;DR: ClickHouse is the data lake; Postgres is the application. Postgres keeps a short hot window of each kind of history, for the console and the jobs. Before a row leaves that window it is copied to ClickHouse and verified there, and only then deleted from Postgres. The lake does not expire anything on its own: deleting from the lake is a deliberate act. Both stores are backed up off their disks: Cloud SQL by daily backups (30 kept) and 7 days of point-in-time recovery, and ClickHouse by a daily disk snapshot (30 kept). Removing the installation keeps the data.

No legal retention requirement applies to this installation. These are operational defaults, decided on 2026-11-13, and Astound administrators can change them. Every number on this page comes from a configuration file named in Where the numbers come from.

Current status (2026-11-13): while the imported pilot history is exported to the lake and lake backups are put in place, the deleting jobs are paused at runtime on the production instance (systemprompt infra jobs disable), so nothing is purged. They are re-enabled by hand once the export has caught up and verified. This page describes the policy that applies once they are re-enabled.

The two stores

Store Role Runs on
Postgres (Cloud SQL, regional) The application. Everything the console, the gateway and the jobs read and write. Holds a hot window of history. Cloud SQL, high availability across two zones
ClickHouse (the lake) Long-term history. Receives requests, governance decisions, tool executions, logs, runs and (once the transcript export is deployed) transcripts. One-replica StatefulSet on a zonal persistent disk in the cluster

Policy by kind of data

"Hot window" is how long a row stays in Postgres after the lake has verified it. "Backstop" is the age at which a second job in Postgres would delete the row if the export ever stopped; it is always longer than the hot window.

Data Lives in Postgres keeps Archived to the lake Lake keeps Protected by
Gateway requests, usage and cost (ai_requests) Postgres, then lake 60 days (backstop 90) Every 15 min, verified nightly 3650 days Cloud SQL backups + PITR; lake snapshots
Daily usage and cost rollups Postgres Indefinitely No n/a Cloud SQL backups + PITR
Governance decisions Postgres, then lake 30 days (backstop 180) Every 15 min, verified nightly 3650 days Cloud SQL backups + PITR; lake snapshots
MCP tool executions (and their artifacts) Postgres, then lake 120 days (backstop 180) Every 15 min, verified nightly 3650 days Cloud SQL backups + PITR; lake snapshots
Server logs Postgres, then lake 14 days (backstop 30) Every 15 min, verified nightly 3650 days Cloud SQL backups + PITR; lake snapshots
Agent runs, workflow runs and steps Postgres and lake Indefinitely (never purged) Once finished 3650 days Cloud SQL backups + PITR; lake snapshots
Conversation transcripts (ai_request_messages), once the transcript export is deployed Postgres, then lake 14 days (backstop 30) Every 15 min, verified nightly 3650 days Cloud SQL backups + PITR; lake snapshots
Conversation transcripts, until then Postgres only 30 days, then deleted by database_cleanup No: older transcripts are not kept anywhere n/a Cloud SQL backups + PITR (inside their window)
Raw request and response bodies (ai_request_payloads) Postgres only 7 days, then emptied (excerpts, hashes and sizes stay with the request) No n/a Cloud SQL backups + PITR (inside their window)
Drive documents and files Cloud Storage bucket (content), Postgres (metadata) Until deleted by a user No n/a Bucket versioning (5 old versions, each kept 30 days), 7-day soft delete; Cloud SQL backups for the metadata
Brain facts Postgres Until superseded or retired by the brain's own review No n/a Cloud SQL backups + PITR
Configuration, access rules, accounts and the audit trail of changes Postgres (projected from the services tree in git) Indefinitely No n/a Cloud SQL backups + PITR; the declarations themselves are in git

Deletion jobs, in order

Every night, in this order (UTC):

  1. 03:20 plugin_usage_retention removes gateway requests older than 90 days and their raw evidence. While the export is active it never goes past what the lake has verified.
  2. 03:30 clickhouse_purge compares each day in both stores (row count and an id checksum) and deletes Postgres rows only up to the last fully matching day and only past each hot window. If the lake is unreachable or a day does not match, it deletes nothing for that kind of data and the run fails.
  3. 04:00 database_cleanup (core) applies the profile's backstop windows. They are longer than the hot windows by at least 14 days, so while the export keeps up the verified purge is always the one that deletes. A gate (scripts/check-retention-hot-windows.sh) fails the build if that stops being true.

The rule: a history row leaves Postgres only after the lake holds it and has verified it. The one exception is the backstop: if the export stalls for longer than the gap between a hot window and its backstop (14 days or more), core's cleanup deletes by age. Watch clickhouse_export_state.last_error.

The lake itself has no deletion job. Its tables carry a 3650-day expiry, which in practice means none.

Backups

Store Mechanism Schedule Kept Survives
Cloud SQL Automated backups Daily, 02:00 UTC 30 backups Instance deletion (retain_backups_on_delete)
Cloud SQL Point-in-time recovery (write-ahead logs) Continuous 7 days (the maximum for this edition) n/a
Cloud SQL Deletion protection Always on n/a Refuses a delete from any client, not only Terraform
ClickHouse Persistent-disk snapshot schedule Daily, 03:00 UTC 30 days Disk, release and cluster deletion (snapshots are kept when the disk goes)
ClickHouse Disk reclaim policy Retain Always on n/a Deleting the claim, the namespace or the release leaves the disk
Drive bucket Object versioning + soft delete On every write 5 old versions, 30 days each; deleted objects 7 days force_destroy is off: a non-empty bucket is never deleted

Removing the installation (just gcp-uninstall-gke) removes compute and keeps all of the above. It prints what it kept; deleting any of it is a separate, deliberate command.

Restore

  • Postgres: restore a Cloud SQL backup, or clone the instance to a point in time within the last 7 days (Cloud SQL console or gcloud sql instances clone --point-in-time).
  • ClickHouse: create a disk from a snapshot and bind it to the StatefulSet's claim. Runbook: docs/runbooks/clickhouse-backup.md in the repository. This procedure has not yet been exercised end to end.
  • Drive: restore an older object version or a soft-deleted object from the bucket.

Known gaps

  • The console and the CLI read transcripts from Postgres only. Once the transcript export is deployed, a conversation older than 14 days is in the lake but not viewable in the console.
  • One ClickHouse replica, one zonal disk. A zone or disk failure takes the lake offline until a restore from the last daily snapshot; up to a day of lake writes can be lost and is re-exported only for rows still inside the Postgres hot windows. Accepted risk, covered by the snapshots.
  • Until the transcript export is deployed, transcripts older than 30 days are deleted, not archived. Core's database_cleanup has no export watermark; the export closes this gap.
  • Raw request and response bodies are not archived. After 7 days only their excerpts, hashes and sizes remain.
  • Snapshots are crash-consistent, not a ClickHouse-native backup. ClickHouse recovers from them as from a power loss.
  • A new ClickHouse disk is not snapshotted until it is registered. After a restore or a disk replacement, the schedule must be attached to the new disk (just gcp-ch-backup-status reports it).
  • No final backup on instance deletion. The retained automated backups cover a deliberate delete; a final on-delete backup needs a newer Terraform provider than this installation pins.

Where the numbers come from

Number Value File and key
Hot window, requests 60 days services/clickhouse/config.yaml hot_window_days.ai_requests: 60
Hot window, governance decisions 30 days services/clickhouse/config.yaml hot_window_days.governance_decisions: 30
Hot window, MCP tool executions 120 days services/clickhouse/config.yaml hot_window_days.mcp_tool_executions: 120
Hot window, logs 14 days services/clickhouse/config.yaml hot_window_days.logs: 14
Hot window, transcripts (once deployed) 14 days services/clickhouse/config.yaml hot_window_days.ai_request_messages: 14 (transcript export)
Lake expiry 3650 days services/clickhouse/config.yaml ttl_days: 3650
Backstop, governance / MCP / logs 180 / 180 / 30 days Helm profile.retention (governance_decisions_days, mcp_tool_executions_days, logs_days)
Transcript backstop (and the only window until the export is deployed) 30 days Helm profile.retention.ai_request_messages_days: 30
Raw bodies 7 days Helm profile.retention.ai_request_payload_raw_days: 7
Request backstop 90 days plugin_usage_retention (fixed floor in the web extension)
Cloud SQL backups 30, daily 02:00 UTC Terraform stacks/gke (backup_retention_count, protect = true)
Cloud SQL PITR 7 days Terraform modules/cloudsql transaction_log_retention_days
ClickHouse snapshots 30 days, daily 03:00 UTC Terraform stacks/gke clickhouse_snapshot_retention_days
Drive versions / soft delete 30 / 7 days Terraform drive_noncurrent_days, drive_soft_delete_days

The decisions behind each default are recorded in the repository's docs/DECISIONS.md (2026-11-13). The mechanics of each job, the archives under storage/exports/ and the audit guard are on Data Retention & Archives.