Turn hooks
Run code before the turn — fetch context, ground the prompt, switch model, or refuse.
A turn hook is JavaScript attached to an agent that runs before the model's
turn. Today there is one hook: beforeTurn. It runs after the user sends a
message and before the model sees it — the place to fetch context, inject
grounding, or decide the turn shouldn't run at all. It runs on every turn,
including turns from embedded and public chats.
Add it in the agent editor under Turn code → Before turn (stored as the
agent's beforeTurn).
The contract
async function beforeTurn(data, runtime) {
log('checking request', { model: data.model });
if (data.messages.length > 200) {
return { block: true, reason: 'This conversation is too long. Start a new chat.' };
}
// Return only what you change.
return { system: (data.system ?? '') + '\nAnswer in French.' };
}The function must be named beforeTurn: if the code declares functions and
none has that name, the hook is skipped.
data | |
|---|---|
messages | The conversation the model is about to see. |
system | The agent's instructions. |
model | The model this turn runs on. |
temperature | The agent's temperature. |
runtime | |
|---|---|
user | { id, email, isAnonymous, roles, metadata } of whoever is chatting. |
tenantId | The organization. |
agentId, threadId | This agent and thread. |
model | The model this turn runs on. |
data arrives as a JSON copy. Return nothing (or null) to leave the turn
alone; editing data in place and returning nothing also works. Fields left
out of a returned object stay as they were.
What a return can change
| Return | Effect |
|---|---|
{ block: true, reason } | Refuses the turn before any model call; reason is streamed as the reply (a generic message without one). Wins over everything else. |
system | Replaces the instructions only — the platform's knowledge, tool, and memory instructions are still added. Append to the system you were given to add rather than replace. |
messages | Replaces what the model sees for this turn. The stored history and the trace keep what the user actually sent. An invalid array is ignored. |
model | Switches model. One the organization can't use is ignored, with a warning. |
temperature | Any finite number. |
Failure and limits
Hooks fail open: a throw, a timeout, a misnamed function, or an oversized
return leaves the turn as it was. The cause is recorded on the turn's trace as
beforeTurnHookError, so a policy hook that never ran is visible.
| Limit | |
|---|---|
| Run time | 30 seconds |
| Return value | 128 KB |
| Logs | 50 log() lines per run, 4 KB each |
Return only the fields you change: echoing the whole data back on a long
conversation can pass the 128 KB cap.
The hook runs in the same sandbox as function tools.
fetch goes out only through the organization's outbound-access rules in
Settings.
Logging from a hook
Hooks use the same log(message, data?, level?) as function tools — level
is debug, info (default), warn, or error. Lines land in the Logs tab,
tagged with the hook that wrote them:
[tool:hook:beforeTurn] checking request
[tool:hook:beforeTurn] attached 3 messages of historyLogging is opt-in: a hook that never calls log() emits nothing.
What hooks are for
- Grounding. Pull the thread history, the customer record, the relevant docs — and attach them before the model reasons.
- Policy. Reject or reshape turns that shouldn't run as asked.
- Routing. Move a turn to a cheaper or stronger model.
- Annotation. Leave a record of what context the turn had, so the trace explains not just what the model did but what it knew.