Skip to content

Commit 13e17a0

Browse files
author
Test
committed
Agent cost control: Fail fast on OpenRouter free models and add hard timeouts
- Disables tool capabilities for models with ':free' or '-free' suffix to prevent planning failures - Limits conductor to 5 consecutive execution errors before aborting (prevents infinite retry loops) - Adds a 25-minute context timeout to the executor to guarantee the process dies and the machine is auto-destroyed on Fly.io
1 parent 98b345c commit 13e17a0

File tree

3 files changed

+24
-3
lines changed

3 files changed

+24
-3
lines changed

cmd/gptcode/agent.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"os/exec"
88
"strings"
9+
"time"
910

1011
"github.com/spf13/cobra"
1112

@@ -157,7 +158,8 @@ Please discover what failed by examining the code, tests, and standard CI config
157158
(such as npm test, mix test, go test, etc.) and write a fix. Once fixed, create a PR.`, branch, sha)
158159
}
159160

160-
ctx := context.Background()
161+
timeoutCtx, cancel := context.WithTimeout(context.Background(), 25*time.Minute)
162+
defer cancel()
161163

162164
backendName := "openrouter"
163165
baseURL := "https://openrouter.ai/api/v1"
@@ -195,7 +197,7 @@ Please discover what failed by examining the code, tests, and standard CI config
195197
executor := modes.NewAutonomousExecutorWithLive(provider, ".", queryModel, language, liveClient, reportConfig, backendName)
196198

197199
fmt.Println("🤖 Starting AutoFix process...")
198-
err = executor.Execute(ctx, prompt)
200+
err = executor.Execute(timeoutCtx, prompt)
199201
if err != nil {
200202
fmt.Printf("❌ AutoFix failed: %v\n", err)
201203
return err

internal/config/model_selector.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ func (ms *ModelSelector) loadCatalog() error {
167167
}
168168
}
169169

170+
// Special case: OpenRouter free models typically lack tool support
171+
if strings.Contains(modelIDLower, ":free") || strings.Contains(modelIDLower, "-free") || strings.Contains(modelNameLower, " free") {
172+
model.Capabilities.SupportsTools = false
173+
model.Capabilities.SupportsFileOperations = false
174+
}
175+
170176
if caps, ok := modelMap["capabilities"].(map[string]interface{}); ok {
171177
if val, ok := caps["supports_tools"].(bool); ok {
172178
model.Capabilities.SupportsTools = val

internal/maestro/conductor.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ func (c *Conductor) ExecuteTask(ctx context.Context, task string, complexity str
288288
fmt.Fprintf(os.Stderr, "[MAESTRO] LoopDetector initialized with intent=%s\n", intent)
289289
}
290290

291+
consecutiveErrors := 0
292+
291293
for {
292294
// Check if we should continue (intent-aware limits + loop detection)
293295
shouldContinue, stopReason := c.loopDetector.ShouldContinue()
@@ -335,8 +337,17 @@ func (c *Conductor) ExecuteTask(ctx context.Context, task string, complexity str
335337
c.ReportProgress("editing", fmt.Sprintf("Completed - %d files changed", len(modifiedFiles)))
336338
c.selector.RecordUsage(editBackend, editModel, err == nil, errorMsg(err))
337339
if err != nil {
340+
consecutiveErrors++
341+
if consecutiveErrors >= 5 {
342+
if os.Getenv("GPTCODE_DEBUG") == "1" {
343+
fmt.Fprintf(os.Stderr, "[MAESTRO] Aborting after %d consecutive execution errors\n", consecutiveErrors)
344+
}
345+
c.ReportError("execution", fmt.Sprintf("Aborted: %d consecutive execution errors. Last error: %v", consecutiveErrors, err))
346+
return fmt.Errorf("task aborted: %d consecutive execution errors: %w", consecutiveErrors, err)
347+
}
348+
338349
// LoopDetector will handle max iterations check on next iteration
339-
fmt.Printf("[WARNING] Execution error: %v\n", err)
350+
fmt.Printf("[WARNING] Execution error (%d/5): %v\n", consecutiveErrors, err)
340351

341352
// Use enhanced recovery system
342353
recoveryCtx := &RecoveryContext{
@@ -374,6 +385,8 @@ func (c *Conductor) ExecuteTask(ctx context.Context, task string, complexity str
374385
continue
375386
}
376387

388+
consecutiveErrors = 0
389+
377390
// Record execution metrics
378391
if c.Tracer != nil {
379392
metrics := observability.Metrics{

0 commit comments

Comments
 (0)