### Install dependencies with bun install Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/apps/server/README.md Installs the project dependencies using Bun. Run this in the project root before starting the server. ```bash bun install ``` -------------------------------- ### Quick start: start system and copy .claude folder Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Quick start commands to run the system: start server and client with `just start` (or the shell script), open the dashboard at http://localhost:5173, run `git ls-files` in Claude Code to trigger events, then copy the `.claude` folder to other projects to emit events from them. ```bash # 1. Start both server and client just start # or: ./scripts/start-system.sh # 2. Open http://localhost:5173 in your browser # 3. Open Claude Code and run the following command: Run git ls-files to understand the codebase. # 4. Watch events stream in the client # 5. Copy the .claude folder to other projects you want to emit events from. cp -R .claude ``` -------------------------------- ### Run server with bun run index.ts Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/apps/server/README.md Starts the server by running the index.ts file with Bun. Ensure dependencies are installed first. ```bash bun run index.ts ``` -------------------------------- ### Start the observability server Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Runs the start-system.sh script from the observability project directory to ensure the server is running. ```bash # From the observability project directory (this codebase) ./scripts/start-system.sh ``` -------------------------------- ### Usage in Agent Code: HITLManager integration example Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/how_human_in_the_loop_v1_works.md Shows how to use the HITLManager in agent code: start the WebSocket server once at startup, request approval with a permission type, extract the answer, and handle approval or timeout. ```python # In your agent's decision point hitl_manager = HITLManager() # Start WebSocket server (do this once at agent startup) await hitl_manager.start_server(port=0) # 0 = random port # Request approval with explicit permission type response_data = await hitl_manager.request_approval( permission_type=DECISION_POINT_ENV_FILE_ACCESS, question="May I access .env files in the working directory?", session_id="session-123", hitl_type="permission" ) # Extract answer if response_data: approved = hitl_manager.extract_answer(response_data, "permission") if approved: # Execute operation modify_env_file() else: # Block operation return {"error": "Permission denied"} else: # Timeout - use default behavior return {"error": "No response - operation blocked"} ``` -------------------------------- ### Bun test example Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/apps/server/CLAUDE.md Use with `bun test` to run tests. Imports `test` and `expect` from `bun:test`. ```ts import { test, expect } from "bun:test"; test("hello world", () => { expect(1).toBe(1); }); ``` -------------------------------- ### just recipes for common operations Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md List of `just` recipes for common operations: start/stop/restart the system, run server or client individually, install dependencies, check health, send a test event, reset the database, list hook scripts, and open the dashboard. ```bash just # List all available recipes just start # Start server + client just stop # Stop all processes just restart # Stop then start just server # Start server only (dev mode) just client # Start client only just install # Install all dependencies just health # Check server/client status just test-event # Send a test event just db-reset # Reset the database just hooks # List all hook scripts just open # Open dashboard in browser ``` -------------------------------- ### README.md New Features Documentation Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/specs/plan_new_feature_sonnet45.md Markdown content for the README.md file describing the new features in v1.2.0, including the extended theme collection, 10-minute activity window, and regex-enabled event search with example search patterns. ```markdown ## New Features (v1.2.0) ### Extended Theme Collection We've added three new beautiful themes to our collection: - **Sunset**: Warm gradient theme with orange, pink, and purple tones - **Cyberpunk**: Futuristic neon theme with purple, cyan, and magenta - **Forest**: Natural theme with deep greens and earthy tones ### 10-Minute Activity Window You can now view up to 10 minutes of agent activity in the Live Pulse Chart, perfect for monitoring longer-running agent sessions. ### Regex-Enabled Event Search Filter events using powerful regex patterns or simple text search. The search bar supports searching across all event fields including source app, session ID, event type, and event data. **Example Searches:** - `tool_use` - Find all tool use events - `session_[a-z]+` - Find sessions with letter-based IDs - `(error|warning)` - Find events containing either error or warning ``` -------------------------------- ### WebSocket response to agent Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/how_human_in_the_loop_v1_works.md Example WebSocket response sent to the agent. Contains permission, response, choice, respondedAt, and the original hookEvent echoed back. ```json { "permission": true, "response": "Use async/await approach", "choice": "Option B", "respondedAt": 1734189245000, "hookEvent": { "id": 9675, "source_app": "big-three-agents: alice", "session_id": "session-123", "payload": { "agent_name": "alice", "permission_type": "env_file_access", "question": "May I access .env files..." }, "humanInTheLoop": { "question": "May I access .env files...", "responseWebSocketUrl": "ws://localhost:50123", "type": "permission" } } } ``` -------------------------------- ### Cache file structure Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Example cache file structure stored at .claude/data/claude-model-cache/{session_id}.json. Contains the model name, a Unix timestamp in milliseconds, and a TTL of 60 seconds. ```json { "model": "claude-haiku-4-5-20251001", "timestamp": 1729123456789, "ttl": 60 } ``` -------------------------------- ### Execute a plan with /build Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Executes a plan created with /plan_w_team. The plan file is expected to be in the specs/ directory. ```bash /build specs/.md ``` -------------------------------- ### Test 1: Verify Cache Creation - Run Hook and Check Cache Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Runs the hook manually with a sample event and checks the created cache file. The expected output shows the cached model, timestamp, and TTL. ```bash # Run a hook manually echo '{"session_id":"test-123","transcript_path":"/path/to/transcript.jsonl"}' | \ python .claude/hooks/send_event.py # Check cache was created (in project directory) cat .claude/data/claude-model-cache/test-123.json ``` ```json { "model": "claude-haiku-4-5-20251001", "timestamp": 1729123456.789, "ttl": 60 } ``` -------------------------------- ### JSON format for DecisionPoint event Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/how_human_in_the_loop_v1_works.md Example JSON payload for a DecisionPoint event. Shows the structure with source_app, session_id, hook_event_type, payload, humanInTheLoop, timestamp, and summary fields. ```json { "source_app": "big-three-agents: alice", "session_id": "session-123", "hook_event_type": "DecisionPoint", "payload": { "agent_name": "alice", "permission_type": "env_file_access", "question": "May I access .env files in the working directory?" }, "humanInTheLoop": { "question": "May I access .env files in the working directory?", "responseWebSocketUrl": "ws://localhost:50123", "type": "permission", "choices": null, "timeout": 300, "requiresResponse": true }, "timestamp": 1734189234000, "summary": "🔐 env_file_access: May I access .env files..." } ``` -------------------------------- ### Project structure tree Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Project structure overview showing the layout of the repository: `apps/` contains the Bun TypeScript server and Vue 3 client, `.claude/` holds hook scripts, agent definitions, commands, status lines, and settings, plus `justfile`, `scripts/`, and `logs/`. ```text claude-code-hooks-multi-agent-observability/ │ ├── apps/ # Application components │ ├── server/ # Bun TypeScript server │ │ ├── src/ │ │ │ ├── index.ts # Main server with HTTP/WebSocket endpoints │ │ │ ├── db.ts # SQLite database management & migrations │ │ │ └── types.ts # TypeScript interfaces │ │ ├── package.json │ │ └── events.db # SQLite database (gitignored) │ │ │ └── client/ # Vue 3 TypeScript client │ ├── src/ │ │ ├── App.vue # Main app with theme & WebSocket management │ │ ├── components/ │ │ │ ├── EventTimeline.vue # Event list with auto-scroll │ │ │ ├── EventRow.vue # Individual event display │ │ │ ├── FilterPanel.vue # Multi-select filters │ │ │ ├── ChatTranscriptModal.vue # Chat history viewer │ │ │ ├── StickScrollButton.vue # Scroll control │ │ │ └── LivePulseChart.vue # Real-time activity chart │ │ ├── composables/ │ │ │ ├── useWebSocket.ts # WebSocket connection logic │ │ │ ├── useEventColors.ts # Color assignment system │ │ │ ├── useChartData.ts # Chart data aggregation │ │ │ └── useEventEmojis.ts # Event type emoji mapping │ │ ├── utils/ │ │ │ └── chartRenderer.ts # Canvas chart rendering │ │ └── types.ts # TypeScript interfaces │ ├── .env.sample # Environment configuration template │ └── package.json │ ├── .claude/ # Claude Code integration │ ├── hooks/ # Hook scripts (Python with uv) │ │ ├── send_event.py # Universal event sender (all 12 event types) │ │ ├── pre_tool_use.py # Tool validation, blocking & summarization │ │ ├── post_tool_use.py # Result logging with MCP tool detection │ │ ├── post_tool_use_failure.py # Tool failure logging │ │ ├── permission_request.py # Permission request logging │ │ ├── notification.py # User interaction events (type-aware TTS) │ │ ├── user_prompt_submit.py # User prompt logging & validation │ │ ├── stop.py # Session completion (stop_hook_active guard) │ │ ├── subagent_stop.py # Subagent completion with transcript path │ │ ├── subagent_start.py # Subagent lifecycle start tracking │ │ ├── pre_compact.py # Context compaction with custom instructions │ │ ├── session_start.py # Session start with agent type & model │ │ ├── session_end.py # Session end with reason tracking │ │ └── validators/ # Stop hook validators │ │ ├── validate_new_file.py # Validate file creation │ │ └── validate_file_contains.py # Validate file content sections │ │ │ ├── agents/team/ # Agent team definitions │ │ ├── builder.md # Engineering agent with linting hooks │ │ └── validator.md # Read-only validation agent │ │ │ ├── commands/ # Custom slash commands │ │ └── plan_w_team.md # Team-based planning command │ │ │ ├── status_lines/ # Status line scripts │ │ └── status_line_v6.py # Context window usage display │ │ │ └── settings.json # Hook configuration (all 12 events) │ ├── justfile # Task runner recipes (just start, just stop, etc.) │ ├── scripts/ # Utility scripts │ ├── start-system.sh # Launch server & client │ ├── reset-system.sh # Stop all processes │ └── test-system.sh # System validation │ └── logs/ # Application logs (gitignored) ``` -------------------------------- ### Configure settings.json for observability hooks Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Modify the .claude/settings.json file to set the source-app parameter and configure hooks for PreToolUse, PostToolUse, and UserPromptSubmit events. Replace YOUR_PROJECT_NAME with a unique identifier for your project. ```json { "hooks": { "PreToolUse": [{ "matcher": "", "hooks": [ { "type": "command", "command": "uv run .claude/hooks/pre_tool_use.py" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type PreToolUse --summarize" } ] }], "PostToolUse": [{ "matcher": "", "hooks": [ { "type": "command", "command": "uv run .claude/hooks/post_tool_use.py" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type PostToolUse --summarize" } ] }], "UserPromptSubmit": [{ "hooks": [ { "type": "command", "command": "uv run .claude/hooks/user_prompt_submit.py --log-only" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type UserPromptSubmit --summarize" } ] }] // ... (similar patterns for all 12 hook events: Notification, Stop, SubagentStop, // SubagentStart, PreCompact, SessionStart, SessionEnd, PermissionRequest, PostToolUseFailure) } } ``` -------------------------------- ### Copy .claude directory to project root Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Copies the entire .claude directory to your project root to integrate observability hooks. ```bash cp -R .claude /path/to/your/project/ ``` -------------------------------- ### Bun.serve with HTML imports and React Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/apps/server/CLAUDE.md Use HTML imports with `Bun.serve()` instead of Vite. The server serves an HTML file that imports a React component; Bun's bundler transpiles and bundles automatically. Run with `bun --hot ./index.ts`. ```ts import index from "./index.html" Bun.serve({ routes: { "/": index, "/api/users/:id": { GET: (req) => { return new Response(JSON.stringify({ id: req.params.id })); }, }, }, // optional websocket support websocket: { open: (ws) => { ws.send("Hello, world!"); }, message: (ws, message) => { ws.send(message); }, close: (ws) => { // handle close } }, development: { hmr: true, console: true, } }) ``` ```html

