Skip to content

Commit e8dbab4

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 618c654 commit e8dbab4

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"
@@ -419,6 +420,8 @@ export namespace SessionPrompt {
419420

420421
// normal processing
421422
const agent = await Agent.get(lastUser.agent)
423+
const maxSteps = agent.maxSteps ?? Infinity
424+
const isLastStep = step >= maxSteps
422425
msgs = insertReminders({
423426
messages: msgs,
424427
agent,
@@ -457,6 +460,7 @@ export namespace SessionPrompt {
457460
modelID: model.info.id,
458461
agent,
459462
system: lastUser.system,
463+
isLastStep,
460464
})
461465
const tools = await resolveTools({
462466
agent,
@@ -545,6 +549,7 @@ export namespace SessionPrompt {
545549
stopWhen: stepCountIs(1),
546550
temperature: params.temperature,
547551
topP: params.topP,
552+
toolChoice: isLastStep ? "none" : undefined,
548553
messages: [
549554
...system.map(
550555
(x): ModelMessage => ({
@@ -567,6 +572,14 @@ export namespace SessionPrompt {
567572
return false
568573
}),
569574
),
575+
...(isLastStep
576+
? [
577+
{
578+
role: "assistant" as const,
579+
content: MAX_STEPS,
580+
},
581+
]
582+
: []),
570583
],
571584
tools: model.info.tool_call === false ? undefined : tools,
572585
model: wrapLanguageModel({
@@ -612,6 +625,7 @@ export namespace SessionPrompt {
612625
agent: Agent.Info
613626
providerID: string
614627
modelID: string
628+
isLastStep?: boolean
615629
}) {
616630
let system = SystemPrompt.header(input.providerID)
617631
system.push(
@@ -623,6 +637,11 @@ export namespace SessionPrompt {
623637
)
624638
system.push(...(await SystemPrompt.environment()))
625639
system.push(...(await SystemPrompt.custom()))
640+
641+
if (input.isLastStep) {
642+
system.push(MAX_STEPS)
643+
}
644+
626645
// max 2 system prompt messages for caching purposes
627646
const [first, ...rest] = system
628647
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
@@ -865,6 +865,10 @@ export type AgentConfig = {
865865
* Hex color code for the agent (e.g., #FF5733)
866866
*/
867867
color?: string
868+
/**
869+
* Maximum number of agentic iterations before forcing text-only response
870+
*/
871+
maxSteps?: number
868872
permission?: {
869873
edit?: "ask" | "allow" | "deny"
870874
bash?:
@@ -885,6 +889,7 @@ export type AgentConfig = {
885889
}
886890
| boolean
887891
| ("subagent" | "primary" | "all")
892+
| number
888893
| {
889894
edit?: "ask" | "allow" | "deny"
890895
bash?:
@@ -1405,6 +1410,7 @@ export type Agent = {
14051410
options: {
14061411
[key: string]: unknown
14071412
}
1413+
maxSteps?: number
14081414
}
14091415

14101416
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)