ArticleOpenAI API

Persisted Reasoning Across API Turns: Test What the Model Actually Reuses

Turning on persisted reasoning is not the same as proving that a later request received useful earlier reasoning. Build a two-turn harness that records the effective context mode, controls how history reaches the request, scores only observable outcomes, and fails clearly when storage or model-family compatibility changes.

Last reviewed: Aug 28 2026

Three dark glass and metal modules connected by cyan-lit cables and a suspended cylindrical component, with two amber indicator lights on the right module.
Continuity survives only when each handoff carries the required state across a compatible boundary.

TL;DR

Test three things separately. First, assert the returned response.reasoning.context; this proves the mode the API applied. Second, make earlier items available through previous_response_id or complete manual replay; all_turns cannot reuse history the request does not carry. Third, compare scored final answers across repeated current_turn and all_turns trials. That comparison is behavioral evidence, not a dump of hidden reasoning. With store: false, replay every output item, including encrypted reasoning items. Reject or restart a continuation that crosses model families.


Know What the API Can Prove

Persisted reasoning and application memory are different layers. Your messages carry visible conversation state. Persisted reasoning lets a supported model use compatible, opaque reasoning items from earlier turns. It does not expose raw reasoning text, so your test must not claim to inspect chain-of-thought. OpenAI's reasoning guide makes that boundary explicit.

The same guide defines three request values. auto selects the model default. current_turn keeps reasoning within the active turn. all_turns can render available, compatible reasoning items from earlier turns into the next sample. GPT-5.6 supports all_turns and uses it by default; earlier models default to current_turn. OpenAI introduced persisted reasoning with GPT-5.6 in the July 9, 2026 API changelog entry.

Defaults are migration conveniences, not test assertions. Read response.reasoning.context from every response and record it beside the requested value, response ID, returned model ID, storage mode, and history mechanism. If you requested auto, the response field is the only one of those two values that tells you whether the API resolved it to current_turn or all_turns.

A First Turn Proves Nothing About Reuse

On the first request, current_turn and all_turns behave the same because there is no earlier reasoning to reuse. Start scoring on turn two. Also remember that all_turns does not retrieve missing history: the request still needs a previous response, a conversation, or complete manual replay.


Build a Two-Turn Matrix

Use one task with a stable goal across both turns. Turn one asks for analysis and a compact decision. Turn two adds a constraint that requires revising that decision. Keep prompts, model, effort, and scoring fixed; vary only the context mode. The paired calls below have the same wrapper and naming so the intended difference is visible.

import OpenAI from "openai";

const client = new OpenAI();
const model = process.env.OPENAI_TEST_MODEL ?? "gpt-5.6";

async function runStoredTrial(
  mode: "current_turn" | "all_turns"
) {
  const first = await client.responses.create({
    model,
    input: `Choose a retry policy for an order API.
Requirements: at-most-once charge, three transient retries,
and a 30-second request deadline. Return a short decision.`,
    reasoning: { effort: "medium", context: mode }
  });

  const second = await client.responses.create({
    model,
    previous_response_id: first.id,
    input: `New constraint: the payment provider can finish after our
deadline. Revise the policy and include an idempotency invariant,
a reconciliation step, and one unresolved tradeoff.`,
    reasoning: { effort: "medium", context: mode }
  });

  return {
    requestedMode: mode,
    firstEffectiveMode: first.reasoning?.context,
    secondEffectiveMode: second.reasoning?.context,
    firstModel: first.model,
    secondModel: second.model,
    answer: second.output_text
  };
}

The Responses create reference defines previous_response_id as the previous response identifier for a multi-turn conversation. It is the shortest stored-state path, and it cannot be combined with the separate conversation parameter.

Run both modes several times rather than treating one sampled answer as a verdict. Score the second answer with a deterministic checklist: it must state an idempotency invariant, distinguish timeout from confirmed failure, include reconciliation, preserve the three-retry limit, and name one tradeoff. The prompt orders five observable requirements; the score measures those same five requirements.

import { expect } from "vitest";

for (const mode of ["current_turn", "all_turns"] as const) {
  const trial = await runStoredTrial(mode);

  expect(trial.firstEffectiveMode).toBe(mode);
  expect(trial.secondEffectiveMode).toBe(mode);
  expect(trial.firstModel).toBe(trial.secondModel);

  const score = scoreRetryPolicy(trial.answer);
  expect(score.idempotencyInvariant).toBe(true);
  expect(score.reconciliationStep).toBe(true);
}

These assertions prove the transport contract and minimum answer quality. A higher aggregate score for all_turns is evidence that earlier reasoning helped this workload. It is not proof of which hidden tokens were used. Store the raw final answers and rubric results, but do not log encrypted reasoning content.


Verify Auto Instead of Trusting It

A migration often omits reasoning.context because GPT-5.6 defaults to all_turns. Add one explicit contract test for that assumption. This catches a model alias, request adapter, or model-family change that silently resolves auto differently.

const probe = await client.responses.create({
  model: "gpt-5.6",
  input: "Return the word ready.",
  reasoning: { context: "auto" }
});

