A3S Docs
A3S Code

Lifecycle Hooks

11 lifecycle hooks for intercepting tool calls, LLM generations, sessions, and skills

Lifecycle Hooks

A3S Code provides 11 lifecycle hooks for intercepting and reacting to events during agent execution. Hooks enable logging, security enforcement, telemetry, and custom behavior without modifying the core agent loop.

Hook Events

Prop

Type

How Hooks Work

Agent Loop
  ├── GenerateStart hooks fire
  ├── LLM call
  ├── GenerateEnd hooks fire
  └── For each tool_use:
      ├── PreToolUse hooks fire
      ├── Permission check
      ├── HITL check
      ├── Tool execution
      └── PostToolUse hooks fire

Built-in SecurityGuard

The SecurityGuard registers hooks at priority 1 (highest) automatically:

Prop

Type

Hook Registration

import { Agent } from '@a3s-lab/code';

const agent = await Agent.create('agent.hcl');
const session = agent.session('/project');

// Metadata-only — fires in Rust, always continues (good for logging via stream events)
session.registerHook('log-tools', 'pre_tool_use');

// With callback — can block, skip, or continue
session.registerHook(
  'guard',
  'pre_tool_use',
  { tool: 'bash' },
  { priority: 5 },
  (event) => {
    const cmd = String((event.payload as any)?.args?.command ?? '');
    if (cmd.includes('rm -rf')) {
      return { action: 'block', reason: 'Dangerous command blocked by guard' };
    }
    return null; // continue
  },
);

console.log(`Hooks registered: ${session.hookCount()}`);

// Unregister when done (also removes the associated callback)
session.unregisterHook('log-tools');
session.unregisterHook('guard');

The handler callback receives the serialized hook event { event_type, payload } and must return null/undefined to continue, or { action: 'block', reason: '…' } / { action: 'skip' } to control execution.

from a3s_code import Agent

agent = Agent.create('agent.hcl')
session = agent.session('/project')

# Metadata-only — fires in Rust, always continues
session.register_hook('log-tools', 'pre_tool_use')

# With callback — can block, skip, or continue
def bash_guard(event):
    cmd = str(event.get('payload', {}).get('args', {}).get('command', ''))
    if 'rm -rf' in cmd:
        return {'action': 'block', 'reason': 'Dangerous command blocked by guard'}
    return None  # continue

session.register_hook(
    'guard', 'pre_tool_use',
    matcher={'tool': 'bash'},
    config={'priority': 5},
    handler=bash_guard,
)

print(f'Hooks registered: {session.hook_count()}')

# Unregister when done (also removes the associated callback)
session.unregister_hook('log-tools')
session.unregister_hook('guard')

The handler callable receives event: dict with event_type and payload fields. Return None or {"action": "continue"} to allow execution, or {"action": "block", "reason": "…"} to cancel.

Hook Priority

Hooks execute in priority order. Lower numbers run first.

Prop

Type

If any hook returns HookResult::Block, the operation is cancelled and subsequent hooks for that event are skipped.

Hook Event Payloads

Each hook event carries a typed payload with event-specific data:

API Reference

Hook registration

Prop

Type

HookHandler

Pass a callback as the fifth argument to registerHook. The callback receives the serialized event and returns a response object:

import { Agent } from '@a3s-lab/code';

const agent = await Agent.create('agent.hcl');
const session = agent.session('/project');

// Block dangerous bash commands
session.registerHook(
  'my-guard',
  'pre_tool_use',
  { tool: 'bash' },
  { priority: 5 },
  (event) => {
    const cmd = String((event.payload as any)?.args?.command ?? '');
    if (cmd.includes('rm -rf')) {
      return { action: 'block', reason: 'Dangerous command blocked' };
    }
    return null; // continue
  },
);

// EventStream uses .next() (not Symbol.asyncIterator)
const stream = await session.stream('List all .ts files');
while (true) {
  const { value: event, done } = await stream.next();
  if (done || !event) break;
  if (event.type === 'tool_start') {
    console.log(`Tool called: ${event.toolName}`);
  } else if (event.type === 'end') break;
}

Callback return values:

  • null / undefined → continue
  • { action: 'block', reason: '…' } → cancel the operation
  • { action: 'skip' } → skip remaining hooks, continue execution
  • { action: 'retry', delayMs: N } → retry after N milliseconds

Pass a callable as the handler keyword argument. It receives event: dict and returns a response dict:

from a3s_code import Agent

agent = Agent.create('agent.hcl')
session = agent.session('/project')

# Block dangerous bash commands
def bash_guard(event):
    cmd = str(event.get('payload', {}).get('args', {}).get('command', ''))
    if 'rm -rf' in cmd or 'DROP TABLE' in cmd:
        return {'action': 'block', 'reason': 'Dangerous command blocked'}
    return None  # continue

session.register_hook(
    'my-guard', 'pre_tool_use',
    matcher={'tool': 'bash'},
    config={'priority': 5},
    handler=bash_guard,
)

for event in session.stream('List all .py files'):
    if event.event_type == 'tool_start':
        print(f"Tool called: {event.tool_name}")
    elif event.event_type == 'end':
        break

Callback return values:

  • None → continue
  • {"action": "block", "reason": "…"} → cancel the operation
  • {"action": "skip"} → skip remaining hooks, continue execution
  • {"action": "retry", "delay_ms": N} → retry after N milliseconds

HookResponse values

Prop

Type

Hook priority

// Priority 5 — runs before standard hooks (priority 10+)
session.registerHook('my-hook', 'pre_tool_use', null, { priority: 5 });
session.register_hook('my-hook', 'pre_tool_use', None, {'priority': 5})

On this page