Skip to content

Commit 7731405

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 74c882d commit 7731405

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",
@@ -182,7 +183,20 @@ export namespace Agent {
182183
tools: {},
183184
builtIn: false,
184185
}
185-
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
186+
const {
187+
name,
188+
model,
189+
prompt,
190+
tools,
191+
description,
192+
temperature,
193+
top_p,
194+
mode,
195+
permission,
196+
color,
197+
maxSteps,
198+
...extra
199+
} = value
186200
item.options = {
187201
...item.options,
188202
...extra,
@@ -205,6 +219,7 @@ export namespace Agent {
205219
if (color) item.color = color
206220
// just here for consistency & to prevent it from being added as an option
207221
if (name) item.name = name
222+
if (maxSteps != undefined) item.maxSteps = maxSteps
208223

209224
if (permission ?? cfg.permission) {
210225
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
@@ -375,6 +375,12 @@ export namespace Config {
375375
.regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color format")
376376
.optional()
377377
.describe("Hex color code for the agent (e.g., #FF5733)"),
378+
maxSteps: z
379+
.number()
380+
.int()
381+
.positive()
382+
.optional()
383+
.describe("Maximum number of agentic iterations before forcing text-only response"),
378384
permission: z
379385
.object({
380386
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"
@@ -434,6 +435,8 @@ export namespace SessionPrompt {
434435

435436
// normal processing
436437
const agent = await Agent.get(lastUser.agent)
438+
const maxSteps = agent.maxSteps ?? Infinity
439+
const isLastStep = step >= maxSteps
437440
msgs = insertReminders({
438441
messages: msgs,
439442
agent,
@@ -472,6 +475,7 @@ export namespace SessionPrompt {
472475
modelID: model.info.id,
473476
agent,
474477
system: lastUser.system,
478+
isLastStep,
475479
})
476480
const tools = await resolveTools({
477481
agent,
@@ -565,6 +569,7 @@ export namespace SessionPrompt {
565569
stopWhen: stepCountIs(1),
566570
temperature: params.temperature,
567571
topP: params.topP,
572+
toolChoice: isLastStep ? "none" : undefined,
568573
messages: [
569574
...system.map(
570575
(x): ModelMessage => ({
@@ -587,6 +592,14 @@ export namespace SessionPrompt {
587592
return false
588593
}),
589594
),
595+
...(isLastStep
596+
? [
597+
{
598+
role: "assistant" as const,
599+
content: MAX_STEPS,
600+
},
601+
]
602+
: []),
590603
],
591604
tools: model.info.tool_call === false ? undefined : tools,
592605
model: wrapLanguageModel({
@@ -647,6 +660,7 @@ export namespace SessionPrompt {
647660
agent: Agent.Info
648661
providerID: string
649662
modelID: string
663+
isLastStep?: boolean
650664
}) {
651665
let system = SystemPrompt.header(input.providerID)
652666
system.push(
@@ -658,6 +672,11 @@ export namespace SessionPrompt {
658672
)
659673
system.push(...(await SystemPrompt.environment()))
660674
system.push(...(await SystemPrompt.custom()))
675+
676+
if (input.isLastStep) {
677+
system.push(MAX_STEPS)
678+
}
679+
661680
// max 2 system prompt messages for caching purposes
662681
const [first, ...rest] = system
663682
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
@@ -900,6 +900,10 @@ export type AgentConfig = {
900900
* Hex color code for the agent (e.g., #FF5733)
901901
*/
902902
color?: string
903+
/**
904+
* Maximum number of agentic iterations before forcing text-only response
905+
*/
906+
maxSteps?: number
903907
permission?: {
904908
edit?: "ask" | "allow" | "deny"
905909
bash?:
@@ -920,6 +924,7 @@ export type AgentConfig = {
920924
}
921925
| boolean
922926
| ("subagent" | "primary" | "all")
927+
| number
923928
| {
924929
edit?: "ask" | "allow" | "deny"
925930
bash?:
@@ -1477,6 +1482,7 @@ export type Agent = {
14771482
options: {
14781483
[key: string]: unknown
14791484
}
1485+
maxSteps?: number
14801486
}
14811487

14821488
export type McpStatusConnected = {

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

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

258258
---
259259

260+
### Max steps
261+
262+
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.
263+
264+
If this is not set, the agent will continue to iterate until the model chooses to stop or the user interrupts the session.
265+
266+
```json title="opencode.json"
267+
{
268+
"agent": {
269+
"general": {
270+
"maxSteps": 10
271+
},
272+
"plan": {
273+
"maxSteps": 10
274+
},
275+
"build": {
276+
"maxSteps": 20
277+
}
278+
}
279+
}
280+
```
281+
282+
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.
283+
284+
---
285+
260286
### Disable
261287

262288
Set to `true` to disable the agent.

0 commit comments

Comments
 (0)