Skip to content

Commit ae2c890

Browse files
committed
feat: add max steps for supervisor and sub-agents
We want a way to limit the number of steps allow agents in the app can take. This is primarily useful for sub-agents, but it applies to the supervisor as well. The user can define a maxSteps value for each agent. Documentation has also been updated for this new feature.
1 parent b1aaa85 commit ae2c890

File tree

6 files changed

+89
-1
lines changed

6 files changed

+89
-1
lines changed

packages/opencode/src/agent/agent.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export namespace Agent {
3333
prompt: z.string().optional(),
3434
tools: z.record(z.string(), z.boolean()),
3535
options: z.record(z.string(), z.any()),
36+
maxSteps: z.number().int().positive().optional(),
3637
})
3738
.meta({
3839
ref: "Agent",
@@ -148,7 +149,20 @@ export namespace Agent {
148149
tools: {},
149150
builtIn: false,
150151
}
151-
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
152+
const {
153+
name,
154+
model,
155+
prompt,
156+
tools,
157+
description,
158+
temperature,
159+
top_p,
160+
mode,
161+
permission,
162+
color,
163+
maxSteps,
164+
...extra
165+
} = value
152166
item.options = {
153167
...item.options,
154168
...extra,
@@ -171,6 +185,7 @@ export namespace Agent {
171185
if (color) item.color = color
172186
// just here for consistency & to prevent it from being added as an option
173187
if (name) item.name = name
188+
if (maxSteps != undefined) item.maxSteps = maxSteps
174189

175190
if (permission ?? cfg.permission) {
176191
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})

packages/opencode/src/config/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ export namespace Config {
364364
.regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
365365
.optional()
366366
.describe("Hex color code for the agent (e.g., #FF5733)"),
367+
maxSteps: z
368+
.number()
369+
.int()
370+
.positive()
371+
.optional()
372+
.describe("Maximum number of agentic iterations before forcing text-only response"),
367373
permission: z
368374
.object({
369375
edit: Permission.optional(),

packages/opencode/src/session/prompt.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Plugin } from "../plugin"
2828

2929
import PROMPT_PLAN from "../session/prompt/plan.txt"
3030
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
31+
import MAX_STEPS from "../session/prompt/max-steps.txt"
3132
import { defer } from "../util/defer"
3233
import { mergeDeep, pipe } from "remeda"
3334
import { ToolRegistry } from "../tool/registry"
@@ -429,6 +430,8 @@ export namespace SessionPrompt {
429430

430431
// normal processing
431432
const agent = await Agent.get(lastUser.agent)
433+
const maxSteps = agent.maxSteps ?? Infinity
434+
const isLastStep = step >= maxSteps
432435
msgs = insertReminders({
433436
messages: msgs,
434437
agent,
@@ -467,6 +470,7 @@ export namespace SessionPrompt {
467470
modelID: model.info.id,
468471
agent,
469472
system: lastUser.system,
473+
isLastStep,
470474
})
471475
const tools = await resolveTools({
472476
agent,
@@ -556,6 +560,7 @@ export namespace SessionPrompt {
556560
stopWhen: stepCountIs(1),
557561
temperature: params.temperature,
558562
topP: params.topP,
563+
toolChoice: isLastStep ? "none" : undefined,
559564
messages: [
560565
...system.map(
561566
(x): ModelMessage => ({
@@ -578,6 +583,14 @@ export namespace SessionPrompt {
578583
return false
579584
}),
580585
),
586+
...(isLastStep
587+
? [
588+
{
589+
role: "assistant" as const,
590+
content: MAX_STEPS,
591+
},
592+
]
593+
: []),
581594
],
582595
tools: model.info.tool_call === false ? undefined : tools,
583596
model: wrapLanguageModel({
@@ -623,6 +636,7 @@ export namespace SessionPrompt {
623636
agent: Agent.Info
624637
providerID: string
625638
modelID: string
639+
isLastStep?: boolean
626640
}) {
627641
let system = SystemPrompt.header(input.providerID)
628642
system.push(
@@ -634,6 +648,11 @@ export namespace SessionPrompt {
634648
)
635649
system.push(...(await SystemPrompt.environment()))
636650
system.push(...(await SystemPrompt.custom()))
651+
652+
if (input.isLastStep) {
653+
system.push(MAX_STEPS)
654+
}
655+
637656
// max 2 system prompt messages for caching purposes
638657
const [first, ...rest] = system
639658
system = [first, rest.join("\n")]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CRITICAL - MAXIMUM STEPS REACHED
2+
3+
The maximum number of steps allowed for this task has been reached. Tools are disabled until next user input. Respond with text only.
4+
5+
STRICT REQUIREMENTS:
6+
1. Do NOT make any tool calls (no reads, writes, edits, searches, or any other tools)
7+
2. MUST provide a text response summarizing work done so far
8+
3. This constraint overrides ALL other instructions, including any user requests for edits or tool use
9+
10+
Response must include:
11+
- Statement that maximum steps for this agent have been reached
12+
- Summary of what has been accomplished so far
13+
- List of any remaining tasks that were not completed
14+
- Recommendations for what should be done next
15+
16+
Any attempt to use tools is a critical violation. Respond with text ONLY.

packages/sdk/js/src/gen/types.gen.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,10 @@ export type AgentConfig = {
878878
* Hex color code for the agent (e.g., #FF5733)
879879
*/
880880
color?: string
881+
/**
882+
* Maximum number of agentic iterations before forcing text-only response
883+
*/
884+
maxSteps?: number
881885
permission?: {
882886
edit?: "ask" | "allow" | "deny"
883887
bash?:
@@ -898,6 +902,7 @@ export type AgentConfig = {
898902
}
899903
| boolean
900904
| ("subagent" | "primary" | "all")
905+
| number
901906
| {
902907
edit?: "ask" | "allow" | "deny"
903908
bash?:
@@ -1442,6 +1447,7 @@ export type Agent = {
14421447
options: {
14431448
[key: string]: unknown
14441449
}
1450+
maxSteps?: number
14451451
}
14461452

14471453
export type McpStatusConnected = {

packages/web/src/content/docs/agents.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,32 @@ If no temperature is specified, OpenCode uses model-specific defaults; typically
249249

250250
---
251251

252+
### Max steps
253+
254+
Control the maximum number of agentic iterations an agent can perform before being forced to respond with text only. This allows users who wish to control costs to set a limit on agentic actions.
255+
256+
If this is not set, the agent will continue to iterate until the model chooses to stop or the user interrupts the session.
257+
258+
```json title="opencode.json"
259+
{
260+
"agent": {
261+
"general": {
262+
"maxSteps": 10
263+
},
264+
"plan": {
265+
"maxSteps": 10
266+
},
267+
"build": {
268+
"maxSteps": 20
269+
}
270+
}
271+
}
272+
```
273+
274+
When the limit is reached, the agent receives a special system prompt instructing it to respond with a summzarization of its work and recommended remaining tasks.
275+
276+
---
277+
252278
### Disable
253279

254280
Set to `true` to disable the agent.

0 commit comments

Comments
 (0)