Control publication with a hook
An optional extension that applies your application's policy before a tool runs.
The research task needs no approval. This optional lesson shows how a hook changes behavior at one point in the loop, without moving the loop into your application.
Choose the policy in your application
The starter defines this tool-call hook:
onToolCall: async (call) => {
if (call.name !== "publish_brief") return { decision: "allow" }
return { decision: "ask", ask: { prompt: "Publish this research brief?" } }
}Source reads are allowed. Publishing asks a person. A production hook can consult your own
workspace settings and return allow, deny, or ask; see decision hooks.
In Ada's conversation, first research the Orion launch, then ask “Publish that brief.”
Cereal records a pending decision before dispatching publish_brief.
The starter's tool returns a publication preview; it does not publish or store content.
Answer through an authorized route
The browser reads agent.approvals.pending(sessionId) through its authenticated server
route, then sends the decision to /api/sessions/:id/decide:
if (
action === "decide" &&
typeof body.toolCallId === "string" &&
(body.decision === "allow" || body.decision === "deny")
) {
try {
return json(
await client.sessions.decide({
id,
toolCallId: body.toolCallId,
decision: body.decision,
actor: member.id
})
)
} catch (error) {
if (
error instanceof DecisionNotPendingError ||
error instanceof DecisionExpiredError
) {
return json(
{
error: "This approval was answered or expired. Refresh the pending approvals."
},
409
)
}
throw error
}
}Ownership and request-origin checks run first. The actor is the authenticated member, not a browser-supplied name. A duplicate or expired decision returns a recoverable 409; the UI refreshes pending approvals.
Check both outcomes
Allow the call and look for preview: true with the brief and its source IDs in the tool
result. In a new session, request publication and deny it; that tool call must not execute.
The session parks at idle with stopReason: requires_action, then continues after the
decision. A stream token can display the pending request but cannot authorize it.
For a real publishing service, enforce workspace permissions in the handler and use a
durable idempotency key for external writes. Hook deliveries can be retried.
Next: recover and debug.