
100% private · no tracking · works offline100% client-side/no data leaves your browser/no accounts/works offline
A production migration plan for replacing Assistants, Threads, Runs, and Run Steps with Responses, Conversations, and Items before the August 26, 2026 shutdown.

Free toolkit
85+ private dev tools
Everything runs in your browser. Zero tracking, no sign-up.
Browse toolsOpenAI Assistants API migration is now deadline work, not technical debt for a later quarter. The Assistants API shuts down on August 26, 2026, so production systems must move their configuration, conversation state, run orchestration, and tool handling to Responses and Conversations without silently losing history or replaying side effects.
This is an object-model migration, not an endpoint rename. OpenAI's official migration guide maps the old concepts to the new ones:
| Assistants API | Replacement | Operational difference |
|---|---|---|
| Assistant | Prompt or request configuration | Behavior can be versioned separately from orchestration |
| Thread | Conversation | Stores messages, tool calls, tool outputs, and other items |
| Run | Response | Accepts input items and returns output items |
| Run Step | Item | A generalized message, reasoning, tool-call, or tool-output record |
The practical change is ownership. A Run previously hid much of the lifecycle behind statuses such as queued, in_progress, and requires_action. A Response gives your application output items, and your application explicitly decides whether to execute a function, request approval, retry, or stop.
That explicitness is useful, but it also exposes assumptions that may be buried in an existing integration. Inventory these before writing migration code:
Create a parity sheet per workload. A support bot with File Search, a data agent with functions, and an internal code assistant should not share one vague “migrated” checkbox.
The Responses API offers two server-managed continuity patterns, and choosing the wrong one makes a later migration harder.
A Conversation has its own durable ID and stores a sequence of items. It fits chat sessions that span devices, background jobs, or multiple Responses:
from openai import OpenAI
client = OpenAI()
conversation = client.conversations.create(
metadata={"account_id": "acct_123", "schema_version": "responses-v1"}
)
response = client.responses.create(
model="gpt-5.6",
conversation=conversation.id,
input=[{"role": "user", "content": "Summarize my open incidents."}],
)
print(response.output_text)
Store the Conversation ID beside your application-level session ID. Do not use model context as your system of record: authorization, account ownership, and business state still belong in your database.
previous_response_id links a new request to an earlier Response. It is convenient for short-lived, linear interactions where you do not need an independently managed Conversation:
first = client.responses.create(
model="gpt-5.6",
input="Draft a deployment checklist.",
)
second = client.responses.create(
model="gpt-5.6",
previous_response_id=first.id,
input="Reduce it to the five highest-risk checks.",
)
Do not mix the two casually. Pick one state strategy for each workload, document retention requirements, and test branching behavior. For long conversations, control what is retained and reintroduced; the principles in context engineering for AI agents still apply after the endpoint changes.
An Assistant bundled behavior and tools in a persistent API object. The migration guide points to dashboard-managed prompts, but it also warns that reusable prompt objects have their own deprecation timeline. Treat a prompt ID as a deployable dependency, not an immortal replacement for an Assistant ID.
A conservative design keeps these pieces explicit:
For a simple chat workload, the new request can be much smaller than the old create-message, create-run, poll-run, and fetch-message sequence:
response = client.responses.create(
prompt={"id": "pmpt_support_v3"},
conversation=conversation_id,
input=[{"role": "user", "content": user_message}],
)
return {"text": response.output_text, "response_id": response.id}
Keep the prompt reference configurable so you can pin, canary, and roll back a version. If you instead send instructions and tools directly, hash that configuration and log the hash with each Response. Either approach gives you a concrete answer to “which behavior produced this output?”
Do not translate Run polling into Response polling by habit. Normal Responses can complete in the request. Use streaming when you need incremental output and background mode when the task genuinely needs asynchronous execution. Map each old Run status to a product behavior before deleting the old state machine.
OpenAI will not provide an automated Thread-to-Conversation migration tool. Its recommendation is to put new chats on Conversations and migrate older Threads as needed. That lazy approach reduces API traffic, migration duration, and the amount of stale history copied into the new system.
The official example lists Thread messages in ascending order, converts supported content parts, and creates a Conversation. Production code needs three more properties: pagination, idempotency, and an explicit unsupported-content policy.
from openai import OpenAI
client = OpenAI()
def message_to_item(message):
content = []
for part in message.content:
if part.type == "text":
content.append(
{
"type": "input_text" if message.role == "user" else "output_text",
"text": part.text.value,
}
)
elif part.type == "image_url":
content.append(
{
"type": "input_image",
"image_url": part.image_url.url,
"detail": part.image_url.detail,
}
)
else:
raise ValueError(
f"Unsupported thread content type: {part.type}"
)
return {"role": message.role, "content": content}
def backfill_thread(thread_id):
items = []
pages = client.beta.threads.messages.list(
thread_id=thread_id,
order="asc",
).iter_pages()
for page in pages:
items.extend(message_to_item(message) for message in page.data)
return client.conversations.create(
items=items,
metadata={"legacy_thread_id": thread_id},
)
Wrap that function with a database transaction or durable job:
migration_in_progress.thread_id -> conversation_id.The mapping table is your idempotency boundary. If a worker crashes after Conversation creation but before persisting the ID, a retry can create a duplicate. Record a job key before the API call and send an alert for ambiguous outcomes instead of guessing which Conversation is canonical.
Attachments need a separate audit. The concise converter above intentionally fails on unhandled content instead of dropping it. Decide whether to convert, summarize, relink, or archive annotations, generated files, images, and application-specific metadata. Silent loss is the worst migration policy.
In the Assistants API, a Run could enter requires_action and accept submitted tool outputs. In Responses, function calls appear as output items. Your code executes allowed calls, appends a function_call_output with the matching call_id, and continues until the model stops requesting tools.
The following loop is intentionally bounded:
import json
MAX_TOOL_ROUNDS = 6
def run_agent(user_input, tools, dispatch):
input_items = [{"role": "user", "content": user_input}]
for _ in range(MAX_TOOL_ROUNDS):
response = client.responses.create(
model="gpt-5.6",
tools=tools,
input=input_items,
)
input_items += response.output
calls = [item for item in response.output if item.type == "function_call"]
if not calls:
return response.output_text
for call in calls:
args = json.loads(call.arguments)
result = dispatch(call.name, args)
input_items.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
}
)
raise RuntimeError("Tool round limit exceeded")
In production, dispatch must reject unknown tools, validate arguments again, enforce tenant authorization, time out downstream calls, and return structured failures. Add idempotency keys to write tools. Require approval for high-impact actions. OpenAI's function-calling guide describes the protocol; your application remains responsible for executing and securing the function.
If you have many tools or nested orchestration, the site's Programmatic Tool Calling guide covers the newer execution model. Migrate the basic loop first, establish parity, and adopt a different orchestration pattern as a separate change.
File Search remains a hosted tool in Responses. Existing vector-store strategy still needs verification, but the request attaches the store to a file_search tool rather than to an Assistant:
response = client.responses.create(
model="gpt-5.6",
conversation=conversation_id,
input="Which policy covers data deletion?",
tools=[
{
"type": "file_search",
"vector_store_ids": ["vs_policy_docs"],
"max_num_results": 5,
}
],
)
Test retrieval quality, citations, permissions, and latency with your real corpus. Do not assume that receiving fluent text proves the intended file was searched. Capture file-search call metadata and evaluate answer grounding.
Streaming consumers also need an event-level rewrite. An old handler that waits for a Run Step or Thread Message event will not understand Response item and text-delta events. Build the UI around semantic states—started, text delta, tool requested, tool completed, completed, failed—then map current SDK events into those states. This keeps product code insulated from raw event names.

