Skip to content

Reasoning ​

Show what a reasoning model is thinking while it answers, and let the caller say how hard it may think.

Available from @cognipeer/agent-server 0.6.0

stream.thinking, AgentInfo.showReasoning and the reasoningEffort request field arrive in 0.6.0. Servers on 0.4.0 or older ignore reasoningEffort in a request and never send thinking frames.

Three pieces work together:

PieceWhereWhat it does
stream.thinkingMessage streamA chunk of the model's reasoning, kept apart from the answer text
AgentInfo.showReasoningAgent metadatafalse means the server sends no thinking frames for that agent
reasoningEffortMessage, regenerate and edit requestsHow hard the model may think for this turn

Reasoning in the stream ​

A host-backed agent reports reasoning with the thinking event:

typescript
server.registerHostAgent({
  info: { id: 'support', name: 'Support', showReasoning: true },

  async invoke({ message, reasoningEffort }, { emit, signal }) {
    for await (const part of backend.stream(message, { effort: reasoningEffort, signal })) {
      if (part.kind === 'reasoning') emit({ type: 'thinking', text: part.text });
      if (part.kind === 'answer') emit({ type: 'text', text: part.text });
    }
    return {};
  },
});

The server sends each chunk as a stream.thinking frame next to the stream.text frames:

data: {"type":"stream.thinking","thinking":"The user asks for a refund, so first I check the order date...","timestamp":1234567891,"seq":2}

data: {"type":"stream.text","text":"Your order is eligible","isFinal":false,"timestamp":1234567892,"seq":3}

What to rely on:

  • Live only. Thinking frames are for the connection that sent the message. They are not stored in the conversation and are not part of the stored answer, so a reload shows the answer without the thinking.
  • Never in the answer. stream.text carries only the answer. A client shows thinking in a separate block, as Chat UI does.
  • Summaries, not raw thoughts. Some providers return a summary of the reasoning instead of the chain of thought. The server forwards whatever the agent emits.
  • Unknown frames are safe to ignore. A client written for an older server keeps working, because clients ignore frame types they do not know.

Only host-backed agents send thinking frames. An agent served with registerSDKAgent, registerChatCompletionsAgent or registerResponsesAgent sends none, and a custom handler sends them only through the thinking event if it emits it.

Hiding reasoning: showReasoning ​

AgentInfo.showReasoning (default true) decides whether callers may see the reasoning text. It is published by GET /agents and GET /agents/:id.

typescript
server.registerHostAgent({
  info: { id: 'support', name: 'Support', showReasoning: false },
  // ...
});

With false:

  • The server sends no stream.thinking frames, even if the agent emits thinking events.
  • Chat clients also hide a reasoning block they already hold for that agent.
  • Token accounting does not change. The model still thinks and the provider still bills the tokens.

Reasoning effort per request ​

The three message routes accept reasoningEffort:

RouteField
POST /conversations/:id/messagesreasoningEffort
POST /conversations/:id/messages/:messageId/regeneratereasoningEffort
POST /conversations/:id/messages/:messageId/editreasoningEffort
json
{ "message": "Compare these two contracts.", "stream": true, "reasoningEffort": "high" }

Allowed values are none, minimal, low, medium and high. Any other value is a 400 VALIDATION_ERROR (reasoningEffort has to be one of none, minimal, low, medium, high). When a request names none, the field is unset and the agent's own setting applies.

The server validates the value and hands it on; the agent decides what to do with it:

Agent kindWhere the value arrives
Host-backed agentHostInvokeInput.reasoningEffort
Custom handlerProcessMessageParams.reasoningEffort

A request without the field leaves both unset. The other agent kinds (registerSDKAgent, registerChatCompletionsAgent, registerResponsesAgent) accept and validate the value but do not use it. Not every model takes every level.

Types ​

typescript
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high';
const REASONING_EFFORTS: readonly ReasoningEffort[];   // exported

interface StreamThinkingEvent {
  type: 'stream.thinking';
  thinking: string;
  timestamp: number;
  seq?: number;
}

See Streaming, Host-backed agents and Types.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub