Hooks Overview
This document provides a conceptual overview of the Agentrix hooks system.
What are Hooks?
Hooks are interception points in the agent lifecycle where you can inject custom behavior. Think of them as event listeners that trigger at specific moments during agent execution.
Analogy
Hooks work like middleware in web frameworks:
User Request → [PreToolUse Hook] → Tool Execution → [PostToolUse Hook] → Response
Just as Express middleware can inspect and modify HTTP requests/responses, hooks can inspect and modify tool calls and their results.
Why Use Hooks?
1. Security & Access Control
// Block dangerous operations
export async function PreToolUse(input: PreToolUseHookInput) {
if (input.tool_name === 'Bash' &&
input.tool_input?.command?.includes('rm -rf /')) {
return { decision: 'deny', message: 'Operation denied' };
}
return { decision: 'approve' };
}
2. Audit & Compliance
// Log all file operations
export async function PostToolUse(input: PostToolUseHookInput) {
if (input.tool_name === 'Write' || input.tool_name === 'Edit') {
await logToAuditSystem({
action: input.tool_name,
file: input.tool_input?.file_path,
timestamp: Date.now()
});
}
return {};
}
3. Prompt and Session Policy
// Add a lightweight audit note when a prompt is submitted
export async function UserPromptSubmit(input: UserPromptSubmitHookInput) {
console.log(`[UserPromptSubmit] ${input.prompt.slice(0, 80)}`);
return {};
}
4. Integration with External Systems
// Notify Slack when session starts
export async function SessionStart(input: SessionStartHookInput) {
await fetch('https://hooks.slack.com/...', {
method: 'POST',
body: JSON.stringify({
text: `Agent session started: ${input.session_id}`
})
});
return {};
}
Hook Architecture
Three-Layer System
Agentrix implements a three-layer hook execution system:
┌─────────────────────────────────────────┐
│ System Hooks (Built-in) │
│ - Git state management │
│ - Workspace initialization │
│ - Error handling │
└──────────────┬──────────────────────────┘
│ delegates to
↓
┌─────────────────────────────────────────┐
│ Global Hooks (User-defined) │
│ - User-wide preferences │
│ - Cross-agent policies │
└──────────────┬──────────────────────────┘
│ delegates to
↓
┌─────────────────────────────────────────┐
│ Agent Hooks (Agent-specific) │
│ - Custom behavior per agent │
│ - Domain-specific logic │
└─────────────────────────────────────────┘
Execution Order:
- System hooks execute first (mandatory)
- If system hooks pass, global hooks execute
- If global hooks pass, agent hooks execute
Each layer can:
- approve: Continue to next layer
- deny: Block and return error
- pending: Request user confirmation
Hook Execution Flow
Hook Categories
1. Tool Lifecycle Hooks
Control tool execution:
-
PreToolUse: Before tool executes
- Can approve, deny, or request confirmation
- Can modify tool inputs
- Use for: Access control, validation, logging
-
PostToolUse: After tool executes
- Can inspect results
- Can modify outputs (future feature)
- Use for: Audit logging, metric collection, result processing
2. Session Lifecycle Hooks
Track agent sessions:
-
SessionStart: Agent session begins
- Initialize resources
- Set up logging
- Connect to external services
-
SessionEnd: Agent session ends
- Clean up resources
- Save final state
- Send notifications
-
UserPromptSubmit: User sends a message
- Log user inputs
- Pre-process prompts
- Enforce content policies
-
Stop: Agent is stopping
- Save work-in-progress
- Commit changes
- Update status
-
SubagentStop: Subagent completes
- Collect subagent results
- Update parent agent state
3. Context Management Hooks
Handle memory limits:
- PreCompact: Before context compaction
- Save important context
- Add custom summary instructions
- Log compaction events
4. System Event Hooks
Respond to system events:
- Notification: System notifications
- Forward to external systems
- Log important events
- Trigger alerts
Hook Signature
All hooks follow a consistent signature:
export async function HookName(
input: HookInput, // Hook-specific input data
toolUseID: string, // Unique ID for this invocation
options: {
signal: AbortSignal // For timeout/cancellation
}
): Promise<HookResult> // Hook-specific return type
Parameters
- input: Hook-specific data (e.g., tool name, session ID)
- toolUseID: Unique identifier for this hook invocation
- options.signal: AbortSignal for timeout handling (30s limit)
Return Values
PreToolUse
return {
decision: 'approve' | 'deny' | 'pending',
message?: string, // Shown to user if denied
toolInput?: any // Modified tool input (future)
};
Other Hooks
return {}; // Most hooks just need empty object
Type Safety
Claude hook input types come from the Claude Agent SDK:
Claude SDK hooks (from @anthropic-ai/claude-agent-sdk):
import type {
// Claude SDK hook types
PreToolUseHookInput,
PostToolUseHookInput,
SessionStartHookInput,
SessionEndHookInput,
UserPromptSubmitHookInput,
StopHookInput,
SubagentStartHookInput,
SubagentStopHookInput,
PreCompactHookInput,
NotificationHookInput,
PermissionRequestHookInput,
} from '@anthropic-ai/claude-agent-sdk';
Agentrix context helper types come from @agentrix/shared:
import type {
HookFactory,
AgentrixContext,
} from '@agentrix/shared';
This provides:
- TypeScript IntelliSense
- Compile-time type checking
- IDE autocomplete
- Breaking change detection
Accessing AgentrixContext
Hooks can access AgentrixContext to get workspace information, task/user IDs, and make RPC calls. Use the factory pattern:
import type { HookFactory, AgentrixContext } from '@agentrix/shared';
const createHooks: HookFactory = (context: AgentrixContext) => ({
PreToolUse: async (input) => {
const workspace = context.getWorkspace();
const taskId = context.getTaskId();
const userId = context.getUserId();
console.log(`[PreToolUse] Task ${taskId} in ${workspace}`);
return { decision: 'approve' as const };
},
});
export default createHooks;
AgentrixContext methods:
getWorkspace(): Absolute path to workspace directorygetTaskId(): Current task IDgetUserId(): Current user IDcreateAgentBuilder(params): Create agent builder via RPC
See Development Guide - Using AgentrixContext for complete documentation.
Hook Lifecycle
1. Development
# Create hooks project
mkdir -p claude/hooks/src
cd claude/hooks
npm init -y
npm install @agentrix/shared @anthropic-ai/claude-agent-sdk
2. Implementation
// src/index.ts
import type { PreToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
export async function PreToolUse(
input: PreToolUseHookInput,
toolUseID: string,
options: { signal: AbortSignal }
) {
return { decision: 'approve' };
}
3. Build
npm run build # Outputs to dist/index.mjs
4. Execution
When agent runs:
- Agentrix loads hooks from
dist/index.mjs - Validates hook signatures
- Wraps hooks with timeout/error handling
- Executes hooks at appropriate lifecycle points
Performance Considerations
Timeout
All hooks have a 30-second timeout. If a hook takes longer:
⚠ [Hook] PreToolUse exceeded timeout (30s), skipping
Best practices:
- Keep hooks fast (< 1s typically)
- Use async I/O for external calls
- Don't block on user input
- Cache expensive computations
Error Handling
Hook errors are non-fatal by default:
export async function PreToolUse(input: PreToolUseHookInput) {
try {
// Your logic
return { decision: 'approve' };
} catch (error) {
console.error('Hook failed:', error);
// Fallback: approve by default
return { decision: 'approve' };
}
}
If a hook crashes, the agent continues with a warning.
Resource Management
Hooks run in the same process as the agent worker:
// ✅ Good: Fast, in-memory check
export async function PreToolUse(input: PreToolUseHookInput) {
const blacklist = ['rm -rf', 'dd if='];
const cmd = input.tool_input?.command || '';
if (blacklist.some(b => cmd.includes(b))) {
return { decision: 'deny' };
}
return { decision: 'approve' };
}
// ❌ Bad: Slow, external API call on every tool use
export async function PreToolUse(input: PreToolUseHookInput) {
const response = await fetch('https://api.example.com/check', {
method: 'POST',
body: JSON.stringify(input)
});
// This adds latency to every tool call!
}
Debugging Hooks
Console Logging
export async function PreToolUse(input: PreToolUseHookInput) {
console.log('[PreToolUse] Tool:', input.tool_name);
console.log('[PreToolUse] Input:', input.tool_input);
return { decision: 'approve' };
}
Logs appear in agent output:
[PreToolUse] Tool: Read
[PreToolUse] Input: { file_path: '/path/to/file.ts' }
Verbose Mode
DEBUG=agentrix:hooks agentrix run --agent=./my-agent
Testing Hooks
// test/hooks.test.ts
import { PreToolUse } from '../src/index';
import type { PreToolUseHookInput } from '@anthropic-ai/claude-agent-sdk';
describe('PreToolUse', () => {
it('approves safe commands', async () => {
const input: PreToolUseHookInput = {
hook_event_name: 'PreToolUse',
session_id: 'test-session',
transcript_path: '/tmp/transcript',
cwd: '/tmp',
tool_name: 'Read',
tool_input: { file_path: 'test.txt' },
tool_use_id: 'test-tool-use-id'
};
const result = await PreToolUse(input, 'test-id', {
signal: new AbortController().signal
});
expect(result.decision).toBe('approve');
});
it('denies dangerous commands', async () => {
const input: PreToolUseHookInput = {
hook_event_name: 'PreToolUse',
session_id: 'test-session',
transcript_path: '/tmp/transcript',
cwd: '/tmp',
tool_name: 'Bash',
tool_input: { command: 'rm -rf /' },
tool_use_id: 'test-tool-use-id'
};
const result = await PreToolUse(input, 'test-id', {
signal: new AbortController().signal
});
expect(result.decision).toBe('deny');
});
});
Common Patterns
See Examples for copy-paste patterns:
- Security controls
- Audit logging
- External integrations
- Custom validations
Limitations
What hooks cannot do:
- ❌ Modify agent's base system prompt
- ❌ Add new tools (use MCP servers)
- ❌ Access other hooks' state
- ❌ Block longer than 30 seconds
- ❌ Modify tool outputs (PostToolUse is read-only currently)
What hooks can do:
- ✅ Approve/deny/request confirmation for tool calls
- ✅ Log and audit all operations
- ✅ Integrate with external systems
- ✅ Enforce custom policies
Next Steps
- Development Guide - Set up TypeScript project
- Hook Types Reference - All hook signatures
- PreToolUse & PostToolUse - Tool control hooks
- Examples - Real-world patterns
Related Documentation
- Agent Structure - Hook directory structure
- Configuration - Enabling hooks in config
- Testing - Testing hooks locally
- API Reference - Complete type definitions