Tool note
OpenAI Assistants Versus Your Own Loop
The migration cost is not the orchestration code — it is the hosted tools you never implemented and the defaults you never wrote down.
There are two ways to run an agent. You can hand the conversation to a hosted assistant API — it stores the thread, assembles the context, runs the tools, and hands you a reply. Or you can own the loop: your code holds the message array, decides what goes into the window, parses every tool call, and talks to a plain chat endpoint.
Both ship. The choice looks like a build-or-buy argument about a week of orchestration work. It is really a question about what you will be able to explain during the first incident.
Why this exists
The failure mode is not an outage. It is a Tuesday afternoon when a customer got a wrong answer, you have the output and the token count, and you cannot reconstruct the input.
With a hosted thread you did not build that prompt. The service assembled it from stored messages, retrieved file chunks, tool results, and a truncation rule you do not control. Which of the eleven earlier turns were still in the window? Which chunks did file search return? You can guess. You cannot replay.
So here is the test that tells you which side of the line you are on, whatever your code looks like: take yesterday’s worst request and replay it offline, byte-identical, with no network calls. If you cannot produce the exact array you sent, you do not own the loop. Everything else here follows from that.
How to use it
Start hosted when the thing might be canceled. Most prototypes are, and the hosted route gets you to a demo in days with retrieval and search you would otherwise spend a month on. That is a real gain, and refusing it on architectural principle is how you lose the argument before you have a product.
But log as though you were leaving. From the first commit, keep your own record of every turn: the messages you contributed, the identifiers of everything you did not, the model, the parameters, each tool call and its result, and the full output. That log is the migration. With it, moving is a week. Without it, the first month goes on reconstructing your own behavior from support tickets.
Then price the move in three parts, because only the first one ever gets estimated:
- Orchestration. A state machine over a chat endpoint, with retries and tool dispatch. Real work, roughly a week.
- The hosted tools you now own. The published tool surface is functions, web search, file search, and computer use. Functions you already wrote. The other three you did not.
file_searchbecomes chunking, embeddings, an index, and retrieval quality you are now accountable for. This is the actual bill, and it is the one nobody prices. - Defaults you never specified. Truncation, message ordering, system-prompt shaping, retry semantics. You never wrote them down, so you cannot port them — you rediscover them as regressions.
The middle position beats both ends: own the loop, rent the tools. Keep message assembly and the state machine in your code, and call hosted search or retrieval as ordinary functions. Switching cost becomes per-tool instead of per-system, and each tool is a day.
Where it goes wrong
Owning the loop means owning the dull half — truncation policy, idempotency on retries, partial tool failure, timeouts, conversation storage, and a migration every time a message shape changes. None of it is hard. All of it is yours at three in the morning.
Do not own the loop if one person is holding the system and nobody else understands it. That trades a vendor dependency for a staffing dependency, and the staffing one takes vacations.
Do not migrate on taste. If the hosted version passes the replay test well enough for your risk level, and nobody is asking you to prove what happened, the honest recommendation is to leave it alone and spend the week on evals instead.
And be clear about what owning the loop does not buy. It buys explainability, not accuracy. A fully reproducible pipeline gives you the exact input that produced a wrong answer, which is worth a great deal — and is still a wrong answer.
Copy and Clear become available when the worksheet is ready.
OWN-THE-LOOP DECISION + MIGRATION COST WORKSHEET
Project: Owner: Date:
PART 1 - THE REPLAY TEST
Do this before anyone argues. Pick the single worst request
from the last 30 days and try to reproduce it offline.
0-2 boxes -> you do not own the loop. Go to Part 2.
3-4 boxes -> you own part of it. Name the missing piece.
5 boxes -> you own the loop. Skip to Part 5.
PART 2 - DOES THAT MATTER HERE? (yes/no, no maybes)
- Will anyone ever ask me to explain one specific bad answer?
- Is a regulator, auditor, clinician or lawyer downstream?
- Do I need to A/B two prompts on byte-identical inputs?
- Do I need to replay last month's traffic on a new model?
- If the provider deleted my threads, is my record gone?
- Will this still exist in twelve months?
Mostly no -> stay hosted. Re-ask on .
Any yes on 1, 2 or 5 -> start the shadow log today (Part 3),
whether or not you ever migrate.
PART 3 - THE SHADOW LOG (write these rows from day one)
One row per model call, not per user turn.
request_id
conversation_id your id, not the provider's
provider_thread_id their id, for reconciliation
provider_run_id
model_id exact string, never "latest"
params temperature / effort / max output / seed
messages_sent_by_me full text of what my code contributed
messages_i_did_not_see ids + counts of provider-injected content
tool_defs_in_scope
tool_calls name, arguments, result, duration, error
retrieval_hits doc id, chunk id, score - if you get them
output_text
usage input / output / cached tokens
latency_ms
prompt_version
verdict unreviewed | good | wrong | harmful
Retention: 90 days minimum, or one full eval cycle
Redaction: field list scrubbed before write
PART 4 - MIGRATION COST (fill in hours; the middle row is the bill)
Orchestration: state machine, retries, tool dispatch
Conversation storage + schema
REPLACING HOSTED TOOLS - price each one you actually use:
functions already mine 0
web search build or rent? which vendor?
file search chunking + embeddings + index + eval
computer use sandbox, isolation, egress rules
Rediscovering unspecified defaults (truncation, ordering)
Eval set to prove parity before cutover
Parallel-run window (both paths live)
TOTAL:
Sanity check: if the hosted-tool row is smaller than the
orchestration row, you have not read your own tool usage.
PART 5 - IF YOU OWN THE LOOP, YOU NOW OWN THESE
Assign a name to each. Unassigned means undone.
Truncation policy (what drops first, and is it documented?)
Idempotency on retry (no duplicate side effects)
Partial tool failure (one of three tools times out)
Timeout + cancellation semantics
Malformed tool-call arguments (the model will send them)
Loop cap (max iterations before forced stop)
Conversation storage, retention and deletion
Model-swap drill: can I re-qualify in one afternoon?
PART 6 - THE HYBRID SPLIT (recommended default)
MINE: message assembly, context budget, state machine,
tool dispatch, logging, retries, storage, evals
RENTED: search, retrieval, sandboxed execution - called as
ordinary tools, each behind my own interface
Rule: nothing rented may hold state my code cannot rebuild.
Test: disable one rented tool. Does the loop degrade, or stop?
Stopping is fine if it is a decision. It usually is not.