feat: session garbage collection (#315)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 14s
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 14s
Add garbage_collect() method to SessionDB that cleans up empty and trivial sessions based on age: - Empty sessions (0 messages) older than 24h - Trivial sessions (1-5 messages) older than 7 days - Sessions with >5 messages kept indefinitely Add `hermes sessions gc` CLI command with: - --empty-hours (default: 24) - --trivial-days (default: 7) - --trivial-max (default: 5) - --source filter - --dry-run preview mode - --yes skip confirmation The dry-run flow: preview what would be deleted, ask for confirmation, then execute. Handles child session FK constraints properly. 7 tests covering: empty/trivial deletion, active session protection, substantial session preservation, dry-run, source filtering, and child session handling. Closes #315
This commit is contained in:
@@ -5004,7 +5004,7 @@ For more help on a command:
|
||||
# =========================================================================
|
||||
sessions_parser = subparsers.add_parser(
|
||||
"sessions",
|
||||
help="Manage session history (list, rename, export, prune, delete)",
|
||||
help="Manage session history (list, rename, export, prune, gc, delete)",
|
||||
description="View and manage the SQLite session store"
|
||||
)
|
||||
sessions_subparsers = sessions_parser.add_subparsers(dest="sessions_action")
|
||||
@@ -5027,6 +5027,14 @@ For more help on a command:
|
||||
sessions_prune.add_argument("--source", help="Only prune sessions from this source")
|
||||
sessions_prune.add_argument("--yes", "-y", action="store_true", help="Skip confirmation")
|
||||
|
||||
sessions_gc = sessions_subparsers.add_parser("gc", help="Garbage-collect empty/trivial sessions")
|
||||
sessions_gc.add_argument("--empty-hours", type=int, default=24, help="Delete empty (0-msg) sessions older than N hours (default: 24)")
|
||||
sessions_gc.add_argument("--trivial-days", type=int, default=7, help="Delete trivial (1-5 msg) sessions older than N days (default: 7)")
|
||||
sessions_gc.add_argument("--trivial-max", type=int, default=5, help="Max messages to consider trivial (default: 5)")
|
||||
sessions_gc.add_argument("--source", help="Only GC sessions from this source")
|
||||
sessions_gc.add_argument("--dry-run", action="store_true", help="Show what would be deleted without deleting")
|
||||
sessions_gc.add_argument("--yes", "-y", action="store_true", help="Skip confirmation")
|
||||
|
||||
sessions_stats = sessions_subparsers.add_parser("stats", help="Show session store statistics")
|
||||
|
||||
sessions_rename = sessions_subparsers.add_parser("rename", help="Set or change a session's title")
|
||||
@@ -5196,6 +5204,49 @@ For more help on a command:
|
||||
size_mb = os.path.getsize(db_path) / (1024 * 1024)
|
||||
print(f"Database size: {size_mb:.1f} MB")
|
||||
|
||||
elif action == "gc":
|
||||
dry_run = getattr(args, "dry_run", False)
|
||||
if dry_run:
|
||||
counts = db.garbage_collect(
|
||||
empty_older_than_hours=args.empty_hours,
|
||||
trivial_max_messages=args.trivial_max,
|
||||
trivial_older_than_days=args.trivial_days,
|
||||
source=args.source,
|
||||
dry_run=True,
|
||||
)
|
||||
print(f"[dry-run] Would delete {counts['total']} session(s):")
|
||||
print(f" Empty (0 msgs, >{args.empty_hours}h old): {counts['empty']}")
|
||||
print(f" Trivial (<={args.trivial_max} msgs, >{args.trivial_days}d old): {counts['trivial']}")
|
||||
else:
|
||||
# Preview first
|
||||
preview = db.garbage_collect(
|
||||
empty_older_than_hours=args.empty_hours,
|
||||
trivial_max_messages=args.trivial_max,
|
||||
trivial_older_than_days=args.trivial_days,
|
||||
source=args.source,
|
||||
dry_run=True,
|
||||
)
|
||||
if preview["total"] == 0:
|
||||
print("Nothing to collect.")
|
||||
else:
|
||||
if not args.yes:
|
||||
if not _confirm_prompt(
|
||||
f"Delete {preview['total']} session(s) "
|
||||
f"({preview['empty']} empty, {preview['trivial']} trivial)? [y/N] "
|
||||
):
|
||||
print("Cancelled.")
|
||||
return
|
||||
counts = db.garbage_collect(
|
||||
empty_older_than_hours=args.empty_hours,
|
||||
trivial_max_messages=args.trivial_max,
|
||||
trivial_older_than_days=args.trivial_days,
|
||||
source=args.source,
|
||||
dry_run=False,
|
||||
)
|
||||
print(f"Collected {counts['total']} session(s):")
|
||||
print(f" Empty: {counts['empty']}")
|
||||
print(f" Trivial: {counts['trivial']}")
|
||||
|
||||
else:
|
||||
sessions_parser.print_help()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user