Zgent
Core Concepts

Hooks

Intercept and modify agent behavior at every lifecycle point.

Hooks let you observe and control agent behavior without modifying core logic. They fire at specific points in the agent lifecycle and can continue, skip, abort, or retry the current operation.

Hook events

EventWhen it fires
BEFORE_TOOLBefore a tool is called
AFTER_TOOLAfter a tool returns successfully
AFTER_TOOL_FAILUREAfter a tool call fails
PRE_COMPACTBefore context compaction
AFTER_COMPACTAfter context compaction
USER_PROMPT_SUBMITWhen a user message is submitted
STOPWhen the agent stops
SUBAGENT_STOPWhen a sub-agent stops
ON_ERROROn unhandled errors
BEFORE_AGENT_CREATIONBefore an agent instance is created
AFTER_AGENT_CREATIONAfter an agent instance is created
ANYWildcard — fires on every event

Hook actions

Each hook returns a HookResult with one of these actions:

ActionEffect
CONTINUEProceed normally (default)
SKIPSkip the current operation
ABORTStop execution and raise HookAbortError
RETRYRetry the current tool call
RETRY_WITH_COMPACTCompact context, then retry

Writing a hook

A hook is an async function that receives a HookContext and returns a HookResult (or None to continue):

from zgent import HookAction, HookContext, HookResult

async def log_tool_calls(ctx: HookContext) -> HookResult | None:
    print(f"Tool called: {ctx.tool_name}({ctx.tool_input})")
    return None  # continue normally

Registering hooks

On the definition

from zgent import AgentDefinition, AgentEngine
from zgent import HookEvent

definition = AgentDefinition(
    system_prompt="You are a helpful assistant.",
    engine_type=AgentEngine.CLAUDE,
    hooks={
        HookEvent.BEFORE_TOOL: [log_tool_calls],
        HookEvent.AFTER_TOOL: [check_tool_output],
    },
)

With on_event

definition.on_event(HookEvent.BEFORE_TOOL, log_tool_calls)
definition.on_event(HookEvent.AFTER_TOOL, check_tool_output)

Example: auto-approve safe tools

SAFE_TOOLS = {"Read", "Glob", "Grep"}

async def auto_approve(ctx: HookContext) -> HookResult | None:
    if ctx.tool_name in SAFE_TOOLS:
        return HookResult(action=HookAction.CONTINUE)
    # Block dangerous tools
    if ctx.tool_name == "Bash":
        return HookResult(
            action=HookAction.ABORT,
            reason="Bash execution is not allowed.",
        )
    return None

Example: modify tool input

async def inject_credentials(ctx: HookContext) -> HookResult | None:
    if ctx.tool_name == "WebFetch" and ctx.tool_input:
        ctx.tool_input["headers"] = {"Authorization": "Bearer xxx"}
        return HookResult(
            action=HookAction.CONTINUE,
            modified_input=ctx.tool_input,
        )
    return None

Hook chain behavior

When multiple hooks are registered for the same event, they execute in order. If any hook returns ABORT, execution stops immediately. Otherwise, results are aggregated — modified outputs are combined.

On this page