Hello, world!

``` ```tsx import React from "react"; // import .css files directly and it works import './index.css'; import { createRoot } from "react-dom/client"; const root = createRoot(document.body); export default function Frontend() { return

Hello, world!

; } root.render(); ``` ```sh bun --hot ./index.ts ``` -------------------------------- ### Create a team-based plan with /plan_w_team Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Creates a team-based implementation plan. Generates a spec document in specs/ with task breakdowns, team member assignments, dependencies, and acceptance criteria. Plans are validated by Stop hook validators. ```bash /plan_w_team "Add a new feature for X" ``` -------------------------------- ### This project's hooks configuration Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Shows the hooks configuration for this project, running both validation and observability. ```json { "type": "command", "command": "uv run .claude/hooks/pre_tool_use.py" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app cc-hook-multi-agent-obvs --event-type PreToolUse" } ``` -------------------------------- ### Test 2: Verify Cache Hit - Run Hook Twice Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Runs the hook twice in quick succession to demonstrate the cache hit. The first run is a cache miss (~50ms), the second is a cache hit (~1ms). ```bash # Run the same hook twice quickly time python .claude/hooks/send_event.py < input.json # ~50ms (cache miss) time python .claude/hooks/send_event.py < input.json # ~1ms (cache hit) ``` -------------------------------- ### Test 3: Verify Cache Expiration - Wait 61 Seconds Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Runs the hook, waits 61 seconds, then runs it again to verify the cache expires and the transcript is re-parsed. ```bash # Run hook python .claude/hooks/send_event.py < input.json # Wait 61 seconds sleep 61 # Run again - should re-parse transcript python .claude/hooks/send_event.py < input.json ``` -------------------------------- ### WebSocket Server: HITLManager class with start_server method Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/how_human_in_the_loop_v1_works.md Defines the HITLManager class with a pending_futures dictionary and a start_server method that runs a WebSocket server to receive responses from an observability server. It matches responses by permission_type and resolves the corresponding asyncio.Future. ```python import asyncio import json from websockets.server import serve class HITLManager: def __init__(self): self.pending_futures = {} # permission_type -> asyncio.Future self.server_running = False async def start_server(self, port): """Start WebSocket server to receive responses""" async def handle_response(websocket): try: # Receive response from observability server message = await websocket.recv() response_data = json.loads(message) # Extract permission_type for matching original_event = response_data.get("hookEvent", {}) payload = original_event.get("payload", {}) permission_type = payload.get("permission_type", "") # Resolve pending future (wake up waiting code) if permission_type in self.pending_futures: future = self.pending_futures.pop(permission_type) if not future.done(): future.set_result(response_data) await websocket.close() except Exception as e: print(f"Error handling response: {e}") self.server_running = True async with serve(handle_response, "localhost", port): while self.server_running: await asyncio.sleep(1) ``` -------------------------------- ### Testing commands Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Commands for testing the system, sending test events, checking health, and testing hook scripts directly. ```bash # System validation ./scripts/test-system.sh # Quick test event via just just test-event # Check server/client health just health # Manual event test curl -X POST http://localhost:4000/events \ -H "Content-Type: application/json" \ -d '{ "source_app": "test", "session_id": "test-123", "hook_event_type": "PreToolUse", "payload": {"tool_name": "Bash", "tool_input": {"command": "ls"}} }' # Test a hook script directly just hook-test pre_tool_use ``` -------------------------------- ### Cache Expiration (After 60 Seconds) - Step-by-step Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Steps for cache expiration after 60 seconds. It re-reads the transcript, extracts the latest model name, updates the cache, and sends the event. ```text 1. Hook fires → send_event.py runs 2. Check cache: .claude/data/claude-model-cache/{session_id}.json (in project) 3. Cache file exists 4. Read cache 5. Check timestamp: current_time - cache_timestamp < 60s? 6. No → Cache is stale (expired) 7. Re-read transcript file 8. Extract latest model name (may have changed if user ran /model) 9. Update cache file with new timestamp 10. Send event with model_name to server ``` -------------------------------- ### First Hook Event (Cache Miss) - Step-by-step Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Steps for the first hook event when cache is missed. It reads the transcript, extracts the model name, caches it, and sends the event. ```text 1. Hook fires → send_event.py runs 2. Check cache: .claude/data/claude-model-cache/{session_id}.json (in project) 3. Cache file doesn't exist 4. Read transcript file (3+ MB) 5. Parse JSONL line by line in REVERSE 6. Find first (most recent) assistant message with "model" field 7. Extract model: "claude-haiku-4-5-20251001" 8. Write to cache file with timestamp 9. Send event with model_name to server ``` -------------------------------- ### Configure PreToolUse hook in settings.json Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/README.md Adds a PreToolUse hook to .claude/settings.json that runs the event sender for all tools. ```json { "hooks": { "PreToolUse": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_APP --event-type PreToolUse" }] }] } } ``` -------------------------------- ### Subsequent Hook Events (Cache Hit) - Step-by-step Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/send_event_with_model_how_to.md Steps for subsequent hook events when cache is hit. It checks the cache file, validates the timestamp, and sends the event with the cached model name. ```text 1. Hook fires → send_event.py runs 2. Check cache: .claude/data/claude-model-cache/{session_id}.json (in project) 3. Cache file exists 4. Read cache (tiny JSON file, < 1 KB) 5. Check timestamp: current_time - cache_timestamp < 60s? 6. Yes → Use cached model name 7. Send event with model_name to server 8. Total time: ~1ms (vs ~50-100ms parsing full transcript) ``` -------------------------------- ### POST /events Source: https://github.com/disler/claude-code-hooks-multi-agent-observability/blob/main/app_docs/how_human_in_the_loop_v1_works.md This endpoint accepts human-in-the-loop events from agents and returns a response via WebSocket. The response includes permission, response, or choice fields depending on the request type, along with the original event echoed back. ```APIDOC ## POST /events ### Description Sends a human-in-the-loop event to the server for processing. The event includes metadata about the source application, session, hook event type, payload, human-in-the-loop data, timestamp, and a summary. ### Method POST ### Endpoint http://localhost:4000/events ### Parameters #### Request Body - **source_app** (string) - Required - Identifier of the source application, e.g., "big-three-agents: alice". - **session_id** (string) - Required - Unique identifier for the session. - **hook_event_type** (string) - Required - Type of hook event, e.g., "DecisionPoint". - **payload** (object) - Required - Contains agent-specific data such as agent_name, permission_type, and question. - **humanInTheLoop** (object) - Required - Human-in-the-loop data including question, responseWebSocketUrl, type, choices, timeout, and requiresResponse. - **timestamp** (integer) - Required - Unix timestamp in milliseconds. - **summary** (string) - Required - Short summary of the event. ### Request Example { "source_app": "big-three-agents: alice", "session_id": "session-123", "hook_event_type": "DecisionPoint", "payload": { "agent_name": "alice", "permission_type": "env_file_access", "question": "May I access .env files in the working directory?" }, "humanInTheLoop": { "question": "May I access .env files in the working directory?", "responseWebSocketUrl": "ws://localhost:50123", "type": "permission", "choices": null, "timeout": 300, "requiresResponse": true }, "timestamp": 1734189234000, "summary": "🔐 env_file_access: May I access .env files..." } ### Response #### Success Response (200) - **permission** (boolean) - Indicates approval (true) or denial (false) for permission requests. - **response** (string) - Text answer for question requests. - **choice** (string) - Selected option from choices array for choice requests. - **respondedAt** (integer) - Unix timestamp in milliseconds when the response was sent. - **hookEvent** (object) - The original event echoed back, including id, source_app, session_id, payload, and humanInTheLoop. #### Response Example { "permission": true, "response": "Use async/await approach", "choice": "Option B", "respondedAt": 1734189245000, "hookEvent": { "id": 9675, "source_app": "big-three-agents: alice", "session_id": "session-123", "payload": { "agent_name": "alice", "permission_type": "env_file_access", "question": "May I access .env files..." }, "humanInTheLoop": { "question": "May I access .env files...", "responseWebSocketUrl": "ws://localhost:50123", "type": "permission" } } } ```