remove maxSteps and ResetsCounter entirely

The step budget mechanism is gone. Agents run until they finish,
are interrupted by the user, or (for sub-agents) hit the timeout.

No replacement. The human is the kill switch.
This commit is contained in:
Levi Neely 2026-08-17 09:48:58 +02:00
parent da0ee4456f
commit 74ba5ae7ac
16 changed files with 27 additions and 79 deletions

View File

@ -30,8 +30,6 @@ type AgentConfig struct {
Tools *bool `json:"tools,omitempty"`
AutoLoad []string `json:"autoLoad,omitempty"`
// MaxSteps caps the number of tool-call rounds per turn. 0 means unlimited.
MaxSteps int `json:"maxSteps,omitempty"`
CompactionModel string `json:"compactionModel,omitempty"`
// SystemPrompt overrides the embedded system prompt with a file path.
SystemPrompt string `json:"systemPrompt,omitempty"`

View File

@ -162,59 +162,31 @@ func (ag *Agent) run(ctx context.Context) error {
}
}
msg := backend.Message{ID: responseID, Role: "assistant", Content: content, Reasoning: reasoning, ToolCalls: toolCalls}
results, interrupted := ag.execToolCalls(ctx, toolCalls)
msg := backend.Message{ID: responseID, Role: "assistant", Content: content, Reasoning: reasoning, ToolCalls: toolCalls}
results, interrupted := ag.execToolCalls(ctx, toolCalls)
h.update(msg, results)
ag.save()
h.update(msg, results)
ag.save()
if interrupted {
return ctx.Err()
}
if len(toolCalls) == 0 {
break
}
if abort := ag.trackErrors(ctx, results, &errs, step); abort != nil {
return abort
}
// Reset step counter when an action-oriented tool succeeds.
if rt.ToolMeta != nil {
for _, r := range results {
if !r.IsError && rt.ToolMeta[r.Name].ResetsCounter {
step = 0
break
}
if interrupted {
return ctx.Err()
}
}
// Step budget warnings.
if rt.MaxSteps > 0 {
halfBudget := rt.MaxSteps / 2
if step == halfBudget {
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-step-budget-warning>\nYou have used %d/%d research steps without taking action. Consider making progress — write code, edit files, or run commands. Action tools reset this counter.\n</system-step-budget-warning>", step, rt.MaxSteps),
}, nil)
} else if step >= rt.MaxSteps-1 {
ag.emit(Event{Role: "maxsteps", Content: fmt.Sprintf("%d", step+1)})
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-step-budget-stop>\nStep budget exhausted (%d/%d steps used). Stop calling tools. Summarize what you have done and what remains, then stop.\n</system-step-budget-stop>", step+1, rt.MaxSteps),
}, nil)
if len(toolCalls) == 0 {
break
}
}
_, compactErr := ag.autoCompact(ctx)
if compactErr != nil {
ag.emit(Event{Role: "error", Content: compactErr.Error()})
break
}
if abort := ag.trackErrors(ctx, results, &errs, step); abort != nil {
return abort
}
step++
}
_, compactErr := ag.autoCompact(ctx)
if compactErr != nil {
ag.emit(Event{Role: "error", Content: compactErr.Error()})
break
}
step++
}
return nil
}

View File

@ -145,7 +145,6 @@ type Runtime struct {
ToolMeta map[string]protocol.ToolInfo
Exec toolExecutor
GenParams backend.GenerationParams
MaxSteps int
CompactionModel string
UserPrompt string // resolved user prompts, prepended to each turn's input
Messages []string
@ -179,7 +178,6 @@ func BuildRuntime(cfg *AgentConfig, srv *toolclient.ToolsrvConn, cwd string, env
var agentPrompt string
var userPrompt string
var genParams backend.GenerationParams
var maxSteps int
if cfg != nil {
if resolved, err := resolvePrompt(cfg.Prompt, cwd, env); err != nil {
fmt.Fprintf(os.Stderr, "resolve prompt: %v\n", err)
@ -192,7 +190,6 @@ func BuildRuntime(cfg *AgentConfig, srv *toolclient.ToolsrvConn, cwd string, env
userPrompt = resolved
}
genParams = cfg.GenerationParams
maxSteps = cfg.MaxSteps
}
genParams.CWD = cwd
@ -235,7 +232,6 @@ func BuildRuntime(cfg *AgentConfig, srv *toolclient.ToolsrvConn, cwd string, env
Exec: exec,
Preamble: preamble,
GenParams: genParams,
MaxSteps: maxSteps,
CompactionModel: compactionModel,
UserPrompt: userPrompt,
Messages: messages,

View File

@ -27,7 +27,6 @@
"lsp_rename",
"lsp_completion"
],
"maxSteps": 25,
"temperature": 0.5,
"maxTokens": 16384
}

View File

@ -41,7 +41,6 @@
],
"backend": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"maxSteps": 25,
"temperature": 0.4,
"maxTokens": 16384
}

