42 lines
2.6 KiB
Markdown
42 lines
2.6 KiB
Markdown
# Implementation: add-outputs-retention
|
|||
|
|
|
||
|
|
## SCOPE
|
||
|
|
|
||
|
|
Add `loop.json` `outputs.retention` field (default 20) to bound growth of the `outputs/` directory. GC runs after step 10 inside `_loop_lock`, deleting tick groups older than the retention window. Source: `add-loop-runner/BUG_REPORT.md` O5.
|
||
|
|
|
||
|
|
## FILES TOUCHED
|
||
|
|
|
||
|
|
- `scripts/loop-runner.py`
|
||
|
|
- Added `_get_retention(cfg) -> int`: reads `cfg.get("outputs", {}).get("retention", 20)`. Non-int types fall back to 20 with WARNING. Negative values are coerced to 0 (unlimited) with WARNING.
|
||
|
|
- Added `_gc_outputs(loop_path, retention)`: lists `outputs/`, finds max tick index from filenames matching `^tick(\d+)-`, computes `cutoff = max_seen - retention + 1`, deletes files with tick index < cutoff. Non-tick files (`README.txt`, etc.) are preserved. Errors logged as WARNING via `_append_tick_log` and swallowed.
|
||
|
|
- Modified `cmd_tick`: calls `_get_retention(cfg)` + `_gc_outputs(loop_path, retention)` after step 10 (`_write_state_loop`) and before step 11 (tick log), inside the `_loop_lock` block.
|
||
|
|
- Updated docstring step list: added `10.5. GC outputs/...`.
|
||
|
|
- `templates/loops/self-improvement/loop.json`
|
||
|
|
- Added `"outputs": {"retention": 20}` block.
|
||
|
|
|
||
|
|
## BUG FOUND AND FIXED INLINE
|
||
|
|
|
||
|
|
**Off-by-one in GC formula**: the initial implementation used `cutoff = max_seen - retention`, which kept `retention + 1` tick groups (21 instead of 20 for retention=20). Fixed to `cutoff = max_seen - retention + 1`. Test `test_gc_keeps_recent_deletes_old` caught this (expected 20 kept, got 21 remaining → obvious failure when the remaining-count length check triggered).
|
||
|
|
|
||
|
|
## DECISIONS LOCKED
|
||
|
|
|
||
|
|
- **D-O1**: retention counts tick GROUPS (all `tick{N}-*` files), not individual files.
|
||
|
|
- **D-O2**: GC runs INSIDE `_loop_lock` critical section (after state write, before tick log).
|
||
|
|
- **D-O3**: Default 20.
|
||
|
|
- **D-O4**: 0 = unlimited (no GC). Negative coerces to 0 with WARNING.
|
||
|
|
- **D-O5**: GC based on `outputs/` filenames (`max_seen`), NOT `state.iteration_count`.
|
||
|
|
- **D-O6**: Regex `^tick(\d+)-`. Non-matching files preserved.
|
||
|
|
- **D-O7**: GC failure → WARNING log + swallow.
|
||
|
|
|
||
|
|
## TESTS
|
||
|
|
|
||
|
|
New file `tests/test_outputs_retention.py` — 13 tests across 2 classes:
|
||
|
|
|
||
|
|
- `TestGetRetention` (5): default `main`, explicit value, negative→0, non-int→20, None cfg→20.
|
||
|
|
- `TestGcOutputs` (8): deletes old keeps recent, retention=0 skip, retention>count, missing dir, non-tick files preserved, unrelated `tick-foo` prefix preserved, single tick, error path.
|
||
|
|
|
||
|
|
## TEST COUNT
|
||
|
|
|
||
|
|
- Baseline: 469 passed (post-`harden-parse-verdict`).
|
||
|
|
- New: +13 in `tests/test_outputs_retention.py`.
|
||
|
|
- Final: **482 passed**, 0 regressions.
|