II.
Page JSON
Structured · livepage:docs-agent-mux-reference-15-hooks
15 — Unified Hooks System json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-agent-mux-reference-15-hooks",
"_kind": "Page",
"_file": "wiki/docs/agent-mux/reference/15-hooks.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/agent-mux/reference/15-hooks.md",
"sourceKind": "repo-docs",
"title": "15 — Unified Hooks System",
"displayName": "15 — Unified Hooks System",
"slug": "docs/agent-mux/reference/15-hooks",
"articlePath": "wiki/docs/agent-mux/reference/15-hooks.md",
"article": "\n# 15 — Unified Hooks System\n\n`agent-mux` provides a harness-agnostic hook system that lets one\nconfiguration drive hook dispatch across all 11 supported harnesses.\n\n## Concepts\n\n- **Hook type** — a harness-specific event name (e.g. `PreToolUse`,\n `StopHook`, `OnToolCall`). See `HOOK_CATALOG` in\n `@a5c-ai/agent-mux-core` for the per-harness list.\n- **Registration** — a record in `.amux/hooks.json` (project) or\n `~/.amux/hooks.json` (global) that maps\n `(agent, hookType) → handler(target)`. Project overrides global by `id`.\n- **Handler** — `builtin` (programmatic, in-process),\n `command` (shell command), or `script` (executable path).\n- **Unified payload** — normalized `UnifiedHookPayload` with\n `{ agent, hookType, sessionId, timestamp, data, raw }`. `data` contains\n fields like `tool_name`, `tool_input`, `prompt`, etc.; `raw` preserves the\n original harness payload for bidirectional round-tripping.\n- **Unified result** — `{ decision: allow|deny|modify, message?,\n modifiedInput?, stdout?, exitCode? }`. `deny` maps to exit code 2 (the\n convention used by most harnesses).\n\n## SDK usage\n\n```ts\nimport {\n HookConfigManager, HookDispatcher, builtInHooks,\n parseHookPayload, formatHookResult,\n} from '@a5c-ai/agent-mux-core';\n\nconst mgr = new HookConfigManager();\nawait mgr.add({\n id: 'log-all',\n agent: '*',\n hookType: '*',\n handler: 'builtin',\n target: 'log',\n priority: 10,\n});\n\nconst dispatcher = new HookDispatcher(mgr, builtInHooks);\nconst payload = parseHookPayload('claude', 'PreToolUse', { tool_name: 'Bash' });\nconst result = await dispatcher.dispatch(payload);\nconst { stdout, exitCode } = formatHookResult('claude', 'PreToolUse', result);\n```\n\n## CLI usage\n\n```\namux hooks <agent> discover\namux hooks <agent> list\namux hooks <agent> add <hookType> [--handler builtin|command|script]\n [--target <id-or-cmd>]\n [--id <id>] [--priority N] [--global]\namux hooks <agent> remove <id> [--global|--project]\namux hooks <agent> set <id> [--priority N] [--enabled true|false] [--target ...]\namux hooks <agent> handle <hookType> # reads JSON payload on stdin\n```\n\n`handle` is the entry point registered with harnesses (e.g. claude's\n`settings.json` `\"hooks\"` section) — the harness pipes its payload to\nstdin and `amux` dispatches all matching registrations.\n\n## Built-in programmatic hooks\n\n| ID | Description |\n|--------------------------|---------------------------------------------------------|\n| `log` | Append payload to `~/.amux/hook-log.jsonl` |\n| `trace` | Emit a one-line trace to stdout |\n| `claude.session-capture` | Capture claude session metadata (CLAUDE_PROJECT_DIR…) |\n\nAdd your own with `builtInHooks.register({ id, description, fn })`.\n\n## Dispatch semantics\n\n1. Registrations matching `(agent, hookType)` are collected (supports `*`).\n2. Disabled entries (`enabled: false`) are excluded.\n3. Remaining entries are sorted by `priority` ascending (default 100).\n4. Each runs in order; results are merged:\n - `deny` is sticky — further entries short-circuit.\n - `modify` wins over `allow` for the final decision.\n - `modifiedInput` objects are shallow-merged.\n - `stdout` fragments are concatenated.\n - First non-zero `exitCode` wins.\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs-agent-mux-reference",
"to": "page:docs-agent-mux-reference-15-hooks",
"kind": "contains_page"
}
]
}