A teammate was testing Riley, the agent on our website, and had to say goodbye three times before the call ended.
Ending a call sounds like the easy part. This is how we fixed it: what we blamed first, the test we built so we could stop guessing, the five seconds of silence that test found, and what you can tune if your agents run on the Live engine.
Two models, one call
A Crulon agent on the Live engine runs on OpenAI’s GPT-Live. Two models share every call:
- The speech model listens and talks. It’s full duplex: it hears the caller while it speaks.
- The backend model does the work: looking things up, running Tools, and ending the call.
The speech model never runs a tool itself. When the caller wants something done, it hands the request to the backend. OpenAI’s prompting guide calls that hand-off <|SpawnThinking|>.
So ending a call takes four steps:
- The caller says goodbye.
- The speech model says goodbye back and hands “end the call” to the backend.
- The backend calls the end-call tool.
- Crulon hangs up once the goodbye has played.
Steps 2, 3 and 4 each went wrong at some point that week.
What we blamed first
The wording. Our first theory was that the end-call tool’s description confused the backend. We reworked it on paper before checking what the call had actually run. The test call didn’t have the end-call tool at all: browser test calls were put together differently from phone calls, and that path left it out. With no way to hang up, the backend replied with text asking the speech model to end the call, which the speech model can’t do. Lesson: diagnose the call that ran, not the prompt you think it ran.
A safety net that wasn’t safe. At the time, the platform listened for goodbye phrases and, after one, hung up itself after four seconds of quiet. A phrase list reads “you have a good day too” as a goodbye, and a burst of noise on the line could cancel the four-second timer for good. We removed both. The model decides when a call is over; the platform carries it out.
Speed, for the narration. Some calls ended with “One moment, I’ll end the call.” It sounded like filler for a slow step, so we wondered whether a faster end-call would make it go away. It wouldn’t: the speech model says that line the moment it hands the request over, before it knows how long the backend will take.
What was actually breaking
With the tool in place, the speech model handed goodbyes over correctly. The backend didn’t always act on them. On one afternoon, in 4 of 7 test calls, it answered “end the call” with words instead: a goodbye of its own, or a correction of a date the speech model had made up. Nothing ended the call after that, and the caller heard “Are you still there?”
Two things fed it:
- The speech model had no clock. The backend’s prompt carried the date and time; the speech model’s didn’t. Asked the date, it guessed, and guessed wrong. The backend then used the end-call request to set the record straight.
- Both models were told the same thing. “Say a short, friendly goodbye” went to a model that doesn’t speak, and the backend’s prompt asked it to return the next thing for the voice to say. So it did.
The fix was to give each model its own job:
- The backend gets the End call instruction, and by default it’s told to end the call without replying anything. It may also decide by itself that a conversation is finished.
- The speech model gets one line about ending, in the shape OpenAI’s guide suggests: “When the caller asks to end the call or says goodbye, always generate <|SpawnThinking|>. Saying goodbye does not end the call.”
- Both models now get the same date and time, behind one switch.
We also moved the speech model’s whole prompt to OpenAI’s current wording. Their GPT-Live guide now asks for “Generate <|SpawnThinking|> when:” where our prompts still said “Delegate to the backend when:”.
Building a test we could trust
Every fix so far came from a handful of manual calls, and a handful of calls can’t tell you whether a change helped. So we built a smoke test: a scripted caller that rings a real agent, through the same browser test call our customers use, and measures how the call ends.
It runs ten short conversations, about a minute each:
- A straight goodbye.
- “Thanks so much,” then “No, that’s everything.”
- “Okay, thanks,” then silence.
- A false ending: “Thanks! Oh wait, one more thing…”
- “Goodbye” used as a word: “I’m about to say goodbye to my current provider.”
- “You can hang up now.”
- A goodbye said over the agent, mid-sentence.
- “Never mind, bye” while the agent is still looking something up.
- Two date questions, then bye.
- A wrong number.
The caller speaks real audio. Its questions are scripted, a small model fills in an answer if the agent asks something the script didn’t expect, and the closing lines are always word for word, because they’re what’s being measured. Each call records how it ended, how many goodbyes it took, the time from the caller’s goodbye to the line dropping, and anything the agent said after the goodbye. Each scenario runs three times, with a fixed seed, so a before-and-after comparison uses the same voices, wording and pauses.
Five seconds of silence
The first full run found something none of us had noticed: after the agent’s goodbye, the line stayed open for about five seconds before it dropped. The caller heard the goodbye, then nothing.
Our first diagnosis was half right. After the backend ends the call, it gets one more turn to reply, and it’s told to say nothing. We were waiting for that empty turn before hanging up, so we stopped waiting for it. Then we ran the same 30 calls again. The time from end-call to the line dropping went from 5.5 seconds to 6.3. No better.
The logs showed why. Closing a call also closes the agent’s session, and that waits for everything still running to finish, the backend’s turn included. GPT-Live winds that turn down on its own schedule, and nothing on our side can cut it short. The caller’s line was closed last, after all of it.
So we changed the order. Once the goodbye has played and the call is recorded as ended, we drop the caller’s line, then let the session tidy up behind it.
Same 30 calls, same seed:
- Passed: 11 of 30 before, 29 of 30 after.
- Silence after the agent’s last word: 6.7 seconds before (median), 1.6 seconds after. Nine calls in ten are now at 2 seconds or less.
- From the caller’s goodbye to the line dropping: 9.6 seconds before, 5.0 after. Most of what’s left is the agent saying goodbye.
Lower reasoning wasn’t better
While we were measuring, we tried the backend at a lower reasoning effort, to see if it would decide faster. Most decisions did get a little quicker. But in 3 of 30 calls the backend answered with words, like “You’re welcome. Goodbye!”, and never ended the call: the failure we started with. We kept it at medium.
One thing we haven’t fixed: now and then the backend takes 15 to 24 seconds to decide to end a call, at either setting. That’s the model’s own latency, and it means a caller occasionally says bye twice. We’re watching it.
Tuning the end of a call in your workspace
If your agent runs on the Live engine, these settings shape how its calls end. They’re all on the agent’s page in Crulon.
Call Handling › End call › Instructions. This goes to the backend, which ends the call. The default is “When the caller asks to end the call or the conversation is finished, end the call without replying anything.” Use it to say when to end, and keep “without replying anything”: words the backend writes here can be cut off by the hang-up, or keep the call from ending at all.
Call Handling › End call › Speech instructions. This goes to the speech model, and it’s where the goodbye itself is shaped. The default tells it to hand every goodbye to the backend. If you rewrite it, keep that hand-off in the same words, “always generate <|SpawnThinking|>”, and keep “Saying goodbye does not end the call.” You can add how to say goodbye, for example: “Say ‘Thanks for calling Summit Heating & Air’ before you go.”
Behavior › Date and time. Leave “Tell the agent the current date and time” on, and set the Timezone if the agent works in a different zone from your workspace. Both models read the same clock.
Behavior › Idle prompt, No-input timeout and Idle retries. These end the calls where the caller goes quiet and never says goodbye. In our tests, “Okay, thanks” followed by silence got “Are you still there?” twice, then a goodbye, about 100 seconds later with the default 30-second timeout. A shorter timeout ends those calls sooner.
Call Handling › End call › Max call length. The backstop for a call that runs on.
Then test it. Open Talk in browser, ask a question, and end the call a few different ways: “bye”, “thanks, that’s all”, “you can hang up now”, and a goodbye over the agent mid-sentence. Listen for three things:
- a goodbye that isn’t followed by the line dropping within a couple of seconds
- a line like “one moment, I’ll end the call”
- “Are you still there?” after you’ve already said bye
Each test call’s transcript is in the test panel’s recent calls.
What’s next
We’ll run the smoke test on every change to how calls end, and next we’ll build the same test for agents on the Classic engine. If an agent in your workspace won’t let a caller go, write to us at hello@crulon.ai and we’ll look at the call.