mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-08-16 03:44:59 +00:00
Time Travel keeps a hidden git repository per workspace under /a0/usr/.time_travel/workspaces/<id>/repo.git and snapshots on every file change, but ships no retention: nothing deletes a shadow repository when its chat or project is removed (chat_remove never touches .time_travel), there is no delete/prune endpoint in the API or web UI, and a workspace whose 'git add' ever exceeds GIT_TIMEOUT_SECONDS strands repo.git/index.lock, failing every later snapshot with 'index.lock: File exists'. Observed on a live instance: 518 shadow repositories / 12 GB, most belonging to long-deleted chats, plus a permanently wedged workspace. This adds a throttled retention sweep driven from job_loop: - Orphans: live workspace paths (project folders, the configured workdir, per-chat workdirs) are forward-enumerated and hashed with the existing workspace_id_for derivation; a shadow directory matching none of them is unreachable from the UI forever and is removed after a grace window. - Optional age-out: retention_max_age_days (default 0 = keep forever) removes a live workspace's history when it has had no snapshot in N days. Time Travel lazily re-initializes an empty history on the next snapshot, so this is always safe for the feature. - Stale locks: repo.git/index.lock older than retention_stale_lock_minutes is removed (git subprocesses are killed at GIT_TIMEOUT_SECONDS, so no legitimate lock lives that long), un-wedging future snapshots. - Corrupt-repo set-asides (repo.git.invalid*) past the grace window. Deletion is refused for any path outside the shadow root. Settings render in the existing plugin-config pattern (default_config.yaml + webui/config.html, settings_sections: agent): enable toggle, sweep interval, age-out days, orphan grace, stale-lock age. Durable evidence lives next to the workspaces dir: retention.json (running totals + last sweep stamp) and retention.log (one JSON line per sweep naming everything removed, tail-capped at 1000). Sweeps run in a worker thread off the event loop, at most one in flight, default every 6 hours. Tests: tests/test_time_travel_retention.py (config defaults/clamps, the full sweep matrix, keep-forever default, disabled no-op, deletion guard, marker accrual, per-sweep history + cap, throttle, and workspace_id_for parity).
104 lines
5.3 KiB
HTML
104 lines
5.3 KiB
HTML
<html>
|
|
|
|
<head>
|
|
<title>Time Travel</title>
|
|
</head>
|
|
|
|
<body>
|
|
<div x-data>
|
|
<template x-if="config">
|
|
<div>
|
|
<div class="section-title">Time Travel retention</div>
|
|
<div class="section-description">
|
|
Time Travel keeps a hidden history of every workspace it snapshots. Retention
|
|
keeps that storage bounded by cleaning up histories nothing can reach anymore
|
|
(deleted chats and projects) and, optionally, aging out old history.
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Enable retention</div>
|
|
<div class="field-description">
|
|
Periodically clean up orphaned workspace histories, stale snapshot
|
|
locks, and corrupt-repository backups. Turning this off means Time
|
|
Travel storage grows without bound.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<label class="toggle">
|
|
<input type="checkbox" x-model="config.retention_enabled"
|
|
x-init="if (config.retention_enabled === undefined || config.retention_enabled === null) config.retention_enabled = true" />
|
|
<span class="toggler"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Sweep interval (hours)</div>
|
|
<div class="field-description">
|
|
How often the cleanup runs.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" min="1" step="1"
|
|
x-init="if (config.retention_sweep_interval_hours === undefined || config.retention_sweep_interval_hours === null) config.retention_sweep_interval_hours = 6"
|
|
x-model.number="config.retention_sweep_interval_hours" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Delete history older than (days)</div>
|
|
<div class="field-description">
|
|
Leave at 0 (the default) and workspace history is kept forever. Set a
|
|
number — say 30 — and a workspace with no snapshot in that many days
|
|
has its history deleted. Time Travel simply starts a fresh history on
|
|
the workspace's next change.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" min="0" step="1"
|
|
x-init="if (config.retention_max_age_days === undefined || config.retention_max_age_days === null) config.retention_max_age_days = 0"
|
|
x-model.number="config.retention_max_age_days" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Orphan grace period (hours)</div>
|
|
<div class="field-description">
|
|
History belonging to a deleted chat or project is unreachable from the
|
|
UI and gets cleaned up — but only after it has been inactive this long,
|
|
so recent work is never raced.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" min="1" step="1"
|
|
x-init="if (config.retention_orphan_grace_hours === undefined || config.retention_orphan_grace_hours === null) config.retention_orphan_grace_hours = 24"
|
|
x-model.number="config.retention_orphan_grace_hours" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Stale snapshot lock age (minutes)</div>
|
|
<div class="field-description">
|
|
A leftover git index.lock older than this is removed so snapshots stop
|
|
failing with "index.lock: File exists". Time Travel's own git
|
|
operations are killed after 20 seconds, so no legitimate lock lives
|
|
this long.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input type="number" min="5" step="1"
|
|
x-init="if (config.retention_stale_lock_minutes === undefined || config.retention_stale_lock_minutes === null) config.retention_stale_lock_minutes = 30"
|
|
x-model.number="config.retention_stale_lock_minutes" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|