Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/core/src/session/model-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ export const layer = Layer.effect(
.filter((part) => part.length > 0)
.map(SystemPart.make)
const history = toLLMMessages(input.context.messages, resolved.ref, providerMetadataKey)
const messages = stepLimitReached ? [...history, Message.assistant(MAX_STEPS_PROMPT)] : history
// Anthropic rejects synthetic assistant prefills, so append the final-step
// guardrail as a synthetic user message.
const messages = stepLimitReached ? [...history, Message.user(MAX_STEPS_PROMPT)] : history
const registry = new Map(tools.definitions.map((tool) => [tool.name, tool]))
// The definition objects we hand to hooks, mapped back to their tools. Hooks rename a
// tool by moving its definition to a new key; recognizing the object recovers the tool.
Expand Down
18 changes: 10 additions & 8 deletions packages/core/test/session-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { SessionExecution } from "@opencode-ai/core/session/execution"
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
import { SessionRunner } from "@opencode-ai/core/session/runner/index"
import * as SessionRunnerLLM from "@opencode-ai/core/session/runner/llm"
import { MAX_STEPS_PROMPT } from "@opencode-ai/core/session/runner/max-steps"
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
import { PromptCacheDiagnostics } from "@opencode-ai/core/session/prompt-cache-diagnostics"
import { SessionUsage } from "@opencode-ai/core/session/usage"
Expand Down Expand Up @@ -4031,8 +4032,8 @@ describe("SessionRunnerLLM", () => {
// Protocols with native "none" keep these definitions for prompt caching.
expect(requests[1]?.tools.map((tool) => tool.name)).toContain("echo")
expect(requests[1]?.messages.at(-1)).toMatchObject({
role: "assistant",
content: [{ type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }],
role: "user",
content: [{ type: "text", text: MAX_STEPS_PROMPT }],
})
expect(executions).toEqual(["done"])
expect(yield* session.context(sessionID)).toMatchObject([
Expand Down Expand Up @@ -4583,16 +4584,13 @@ describe("SessionRunnerLLM", () => {
expect(requests[0]?.tools.map((tool) => tool.name)).toContain("echo")
expect(requests[1]?.toolChoice).toBeUndefined()
expect(requests[1]?.tools.map((tool) => tool.name)).toContain("echo")
expect(requests[1]?.messages.at(-1)).not.toMatchObject({
role: "assistant",
content: [{ type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }],
})
expect(JSON.stringify(requests[1]?.messages)).not.toContain("MAXIMUM STEPS REACHED")
expect(requests[2]?.toolChoice).toMatchObject({ type: "none" })
// The final step keeps tool definitions to preserve provider prompt caching.
expect(requests[2]?.tools.map((tool) => tool.name)).toContain("echo")
expect(requests[2]?.messages.at(-1)).toMatchObject({
role: "assistant",
content: [{ type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }],
role: "user",
content: [{ type: "text", text: MAX_STEPS_PROMPT }],
})
expect(executions).toEqual(["recovered"])
const eventTypes = yield* recordedEventTypes(sessionID)
Expand Down Expand Up @@ -4946,6 +4944,10 @@ describe("SessionRunnerLLM", () => {
expect(requests).toHaveLength(2)
expect(requests[0]?.toolChoice).toBeUndefined()
expect(requests[1]?.toolChoice).toMatchObject({ type: "none" })
expect(requests[1]?.messages.at(-1)).toMatchObject({
role: "user",
content: [{ type: "text", text: MAX_STEPS_PROMPT }],
})
expect((yield* recordedEventTypes(sessionID)).filter((type) => type === "session.tool.failed.2")).toHaveLength(2)
}),
)
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/content/docs/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ If this is not set, the agent will continue to iterate until the model chooses t
}
```

When the limit is reached, the agent receives a special system prompt instructing it to respond with a summarization of its work and recommended remaining tasks.
When the limit is reached, the agent receives a special user message instructing it to respond with a summarization of its work and recommended remaining tasks.

:::caution
The legacy `maxSteps` field is deprecated. Use `steps` instead.
Expand Down