feat(agentic): schedule plan retention cleanup

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 14:31:46 +00:00
parent eab30f578e
commit 88f698a608
3 changed files with 66 additions and 0 deletions

View file

@ -3,6 +3,7 @@
package agentic
import (
"context"
"sort"
"time"
@ -10,6 +11,7 @@ import (
)
const planRetentionDefaultDays = 90
const planRetentionScheduleInterval = 24 * time.Hour
type PlanCleanupOutput struct {
Success bool `json:"success"`
@ -55,6 +57,26 @@ func (s *PrepSubsystem) cmdPlanCleanup(options core.Options) core.Result {
return core.Result{Value: output, OK: true}
}
// ctx, cancel := context.WithCancel(context.Background())
// go s.runPlanCleanupLoop(ctx, time.Minute)
func (s *PrepSubsystem) runPlanCleanupLoop(ctx context.Context, interval time.Duration) {
if ctx == nil || interval <= 0 {
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.planCleanup(core.NewOptions())
}
}
}
func (s *PrepSubsystem) planCleanup(options core.Options) core.Result {
days := planRetentionDays(options)
if days <= 0 {

View file

@ -3,6 +3,7 @@
package agentic
import (
"context"
"testing"
"time"
@ -141,3 +142,43 @@ func TestPlanRetention_PlanArchivedAt_Good_FallsBackToFileModifiedTime(t *testin
_, ok := stat.Value.(interface{ ModTime() time.Time })
assert.True(t, ok)
}
func TestPlanRetention_RunPlanCleanupLoop_Good_DeletesExpiredPlans(t *testing.T) {
dir := t.TempDir()
t.Setenv("CORE_WORKSPACE", dir)
s := newTestPrep(t)
plan := &Plan{
ID: "scheduled-plan-abc123",
Title: "Scheduled Plan",
Status: "archived",
Objective: "Remove me on the next retention pass",
ArchivedAt: time.Now().AddDate(0, 0, -100),
}
_, err := writePlan(PlansRoot(), plan)
require.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() {
s.runPlanCleanupLoop(ctx, time.Millisecond)
close(done)
}()
require.Eventually(t, func() bool {
return !fs.Exists(core.JoinPath(PlansRoot(), "scheduled-plan-abc123.json"))
}, time.Second, 5*time.Millisecond)
cancel()
require.Eventually(t, func() bool {
select {
case <-done:
return true
default:
return false
}
}, time.Second, 5*time.Millisecond)
}

View file

@ -257,6 +257,9 @@ func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
c.Action("agentic.complete", s.handleComplete).Description = "Run completion pipeline (QA → PR → Verify → Ingest → Poke) in background"
s.hydrateWorkspaces()
if planRetentionDays(core.NewOptions()) > 0 {
go s.runPlanCleanupLoop(ctx, planRetentionScheduleInterval)
}
c.RegisterQuery(s.handleWorkspaceQuery)