package run_test import ( "context" "sync" "testing" "time" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" "gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake" "gitea.stevedudenhoeffer.com/steve/executus/run" "gitea.stevedudenhoeffer.com/steve/executus/tool" ) type fakeCritic struct{ h *fakeCriticHandle } func (c *fakeCritic) Monitor(_ context.Context, _ run.RunInfo, _ time.Duration) run.CriticHandle { return c.h } type fakeCriticHandle struct { mu sync.Mutex steps, tools, stops int steered int } func (h *fakeCriticHandle) RecordStep(int) { h.mu.Lock(); h.steps++; h.mu.Unlock() } func (h *fakeCriticHandle) RecordToolStart(string, string) { h.mu.Lock() h.tools++ h.mu.Unlock() } func (h *fakeCriticHandle) Steer() []llm.Message { h.mu.Lock(); h.steered++; h.mu.Unlock(); return nil } func (h *fakeCriticHandle) Deadline() time.Time { return time.Time{} } // no hard deadline func (h *fakeCriticHandle) Stop() { h.mu.Lock(); h.stops++; h.mu.Unlock() } // TestCriticWired: an agent with Critic.Enabled gets monitored — Monitor returns // a handle the executor feeds (RecordStep), drains (Steer), and stops. func TestCriticWired(t *testing.T) { h := &fakeCriticHandle{} fp := fake.New("fake") fp.Enqueue("m", fake.Reply("done")) m, _ := fp.Model("m") ex := run.New(run.Config{ Registry: tool.NewRegistry(), Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil }, Ports: run.Ports{Critic: &fakeCritic{h: h}}, }) res := ex.Run(context.Background(), run.RunnableAgent{Name: "watched", ModelTier: "m", Critic: run.CriticConfig{Enabled: true}}, tool.Invocation{RunID: "r"}, "go") if res.Err != nil { t.Fatalf("run error: %v", res.Err) } h.mu.Lock() defer h.mu.Unlock() if h.steps < 1 { t.Errorf("critic should have seen >=1 step, got %d", h.steps) } if h.steered < 1 { t.Errorf("critic Steer should be drained at least once, got %d", h.steered) } if h.stops != 1 { t.Errorf("critic Stop should be called exactly once, got %d", h.stops) } } // TestCriticDisabledNotMonitored: Critic.Enabled=false → Monitor never called. func TestCriticDisabledNotMonitored(t *testing.T) { h := &fakeCriticHandle{} fp := fake.New("fake") fp.Enqueue("m", fake.Reply("done")) m, _ := fp.Model("m") ex := run.New(run.Config{ Registry: tool.NewRegistry(), Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil }, Ports: run.Ports{Critic: &fakeCritic{h: h}}, }) ex.Run(context.Background(), run.RunnableAgent{Name: "x", ModelTier: "m"}, // Critic.Enabled=false tool.Invocation{RunID: "r"}, "go") h.mu.Lock() defer h.mu.Unlock() if h.stops != 0 || h.steps != 0 { t.Errorf("disabled critic should not be monitored: steps=%d stops=%d", h.steps, h.stops) } }