Cancel a Turn¶
Stop an agent's in-flight turn early — a chat Stop button, a barge-in, or a time-boxed decision that ran out of budget. You choose whether to keep the partial reply or discard the whole interaction as if it never happened.
Prerequisites
- An agent created and ready — see Build a Chat Agent with a Graph.
- A turn actually running (you typically call
Cancelfrom your UI whileAnswerTextis streaming). Cancelling an idle agent is a no-op.
How it works¶
Cancel sends a CancelRequest for the agent's current turn. The stop is
cooperative: the node currently running finishes its step, then text
generation stops at the next token and any TTS stops after the current audio
chunk. The turn then ends normally through your turn-complete callback with
status Cancelled — so the same handler that detects a finished turn also
detects a cancelled one.
Two modes decide what happens to the turn's interaction:
| Mode | Effect | Use for |
|---|---|---|
StopAndKeep (default) |
Stop, but keep the partial reply in dialogue history. | A chat Stop button — the user sees what was generated so far and it stays in context. |
StopAndDiscard |
Stop and remove the last interaction (the user turn and the partial reply), as if it never happened. | A time-boxed decision agent that reached no decision, or a rollback where the half-finished turn must not pollute history. |
All four clients default to StopAndKeep (ordinal 0).
Cancel from a different context than the blocking send
Cancel interrupts a turn that is already running, so it must come from a
different thread or callback than the one blocked inside a synchronous
send. In particular the Python agent.send_message(...) call blocks until
the turn ends — call agent.cancel() from another thread. In the engines
and the C++ callback model, send is non-blocking, so you cancel from the UI
thread (or a streaming callback) while the turn streams.
Stop button (StopAndKeep)¶
The common case: wire a Stop control to cancel the streaming turn, keeping what was generated.
using Tryll.Client;
var agentComp = GetComponent<TryllAgentComponent>();
// Hook a UI button. Default mode = StopAndKeep.
stopButton.onClick.AddListener(() => agentComp.Cancel());
// The turn ends through the same OnTurnComplete you already handle:
agentComp.OnTurnComplete.AddListener((status, _, _) =>
{
if (status == TryllTurnStatus.Cancelled)
Debug.Log("[Tryll] turn cancelled — kept partial reply");
});
// Call from your UI handler while the turn streams. Default = StopAndKeep.
AgentComponent->Cancel();
// React in your OnTurnComplete binding:
void AThisClass::HandleTurnComplete(ETryllTurnStatus Status,
const FString& DebugInfo, int32 Tokens)
{
if (Status == ETryllTurnStatus::Cancelled)
UE_LOG(LogTemp, Log, TEXT("Turn cancelled — kept partial reply"));
}
- On your Stop button, get the
TryllAgentComponentand call Cancel (leave Mode atStopAndKeep). - Bind On Turn Complete and branch on
Status == Cancelledto reset your "typing…" UI.
// Cancel is fire-and-forget (returns immediately). Call it from your UI/other
// thread while a turn streams; default mode = StopAndKeep.
agent.Cancel(); // ::Tryll::CancelMode_StopAndKeep
agent.SetOnTurnComplete([](::Tryll::TurnStatus status, std::string_view, std::int32_t)
{
if (status == ::Tryll::TurnStatus_Cancelled)
std::cout << "\n[cancelled]\n";
});
import threading
from tryll_client.graph import TurnStatus
# Observe the outcome via the turn-complete callback — it fires for a
# cancelled turn too.
agent.set_on_turn_complete(
lambda status, debug_info, tokens:
print("[cancelled] kept partial reply")
if status == TurnStatus.Cancelled else None
)
# send_message blocks until the turn ends and returns the (partial) reply
# text, so run it off the main thread…
threading.Thread(
target=lambda: agent.send_message("Tell me a very long story.")
).start()
# …then cancel from the main thread (e.g. on a keypress). Default = StopAndKeep.
agent.cancel()
agent.cancel(mode=CancelMode.StopAndKeep, timeout=30.0) blocks briefly
waiting for the server Ack. The backgrounded send_message returns the
partial reply that was streamed before the cancel.
Rollback (StopAndDiscard)¶
When the half-finished turn must leave no trace — e.g. a decision-maker agent that timed out, or a speculative turn you're abandoning — discard it:
The user's message and the partial reply are both removed from the agent's dialogue, so the next turn sees history as though this turn never ran. (Any KV cache self-heals on the next send.)
Notes¶
- Cancel vs pause.
Cancelends the turn; Pause suspends it between nodes so you can mutate params and resume.Cancelis also the only way to abort a turn that is parked at a pause with no timeout. - Idle is a no-op. Cancelling when no turn is running does nothing — safe to wire a Stop button that's always visible.
- The editor chat windows already do this: they swap Send for Stop
(a
StopAndKeepcancel) while a turn streams.