Skip to content

Pause Node

NodeType = Pause (NodeParams union discriminator 12)

A no-op node. When it exits, the workflow executor suspends the turn between nodes — after PauseNode exits but before the graph resolves its next node — instead of continuing immediately. The turn stays open (the client still owns it and it still counts as busy for SendMessage), but unlike a normal in-flight turn, ChangeAgentParam is allowed while paused.

The node itself does not call a model and does not modify the conversation history. It exists purely to give game logic a checkpoint to react to before the turn continues.

Resume a paused turn with ResumeAgentRequest:

  • Plain resume (resume_node empty) — continues via default_exit, same as any other exit.
  • Jump resume (resume_node set) — routes to the named node instead, skipping the node's own wiring. Fails with 3005 UnknownNode if the name doesn't exist in the graph.

See How to pause and resume a turn for the full client-side flow. Unity binds TryllAgentComponent.Paused (nodeName, pendingExit) and Unreal binds UTryllAgentComponent::OnPaused. TryllClient.Paused and UTryllSubsystem::OnPaused are the all-agents form and still pass an agent id. ToolCall is the other way to trigger a pause (a pausing disposition: Pause, PauseAndAcknowledge, or AwaitResult).

Timing out a pause

By default (pause_timeout_ms = 0) a pause waits forever. That is the right behaviour when your game is guaranteed to answer — and a soft-lock when it isn't, because the agent stays busy and every later SendMessage is rejected with AgentBusy. The server logs a warning naming the parked node once a pause outlives workflow.pause_stall_warn_ms (15 s by default), and your client logs one immediately if nothing it holds can resume the pause at all.

Set pause_timeout_ms to give the pause a deadline. What happens on expiry depends on whether you authored timeout_exit:

timeout_exit On expiry
set The graph continues from that exit — author a fallback line, or a Branch that picks one. The turn ends normally.
empty The turn is abandoned with TurnStatus.PauseTimedOut. The partial interaction is kept, so the conversation is intact.

Prefer authoring a timeout_exit for anything player-facing: the character says something instead of going quiet.

Two things to keep in mind:

  • The deadline is wall-clock on the server. A minimised or alt-tabbed game stops pumping its main thread, so the deadline can elapse while your game is suspended. Make the timeout branch harmless — "shrug and answer without the tool" — rather than something that only makes sense the instant it fires.
  • pause_timeout_ms and timeout_exit are structural: they are fixed when the agent is created and ChangeAgentParam rejects them with 3006 ParamNotMutable. Vary them per agent, not per turn.

Parameters

Behavior

Param Type Default Range Structural Localizable Description
pause_timeout_ms int 0 ≥ 0.0 ✓ — How long to wait for the game to resume, in ms. 0 = forever. Set a limit unless the game always resumes.

Exits

Each exit is a structural string field on the node's params; its value names the target node (empty = END).

Exit Param field Description
default default_exit Node to run when the game resumes without choosing one. Empty = end the turn.
timeout timeout_exit Node to run if the wait times out, such as a fallback line. Empty = end the turn as timed out. Unused when Pause Timeout Ms is 0.

Exit routes

Exit Condition
default Taken on a plain resume (empty resume_node). A jump resume skips this node's wiring and goes straight to the named node.
timeout Taken when pause_timeout_ms elapses and timeout_exit is set. With timeout_exit empty, expiry ends the turn with TurnStatus.PauseTimedOut.

Diagnostics

Pause records no diagnostics of its own: its node entry in debug_info.nodes[] carries _type, name, exit_route and duration_s, with no diagnostics object at all.

{
  "_type": "PauseNode",
  "name": "checkpoint",
  "exit_route": "default",
  "duration_s": 3.5e-06
}

duration_s is microseconds even for a pause the client held for minutes, because paused time is excluded from every per-node duration. The pause is measured at the turn level instead — paused_duration_s and pause_count in the envelope — and a pause that timed out shows up as the turn status pause_timed_out.


Example

from tryll_client.graph import GraphDescription, PauseParams, GenerateParams, Placement

graph = (
    GraphDescription()
    .add_node("checkpoint", PauseParams(default_exit="generate"))
    .add_node("generate", GenerateParams(
        template="{{user_message}}",
        placement=Placement.BeforeUserAsSystem,
        default_exit="",   # empty = END
    ))
    .set_start_node("checkpoint")
    .set_default_model_name("Llama 3.2 3B Instruct (Q4_K_M)")
)
agent = client.create_agent(graph)

agent.set_on_paused(lambda node, exit_route: print(f"paused at {node} -> {exit_route}"))
agent.send_message("hello")
# ... game logic runs, may call agent.change_params(...) here ...
agent.resume()  # or agent.resume("generate") to jump directly
using namespace Tryll::Client;
using namespace Tryll::NodeParams;

PauseParamsT pp;
pp.default_exit = "generate";

GenerateParamsT gp;
gp.template_ = "{{user_message}}";
gp.placement = ::Tryll::Placement_BeforeUserAsSystem;

GraphDescription graph;
graph.AddPause("checkpoint", std::move(pp))
     .AddGenerate("generate", std::move(gp))
     .SetStartNode("checkpoint")
     .SetDefaultModelName("Llama 3.2 3B Instruct (Q4_K_M)");
auto agent = client.CreateAgent(graph);

agent.SetOnPaused([](std::string_view node, std::string_view exitRoute)
{
    // game logic; may call agent.ChangeParams(...) here
});
agent.SendText("hello");
agent.Resume(); // or agent.Resume("generate") to jump directly