Hook System
Overview
Hooks intercept actions at defined points. Unlike events (which are notifications after the fact), hooks run before an action and can abort or modify it.
Hook Points
| Point | Timing | Can Abort | Can Modify |
|---|---|---|---|
pre:message | Before sending to LLM | Yes | Yes (modify prompt) |
post:message | After LLM response | No | Yes (modify output) |
pre:tool | Before tool execution | Yes | Yes (modify args) |
post:tool | After tool returns | No | Yes (modify result) |
pre:agent:spawn | Before creating agent | Yes | Yes (modify config) |
post:agent:complete | After agent finishes | No | No |
pre:session:phase | Before phase transition | Yes | No |
post:session:phase | After phase transition | No | No |
pre:git:write | Before file write (shadow git) | Yes | Yes (modify content) |
post:git:write | After file write | No | No |
pre:view:render | Before rendering frame | No | Yes (modify render data) |
Registration
import { HookSystem } from 'armament/core';
const hooks = new HookSystem();
// Basic hook
hooks.register('pre:message', async (context) => {
console.log('About to send:', context.prompt);
});
// With priority (lower = earlier, default 100)
hooks.register('pre:message', handler, { priority: 10 });
// Named hook (for enable/disable)
hooks.register('pre:tool', handler, { name: 'safety-check' });
Abort Capability
Pre-hooks can abort the action:
hooks.register('pre:tool', async (context) => {
if (context.tool === 'shell' && context.args.includes('rm')) {
return { abort: true, reason: 'Destructive command blocked by hook' };
}
});
hooks.register('pre:message', async (context) => {
if (context.prompt.length > 100000) {
return { abort: true, reason: 'Prompt too large' };
}
});
Modify Capability
Hooks can transform the action’s data:
// Add system context to all prompts
hooks.register('pre:message', async (context) => {
return {
modify: {
prompt: `[Project: ${projectName}]\n${context.prompt}`
}
};
});
// Redact secrets from output
hooks.register('post:message', async (context) => {
return {
modify: {
output: redactSecrets(context.output)
}
};
});
Execution Order
Hooks at the same point execute in priority order:
- Priority 1 hooks (system-critical)
- Priority 10 hooks (security)
- Priority 50 hooks (user scripts)
- Priority 100 hooks (default)
If any hook aborts, subsequent hooks and the action itself don’t run.
Error Isolation
A failing hook doesn’t crash the system:
hooks.register('pre:message', async () => {
throw new Error('hook bug');
// Error is logged, other hooks still run
});
Configure error behavior per hook:
hooks.register('pre:tool', handler, {
onError: 'skip' // skip | abort | log
});
Enable/Disable
Toggle hooks by name without unregistering:
hooks.disable('safety-check');
hooks.enable('safety-check');
hooks.isEnabled('safety-check'); // boolean
Scripting Integration
Register hooks from .arma scripts:
hook pre:message /log debug "Sending: $prompt"
hook pre:tool:shell /perms check shellExec
hook post:agent:complete /notify "Agent $agent finished"
Configuration
hooks:
errorBehavior: log # skip | abort | log
timeout: 5000 # Max hook execution time (ms)
maxHooksPerPoint: 50 # Safety limit