Files
clawgo/pkg/tools/subagent_budget_test.go
2026-04-13 13:41:01 +08:00

124 lines
3.4 KiB
Go

package tools
import (
"context"
"errors"
"testing"
"time"
"github.com/YspCoder/clawgo/pkg/providers"
)
func TestSubagentRunPreservesRemainingIterationBudgetAcrossRetry(t *testing.T) {
t.Parallel()
manager := NewSubagentManager(nil, t.TempDir(), nil)
attempts := 0
manager.SetRunFunc(func(ctx context.Context, run *SubagentRun) (string, error) {
attempts++
budget, ok := SubagentIterationBudget(ctx)
if !ok {
t.Fatal("expected subagent iteration budget in context")
}
if attempts == 1 {
if budget != 3 {
t.Fatalf("expected first attempt budget 3, got %d", budget)
}
RecordSubagentExecutionStats(ctx, SubagentExecutionStats{
Iterations: 2,
Attempts: 1,
FailureCode: "stream_failed",
})
return "", providers.NewProviderExecutionError("stream_failed", "stream failed", "stream", true, "test")
}
if budget != 1 {
t.Fatalf("expected retry to inherit remaining budget 1, got %d", budget)
}
RecordSubagentExecutionStats(ctx, SubagentExecutionStats{
Iterations: 1,
Attempts: 1,
})
return "done", nil
})
run, err := manager.SpawnRun(context.Background(), SubagentSpawnOptions{
Task: "finish task",
AgentID: "tester",
MaxRetries: 1,
RetryBackoff: 1,
MaxToolIterations: 3,
})
if err != nil {
t.Fatalf("spawn run: %v", err)
}
waitCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
finalRun, _, err := manager.waitRun(waitCtx, run.ID)
if err != nil {
t.Fatalf("wait run: %v", err)
}
if finalRun.Status != RuntimeStatusCompleted {
t.Fatalf("expected completed run, got %+v", finalRun)
}
if finalRun.IterationCount != 3 {
t.Fatalf("expected 3 consumed iterations, got %d", finalRun.IterationCount)
}
if finalRun.AttemptCount != 2 {
t.Fatalf("expected 2 attempts, got %d", finalRun.AttemptCount)
}
events, err := manager.Events(run.ID, 10)
if err != nil {
t.Fatalf("events: %v", err)
}
if len(events) == 0 {
t.Fatal("expected persisted events")
}
foundFailure := false
for _, evt := range events {
if evt.Type == "attempt_failed" && evt.FailureCode == "stream_failed" {
foundFailure = true
}
}
if !foundFailure {
t.Fatalf("expected stream_failed event, got %#v", events)
}
}
func TestSubagentRunStopsWhenIterationBudgetExhausted(t *testing.T) {
t.Parallel()
manager := NewSubagentManager(nil, t.TempDir(), nil)
manager.SetRunFunc(func(ctx context.Context, run *SubagentRun) (string, error) {
RecordSubagentExecutionStats(ctx, SubagentExecutionStats{Iterations: 2, Attempts: 1})
return "", errors.New("max tool iterations exceeded")
})
run, err := manager.SpawnRun(context.Background(), SubagentSpawnOptions{
Task: "exhaust budget",
AgentID: "tester",
MaxRetries: 2,
RetryBackoff: 1,
MaxToolIterations: 2,
})
if err != nil {
t.Fatalf("spawn run: %v", err)
}
waitCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
finalRun, _, err := manager.waitRun(waitCtx, run.ID)
if err != nil {
t.Fatalf("wait run: %v", err)
}
if finalRun.Status != RuntimeStatusFailed {
t.Fatalf("expected failed run, got %+v", finalRun)
}
if finalRun.LastFailureCode != "retry_limit" {
t.Fatalf("expected retry_limit failure code, got %q", finalRun.LastFailureCode)
}
if finalRun.IterationCount != 2 {
t.Fatalf("expected consumed iterations to stay at 2, got %d", finalRun.IterationCount)
}
}