Skip to content

Hooks & Lifecycle

The Hook System provides event-driven customization points throughout the agent lifecycle, allowing you to add logging, validation, rate limiting, and custom behavior without modifying core code.

Hook Points

agent:init


turn:before ──► llm:before ──► llm:after ──► tool:before ──► tool:after
    │                                                              │
    │◄─────────────────────────────────────────────────────────────┘

turn:after


agent:complete

Registering Hooks

typescript
import { HookSystem } from '@opensin/sdk'

const hooks = new HookSystem()

// Log every tool call
hooks.on('tool:before', ({ name, args }) => {
  console.log(`[TOOL] ${name}(${JSON.stringify(args)})`)
})

// Track token usage
hooks.on('llm:after', ({ model, inputTokens, outputTokens }) => {
  totalTokens += inputTokens + outputTokens
  console.log(`[LLM] ${model}: ${inputTokens}+${outputTokens} tokens`)
})

// Validate outputs before returning
hooks.on('agent:complete', ({ response }) => {
  if (response.includes('TODO')) {
    console.warn('[WARN] Response contains TODO markers')
  }
})

Available Hooks

HookTimingArguments
agent:initAgent starts{ config, tools }
turn:beforeBefore each ReAct turn{ turn, messages }
llm:beforeBefore LLM call{ messages, model }
llm:afterAfter LLM response{ response, model, tokens }
tool:beforeBefore tool execution{ name, args }
tool:afterAfter tool execution{ name, args, result, duration }
turn:afterAfter each ReAct turn{ turn, toolCalls, response }
agent:completeAgent finishes{ response, turns, tokens }
agent:errorOn unhandled error{ error, turn }

Async Hooks

Hooks can be async and will be awaited:

typescript
hooks.on('tool:after', async ({ name, result, duration }) => {
  // Upload to GitLab LogCenter
  await logCenter.upload({
    tool: name,
    duration,
    resultSize: result.content.length,
  })
})

Hook Ordering

Multiple hooks on the same event run in registration order:

typescript
hooks.on('turn:before', () => console.log('First'))
hooks.on('turn:before', () => console.log('Second'))
// Output: First, Second

Practical Examples

Rate Limiting

typescript
const rateLimiter = { calls: 0, resetAt: Date.now() + 60000 }

hooks.on('llm:before', async () => {
  rateLimiter.calls++
  if (rateLimiter.calls > 20) {
    const waitMs = rateLimiter.resetAt - Date.now()
    if (waitMs > 0) await sleep(waitMs)
    rateLimiter.calls = 0
    rateLimiter.resetAt = Date.now() + 60000
  }
})

Audit Logging

typescript
hooks.on('tool:after', async ({ name, args, result, duration }) => {
  await auditLog.append({
    timestamp: new Date(),
    tool: name,
    args: sanitize(args),
    success: !result.isError,
    duration,
  })
})

Cost Tracking

typescript
const pricing = new UsagePricing()

hooks.on('llm:after', ({ model, inputTokens, outputTokens }) => {
  pricing.record({ model, inputTokens, outputTokens })
})

hooks.on('agent:complete', () => {
  const summary = pricing.getSummary()
  console.log(`Session cost: $${summary.totalCost.toFixed(4)}`)
})

Integration

typescript
const agent = new AgentLoop({
  model: 'claude-sonnet-4-6',
  tools: toolRegistry,
  hooks: hooks,
})

Released under the Apache 2.0 License.