odek accumulates local state under ~/.odek/ — session transcripts, prompt-
injection audit records, plans, and log files. The storage
janitor keeps that growth bounded: a background sweep that removes expired
entries and rotates oversized logs, plus an odek cleanup command for one-shot,
operator-invoked runs.
# Sweep expired storage once, right now
odek cleanup
# See what a sweep WOULD remove, without deleting anything
odek cleanup --dry-runThe background janitor starts automatically in every long-lived odek process
(when maintenance.enabled is true):
| Process | Notes |
|---|---|
odek telegram |
Starts with the bot, stops on shutdown. |
odek serve |
Starts with the Web UI server. |
odek schedule daemon |
Starts with the scheduler daemon. |
Each prints odek: storage maintenance enabled (interval 60m) at startup. The
janitor sweeps on a fixed interval and stops with the process. Short-lived
commands (odek run, odek repl, …) do not run the janitor — use
odek cleanup for those workflows.
| Category | Location | Retention key | Default |
|---|---|---|---|
| Sessions | ~/.odek/sessions/*.json (by updated_at) |
sessions_max_age_days |
30 days |
| Audit records | ~/.odek/sessions/audit/*.json (by mtime) |
audit_max_age_days |
14 days |
| Plans | ~/.odek/plans/**/*.md (by mtime) |
plans_max_age_days |
30 days |
| Sub-agent artifacts | ~/.odek/artifacts/<session>/<task>/ and ~/.odek/artifacts/unfiled/<task>/ (task dirs by their own mtime; aged session dirs also go wholesale) |
artifacts_max_age_hours |
24 hours (backstop — live removal happens on session delete) |
| Telegram media | ~/.odek/media/ (by mtime) |
fixed: 1 hour | freed bytes reported |
| Logs | ~/.odek/telegram.log, ~/.odek/schedule.log, ~/.odek/serve.log, ~/.odek/runtime.log |
log_max_mb |
50 MB (rotated) |
Age for sessions is measured from the session's updated_at; for audit
records, plans, and media from the file's modification time. Sub-agent
artifacts age per task dir: each expired task-* subtree is removed
individually inside its parent (session dir or the shared unfiled bucket),
and a parent left empty is pruned. Oversized logs
are rotated, not deleted — the current log is renamed to <name>.1
(one backup generation) and a fresh log is started. The sweep report includes
the rotated paths. Downloaded Telegram media is transient and expires after a
fixed 1 hour.
The janitor only expires the categories above. It never touches:
- Memory — atoms, facts, episodes, buffers (
~/.odek/memory/) - Skill files —
SKILL.mddefinitions - Schedules —
schedules.json,schedule-state.json - Trust anchors —
config.json,secrets.env,IDENTITY.md, approval stores, lock files, and everything else under~/.odek/that is not in the "What is cleaned" table
The [maintenance] section (all keys optional — defaults shown):
{
"maintenance": {
"enabled": true,
"interval_minutes": 60,
"sessions_max_age_days": 30,
"audit_max_age_days": 14,
"log_max_mb": 50,
"plans_max_age_days": 30,
"artifacts_max_age_hours": 24
}
}| Key | Default | Description |
|---|---|---|
enabled |
true |
Run the background janitor in long-lived processes |
interval_minutes |
60 |
Minutes between automatic sweeps |
sessions_max_age_days |
30 |
Delete sessions older than this |
audit_max_age_days |
14 |
Delete prompt-injection audit records older than this |
log_max_mb |
50 |
Rotate logs larger than this |
runtime_log_max_age_hours |
168 |
Remove older timestamped runtime records from current and backup logs; 0 keeps them indefinitely |
plans_max_age_days |
30 |
Delete plans older than this |
artifacts_max_age_hours |
24 |
Sweep sub-agent artifact task dirs older than this, in any parent (including unfiled; emptied parent dirs are pruned; 0 keeps them forever; live removal still happens on session delete) |
The maintenance config is operator-only: like provider / providers,
and the dangerous section, it is honored from ~/.odek/config.json (and process
environment) but ignored from a project-level ./odek.json, so a checked-
out repository cannot disable the janitor or relax its own retention.
odek cleanup runs one sweep immediately, using the same resolved config as
the background janitor, and prints a per-category report:
$ odek cleanup
Cleanup complete:
sessions removed: 12
audit records removed: 34
plans removed: 2
artifacts removed: 3
media freed: 48.2 MB
log rotated: /home/you/.odek/schedule.log
When there is nothing to do it prints a single quiet line:
Storage is clean — nothing to remove.
odek cleanup --dry-run removes nothing and reports what a sweep would
remove:
$ odek cleanup --dry-run
Dry run — nothing removed. Would remove:
sessions: 12
audit records: 34
plans: 2
artifact subtree: /home/you/.odek/artifacts/20260101-abc
log rotated: /home/you/.odek/schedule.log
Like odek session cleanup, the command deletes data without a confirmation
prompt — it is a local, operator-invoked command. Use --dry-run first if
you want to inspect the candidate list.
- CLI.md — command reference
- SCHEDULES.md — the schedule daemon (hosts the janitor)
- CONFIG.md — full configuration reference
internal/session— session files are also capped at 32 MiB at write time: an oversized transcript is trimmed (oldest turns first, keeping the system message and the most recent turns) so it never becomes unloadable.
See Runtime logging for runtime log expiration, correlation, and rotation semantics.