if (probe.reasoning?.context !== "all_turns") {
  throw new Error(
    `Expected all_turns, received ${probe.reasoning?.context ?? "missing"}`
  );
}

Keep the explicit all_turns value in production if continuity is a requirement rather than a preference. Keep the probe anyway: a returned mismatch is a clearer deployment failure than a gradual quality regression. Before changing the model or adapter, review the current GPT-5.6 model guidance and rerun the matrix on representative tasks.


Replay the Complete History With store: false

Disabling storage changes how history reaches the next request, not the definition of all_turns. In stateless mode, reasoning items in the response output include an encrypted_content property by default. Treat that property as an opaque transport value. Preserve every output item, append the next user message, and send the complete array as the next input.

import type OpenAI from "openai";

const history: OpenAI.Responses.ResponseInput = [{
  role: "user",
  content: "Diagnose the retry policy and return a short decision."
}];

const first = await client.responses.create({
  model: "gpt-5.6",
  store: false,
  input: history,
  reasoning: { effort: "medium", context: "all_turns" }
});

const reasoningItems = first.output.filter(
  (item) => item.type === "reasoning"
);
if (!reasoningItems.every((item) => Boolean(item.encrypted_content))) {
  throw new Error("Stateless reasoning item lacks encrypted_content");
}

history.push(...first.output);
history.push({
  role: "user",
  content: "Revise it for late provider completion and add reconciliation."
});

const second = await client.responses.create({
  model: "gpt-5.6",
  store: false,
  input: history,
  reasoning: { effort: "medium", context: "all_turns" }
});

if (second.reasoning?.context !== "all_turns") {
  throw new Error("Stateless continuation did not apply all_turns");
}

Do not rebuild history from output_text. That drops the typed response envelope, reasoning items, tool calls, tool outputs, and assistant phase information that a longer workflow may need. Do not retain only items whose type is reasoning either. The documented replay unit is the complete output array.

The legacy include: ["reasoning.encrypted_content"] value is still accepted, but the current guide says stateless responses provide encrypted reasoning content without requiring it. Your fixture should therefore test the returned shape from the SDK version you deploy, while your application preserves the property without printing or decoding it.

Keep Memory Out of This Test

If the workflow also stores user preferences or durable facts, test those through your application-memory layer. The guide at AI Memory and Context Management covers retention, forgetting, and durable facts. This harness tests only the Responses API path that transports opaque reasoning items.


Make the Model-Family Boundary Executable

Persisted reasoning is reusable only within the same model family. The current reasoning guide gives GPT-5.6 Sol, Terra, and Luna as compatible with one another, but says reasoning does not carry between GPT-5.6 and GPT-5.5. On a cross-family request, incompatible reasoning is omitted from model context even if the effective destination mode is all_turns.

That omission is not exposed as readable reasoning text, so detect the boundary before the request. Store the model family beside each response ID or stateless history bundle. Continue when the destination is another GPT-5.6 member; otherwise start a new reasoning thread or require a deliberate fallback that relies on visible conversation state.

function reasoningFamily(model: string) {
  if (/^gpt-5\.6(?:-|$)/.test(model)) return "gpt-5.6";
  if (/^gpt-5\.5(?:-|$)/.test(model)) return "gpt-5.5";
  return `unknown:${model}`;
}

function requireCompatibleReasoning(
  previousModel: string,
  nextModel: string
) {
  const previous = reasoningFamily(previousModel);
  const next = reasoningFamily(nextModel);
  if (previous !== next || previous.startsWith("unknown:")) {
    throw new Error(
      `Reasoning-family boundary: ${previousModel} -> ${nextModel}`
    );
  }
}

requireCompatibleReasoning("gpt-5.6-luna", "gpt-5.6-terra"); // allowed
requireCompatibleReasoning("gpt-5.6", "gpt-5.5"); // throws

Do not parse a family from an alias once and assume it forever. Record the returned model ID, keep an allowlist based on current provider documentation, and update it as an intentional compatibility change. If a fallback crosses the boundary, the safe test expectation is a new baseline, not identical persisted-reasoning behavior.


Turn the Matrix Into a Regression Gate

Use a small unit layer for history assembly and family checks, then run the live two-turn matrix on demand or in a scheduled provider-contract job. A practical result record contains requested mode, effective mode on both turns, storage mode, history mechanism, returned model IDs, rubric score, token usage, and latency. Exclude prompt content when it contains sensitive data, and exclude encrypted reasoning content entirely.

Compare current_turn and all_turns across the same fixtures. A rollout passes when the effective modes match the request, stateless history is complete, family guards behave as designed, and final-answer quality does not regress beyond your declared threshold. This applies the contract-testing mindset from Test Truncated Structured Outputs to cross-turn state: assert the response envelope first, then score the application outcome.

Persisted Reasoning Test Checklist


Primary References — Verified Aug 28 2026

Related Guides

AI Memory and Context Management

Design the separate application layer for conversation history, durable facts, retention, and forgetting.

Test Truncated Structured Outputs

Assert the Responses API envelope before model output reaches parsing or side effects.

Prompt Cache Hit Rate

Instrument cache behavior without confusing lower token cost with correctness.

Back toHome