A useful rollout has three lanes:
| Lane | Traffic | Purpose |
|---|---|---|
| New sessions | Responses and Conversations only | Prove the new path without legacy conversion |
| Existing sessions | Lazy Thread backfill | Preserve active history when a user returns |
| Evaluation traffic | Recorded, sanitized inputs | Compare quality without duplicating real actions |
Do not shadow live requests through tools that send messages, create tickets, charge cards, modify repositories, or mutate customer data. Either replace those tools with read-only simulators in the shadow path or stop before execution and compare the requested call.
Measure parity at the item level:
Compare outcomes, not exact wording. Model output can vary while remaining correct. Conversely, a semantically similar answer can still be unsafe if it selected the wrong account or attempted a duplicate action.
Roll out by account or session hash, not random request. A single conversation must not bounce between Thread and Conversation state. Keep a kill switch that routes eligible sessions back to the old path before the shutdown date, and separately test the behavior when the old path is unavailable.
For cost guardrails during the rollout, use project budgets and application-side limits described in OpenAI API hard spend limits. A migration that improves architecture but can exhaust the project budget is not production-ready.
OpenAI states that the Assistants API shuts down on August 26, 2026. Treat that as a removal deadline, not the day to begin testing.
No. The capabilities overlap, but the object model and orchestration change. Assistants become prompt or request configuration, Threads become Conversations, Runs become Responses, and Run Steps become Items. Tool loops are explicitly managed by your application.
No automated migration tool is planned. OpenAI recommends sending new chats to Conversations and migrating older Threads as needed. Preserve a durable Thread-to-Conversation mapping and make the backfill idempotent.
No. Responses can be stateless, chained with previous_response_id, or attached to a Conversation. Durable user sessions usually fit Conversations; small linear chains may fit previous_response_id.
Responses File Search accepts vector-store IDs, so the underlying knowledge base may be reusable. You still need to verify permissions, attachment strategy, retrieval quality, citations, and any lifecycle assumptions previously hidden in Assistant configuration.
The safest OpenAI Assistants API migration separates four jobs: configuration, state, execution, and cutover. Move new sessions first, lazily backfill active Threads, make tool loops explicit and bounded, and evaluate side effects as carefully as text quality. That leaves enough time to remove the old path deliberately instead of discovering its hidden dependencies when the shutdown arrives.
Comments
Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.