Skip to content

Hooks

Hooks intercept agent lifecycle events to add custom behavior: logging, guards, state modification, tool blocking, and more.

Lifecycle Events

Trigger When
BeforeExecution Before the loop starts
BeforeStep Before each LLM call
BeforeToolUse Before each tool execution
AfterToolUse After each tool execution
AfterStep After each loop iteration
OnStop When a stop signal is detected
AfterExecution After the loop ends
OnError When an error occurs

Implementing a Hook

Implement HookInterface:

use Cognesy\Agents\Hooks\Contracts\HookInterface;
use Cognesy\Agents\Hooks\Data\HookContext;

class LogStepsHook implements HookInterface
{
    public function handle(HookContext $context): HookContext
    {
        $steps = $context->state()->stepCount();
        echo "Current step: {$steps}\n";
        return $context;
    }
}

Registering Hooks

Use HookStack to compose hooks with trigger filters and priorities:

use Cognesy\Agents\Hooks\Collections\HookTriggers;
use Cognesy\Agents\Hooks\Collections\RegisteredHooks;
use Cognesy\Agents\Hooks\Interceptors\HookStack;

$stack = new HookStack(new RegisteredHooks());
$stack = $stack->with(
    hook: new LogStepsHook(),
    triggerTypes: HookTriggers::afterStep(),
    priority: 10,
    name: 'log_steps',
);

$loop = AgentLoop::default()->withInterceptor($stack);

CallableHook

Quick hooks without a class:

use Cognesy\Agents\Hooks\Defaults\CallableHook;

$hook = new CallableHook(function (HookContext $ctx): HookContext {
    echo "Step done!\n";
    return $ctx;
});

$stack = $stack->with($hook, HookTriggers::afterStep());

Blocking Tool Execution

In a BeforeToolUse hook, block a tool:

class BlockDangerousTools implements HookInterface
{
    public function handle(HookContext $context): HookContext
    {
        if ($context->toolCall()?->name() === 'dangerous_tool') {
            return $context->withToolExecutionBlocked('Not allowed');
        }
        return $context;
    }
}

$stack = $stack->with(new BlockDangerousTools(), HookTriggers::beforeToolUse());

Modifying State

Hooks can modify AgentState through HookContext:

$hook = new CallableHook(function (HookContext $ctx): HookContext {
    $state = $ctx->state()->withMetadata('custom_key', 'value');
    return $ctx->withState($state);
});

Built-in Guard Hooks

use Cognesy\Agents\Hooks\Guards\StepsLimitHook;
use Cognesy\Agents\Hooks\Guards\TokenUsageLimitHook;
use Cognesy\Agents\Hooks\Guards\ExecutionTimeLimitHook;

// Stop after 10 steps
$stepsGuard = new StepsLimitHook(
    maxSteps: 10,
    stepCounter: fn($state) => $state->stepCount(),
);

// Stop after 5000 tokens
$tokenGuard = new TokenUsageLimitHook(maxTotalTokens: 5000);

// Stop after 30 seconds
$timeGuard = new ExecutionTimeLimitHook(maxSeconds: 30.0);

$stack = $stack
    ->with($stepsGuard, HookTriggers::beforeStep(), priority: 100)
    ->with($tokenGuard, HookTriggers::beforeStep(), priority: 100)
    ->with($timeGuard, HookTriggers::with(HookTrigger::BeforeExecution, HookTrigger::BeforeStep), priority: 100);

Hook Priority

Higher priority hooks run first. Use priorities to ensure guards run before business logic.