AI
OpenAI Agent Builder Shutdown: Migrate Before Nov 30, 2026
October 202611 min read

OpenAI's deprecations page lists Agent Builder as announced on 3 June 2026 and scheduled to shut down on 30 November 2026. Existing users can keep using it during the transition window. The Evals platform becomes read-only on 31 October 2026 and shuts down on the same 30 November date.
No. OpenAI states that ChatKit remains available. What ends is the Agent Builder-hosted path, where ChatKit points at a workflow ID; new work should use the self-hosted integration, with the ChatKit Python SDK on your own server connected to your own agent.
Yes. Open the workflow, choose Code in the top navigation, choose Agents SDK, then pick TypeScript or Python and copy the output. OpenAI warns that this does not convert the workflow graph or guarantee unchanged behaviour, so control flow, triggers, tools and permissions may need rebuilding by hand.
Use the Agents SDK when the workflow is embedded in your product, takes actions in other systems, or depends on deterministic branching, because OpenAI notes such workflows may not migrate faithfully to a workspace agent. Choose a Workspace Agent for internal team assistants, provided you are on ChatGPT Business, Enterprise or Edu with permission to create agents.
Mark the side-effecting tool with needs_approval so the run pauses and the pending call appears in result.interruptions. Convert the result with to_state, store the serialised state on your server, then load it with RunState.from_string, call approve or reject, and resume with Runner.run. Never accept serialised state from the client, since the SDK does not authenticate snapshots.

