Skip to content

Commit 84df555

Browse files
committed
docs: storage maintenance guide, CLI/CONFIG updates, AGENTS layout
1 parent 5aa099c commit 84df555

5 files changed

Lines changed: 200 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ cmd/odek/
4848
skill_promote.go `odek skill promote` — clear NeedsReview on a tainted skill
4949
schedule.go `odek schedule` command and scheduler wiring
5050
memory_cmd.go `odek memory` command
51+
cleanup.go `odek cleanup [--dry-run]` one-shot storage sweep + janitor wiring (telegram/serve/schedule daemon)
5152
parallel.go Parallelism helpers
5253
toolctx.go Tool-call context plumbing
5354
security_report_validation_test.go Regression bar for every documented mitigation
@@ -60,6 +61,7 @@ internal/
6061
auth/ Interactive approval system
6162
memory/ MemoryManager (facts, buffer, episodes, merge, scan). EpisodeProvenance — tainted episodes never auto-replayed.
6263
session/ Session store (CRUD, trim, cleanup, compact JSON). AuditStore + divergence heuristic.
64+
maintenance/ Storage-maintenance janitor (session/audit/plan retention, log rotation, media sweep, skill skip-list GC). Config: `maintenance` section (operator-only).
6365
skills/ Skill system (types, loader, triggers, self-improve, curator, import, cache). SkillProvenance gate.
6466
config/ Config file loading, env vars, secrets.env, priority merge
6567
telegram/ Telegram bot: bot.go, poller.go, handler.go, commands.go, session.go, health.go, plan.go, media_path.go
@@ -91,6 +93,7 @@ ReAct cycle: observe → think → act → repeat.
9193
- **Interaction modes** — engaging (narrated), enhance (persistent), verbose (raw), off.
9294
- Max 300 iterations by default.
9395
- **Post-response async processing** — skill learning, episode extraction, and per-turn extended-memory extraction run in background goroutines tracked by `MemoryManager.RunBackground`; `Agent.Close` drains them via `WaitForBackground` (bounded, ~15s) so the work survives CLI exit without hanging `odek run`.
96+
- **Storage maintenance janitor**`maintenance.Start` (internal/maintenance) runs a periodic sweep of `~/.odek` (expired sessions/audit/plans, oversized-log rotation, media sweep, skill skip-list GC) inside `odek telegram`, `odek serve`, and `odek schedule daemon` when `maintenance.enabled` is set; `odek cleanup [--dry-run]` runs the same sweep on demand. Session files are also trimmed at write time (oldest groups first, system message kept) when they would exceed `MaxSessionFileBytes`, so a session can never grow past the load cap. Operator-only config — project `./odek.json` cannot set it. See docs/MAINTENANCE.md.
9497
- **Artifact-aware file search**`search_files` and `multi_grep` skip build/artifact directories (`node_modules`, `vendor`, `.git`, `__pycache__`, `.venv`, etc.) automatically, reducing noise and speeding scans.
9598
- **Semantic session search** — the `session_search` tool uses go-vector RandomProjections + k-NN for semantic similarity search through session content, with a two-tier pipeline: vector index (fast, ~1ms) → deepSearch fallback (exhaustive, slower).
9699
- **Security-first defaults** — the latest hardening closes the high/medium/low findings tracked in `sec_findings.md`: default `non_interactive` is `deny`, project-level `odek.json` cannot redirect backends or hijack delivery, project-level sandbox knobs require explicit operator approval, `~/.odek` trust anchors are protected, WebSocket upgrades require a per-instance CSRF token, and all untrusted content is wrapped before reaching the model. See Security Architecture below for the full list.

docs/CLI.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
| `odek session show [id]` | Show session details (default: latest) |
1414
| `odek session delete <id>` | Delete a session |
1515
| `odek session trim <id> <n>` | Keep only the `n` most recent messages |
16-
| `odek session cleanup <days>` | Delete sessions older than N days |
16+
| `odek session cleanup <days>` | Delete sessions older than N days (see also: automatic storage maintenance below) |
17+
| `odek cleanup [--dry-run]` | One-shot storage sweep of `~/.odek`: expired sessions, audit records, plans, skill skip entries, and oversized-log rotation, per the `[maintenance]` config. `--dry-run` previews without deleting. The same sweep runs automatically in the Telegram bot, `odek serve`, and `odek schedule daemon`. See [MAINTENANCE.md](MAINTENANCE.md) |
1718
| `odek skill list` | List all available skills |
1819
| `odek skill view <name>` | View a skill's full content |
1920
| `odek skill delete <name>` | Delete a skill |
@@ -389,6 +390,12 @@ odek session cleanup 30
389390
# Wipe all sessions
390391
odek session cleanup 0
391392
393+
# Sweep all expired storage (sessions, audit, plans, skips, log rotation)
394+
odek cleanup
395+
396+
# Preview the sweep without deleting anything
397+
odek cleanup --dry-run
398+
392399
# OpenAI
393400
odek run --model gpt-4o --base-url https://api.openai.com/v1 "Explain this code"
394401

