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.
On this page
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):
- 03:20
plugin_usage_retentionremoves gateway requests older than 90 days and their raw evidence. While the export is active it never goes past what the lake has verified. - 03:30
clickhouse_purgecompares 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. - 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.mdin 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_cleanuphas 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-statusreports 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.