null. The tool has to be published before you
attach it, exactly as an attached tool does, and publishing the agent pins it to the
tool version live at that moment. A tool can be both attached and wired to a hook —
it is one tool either way.
When each fires
on_enter
The hook runs to completion first, and the greeting waits for it. That is the point: the greeting resolves{{userdata.…}} against the live session bag the hook
has just written to, so the opening line can be true about what the hook found.
say — it plays while the rest of the tree runs.
Then exactly one opening line, or none:
- The greeting, wherever there is one. An agent reached by a handoff runs its
on_enterand then speaks its own greeting, like any other agent. - Failing that, and only on a handoff, a generated line: the caller has just asked for something and is waiting on this agent to answer it. On the first agent of a call the same silence is the “let the caller speak first” shape, and is left alone.
on_enter fires when a cold conversation window opens — the first
message, or the first after the previous window idled out. A warm window that is
already open does not run it again. Text agents have no greeting, so nothing follows
it.
If the hook hands the call on or hangs up, the agent has no opening line left to
speak and none is attempted.
on_exit
Fires when the session closes: the call ending, or a handoff away from this agent, on voice and video. On text it isend_call, the conversation window being closed, or
roughly a minute of no messages.
This is where an outcome gets logged, a lead gets posted, a CRM gets a note. A
failure here never crashes the call — a failing POST leaves a log line, not a dropped
caller.
on_user_turn_completed
Fires after every user turn and before the language model replies, with the user’s words available as{{args.user_message}}. On voice and video that is each
spoken turn; on text, each inbound message.
On text, {{args.user_message}} is the message text only. An image attached to the
same message goes into the context before the hook runs, but it has no string form,
so it is not in the argument.
On an agent with screen share on, the newest frame of
the shared screen is already in the context this hook runs against, so anything the
hook adds to the context lands after it.
The hook can stop the turn: ending the call or handing off from inside it suppresses
this agent’s reply, and the target agent answers the turn instead. A hook that raises
is logged and the turn continues.
What a hook tool may not be
A hook tool cannot contain atransfer operation. A hook runs outside a turn,
so the transfer’s wait for outstanding speech does nothing and the caller would be
handed over mid-word. Checked at agent publish, because the same tool is perfectly
valid attached normally:
user_message for on_user_turn_completed, and nothing at all for
on_enter and on_exit. So a tool whose tree reads {{args.account_id}} publishes
fine on its own and is refused as an on_enter hook:
{{userdata.…}}, {{vars.…}} or {{system_vars.…}} instead.
transfer is the only operation a hook is refused. The ordinary ones behave as they
do anywhere — http, code, if, say, add_message, set_variable, end_call
and handoff all work. Publishing fields into userdata from a hook is the normal
way to get a value in front of the agent before it speaks.
Worked examples
Load a CRM record on enter
The tool takes no arguments — a hook has nothing to give it — and identifies the caller from{{system_vars.human_phone_number}}, which is filled in by the platform.
on_error: "continue" is deliberate: a CRM that is down should leave a thinner
greeting, not a failed call.
The prompt then reads what the hook published:
"Hi {{userdata.name}}, thanks for calling" becomes
"Hi , thanks for calling" when the CRM had no name. A prompt can be told to work
around a blank; a greeting is spoken exactly as it resolves.
Log an outcome on exit
{{system_vars.now}} inside a tool is the moment the tool ran, so this timestamp is
the end of the call rather than the start. In a prompt it would be frozen at the
moment the agent picked up — see variables.
Flag a turn as it happens
{{args.user_message}} is the one argument this hook is given. Note the
json_schema: a tool is validated against its own declared parameters when you
publish the tool, so user_message has to be declared there or the tool itself will
not publish. Then, at agent publish, the tree is checked again against what the hook
actually supplies — and user_message is exactly that.
Keep the timeout short. This runs between the caller finishing and the agent
starting to answer, and every millisecond is silence on the line.
A hook is not the place for a slow job.
on_enter delays the greeting,
on_user_turn_completed delays every reply, and on_exit runs while the call is
being torn down. If the work can take seconds, post it to something that can take
its time.Attaching one
Next
Operation tree
What a hook tool’s tree can contain.
Userdata
What a hook publishes, and who reads it afterwards.
Handoffs
How a target agent enters, and what runs when it does.
Versions
Publishing, and the tool version a hook is pinned to.