- Implement tests for KeywordDetector including keyword detection, sanitization, and priority handling. - Add tests for SessionStateService covering session validation, loading, saving, and state updates. - Create tests for UserAbortDetector to validate user abort detection logic and pattern matching.
20 KiB
Hooks Integration for Progressive Disclosure
This document describes how to integrate session hooks with CCW's progressive disclosure system, including the new Soft Enforcement Stop Hook, Mode System, and Checkpoint/Recovery features.
Overview
CCW supports automatic context injection via hooks. When a session starts, the system can automatically provide a progressive disclosure index showing related sessions from the same cluster.
Key Features
- Automatic Context Injection: Session start hooks inject cluster context
- Progressive Disclosure: Shows related sessions, their summaries, and recovery commands
- Silent Failure: Hook failures don't block session start (< 5 seconds timeout)
- Multiple Hook Types: Supports
session-start,context,PreCompact,Stop, and custom hooks - Soft Enforcement: Stop hooks never block - they inject continuation messages instead
- Mode System: Keyword-based mode activation with exclusive mode conflict detection
- Checkpoint/Recovery: Automatic state preservation before context compaction
Hook Configuration
Location
Place hook configurations in .claude/settings.json:
{
"hooks": {
"session-start": [
{
"name": "Progressive Disclosure",
"description": "Injects progressive disclosure index at session start with recovery detection",
"enabled": true,
"handler": "internal:context",
"timeout": 5000,
"failMode": "silent",
"notes": [
"Checks for recovery checkpoints and injects recovery message if found",
"Uses RecoveryHandler.checkRecovery() for session recovery"
]
}
]
}
}
Hook Types
session-start
Triggered when a new session begins. Ideal for injecting context and checking for recovery checkpoints.
PreCompact
Triggered before context compaction. Creates checkpoints to preserve session state including:
- Active mode states
- Workflow progress
- TODO summaries
Stop
Triggered when a stop is requested. Uses Soft Enforcement - never blocks, but may inject continuation messages.
UserPromptSubmit
Triggered when a prompt is submitted. Detects mode keywords and activates corresponding execution modes.
session-end
Triggered when a session ends. Useful for:
- Updating cluster metadata
- Cleaning up mode states
- Final checkpoint creation
file-modified
Triggered when files are modified. Can be used for auto-commits or notifications.
Hook Properties
name: Human-readable hook namedescription: What the hook doesenabled: Boolean to enable/disable the hookhandler:internal:contextfor built-in context generation, or usecommandfieldcommand: Shell command to execute (alternative tohandler)timeout: Maximum execution time in milliseconds (default: 5000)failMode: How to handle failuressilent: Ignore errors, don't loglog: Log errors but continuefail: Abort on error
async: Run in background without blocking (default: false)
Available Variables
In command fields, use these variables:
$SESSION_ID: Current session ID$FILE_PATH: File path (for file-modified hooks)$PROJECT_PATH: Current project path$CLUSTER_ID: Active cluster ID (if available)
Soft Enforcement Stop Hook
The Stop Hook implements Soft Enforcement: it never blocks stops, but injects continuation messages to encourage task completion.
Priority Order
- context-limit: Always allow (deadlock prevention)
- user-abort: Respect user intent
- active-workflow: Inject continuation message
- active-mode: Inject continuation message via ModeRegistryService
Configuration Example
{
"hooks": {
"Stop": [
{
"name": "Soft Enforcement Stop",
"description": "Injects continuation messages for active workflows/modes",
"enabled": true,
"command": "ccw hook stop --stdin",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
Behavior Matrix
| Condition | continue | mode | message |
|---|---|---|---|
| Context limit reached | true |
context-limit |
None |
| User requested stop | true |
user-abort |
None |
| Active workflow | true |
active-workflow |
Continuation message |
| Active mode | true |
active-mode |
Mode-specific message |
| Normal stop | true |
none |
None |
Context Limit Detection
Detected via ContextLimitDetector:
stop_reason: "context_limit_reached"stop_reason: "end_turn_limit"end_turn_reason: "max_tokens"stop_reason: "max_context"
User Abort Detection
Detected via UserAbortDetector:
user_requested: truestop_reason: "user_cancel"stop_reason: "cancel"
Mode System
The Mode System provides centralized mode state management with file-based persistence.
Supported Modes
| Mode | Type | Description |
|---|---|---|
autopilot |
Exclusive | Autonomous execution mode for multi-step tasks |
ralph |
Non-exclusive | Research and Analysis Learning Pattern Handler |
ultrawork |
Non-exclusive | Ultra-focused work mode for deep tasks |
swarm |
Exclusive | Multi-agent swarm execution mode |
pipeline |
Exclusive | Pipeline execution mode for sequential tasks |
team |
Non-exclusive | Team collaboration mode |
ultraqa |
Non-exclusive | Ultra-focused QA mode |
Exclusive Mode Conflict
Exclusive modes (autopilot, swarm, pipeline) cannot run concurrently. Attempting to start one while another is active will be blocked.
Mode State Storage
Mode states are stored in .workflow/modes/:
.workflow/modes/
├── sessions/
│ ├── {session-id}/
│ │ ├── autopilot-state.json
│ │ └── ralph-state.json
│ └── ...
└── (legacy shared states)
Stale Marker Cleanup
Mode markers older than 1 hour are automatically cleaned up to prevent crashed sessions from blocking indefinitely.
Mode Activation via Keyword
Configure the UserPromptSubmit hook to detect keywords:
{
"hooks": {
"UserPromptSubmit": [
{
"name": "Keyword Detection",
"description": "Detects mode keywords in prompts and activates corresponding modes",
"enabled": true,
"command": "ccw hook keyword --stdin",
"timeout": 5000,
"failMode": "silent"
}
]
}
}
Supported Keywords
| Keyword | Mode | Aliases |
|---|---|---|
autopilot |
autopilot | - |
ultrawork |
ultrawork | ulw |
ralph |
ralph | - |
swarm |
swarm | - |
pipeline |
pipeline | - |
team |
team | - |
ultraqa |
ultraqa | - |
cancelomc |
Cancel | stopomc |
codex |
Delegate | gpt |
gemini |
Delegate | - |
Checkpoint and Recovery
The Checkpoint System preserves session state before context compaction.
Checkpoint Triggers
| Trigger | Description |
|---|---|
manual |
User-initiated checkpoint |
auto |
Automatic checkpoint |
compact |
Before context compaction |
mode-switch |
When switching modes |
session-end |
At session termination |
Checkpoint Storage
Checkpoints are stored in .workflow/checkpoints/:
.workflow/checkpoints/
├── 2025-02-18T10-30-45-sess123.json
├── 2025-02-18T11-15-22-sess123.json
└── ...
Checkpoint Contents
{
"id": "2025-02-18T10-30-45-sess123",
"created_at": "2025-02-18T10:30:45.000Z",
"trigger": "compact",
"session_id": "sess123",
"project_path": "/path/to/project",
"workflow_state": null,
"mode_states": {
"autopilot": {
"active": true,
"activatedAt": "2025-02-18T10:00:00.000Z"
}
},
"memory_context": null,
"todo_summary": {
"pending": 3,
"in_progress": 1,
"completed": 5
}
}
Recovery Flow
┌─────────────────┐
│ Session Start │
└────────┬────────┘
│
v
┌─────────────────┐ No checkpoint
│ Check Recovery │ ──────────────────► Continue normally
└────────┬────────┘
│ Checkpoint found
v
┌─────────────────┐
│ Load Checkpoint │
└────────┬────────┘
│
v
┌─────────────────┐
│ Restore Modes │
└────────┬────────┘
│
v
┌─────────────────┐
│ Inject Message │
└─────────────────┘
Automatic Cleanup
Only the last 10 checkpoints per session are retained. Older checkpoints are automatically removed.
PreCompact Hook with Mutex
The PreCompact hook uses a mutex to prevent concurrent compaction operations for the same directory.
Mutex Behavior
Request 1 ──► PreCompact ──► Create checkpoint ──► Return
▲
Request 2 ──► Wait for Request 1 ────────────────┘
This prevents race conditions when multiple subagent results arrive simultaneously (e.g., in swarm/ultrawork modes).
API Endpoint
Trigger Hook
POST http://localhost:3456/api/hook
Content-Type: application/json
{
"type": "session-start",
"sessionId": "WFS-20241218-001",
"projectPath": "/path/to/project"
}
Response Format
{
"success": true,
"type": "context",
"format": "markdown",
"content": "<ccw-session-context>...</ccw-session-context>",
"sessionId": "WFS-20241218-001"
}
Query Parameters
?path=/project/path: Override project path?format=markdown|json: Response format (default: markdown)
Progressive Disclosure Output Format
The hook returns a structured Markdown document:
<ccw-session-context>
## Related Sessions Index
### Active Cluster: {cluster_name} ({member_count} sessions)
**Intent**: {cluster_intent}
| # | Session | Type | Summary | Tokens |
|---|---------|------|---------|--------|
| 1 | WFS-001 | Workflow | Implement auth | ~1200 |
| 2 | CLI-002 | CLI | Fix login bug | ~800 |
**Resume Commands**:
```bash
# Load specific session
ccw core-memory load {session_id}
# Load entire cluster context
ccw core-memory load-cluster {cluster_id}
Timeline
2024-12-15 -- WFS-001 (Implement auth)
│
2024-12-16 -- CLI-002 (Fix login bug) <- Current
Tip: Use ccw core-memory search <keyword> to find more sessions
---
## Complete Configuration Example
```json
{
"hooks": {
"session-start": [
{
"name": "Progressive Disclosure",
"description": "Injects progressive disclosure index at session start with recovery detection",
"enabled": true,
"handler": "internal:context",
"timeout": 5000,
"failMode": "silent"
}
],
"session-end": [
{
"name": "Update Cluster Metadata",
"description": "Updates cluster metadata after session ends",
"enabled": true,
"command": "ccw core-memory update-cluster --session $SESSION_ID",
"timeout": 30000,
"async": true,
"failMode": "log"
},
{
"name": "Mode State Cleanup",
"description": "Deactivates all active modes for the session",
"enabled": true,
"command": "ccw hook session-end --stdin",
"timeout": 5000,
"failMode": "silent"
}
],
"PreCompact": [
{
"name": "Create Checkpoint",
"description": "Creates checkpoint before context compaction",
"enabled": true,
"command": "ccw hook precompact --stdin",
"timeout": 10000,
"failMode": "log"
}
],
"Stop": [
{
"name": "Soft Enforcement Stop",
"description": "Injects continuation messages for active workflows/modes",
"enabled": true,
"command": "ccw hook stop --stdin",
"timeout": 5000,
"failMode": "silent"
}
],
"UserPromptSubmit": [
{
"name": "Keyword Detection",
"description": "Detects mode keywords in prompts and activates corresponding modes",
"enabled": true,
"command": "ccw hook keyword --stdin",
"timeout": 5000,
"failMode": "silent"
}
],
"file-modified": [
{
"name": "Auto Commit Checkpoint",
"description": "Creates git checkpoint on file modifications",
"enabled": false,
"command": "git add . && git commit -m \"[Auto] Checkpoint: $FILE_PATH\"",
"timeout": 10000,
"async": true,
"failMode": "log"
}
]
},
"notes": {
"handler": "Use 'internal:context' for built-in context generation, or 'command' for external commands",
"failMode": "Options: 'silent' (ignore errors), 'log' (log errors), 'fail' (abort on error)",
"variables": "Available: $SESSION_ID, $FILE_PATH, $PROJECT_PATH, $CLUSTER_ID",
"async": "Async hooks run in background and don't block the main flow",
"Stop hook": "The Stop hook uses Soft Enforcement - it never blocks but may inject continuation messages",
"PreCompact hook": "Creates checkpoint before compaction; uses mutex to prevent concurrent operations",
"UserPromptSubmit hook": "Detects mode keywords and activates corresponding execution modes",
"session-end hook": "Cleans up mode states using ModeRegistryService.deactivateMode()"
}
}
Testing
Test Hook Trigger
# Using curl
curl -X POST http://localhost:3456/api/hook \
-H "Content-Type: application/json" \
-d '{"type":"session-start","sessionId":"test-001"}'
# Using ccw (if CLI command exists)
ccw core-memory context --format markdown
Run Integration Tests
# Run all hook integration tests
node --test tests/integration/hooks-integration.test.ts
# Run with verbose output
node --test tests/integration/hooks-integration.test.ts --test-name-pattern="INT-.*"
Expected Output
If a cluster exists:
- Table of related sessions
- Resume commands
- Timeline visualization
If no cluster exists:
- Message indicating no cluster found
- Commands to search or trigger clustering
Troubleshooting
Hook Not Triggering
- Check that hooks are enabled in
.claude/settings.json - Verify the hook type matches the event
- Ensure the server is running on the correct port
Timeout Issues
- Increase
timeoutvalue for slow operations - Use
async: truefor long-running commands - Check logs for performance issues
Empty Context
- Ensure clustering has been run:
ccw core-memory cluster --auto - Verify session metadata exists
- Check that the session has been added to a cluster
Mode Not Activating
- Check for conflicting exclusive modes
- Verify keyword spelling (case-insensitive)
- Check that keyword is not inside code blocks
Checkpoint Not Created
- Verify
.workflow/checkpoints/directory exists - Check disk space
- Review logs for error messages
Recovery Not Working
- Verify checkpoint exists in
.workflow/checkpoints/ - Check that session ID matches
- Ensure checkpoint file is valid JSON
Performance Considerations
- Progressive disclosure index generation is fast (< 1 second typical)
- Uses cached metadata to avoid full session parsing
- Timeout enforced to prevent blocking
- Failures return empty content instead of errors
- Mutex prevents concurrent compaction operations
- Stale marker cleanup runs automatically
Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ Claude Code Session │
└─────────────────────────────────────────────────────────────┘
│
│ Hook Events
v
┌─────────────────────────────────────────────────────────────┐
│ Hook System │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Session Start │ │ PreCompact │ │ Stop │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
└─────────┼─────────────────┼──────────────────┼──────────────┘
│ │ │
v v v
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│RecoveryHandler │ │CheckpointService│ │ StopHandler │
│ │ │ │ │ │
│ - checkRecovery │ │ - create │ │ - SoftEnforce │
│ - formatMessage │ │ - save │ │ - detectMode │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
v v v
┌─────────────────────────────────────────────────────────────┐
│ ModeRegistryService │
│ │
│ - activateMode / deactivateMode │
│ - getActiveModes / canStartMode │
│ - cleanupStaleMarkers │
│ │
│ Storage: .workflow/modes/sessions/{sessionId}/ │
└─────────────────────────────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────┐
│ Checkpoint Storage │
│ │
│ .workflow/checkpoints/{checkpoint-id}.json │
│ - session_id, trigger, mode_states, workflow_state │
│ - Automatic cleanup (keep last 10 per session) │
└─────────────────────────────────────────────────────────────┘
References
- Session Clustering: See
session-clustering-service.ts - Core Memory Store: See
core-memory-store.ts - Hook Routes: See
routes/hooks-routes.ts - Stop Handler: See
core/hooks/stop-handler.ts - Mode Registry: See
core/services/mode-registry-service.ts - Checkpoint Service: See
core/services/checkpoint-service.ts - Recovery Handler: See
core/hooks/recovery-handler.ts - Keyword Detector: See
core/hooks/keyword-detector.ts - Example Configuration: See
templates/hooks-config-example.json