Key Takeaway
OpenAI Agent Builder shuts down on 30 November 2026, and the Evals platform turns read-only on 31 October. The built-in export produces Agents SDK code, not a converted graph, so every workflow needs an inventory, a rebuilt approval and branching layer, re-entered credentials and a parity test before the deadline. ChatKit and the Agents SDK both remain.
The line on OpenAI's deprecations page is short: Agent Builder, announced 3 June 2026, scheduled to shut down on 30 November 2026. Two rows above it, the Evals platform gets a read-only date of 31 October. If a drag-and-drop workflow is answering customers inside a ChatKit widget, or quietly approving purchase orders behind an internal tool, that is two months from today to move it, and one month to rescue the test cases that prove it still works.
This post covers the OpenAI Agent Builder shutdown as a migration project rather than a news item: what disappears and what survives, how to inventory workflows that may be referenced from code nobody remembers, what the Code export really gives you, how to rebuild the two nodes that never transfer cleanly, and how to choose between the Agents SDK and ChatGPT Workspace Agents per workflow. Every date and API name is taken from OpenAI's own deprecation notice, migration guide, node reference and Agents SDK documentation.
The wind-down is narrower than the headlines suggest, but it lands three deadlines on the same platform at once. The visual canvas goes, the hosted eval tooling goes, and the reusable prompt objects many workflows leaned on go on the same day. The runtime pieces underneath, the Agents SDK and ChatKit, stay.
| Product | Status | Key date | Where it goes |
|---|---|---|---|
| Agent Builder (visual workflow canvas) | Shut down | 30 November 2026 | Agents SDK, or ChatGPT Workspace Agents |
| Evals dashboard and API | Read-only, then shut down | 31 October, then 30 November 2026 | Promptfoo, with an OpenAI migration guide |
| Reusable prompts (the v1/prompts API) | Shut down | 30 November 2026 | Prompt content moved into application code |
| ChatKit | Remains available | No end date announced | Self-hosted ChatKit backed by your own server |
| Agents SDK (Python and TypeScript) | Remains, and is the target of the export | No end date announced | Your own deployment |
The row people miss is the third one. A workflow you migrate to code but keep pointing at a stored prompt object has not been migrated, it has been moved from one 30 November deadline to another. Treat the prompt text as source code: copy it into the repository, review it like code, and version it with the agent that uses it.
The canvas shows you the workflows that exist. It does not show you where they are used. Agent Builder deployments reach production in two ways, through a ChatKit session created against a workflow ID, or through exported code someone already copied out, so the inventory has to start from the consuming side. For each workflow, write down five things:
# 1. Every Agent Builder workflow your code still points at.
# ChatKit sessions created against Agent Builder carry a wf_ ID.
grep -rnoE "wf_[0-9a-f]{20,}" \
--include=*.{ts,tsx,js,py,json,yaml,yml} --include=.env* . | sort -u
# 2. Reusable prompt objects: v1/prompts shuts down on the same day,
# so a workflow migrated onto a pmpt_ reference just moves the problem.
grep -rnoE "pmpt_[0-9a-zA-Z]+" --include=*.{ts,js,py,json} . | sort -u
# 3. Vector stores the File search nodes read from. These survive the
# shutdown, but the new code has to name them explicitly.
grep -rnoE "vs_[0-9a-zA-Z]{20,}" . | sort -uThe search below finds the references a dashboard cannot. Run it across every repository that talks to OpenAI, not just the one you think owns the agent. Workflow IDs in the ChatKit documentation take the wf_ form, vector store IDs start with vs_, and stored prompt references start with pmpt_, so three patterns cover the lot. Anything that turns up in an environment file belongs to someone, and that person goes on the migration list.
The export itself takes a minute. Open the workflow, choose Code in the top navigation, choose Agents SDK, pick TypeScript or Python, and copy the complete output. OpenAI's own migration guide is blunt about what that output is: the process does not convert your workflow graph or guarantee that every behaviour transfers unchanged. It is a starting point written in code, and the guide lists control flow, triggers, tools and permissions as things you may have to recreate by hand.
The useful way to read an export is node by node, against the node reference. Some nodes map one-to-one onto an Agents SDK construct. Others were canvas features with no runtime object behind them, and those are where behaviour silently changes.
| Agent Builder node | Where it lives in the Agents SDK | What to verify after export |
|---|---|---|
| Start, Set state, Transform | The input you pass to Runner.run, plus ordinary code and a context object you own | Every state variable the canvas defined has an owner, and input_as_text still means the same thing |
| Agent | An Agent with instructions, tools and a model setting | The instructions match the published version, not the last draft |
| File search | FileSearchTool with vector_store_ids and max_num_results | The same vector store IDs and the same result count |
| Guardrails (PII, jailbreak, hallucination checks) | Agents SDK input and output guardrails | A failed check still stops the run instead of only being logged |
| MCP | HostedMCPTool, or a local server class such as MCPServerStreamableHttp | Credentials are re-entered and approval rules on each server are set again |
| If/else and While (CEL expressions) | Plain if and while statements in your application | The exact condition, including the boundary values |
| Human approval | needs_approval on the side-effecting tool, then result.interruptions and RunState | Where a paused run is stored, and who is allowed to resume it |
Do not deploy an export just because it runs. A workflow can execute end to end with an approval gate missing, a guardrail downgraded to a log line, or an MCP server answering with a different account. All three fail quietly. Diff the behaviour, not the code, using the parity check later in this post.
Two node types deserve a rewrite rather than a review. If/else and While nodes were CEL expressions evaluated by the canvas; in code they become plain conditions, which is an improvement, because a constant in a module can be unit-tested and a condition hidden in a node could not. Human approval is harder, because the canvas held the paused workflow for you. In the Agents SDK the pause becomes data: a tool marked needs_approval stops the run, the pending call appears in result.interruptions, and result.to_state() turns the whole run into a string you store until someone decides. The example below is a purchase-order release for an ERP back office.
from agents import Agent, Runner, RunConfig, RunState, FileSearchTool
from agents.decorators import tool
# Was: a CEL expression in an If/else node, e.g. state.amount_idr < 50000000.
# Now: a constant in code, which a unit test can pin down.
AUTO_RELEASE_LIMIT_IDR = 50_000_000
# Was: a Human approval node after the reviewing agent.
# Now: the approval sits on the one tool that has a side effect,
# so the model can read and reason freely but cannot release alone.
@tool(needs_approval=True)
async def release_purchase_order(po_number: str, amount_idr: int) -> str:
# call the ERP here; this body only runs after a human approves
return f"PO {po_number} released"
reviewer = Agent(
name="PO reviewer",
instructions="Check the purchase order against the budget policy, then release it.",
tools=[
FileSearchTool(vector_store_ids=["vs_..."], max_num_results=5),
release_purchase_order,
],
)
async def review(po: dict, store) -> str:
if po["amount_idr"] < AUTO_RELEASE_LIMIT_IDR:
return "auto-release" # this branch never reaches the model
result = await Runner.run(
reviewer,
f"Review PO {po['number']} for {po['amount_idr']} IDR",
run_config=RunConfig(
workflow_name="po-release",
trace_metadata={"migrated_from": "agent-builder"},
),
)
if result.interruptions:
# The canvas held this pause for you. Now it is a row in your database,
# because the approver may answer tomorrow, from another process.
await store.save(po["number"], result.to_state().to_string())
return "awaiting-approval"
return str(result.final_output)
async def decide(po_number: str, approved: bool, store) -> str:
# Load the snapshot from YOUR storage, never from the request body.
state = await RunState.from_string(reviewer, await store.load(po_number))
for item in state.get_interruptions():
if approved:
state.approve(item)
else:
state.reject(item, rejection_message="Rejected by the finance approver.")
result = await Runner.run(reviewer, state)
return str(result.final_output)Two design decisions are visible in that code. The branch that never needs a model runs before the model is called, so small purchase orders cost no tokens and cannot be talked into a different path. And the approval sits on the one tool with a side effect, not on the whole agent, so the reviewer can read the budget policy and explain its reasoning freely while the release itself waits for a person. When the approver answers, RunState.from_string rebuilds the run, state.approve or state.reject records the decision, and Runner.run resumes where it stopped.
The Agents SDK documentation is explicit that RunState.from_string and RunState.from_json do not authenticate a snapshot or the person submitting it. Store the serialised state on the server, look it up by a key you issued, and accept only an approve or reject decision from the client. Never accept serialised state, replacement tool calls or arguments from a browser.
OpenAI offers two destinations and describes them differently: the Agents SDK is best for building agents through code, Workspace Agents are best for building agents through natural language and sharing them with teams. The migration guide adds the sentence that should decide most cases, that workflows with strong determinism at their core may not migrate faithfully to a workspace agent. Sort each workflow with four questions:
The Workspace Agent path also starts from the export. You paste the exported code into ChatGPT with a prompt asking it to convert the workflow into an agent, read the behaviour changes the builder points out, configure the apps, skills, authentication and permissions it needs, and use Preview with representative inputs before selecting Create. The guide is clear that connected apps, publishing and permissions are configured separately in ChatGPT, so the credential inventory from the second step still applies.
ChatKit survives, but one of its two integration paths does not. The Agent Builder-hosted path, where you embed ChatKit and point it at a workflow ID, is now documented for existing users only. The recommended path is the custom server integration: ChatKit runs on your own infrastructure, the ChatKit Python SDK serves the conversation, and behind it sits whatever agent you migrated, typically the Agents SDK code from the previous sections.
In practice this means the front-end widget changes least and the back end changes most. The session endpoint that used to hand ChatKit a workflow ID becomes a server that runs your agent and streams its output, and the approval pause from the fourth section needs a way to surface in the chat. Plan the widget and the server as one change, deployed together, rather than swapping the back end under a live widget on the last week of November.
Both packages to install are maintained by OpenAI and stay after 30 November: openai-agents for Python or @openai/agents for TypeScript as the runtime the export targets, and the ChatKit Python SDK for a self-hosted chat back end.
An export that compiles proves nothing about behaviour, and the evidence of what correct looked like lives in the platform that turns read-only on 31 October. Before that date, copy out the datasets and the expected outcomes for each workflow, and save a set of real inputs from production traffic. Then run the migrated agent over them with a check that compares the things that matter most: whether the approval gate fires and whether the answer still contains what it must.
import asyncio, json
from agents import Runner
# cases.jsonl: one line per captured run from the old workflow, e.g.
# {"input": "...", "expect_approval": true, "must_mention": ["budget"]}
async def parity(agent, path: str = "cases.jsonl") -> None:
failures = []
for line in open(path, encoding="utf-8"):
case = json.loads(line)
result = await Runner.run(agent, case["input"])
paused = bool(result.interruptions)
text = "" if paused else str(result.final_output).lower()
if paused != case["expect_approval"]:
failures.append((case["input"], "approval gate differs"))
for word in case.get("must_mention", []):
if not paused and word not in text:
failures.append((case["input"], f"missing '{word}'"))
for inp, why in failures:
print(f"FAIL {why}: {inp[:60]}")
raise SystemExit(1 if failures else 0)A script like this is deliberately small. It encodes the two failure modes that matter most in a migration, an approval that no longer pauses and an answer that lost a required fact, and it exits non-zero so it can block a deploy in CI. For a fuller suite with model-graded rubrics and tool-call assertions, Promptfoo is the destination OpenAI itself names for Evals users, and a sibling post on this site covers that move in detail.
The deadline is fixed and the tooling makes the first step look finished. The rule I would carry out of this migration is that a workflow is only migrated when three things exist outside Agent Builder: its prompts in your repository, its branching and approvals in code you can test, and a parity run against real inputs that passed. Anything without all three on 30 November is a workflow that simply stops.
Sources and further reading