Data retention & gc
koryph gc applies koryph's data lifecycle policy: it compresses and
eventually deletes old run phase-directories, bounds project-scoped Go
caches, and size-rotates append-only audit logs. The binary-native autonomous
loop runs this same policy at terminal and idle boundaries. Manual engine
health patrols remain opt-in (see Automatic gc
below).
koryph gc [--dry-run] [--project ID] [--json]
--dry-run— scan and report what would be compressed/deleted, change nothing. Run this first.--project ID— also process the project's run phase-directories (<repo>/.plan-logs/koryph/<run-id>/). Without it, only the global artifact classes (~/.koryph/audit.jsonl,~/.koryph/runs.jsonl) are processed.--json— emit the result as JSON instead of a table.
Exit codes: 0 clean, 1 when any artifact class reported a non-fatal
error (the table lists each one as a gc warning).
What gc manages — and what it refuses to touch
gc covers four artifact classes:
| Class | Policy | Default |
|---|---|---|
Run phase-dirs (<repo>/.plan-logs/koryph/<run-id>/) |
compress whole run dir to .tar.gz after N days; delete after M days |
compress at 7 days, delete at 90 days |
~/.koryph/audit.jsonl |
size-based rotation to audit-<date>.jsonl.gz |
rotate at 10 MiB, retain rotated files forever |
~/.koryph/runs.jsonl |
size-based rotation to runs-<date>.jsonl.gz |
rotate at 10 MiB, retain rotated files forever |
| Project artifacts (shared Go caches, eligible terminal transcripts and filed planning snapshots) | age-based retention plus soft/hard byte budget | warn at 2 GiB, reclaim toward 2 GiB above a 5 GiB hard limit |
Telemetry (~/.koryph/telemetry/) |
managed by the observability layer, not by koryph gc |
see Observability |
As soon as an individual slot becomes terminal, gc removes only its recognized
mutable roots: current cache, go-cache, go-mod-cache,
go-build<digits>, go-tmp, and go-telemetry directories; known legacy
gocache, gomodcache, go-build-cache, and runtime-cache layouts; and
engine-private reviewer .runtime-scratch. A live sibling slot is untouched.
Ledgers, manifests, status, streams, logs, summaries, unknown directories,
latest, and nonterminal slot trees are preserved. The latest symlink
protects durable evidence from archival, but does not retain disposable
compiler state. --dry-run reports exact known cache bytes but conservatively
reports zero for unknown future compression savings.
Safety exemptions, always in force:
- Active runs are never archived or deleted — gc reads
ledger.jsonand requires a terminal run plus terminal slots. Only already-terminal slot scratch is eligible for immediate cleanup. - Shared caches are admission-coordinated — engine startup and cache pruning use one kernel-backed guard, so a new run cannot enter while gc is deleting a project cache.
- Durable evidence is authenticated before deletion — a compact archive must be a regular, readable gzip/tar containing the expected terminal ledger. Symlinks, empty files, corrupt archives, and wrong-run ledgers fail closed. Selected transcript tails are created before source compression.
- Filed planning evidence is authenticated — only snapshots under
.plan-logs/koryph-plan/with exact snapshot/post-file digests, a committed design blob, and the matching digest in the epic's Beads notes age out. - Posture snapshots are exempt by design — they are your rollback evidence and are never auto-deleted.
- When a run dir is compressed, a companion
<run-id>.manifest.jsonis written beside the archive (the per-phase manifests plus the ledger), so history queries can introspect archived runs without decompressing them. - Rotation retention defaults to forever for both audit logs — they are
audit trails; you must explicitly configure
retain_daysto prune them. Appenders and rotation take the same inode lock, so copy/truncate cannot lose a concurrent audit record.
The retention policy: retention.json
The single config surface is ~/.koryph/retention.json (global), with an
optional per-project overlay at <repo>/.koryph/retention.json. Non-zero
fields in the project file win over the global file. Missing files simply
mean "defaults". All fields are optional.
{
"run_dirs": {
"compress_after_days": 7,
"delete_after_days": 90
},
"audit_log": {
"rotate_size_mb": 10,
"retain_days": "never"
},
"runs_index": {
"rotate_size_mb": 10,
"retain_days": "never"
},
"project_budget": {
"soft_mb": 2048,
"hard_mb": 5120,
"transcript_retain_days": 14,
"failure_retain_days": 30,
"log_tail_kb": 64
},
"footprint_warn_gb": 1.0,
"gc_auto": false
}
Field reference:
| Field | Meaning | Default |
|---|---|---|
run_dirs.compress_after_days |
age (days) after which a completed run dir is compressed to .tar.gz; "never" disables compression |
7 |
run_dirs.delete_after_days |
age (days) after which the run dir (or its archive + companion manifest) is deleted; "never" disables deletion |
90 |
audit_log.rotate_size_mb |
size (MiB) at which audit.jsonl is rotated to audit-<date>.jsonl.gz |
10 |
audit_log.retain_days |
days to keep rotated audit-*.jsonl.gz files; 0 or "never" means keep forever |
never |
runs_index.rotate_size_mb |
size (MiB) at which runs.jsonl is rotated |
10 |
runs_index.retain_days |
days to keep rotated runs-*.jsonl.gz files; 0 or "never" means keep forever |
never |
project_budget.soft_mb |
project artifact target and warning threshold | 2048 |
project_budget.hard_mb |
threshold that makes eligible successful transcripts, full archives with compact evidence, and idle shared caches immediately reclaimable | 5120 |
project_budget.transcript_retain_days |
minimum age for successful terminal transcripts and filed planning snapshots | 14 |
project_budget.failure_retain_days |
minimum age for failed terminal transcripts, even above the hard budget | 30 |
project_budget.log_tail_kb |
tail retained when a full transcript is pruned | 64 |
footprint_warn_gb |
pending-gc footprint (GiB) above which the health patrol and koryph doctor warn |
1.0 |
gc_auto |
opt-in: let the health patrol run a live gc pass automatically | false |
The "never" sentinel
Every retention value accepts the string "never":
{ "run_dirs": { "compress_after_days": "never", "delete_after_days": "never" } }
"never" in the project overlay always overrides a numeric value in the
global config — you can globally delete runs at 90 days while pinning one
project's history forever.
Footprint monitoring
koryph doctor includes a gc-footprint check: it performs a dry-run scan,
reports per-class reclaimable sizes and the active policy, and warns when the
pending-gc footprint exceeds footprint_warn_gb. The engine's in-run health
patrol performs the same check on its patrol tick, so a long-running loop
tells you when it is time to run koryph gc — it does not delete anything on
its own.
Automatic gc (gc_auto)
The binary-native koryph loop always performs bounded maintenance at idle
and terminal boundaries; this is part of its autonomous disk-safety contract.
It preserves live runs, retained failures, compact evidence, audit records,
posture snapshots, and active/final canary reports even when that means
reporting an unreclaimable hard-budget overage.
Setting "gc_auto": true in retention.json separately opts the in-run
health patrol into
running a live (non-dry-run) gc pass whenever the reclaimable footprint
exceeds footprint_warn_gb during a run. The patrol finding then reports
what was reclaimed instead of warning.
This is deliberately opt-in and off by default: it authorizes unattended deletion under the retention policy above. Before enabling it, confirm your policy with:
koryph gc --dry-run --project <ID>
All the safety exemptions still apply — auto-gc can never touch the active run, a run with non-terminal slots, or posture snapshots.
gc_auto also gates a second, independent mechanism: on every patrol tick
(regardless of whether the run-dir footprint has crossed footprint_warn_gb)
the patrol runs the same retention pass as koryph obs prune against
~/.koryph/telemetry/, so telemetry volume no longer grows unbounded on a
long-lived project between manual prunes. Its outcome is appended to the
gc-footprint finding, e.g. ... [telemetry: pruned 3 stale file(s)].