View File

@ -26,7 +26,6 @@
"code_rewrite",
"code_dependencies"
],
"maxSteps": 40,
"temperature": 0.3,
"maxTokens": 16384
}

View File

@ -15,7 +15,6 @@
"memory_recall",
"memory_remember"
],
"maxSteps": 20,
"temperature": 0.3,
"maxTokens": 16384
}

View File

@ -28,6 +28,5 @@
"code_dependencies"
],
"temperature": 0.3,
"maxTokens": 8192,
"maxSteps": 30
"maxTokens": 8192
}

View File

@ -23,7 +23,6 @@
"lsp_rename",
"lsp_completion"
],
"maxSteps": 5,
"temperature": 0.3,
"maxTokens": 4096
}

View File

@ -17,7 +17,6 @@
"memory_recall",
"memory_remember"
],
"maxSteps": 20,
"temperature": 0.3,
"maxTokens": 8192
}

View File

@ -17,7 +17,6 @@
"file_grep",
"file_write"
],
"maxSteps": 30,
"temperature": 0.3,
"maxTokens": 8192
}

View File

@ -1,7 +1,6 @@
{
"description": "Replace text in a file. Has fuzzy matching (whitespace/indent flexible).",
"outputFormat": "diff",
"resetsCounter": true,
"prompt": "## file_edit\n\nReplace text in a file. Has fuzzy matching (whitespace/indent flexible).\n\n**Args**: `path`, `old_string`, `new_string` (required); `replace_all` (optional)\n\n```\nfile_edit(path=\"<ABSOLUTE_PATH_FROM_PWD>\", old_string=\"old text\", new_string=\"new text\")\nfile_edit(path=\"<ABSOLUTE_PATH_FROM_PWD>\", old_string=\"old text\", new_string=\"new text\", replace_all=\"true\")\n```\n\n**Constraints**: Absolute paths only. Errors if `old_string` matches multiple locations \u2014 add surrounding context to disambiguate, or use `replace_all=true`.",
"args": {
"type": "object",

View File

@ -1,7 +1,6 @@
{
"description": "Create or overwrite a file. Produces a unified diff.",
"outputFormat": "diff",
"resetsCounter": true,
"prompt": "## file_write\n\nCreate or overwrite a file. Produces a unified diff.\n\n**Args**: `[path, content]`\n\n```\nfile_write(path=\"<ABSOLUTE_PATH_FROM_PWD>\", content=\"file content here\")\n```\n\n**Constraints**: Absolute paths only. Parent directory must exist.",
"args": {
"type": "object",

View File

@ -1,6 +1,5 @@
{
"description": "Execute a single bash command in a sandboxed environment.",
"resetsCounter": true,
"prompt": "Execute a single bash command in a sandboxed environment.\n\nUsage: {\"cmd\": \"your command here\"}\n\nSandbox prevents dangerous operations. Use for computation, builds, scripting.\ntimeout applies to each call (default: 30s). A non-zero exit is an error.",
"args": {
"type": "object",

View File

@ -19,7 +19,6 @@ type MetaFile struct {
Scope string `json:"scope,omitempty"`
OutputFormat string `json:"outputFormat,omitempty"`
Cmd string `json:"cmd,omitempty"`
ResetsCounter bool `json:"resetsCounter,omitempty"`
Variants []Variant `json:"variants,omitempty"`
}
@ -35,7 +34,6 @@ type Variant struct {
Scope string `json:"scope,omitempty"`
OutputFormat string `json:"outputFormat,omitempty"`
Cmd string `json:"cmd,omitempty"`
ResetsCounter bool `json:"resetsCounter,omitempty"`
}
// LoadMetaFile reads and parses a .meta sidecar file for the given tool name.
@ -86,9 +84,6 @@ func (m *MetaFile) Resolve() *MetaFile {
if v.Scope != "" {
resolved.Scope = v.Scope
}
if v.ResetsCounter {
resolved.ResetsCounter = true
}
if v.Cmd != "" {
resolved.Cmd = v.Cmd
}
@ -184,7 +179,6 @@ func ToolInfoFromMeta(name string, m *MetaFile) protocol.ToolInfo {
Tier: tier,
Scope: resolved.Scope,
OutputFormat: resolved.OutputFormat,
ResetsCounter: resolved.ResetsCounter,
}
}

View File

@ -5,15 +5,14 @@ import "encoding/json"
// ToolInfo describes a tool provided by a server.
type ToolInfo struct {
Server string
Name string
Description string
InputSchema json.RawMessage
Prompt string
Tier string
Scope string
OutputFormat string
ResetsCounter bool
Server string
Name string
Description string
InputSchema json.RawMessage
Prompt string
Tier string
Scope string
OutputFormat string
}
// ToolResult is the structured result from a tool execution.