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.