Moving Off OpenAI Assistants Means Rebuilding State
OpenAI’s Responses API unifies model input and tool use, but it does not turn every Assistant, Thread, or Run into an equivalent object. Here is the migration map.
August 9, 2026 · 8 min read

OpenAI deprecated the Assistants API in August 2025 and documents a shutdown in August 2026. The replacement is the Responses API, introduced in March 2025 as a common interface for model output, multimodal input, built-in tools, function calls, and stored conversation state.
That description makes the move sound mechanical. It is not.
Consider one support workflow: a customer submits an order number and a photo of a cracked coffee maker, the assistant searches a warranty PDF, calls an internal `get_order` function, and returns either replacement instructions or a request for human review. Under the Assistants API, an Assistant held instructions and tools, a Thread collected messages, and a Run advanced the work while exposing steps and tool-output checkpoints.
Responses rearranges those responsibilities. The API can store state and invoke hosted tools, but the application must decide which state mechanism to use, where instructions live, how tool loops terminate, and what evidence counts as equivalent behavior. That control makes mixed text, image, file, and tool workflows easier to assemble. It also removes several assumptions that older applications quietly relied on.
Map behavior before mapping objects
OpenAI’s migration documentation gives developers a useful conceptual correspondence: Assistants become prompts or application configuration, Threads become Conversations, Messages become Items, Runs become Responses, and Run Steps become output items or streaming events. Treat that as a navigation aid, not a promise of one-for-one parity.
Start by tracing the coffee-maker request through production. Record where the application creates the Assistant, attaches the warranty material, starts a Thread, adds the image, launches a Run, receives the `get_order` arguments, submits the function result, and reads the final answer. Include retry code, timeout handling, metadata, citations, and any database writes triggered after completion.
This trace is the real migration inventory. An endpoint list will miss behavior hidden between calls, such as code that polls a Run until its status changes or assumes every tool invocation appears under a particular run-step structure.
An Assistant’s durable configuration can move into application code or an OpenAI prompt, a versioned configuration managed separately from each request. Either way, pin the exact instructions, tools, and expected output schema used during migration. If all three change with the API, a failed test cannot tell you which change caused the break.
There is another instruction trap. When an application chains calls with `previous_response_id`, instructions supplied to an earlier Response are not automatically treated as instructions for the next one. Send the governing instructions on each turn or reference a controlled prompt configuration. Otherwise, the coffee-maker workflow may follow the warranty policy on turn one and lose that policy after the customer adds a shipping address.
Choose one state model deliberately
Responses offers more than one way to continue an interaction. A Conversation is a durable container whose Items can include user input, model output, tool calls, and tool results. Passing `previous_response_id` creates a lighter chain from one Response to the next. An application can also remain stateless at the vendor boundary by sending the relevant history on every request.
Those choices have different operational consequences.
Use a Conversation when the support case needs a stable identifier that can collect multiple turns and tool outputs beyond a single response chain. Use `previous_response_id` when the application primarily needs linear continuation and already owns the case record elsewhere. Resend history when retention policy requires `store: false`, when state must live in the company’s database, or when replayability matters more than convenience.
OpenAI documents that Response objects are stored for 30 days by default, while items attached to Conversations persist without that Response-object expiration window. Retention must therefore be an explicit architecture decision, not a default inherited from the sample code. The application should also store its own mapping between the old Thread ID, the customer case ID, and the new Conversation or Response identifier until rollback is no longer required.
Context still costs tokens. Chaining with `previous_response_id` saves the application from resending the transcript itself, but OpenAI documents that earlier input tokens in the chain remain billable when they are included as context. Long support cases still need compaction, truncation, or a fresh conversation built from an approved summary.
For the cracked coffee maker, preserve the customer’s original image, the retrieved warranty passage, the order lookup result, and the final disposition in the company case record. A model-generated recap alone is a weak audit artifact because it can omit the exact tool arguments or qualification that determined eligibility.
Split files by what the model must do with them
The old workflow may refer to everything as an attachment, but the new design should separate direct file input from retrieval.
If the customer uploads one damage photo or a short document that the model must inspect in the current turn, send it as input content using a file reference or supported image input. For a PDF provided directly to a vision-capable model, OpenAI says the API can supply both extracted text and page images, which makes diagrams and scanned layouts newly practical but increases token use compared with text alone.
The warranty library belongs in file search. File search is a hosted retrieval tool that searches a vector store, an indexed collection of document chunks, before the model answers. Reuse or rebuild the relevant vector store, put its identifier in the Responses tool configuration, and verify that returned annotations still expose the citations the user interface expects.
A file ID by itself does not mean the model will search that file on future turns. Nor should migration code assume that an Assistant’s `tool_resources`, message attachments, and Responses input files share identical scope. For the coffee-maker case, test the specific clause returned from the warranty PDF, not merely whether the final answer mentions “warranty.” A plausible answer with no supporting passage is a retrieval regression.
Own the function-call loop
Hosted tools such as file search execute within the Responses workflow, subject to their configuration and charges. Custom functions remain application work.
When the model emits a function call for `get_order`, the application validates its arguments, executes the internal service, and sends back a `function_call_output` linked to the call identifier. The model then receives another turn to interpret that result. Each extra exchange adds model and network latency, while a hosted tool may add its own usage charge; the migration test should therefore count response turns and tool invocations even when exact wall-clock time varies.
Do not flatten all tool events into assistant text. Store the call name, validated arguments, call identifier, result, error, and final model response as separate records. If the model emits multiple calls, preserve their identities and decide whether parallel execution is safe. Two read-only catalog lookups may run together.
A refund and a replacement order should not.
The fallback also needs a named state. If `get_order` times out, return a structured error to the model only when the instructions define an allowed recovery, such as one retry followed by human review. Never let the model infer that a missing order result means the customer is eligible.
This is where the lower-level API pays off: the support team can combine the customer’s image, retrieved policy text, and live order data in one response flow, while retaining control over which side effects execute. The cost is orchestration code that the Assistant abstraction previously organized around Runs and required-action states.
Build regression tests around decisions
Begin with captured cases from the existing workflow, including the cracked coffee maker, an expired warranty, a missing order, an unreadable image, and a tool timeout. Remove personal data or replace it with controlled fixtures. Replay each case against the old path and the migration candidate while pinning the same model snapshot where OpenAI makes that possible.
Do not compare final prose character for character. Model wording can vary without changing the decision. Assert the properties the system depends on: the selected tool, the validated order identifier, whether file search ran, the cited policy passage, the structured disposition, and whether a human-review flag was set before any write action.
Add state tests separately. Continue a case through `previous_response_id`, through a Conversation, and through manually supplied history, then confirm that instructions remain active and no second customer’s items appear. Delete or expire the application-side mapping and verify that the fallback starts a new case rather than attaching input to an unrelated chain.
Shadow traffic can expose differences, but it must not execute side effects twice. Route recorded or live inputs through the Responses path with write-capable functions replaced by stubs, compare traces, then canary the new path for a limited slice of traffic. Keep the old Thread ID beside the new state identifier until the canary can be rolled back without reconstructing the case from model prose.
Questions people ask
Can
I replace every Thread ID with `previous_response_id`?
No. `previous_response_id` works for linear continuation, while a Conversation is better suited to a durable case containing multiple items and tool results. Whichever route you choose, keep your own case identifier and resend governing instructions because earlier Response instructions do not automatically carry forward as new instructions.
Do files attached to an Assistant migrate automatically?
Do not assume so. Inventory whether each file was direct message input, an Assistant-level attachment, or part of a vector store used by file search. Reconnect each file according to that role, then test the retrieved passage and citation rather than checking only that the model produced an answer.
Will using the same model preserve the old behavior?
No parity guarantee follows from keeping the model name. The request structure, instructions, available tools, state history, and returned event types can all change behavior around the model. Pin what you can, then compare tool arguments, citations, structured decisions, and escalation outcomes instead of demanding identical wording.
Can
I run both APIs against production traffic during migration?
Yes, but the shadow Responses path should use read-only tools or stub every function that can issue a refund, create a shipment, or modify a case. Store both traces under the same internal case ID, and permit writes only after routing has selected one path as authoritative.
One story a day
The story of the day, in your inbox
One real story about AI each morning — no hype, no alarm, just company for the road.



