Reasoning

reasoning tunes how hard an agent() route's model thinks, and whether the thinking is shown. It is keyed by provider, and only the block for the route's resolved provider is read.

Quick startCopy link to section: Quick start

src/app/support/[tenant]/index.ts
import { agent } from "@b4run/sdk"
 
export default agent({
  model: "gpt-5-mini",
  reasoning: { openai: { effort: "high", summary: "auto" } },
  systemPrompt: "You are a careful support assistant.",
})

ReasoningConfig, OpenAIReasoningConfig and AnthropicReasoningConfig are exported from @b4run/sdk.

OpenAICopy link to section: OpenAI

ts
reasoning: {
  openai: {
    effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh"
    summary?: "auto" | "concise" | "detailed"
  }
}

effort is the request's reasoning effort. The provider decides which values mean something for each model, and models without reasoning ignore it.

summary asks the Responses API to stream a summary of the model's reasoning. Setting it moves the route to the Responses API (B4.run sets ChatOpenAI's useResponsesApi), and the summary streams to clients as reasoning: AG-UI REASONING_* events, which CopilotKit renders as a reasoning message. Without summary, the model still reasons at the requested effort but nothing is shown.

AnthropicCopy link to section: Anthropic

ts
reasoning: {
  anthropic: {
    budgetTokens: number // a whole number of at least 1024
  }
}

budgetTokens enables extended thinking with that budget (ChatAnthropic's thinking: { type: "enabled", budget_tokens }). The thinking text streams to clients the same way an OpenAI summary does.

What B4.run checksCopy link to section: What B4.run checks

The block must match the route's provider. reasoning.openai on a route that resolves to Anthropic, or either block on a provider with no reasoning controls, fails the route when its model is built, naming the misplaced key. A setting is never silently ignored. An unknown key (including the pre-0.14 flat effort) and a budgetTokens under 1024 fail the same way.

B4.run does not check whether a particular model accepts the setting; that is the provider's error, surfaced as a model-call failure.

Leave out reasoning and B4.run sends nothing, so the provider's default applies.

Where it appliesCopy link to section: Where it applies

Reasoning applies to agent() routes, where @b4run/langchain builds the model.

In workflow, raw graph and chain routes, and for any model you create yourself, set reasoning on that model instance.

GET /agui/{routeId} advertises reasoning: { supported: true, streaming: true, encrypted: false } exactly when the route's config makes reasoning stream, and { supported: false } otherwise. See AG-UI.

Choosing a valueCopy link to section: Choosing a value

Leave it unset until you see that a route needs it.

Use lower effort for routes where speed and cost matter more than careful thinking:

ts
reasoning: { openai: { effort: "minimal" } }

Use higher effort when the route calls many tools, plans a lot, or has to combine several pieces of evidence:

ts
reasoning: { openai: { effort: "high" } }

Show the reasoning (summary, budgetTokens) when users benefit from seeing the plan, such as a research assistant or a long tool loop. Leave it hidden for short answers, where it is noise.

Of course, more reasoning won't make up for missing tools or context. If the agent doesn't have the information, thinking harder won't find it.

SubagentsCopy link to section: Subagents

Each agent() sets its own reasoning, so a parent and child can use different values:

ts
// parent
export default agent({
  model: "gpt-5-mini",
  reasoning: { openai: { effort: "low" } },
  systemPrompt: "Coordinate the work.",
})
 
// child
export default agent({
  model: "gpt-5-mini",
  reasoning: { openai: { effort: "high", summary: "auto" } },
  systemPrompt: "Analyze the evidence carefully.",
})

A child keeps its own setting, whatever the parent uses.