Hooks
Hooks let you attach your own code to points in the agent loop. Where permissions answer “is this allowed?”, hooks answer “what should happen around it?” — logging, validation, enrichment, or outright blocking. Amara fires each hook at a well-defined moment, passes it a payload describing what is about to happen, and lets your callback observe or influence the outcome.
Lifecycle events
The SDK exposes hook points that map to the phases of a turn. The most commonly used are:
preToolUse— fires before a tool executes, with the tool name and input. Return a decision to allow, block, or modify the call.postToolUse— fires after a tool returns, with the result. Useful for auditing, redaction, or triggering side effects.sessionStart/sessionEnd— fire when a session begins and ends, ideal for setting up and tearing down external resources.userPromptSubmit— fires when a new prompt enters the loop, letting you inject context or reject the request.
Registering hooks
Hooks are supplied through the query options as a map from event name to one or more callbacks. Each callback is async and receives the event payload.
import { query } from "@syntic/agent-sdk";
for await (const message of query({
prompt: "Update the changelog.",
options: {
hooks: {
preToolUse: [
async ({ toolName, input }) => {
console.log(`About to run ${toolName}`);
if (toolName === "Bash" && input.command.includes("curl")) {
return { decision: "block", reason: "Network calls are disabled." };
}
return { decision: "continue" };
},
],
},
},
})) {
// ...
}Observing versus intervening
A hook that returns continue (or nothing) simply observes — perfect for telemetry and audit trails. A hook that returns block stops the action and feeds your reason back to Amara, which can adapt its plan. This makes hooks a natural place to encode organization-specific guardrails that go beyond static allow/deny lists.
async def guard_writes(event):
if event.tool_name == "Write" and "secrets" in event.input["path"]:
return {"decision": "block", "reason": "Cannot write to secrets."}
return {"decision": "continue"}
async for message in query(
prompt="Persist the config.",
options={"hooks": {"pre_tool_use": [guard_writes]}},
):
...