P4c: remaining batteries — checkpoint + schedule + critic
executus CI / test (push) Failing after 1m6s
executus CI / test (push) Failing after 1m6s
Completes the P4 battery set (squashed onto main from phase-4c-batteries). - checkpoint/: run.Checkpointer durable-resume (CheckpointStore + throttled handle + Memory). - schedule/: generic cron Runner (Tick/Loop; no cron grammar of its own). - critic/: two-tier timeout watchdog (run.Critic) + Escalator policy seam + ExtendOnce default. Includes the verified gadfly #6 fixes (ExtendOnce per-run, Kill-sticky, watch panic-recovery; checkpoint throttle-after-success; schedule Next-before-Run + nil-guard + Loop recovery). P4 battery set complete: audit, budget, persona, skill, checkpoint, schedule, critic — each nil-safe, each with a default, each core-import-clean. Executor wiring for Critic/Checkpointer remains a P2 follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/executus/run"
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
||||
)
|
||||
|
||||
func TestHandleSaveCompleteDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mem := NewMemory()
|
||||
meta := RunCheckpointMeta{RunID: "r1", AgentID: "a1", CallerID: "c1"}
|
||||
cp := New(mem, meta, 0, nil) // throttle 0 = save every call
|
||||
|
||||
if err := cp.Save(ctx, run.RunCheckpointState{Messages: []llm.Message{{Role: "user"}}, Iteration: 2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, _ := mem.Load(ctx, "r1")
|
||||
if got == nil || got.Iteration != 2 || got.Meta.AgentID != "a1" {
|
||||
t.Fatalf("checkpoint not persisted: %+v", got)
|
||||
}
|
||||
if il, _ := mem.ListInterrupted(ctx); len(il) != 1 {
|
||||
t.Errorf("ListInterrupted = %d, want 1 (in-flight)", len(il))
|
||||
}
|
||||
// Complete clears it (no longer a recovery candidate).
|
||||
if err := cp.Complete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if il, _ := mem.ListInterrupted(ctx); len(il) != 0 {
|
||||
t.Errorf("after Complete, ListInterrupted = %d, want 0", len(il))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleThrottle(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mem := NewMemory()
|
||||
now := time.Now()
|
||||
cp := New(mem, RunCheckpointMeta{RunID: "r"}, time.Minute, func() time.Time { return now })
|
||||
|
||||
cp.Save(ctx, run.RunCheckpointState{Iteration: 1})
|
||||
now = now.Add(10 * time.Second) // within throttle window
|
||||
cp.Save(ctx, run.RunCheckpointState{Iteration: 2})
|
||||
if got, _ := mem.Load(ctx, "r"); got.Iteration != 1 {
|
||||
t.Errorf("throttled save should keep iteration 1, got %d", got.Iteration)
|
||||
}
|
||||
now = now.Add(time.Minute) // past throttle
|
||||
cp.Save(ctx, run.RunCheckpointState{Iteration: 3})
|
||||
if got, _ := mem.Load(ctx, "r"); got.Iteration != 3 {
|
||||
t.Errorf("post-throttle save should land iteration 3, got %d", got.Iteration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilStoreNoop(t *testing.T) {
|
||||
cp := New(nil, RunCheckpointMeta{RunID: "r"}, 0, nil)
|
||||
if err := cp.Save(context.Background(), run.RunCheckpointState{}); err != nil {
|
||||
t.Errorf("nil-store Save should be a no-op, got %v", err)
|
||||
}
|
||||
if err := cp.Complete(context.Background()); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user