|
| 1 | +# Storage Maintenance |
| 2 | + |
| 3 | +odek accumulates local state under `~/.odek/` — session transcripts, prompt- |
| 4 | +injection audit records, plans, skill skip lists, and log files. The storage |
| 5 | +**janitor** keeps that growth bounded: a background sweep that removes expired |
| 6 | +entries and rotates oversized logs, plus an `odek cleanup` command for one-shot, |
| 7 | +operator-invoked runs. |
| 8 | + |
| 9 | +```bash |
| 10 | +# Sweep expired storage once, right now |
| 11 | +odek cleanup |
| 12 | + |
| 13 | +# See what a sweep WOULD remove, without deleting anything |
| 14 | +odek cleanup --dry-run |
| 15 | +``` |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Where the janitor runs |
| 20 | + |
| 21 | +The background janitor starts automatically in every long-lived odek process |
| 22 | +(when `maintenance.enabled` is true): |
| 23 | + |
| 24 | +| Process | Notes | |
| 25 | +|---|---| |
| 26 | +| **`odek telegram`** | Starts with the bot, stops on shutdown. | |
| 27 | +| **`odek serve`** | Starts with the Web UI server. | |
| 28 | +| **`odek schedule daemon`** | Starts with the scheduler daemon. | |
| 29 | + |
| 30 | +Each prints `odek: storage maintenance enabled (interval 60m)` at startup. The |
| 31 | +janitor sweeps on a fixed interval and stops with the process. Short-lived |
| 32 | +commands (`odek run`, `odek repl`, …) do not run the janitor — use |
| 33 | +`odek cleanup` for those workflows. |
| 34 | + |
| 35 | +## What is cleaned |
| 36 | + |
| 37 | +| Category | Location | Retention key | Default | |
| 38 | +|---|---|---|---| |
| 39 | +| Sessions | `~/.odek/sessions/*.json` (by `updated_at`) | `sessions_max_age_days` | 30 days | |
| 40 | +| Audit records | `~/.odek/sessions/audit/*.json` (by mtime) | `audit_max_age_days` | 14 days | |
| 41 | +| Plans | `~/.odek/plans/**/*.md` (by mtime) | `plans_max_age_days` | 30 days | |
| 42 | +| Skill skip entries | `~/.odek/skills/.skipped.json` | `skills_skip_max_age_days` | 90 days | |
| 43 | +| Telegram media | `~/.odek/media/` (by mtime) | fixed: 1 hour | freed bytes reported | |
| 44 | +| Logs | `~/.odek/telegram.log`, `~/.odek/schedule.log` | `log_max_mb` | 50 MB (rotated) | |
| 45 | + |
| 46 | +Age for sessions is measured from the session's `updated_at`; for audit |
| 47 | +records, plans, and media from the file's modification time. Oversized logs |
| 48 | +are **rotated**, not deleted — the current log is renamed to `<name>.1` |
| 49 | +(one backup generation) and a fresh log is started. The sweep report includes |
| 50 | +the rotated paths. Downloaded Telegram media is transient and expires after a |
| 51 | +fixed 1 hour. |
| 52 | + |
| 53 | +## What is NEVER touched |
| 54 | + |
| 55 | +The janitor only expires the categories above. It never touches: |
| 56 | + |
| 57 | +- **Memory** — atoms, facts, episodes, buffers (`~/.odek/memory/`) |
| 58 | +- **Skill files** — `SKILL.md` definitions (only stale `.skipped.json` |
| 59 | + entries age out) |
| 60 | +- **Schedules** — `schedules.json`, `schedule-state.json` |
| 61 | +- **Trust anchors** — `config.json`, `secrets.env`, `IDENTITY.md`, |
| 62 | + approval stores, lock files, and everything else under `~/.odek/` that is |
| 63 | + not in the "What is cleaned" table |
| 64 | + |
| 65 | +## Configuration |
| 66 | + |
| 67 | +The `[maintenance]` section (all keys optional — defaults shown): |
| 68 | + |
| 69 | +```json |
| 70 | +{ |
| 71 | + "maintenance": { |
| 72 | + "enabled": true, |
| 73 | + "interval_minutes": 60, |
| 74 | + "sessions_max_age_days": 30, |
| 75 | + "audit_max_age_days": 14, |
| 76 | + "log_max_mb": 50, |
| 77 | + "plans_max_age_days": 30, |
| 78 | + "skills_skip_max_age_days": 90 |
| 79 | + } |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +| Key | Default | Description | |
| 84 | +|---|---|---| |
| 85 | +| `enabled` | `true` | Run the background janitor in long-lived processes | |
| 86 | +| `interval_minutes` | `60` | Minutes between automatic sweeps | |
| 87 | +| `sessions_max_age_days` | `30` | Delete sessions older than this | |
| 88 | +| `audit_max_age_days` | `14` | Delete prompt-injection audit records older than this | |
| 89 | +| `log_max_mb` | `50` | Rotate logs larger than this | |
| 90 | +| `plans_max_age_days` | `30` | Delete plans older than this | |
| 91 | +| `skills_skip_max_age_days` | `90` | Drop skill skip entries older than this | |
| 92 | + |
| 93 | +The maintenance config is **operator-only**: like `base_url`, `api_key`, and |
| 94 | +the `dangerous` section, it is honored from `~/.odek/config.json` (and process |
| 95 | +environment) but **ignored from a project-level `./odek.json`**, so a checked- |
| 96 | +out repository cannot disable the janitor or relax its own retention. |
| 97 | + |
| 98 | +## `odek cleanup` |
| 99 | + |
| 100 | +`odek cleanup` runs one sweep immediately, using the same resolved config as |
| 101 | +the background janitor, and prints a per-category report: |
| 102 | + |
| 103 | +``` |
| 104 | +$ odek cleanup |
| 105 | +Cleanup complete: |
| 106 | + sessions removed: 12 |
| 107 | + audit records removed: 34 |
| 108 | + plans removed: 2 |
| 109 | + skip entries removed: 5 |
| 110 | + media freed: 48.2 MB |
| 111 | + log rotated: /home/you/.odek/schedule.log |
| 112 | +``` |
| 113 | + |
| 114 | +When there is nothing to do it prints a single quiet line: |
| 115 | + |
| 116 | +``` |
| 117 | +Storage is clean — nothing to remove. |
| 118 | +``` |
| 119 | + |
| 120 | +`odek cleanup --dry-run` removes nothing and reports what a sweep would |
| 121 | +remove: |
| 122 | + |
| 123 | +``` |
| 124 | +$ odek cleanup --dry-run |
| 125 | +Dry run — nothing removed. Would remove: |
| 126 | + sessions: 12 |
| 127 | + audit records: 34 |
| 128 | + plans: 2 |
| 129 | + skip entries: 5 |
| 130 | + log rotated: /home/you/.odek/schedule.log |
| 131 | +``` |
| 132 | + |
| 133 | +Like `odek session cleanup`, the command deletes data without a confirmation |
| 134 | +prompt — it is a local, operator-invoked command. Use `--dry-run` first if |
| 135 | +you want to inspect the candidate list. |
| 136 | + |
| 137 | +## Related |
| 138 | + |
| 139 | +- [CLI.md](CLI.md) — command reference |
| 140 | +- [SCHEDULES.md](SCHEDULES.md) — the schedule daemon (hosts the janitor) |
| 141 | +- [CONFIG.md](CONFIG.md) — full configuration reference |
| 142 | +- `internal/session` — session files are also capped at 32 MiB **at write |
| 143 | + time**: an oversized transcript is trimmed (oldest turns first, keeping the |
| 144 | + system message and the most recent turns) so it never becomes unloadable. |
0 commit comments