docs/CONFIG.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ The `telegram` section configures the Telegram bot integration and the `--delive
650650
| `poll_interval` || 1 | Seconds between poll cycles |
651651
| `poll_timeout` || 30 | Long-poll timeout (1-60 seconds) |
652652
| `max_msg_length` || 4096 | Max characters per message |
653-
| `session_ttl_hours` || 24 | Hours before inactive session expires |
653+
| `session_ttl_hours` || 24 | Hours an inactive chat's session stays in the in-memory cache before being reloaded from disk. This is cache-only — on-disk session expiry is `maintenance.sessions_max_age_days` (see [MAINTENANCE.md](MAINTENANCE.md)) |
654654
| `max_download_size` | `ODEK_TELEGRAM_MAX_DOWNLOAD_SIZE` | 5242880 (5 MiB) | Per-file byte cap for Telegram voice/photo/document downloads. Set to `-1` to disable. |
655655
| `media_quota_per_chat` | `ODEK_TELEGRAM_MEDIA_QUOTA_PER_CHAT` | 0 (disabled) | Total bytes of downloaded media allowed per chat. `0` disables the quota. |
656656
| `log_level` || info | Log level: debug, info, warn, error |
@@ -740,6 +740,46 @@ Project-level `odek.json` cannot set `schedules.dangerous`; configure it via `~/
740740

741741
Full guide: [docs/SCHEDULES.md](SCHEDULES.md).
742742

743+
## Storage maintenance
744+
745+
Configures the background storage janitor (`internal/maintenance`). It runs a
746+
sweep over `~/.odek` every `interval_minutes`: expiring old sessions and
747+
audit records, rotating oversized logs, deleting stale Telegram plans and
748+
downloaded media, and garbage-collecting expired skill skip-list entries.
749+
Every field has an `ODEK_MAINTENANCE_*` environment override.
750+
751+
```json
752+
{
753+
"maintenance": {
754+
"enabled": true,
755+
"interval_minutes": 60,
756+
"sessions_max_age_days": 30,
757+
"audit_max_age_days": 14,
758+
"log_max_mb": 50,
759+
"plans_max_age_days": 30,
760+
"skills_skip_max_age_days": 90
761+
}
762+
}
763+
```
764+
765+
| Field | Env | Default | Description |
766+
|---|---|---|---|
767+
| `enabled` | `ODEK_MAINTENANCE_ENABLED` | `true` | Run the janitor. Set false to disable all storage maintenance. |
768+
| `interval_minutes` | `ODEK_MAINTENANCE_INTERVAL_MINUTES` | `60` | Minutes between sweeps. The first sweep runs after one interval, never at startup. |
769+
| `sessions_max_age_days` | `ODEK_MAINTENANCE_SESSIONS_MAX_AGE_DAYS` | `30` | Delete sessions (and their index/vector-index entries) older than this. `0` = keep forever. |
770+
| `audit_max_age_days` | `ODEK_MAINTENANCE_AUDIT_MAX_AGE_DAYS` | `14` | Delete `~/.odek/sessions/audit/*.json` records older than this. `0` = keep forever. |
771+
| `log_max_mb` | `ODEK_MAINTENANCE_LOG_MAX_MB` | `50` | Rotate `~/.odek/telegram.log` and `~/.odek/schedule.log` larger than this: current log becomes `<name>.1` (one backup generation) and a fresh empty log is started. `0` = no rotation. |
772+
| `plans_max_age_days` | `ODEK_MAINTENANCE_PLANS_MAX_AGE_DAYS` | `30` | Delete Telegram plan files (`~/.odek/plans/**/*.md`) older than this; emptied chat directories are removed. `0` = keep forever. |
773+
| `skills_skip_max_age_days` | `ODEK_MAINTENANCE_SKILLS_SKIP_MAX_AGE_DAYS` | `90` | Remove skill skip-list entries (`~/.odek/skills/.skipped.json`) older than this. `0` = keep forever. |
774+
775+
Downloaded Telegram media (`~/.odek/media/`, including per-chat `chat<id>/`
776+
subdirectories) is always swept after 1 hour; that policy is not configurable.
777+
778+
The `maintenance` section is **operator-only**: it governs deletion of user
779+
data, so the project-level `./odek.json` cannot set it (a `maintenance`
780+
section there is ignored with a stderr warning). Configure it via
781+
`~/.odek/config.json` or the `ODEK_MAINTENANCE_*` environment variables.
782+
743783
## Tool configuration
744784

745785
Control which tools are exposed to the LLM. Use this to deploy locked-down

docs/MAINTENANCE.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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.

docs/SCHEDULES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ running, just without scheduling); if the bot holds it, a standalone
3939
bot's embedded scheduler with `schedules.enabled = false` if you prefer to run
4040
the daemon separately.)
4141

42+
Both also host the storage-maintenance janitor, which expires old sessions,
43+
audit records, and plans and rotates oversized logs — see
44+
[MAINTENANCE.md](MAINTENANCE.md).
45+
4246
---
4347

4448
## Managing jobs

0 commit comments

Comments
 (0)