Hooks
Agentick hooks follow React conventions with AI-specific extensions. They manage state, lifecycle, and reactive behavior within agent components.
State Hooks
useState
Identical to React's useState. State persists across ticks within an execution.
const [count, setCount] = useState(0);useSignal
Reactive signal — updates propagate without re-rendering the full component tree.
const count = useSignal(0);
count.value++; // Triggers targeted update
console.log(count.value); // Current valueuseKnob
Creates model-visible, model-settable reactive state. See Knobs for full documentation.
const [depth, setDepth] = useKnob("search_depth", 3, {
min: 1,
max: 10,
description: "Number of results to analyze",
});Lifecycle Hooks
All lifecycle hooks follow the "data first, ctx last" convention.
useOnMount
Runs once when the component first mounts (tick 1).
useOnMount((ctx) => {
console.log("Agent initialized");
});useOnTickStart
Runs at the start of each tick, including the mount tick. Newly-mounted components receive a catch-up call after their first render.
useOnTickStart((tickState, ctx) => {
console.log(`Starting tick ${tickState.tickNumber}`);
});useOnTickEnd
Runs after each tick completes (including the mount tick).
useOnTickEnd((result, ctx) => {
console.log(`Tick ended. Response: ${result.response}`);
});useAfterCompile
Runs after the compiler produces the model input, before the model call. Useful for inspection or telemetry.
useAfterCompile((compiled, ctx) => {
console.log(`Sending ${compiled.messages.length} messages`);
});useContinuation
Controls whether execution continues with another tick. result.shouldContinue shows the framework's decision. Return nothing to defer, or override. See Controlling the Tick Loop below.
useContinuation((result) => {
if (result.tick >= 10) return { stop: true, reason: "max-ticks" };
// No return = defer to framework
});useOnExecutionEnd
Runs once when execution completes (after all ticks finish). Fires before the session snapshot is persisted, so state changes here are captured cleanly. Always fires — even on abort or error.
useOnExecutionEnd((ctx) => {
console.log("Execution complete");
ctx.setState("lastCompleted", Date.now());
});useOnMessage
Fires when a new message is added to the timeline.
useOnMessage((message, ctx, state) => {
if (message.role === "assistant") {
logResponse(message.content);
}
});Controlling the Tick Loop
An execution is a sequence of ticks. Each tick is one model API call. By default, execution continues when the model returns tool calls or messages are queued, and stops otherwise.
You control this with useContinuation. The result.shouldContinue property shows the framework's current decision (including overrides from prior callbacks in the chain). Return nothing to defer, or override.
Default behavior
No tool calls and no queued messages → stop. Tool calls or queued messages → continue.
useContinuation
Override the default with a boolean, object, imperative methods, or defer:
// Defer: no return = accept framework decision
useContinuation((result) => {
logger.info(`tick ${result.tick}, continuing: ${result.shouldContinue}`);
});
// Boolean shorthand
useContinuation((result) => !result.text?.includes("<DONE>"));
// Object with reason
useContinuation((result) => {
if (result.tick >= 10) return { stop: true, reason: "max-ticks" };
});
// Imperative methods
useContinuation((result) => {
if (result.text?.includes("<DONE>")) result.stop("task-complete");
});result.stop() and result.continue()
These methods are available on the TickResult passed to both useContinuation and useOnTickEnd. They accept an optional reason string or options object:
result.stop("task-complete");
result.stop({ reason: "verified", status: "completed" });
result.continue("verification-pending");
result.continue({ reason: "retry", priority: 10 });Stopping from useOnTickEnd
You can also control the loop from useOnTickEnd — useful when you want to run side effects and control flow in the same callback:
useOnTickEnd((result, ctx) => {
// Log the result
saveToDatabase(result.response);
// Stop if the agent found what it was looking for
if (result.response?.includes("FOUND")) {
result.stop("target-found");
}
});Tick limit
Set maxTicks on the input to cap the number of ticks per execution:
await session.send({
messages: [...],
maxTicks: 5, // Hard limit: stop after 5 ticks regardless
});Signature Reference
useOnMount((ctx) => {});
useOnTickStart((tickState, ctx) => {});
useOnTickEnd((result, ctx) => {});
useAfterCompile((compiled, ctx) => {});
useOnExecutionEnd((ctx) => {});
useContinuation((result, ctx) => boolean | void); // result.shouldContinue shows framework default
useOnMessage((message, ctx, state) => {});