Most advice about AI on Salesforce argues about whether to use a model. The more useful question is where the call runs, because that is what decides whether the feature survives contact with your whole record set.
There are three places it can run. Inside a conversational agent outside the platform. Inside Apex, through a named credential. Or inside a worker process that talks to Salesforce over the API. Most teams only know the middle one, and the middle one is the right place to start.
It is not always the right place to stay. Three limits decide.
Limit one: 120 seconds of callout, cumulative
Apex allows 120 seconds of cumulative callout time per transaction. Not per callout. Every model call in that transaction draws from the same budget, and exceeding it kills the transaction outright.
You design around it by making the budget explicit. Four records per execution. Timeouts of 25 seconds for batch work, 45 for anything a person is waiting on, 60 for a whole-portfolio call. A 90-second wall-clock stop that ends the transaction cleanly. Never 120, because the last call needs somewhere to fail politely.
Limit two: Apex cannot wait
There is no sleep in Apex. When a provider returns a rate-limit response with a retry-after header, the header is useless. The only options are to fail, or to spend callout budget spinning, which is the same thing more expensively.
Any provider with a per-minute or per-day quota therefore needs something outside the platform that can pace itself. This is the limit that actually forces the move, and it is the one people discover last.
Limit three: no durable queue
A queueable chain that dies mid-run loses its batch. For a nightly refresh of a few hundred records that is survivable. For anything where a partial result has to be kept, it is not.
The run that settled it
On one engagement the in-platform chain was pointed at the full book for the first time. The provider free tier capped the project at 20 requests per day. Two of 256 records came back with an insight. The other 250 were stamped with a rate-limit error and stored as failures.
The chain was correct. It had simply been asked to do something the platform gives it no way to do: wait, back off, and come back tomorrow where it left off.
The rule
Stay in Apex when the call is for one record, triggered by a person who is waiting, finishes inside 60 seconds, and can simply fail. Move to a worker when you are processing the whole book, when pacing matters, or when a provider quota exists.
And before either, apply the cheaper rule. If the answer is a lookup or a calculation, use Apex or Flow. It is free, it is exact, and it does not need an evaluation harness. Use a model only for language.
How to move without forking the system
The obvious mistake is to port the prompt into the worker. Within a month there are two prompts, they differ, and the interactive answer disagrees with the nightly answer about the same record. Nobody can tell which one is wrong because both are running the code their author intended.
So the split is drawn somewhere else. The thinking stays on the platform. The transport leaves it.
- Prompts stay in Apex, as the single definition of what is asked.
- Input assembly stays in Apex, so both paths see identical inputs.
- The cache rule stays in Apex, so both paths agree on what is stale.
- The worker owns pacing, retries, quota accounting and delivery. Nothing else.
The worker asks Apex for the next batch of prompts and inputs, calls the provider, and posts results back in batches. Two code paths, one definition. They cannot drift, because there is nothing duplicated to drift.
What you owe the system the day you leave the platform
Off-platform is not an upgrade. It is infrastructure you now own, and it fails in ways the platform used to absorb for you.
- Paced requests and exponential back-off that honours the retry-after header.
- A per-day quota bucket that stops the run entirely rather than burning the day on errors.
- Results posted back in batches, so a crash costs one batch rather than one run.
- A heartbeat record written on every cycle.
- A watchdog that emails when the heartbeat goes quiet. A job that stops silently is worse than one that fails loudly.
- A unit test asserting the quota classifier against a real rate-limit payload, because that classifier is the thing standing between you and 250 stored failures.
The cache rule is the cost control
One rule does more for the bill than any model choice. An error row is never fresh. The input hash must match. Active work expires after seven days, because on live work the passage of time is itself a signal. Finished work is cached indefinitely, because nothing about a closed record changes with the calendar.
That is what keeps a 250-record book to a handful of calls a day, and a full rebuild of the entire book to a dollar or two of provider spend.
Move when a limit forces it, not when it feels more serious. Keep the prompts, the inputs and the cache rule in Apex. Then the platform still owns the meaning, and the worker only owns the waiting.