halter-hooks 0.1.0

Hooks crate for halter
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# halter-hooks

`halter-hooks` is halter's policy and extension surface for intercepting runtime events, modifying inputs and outputs, attaching extra context, enforcing approvals, and integrating external hook executables or in-process hook implementations.

If you need to shape runtime behavior without forking the runtime, this is the crate you reach for.

---

## Who this crate is for

### Primary: programmers extending the runtime

Use `halter-hooks` when you want to:

- observe important runtime lifecycle events
- block unsafe or undesired actions
- require approvals or permission decisions
- inject system messages or contextual guidance
- rewrite tool inputs or outputs
- integrate script-based hooks discovered from resources
- register SDK hooks directly in Rust

### Secondary: CLI users and platform operators

If you run the CLI, hooks affect you even if you never import this crate directly. They can:

- block tool calls
- request permission
- add extra system guidance
- emit notifications
- stop sessions on policy violations

For user-facing command behavior, also read `../halter-cli/README.md`.

---

## Mental model

A hook in halter is an event-driven policy function.

At runtime, the system:

1. emits a typed hook event
2. constructs a dispatch request with payload and context
3. runs registered hooks and/or external hook executables
4. merges their outputs deterministically
5. feeds the merged result back into the runtime

Hooks can do more than just observe. They can actively influence execution.

They can:

- approve or block
- stop execution
- attach system messages
- add additional structured context
- modify inbound or outbound payloads
- make permission decisions
- suppress output visibility

---

## Public API at a glance

Core types exported by the crate include:

- `HookEventName`
- `HookDecision`
- `PermissionDecision`
- `HookResponse`
- `HookOutput`
- `HookDispatchRequest`
- `PreparedHookDispatch`
- `HookDispatchOutcome`
- `Hooks`
- `HooksEngine`
- `MergeError`
- `HookConfig`
- `HooksConfig`
- `ConfiguredHook`
- `register_runtime_hook`
- `clear_runtime_hook_registry`

The SDK layer in `halter` uses these to install plugin and in-process hooks.

---

## Event model

## `HookEventName`

The runtime exposes a broad event surface. Important events include:

- `SessionStart`
- `SessionEnd`
- `UserPromptSubmit`
- `PreToolUse`
- `PostToolUse`
- `PostToolUseFailure`
- `Notification`
- `Stop`
- `SubagentStart`
- `SubagentStop`
- `PreCompact`
- `PostCompact`
- `PermissionRequest`
- `PermissionDenied`
- `Elicitation`
- `ElicitationResult`
- `WorktreeCreate`
- `WorktreeRemove`
- `FileChanged`
- `CwdChanged`
- `InstructionsLoaded`
- `ConfigChange`
- `Setup`
- `TeammateIdle`
- `TaskCreated`
- `TaskCompleted`
- `StopFailure`
- `PostSampling`

In practice, the most operationally important events are usually:

- `UserPromptSubmit`
- `PreToolUse`
- `PostToolUse`
- `PostToolUseFailure`
- `PermissionRequest`
- `SubagentStart`
- `SubagentStop`
- `PreCompact`
- `PostCompact`

---

## Hook responses

## `HookResponse`

A hook produces a response, which can then be converted into a `HookOutput`.

Convenience constructors/helpers include:

- `HookResponse::passthrough()`
- `HookResponse::block(...)`
- `HookResponse::stop(...)`
- `.with_system_message(...)`
- `.with_additional_context(...)`
- `.with_updated_input(...)`
- `.with_updated_output(...)`
- `.with_permission(...)`
- `.with_suppress_output(...)`
- `.into_output(...)`

### Core capabilities

A response can express:

- a decision (`Approve` or `Block`)
- an optional permission decision (`Allow`, `Ask`, `Deny`, `Passthrough`)
- additional system messages
- appended additional context
- transformed input
- transformed output
- whether tool/output display should be suppressed
- a stop condition

---

## Merge semantics

Multiple hooks may respond to the same event.

The crate merges them using explicit rules, rather than "last one wins" guessing.

Key types:

- `HookDecision::{Approve, Block}`
- `PermissionDecision::{Deny, Ask, Allow, Passthrough}`
- `merge_outputs(...)`

### Practical consequences

- any blocking hook matters
- permission decisions are merged deliberately
- contextual additions may accumulate
- input/output mutations must merge coherently or fail

This matters when you combine:

- repo-local script hooks
- installed plugin hooks
- in-process SDK hooks

---

## Creating hooks in Rust

The exact hook registration surface is intentionally simple from the consumer side: register a runtime hook, then let the runtime dispatch it.

A typical policy hook looks like this conceptually:

```rust
use halter_hooks::{HookEventName, HookResponse};

fn deny_rm_rf(event: HookEventName, payload: serde_json::Value) -> HookResponse {
    if event == HookEventName::PreToolUse {
        let tool_name = payload.get("tool_name").and_then(|v| v.as_str()).unwrap_or("");
        let input = payload.get("input").cloned().unwrap_or_default();

        if tool_name == "shell" && input.to_string().contains("rm -rf /") {
            return HookResponse::block("dangerous shell command blocked by policy");
        }
    }

    HookResponse::passthrough()
}
```

Then register it with the runtime-facing registry using the crate's registration helpers.

### When to use SDK hooks vs external hooks

Use SDK hooks when:

- you want typed Rust integration
- you need internal state or shared process access
- you package policy with an application embedding halter

Use external hooks when:

- you want repo-local policy scripts
- you want non-Rust implementations
- you need easy operator customization without recompiling

---

## Loading hooks from resources

`Hooks::from_sources(...)` builds a hook set from discovered hook definitions.

This is how repo/resource-level hooks become active.

Typical source categories include:

- hooks declared by resources/plugins
- hooks loaded from configuration
- hooks resolved from runtime registries

`Hooks::from_registered(...)` builds from registered in-process hooks.

The `halter` crate bridges plugin resource loading and these dispatch structures.

---

## Dispatch pipeline

Important runtime-facing types:

- `HookDispatchRequest`
- `PreparedHookDispatch`
- `HookDispatchOutcome`

### Conceptual flow

1. build a `HookDispatchRequest`
2. normalize/prepare it into `PreparedHookDispatch`
3. execute matching hooks
4. collect responses
5. merge them into final `HookDispatchOutcome`

The runtime then interprets the outcome.

That may mean:

- continue as normal
- block an operation
- ask the user/operator for permission
- inject additional instructions before the next model call
- stop the session entirely

---

## Practical patterns

## Pattern: add safety guidance before tool use

A `PreToolUse` hook can attach a system message reminding the model about local policy.

```rust
use halter_hooks::HookResponse;

let response = HookResponse::passthrough()
    .with_system_message("Only modify files under the active repository root.")
    .with_additional_context(serde_json::json!({
        "policy": { "write_scope": "repo-root-only" }
    }));
```

This is useful when you want soft guidance rather than hard blocking.

---

## Pattern: hard-block dangerous file writes

Example policy idea:

- on `PreToolUse`
- inspect `tool_name == "write"` or `tool_name == "edit"`
- reject paths outside approved roots

In practice, the tool policy layer already enforces filesystem scope. Hooks are best for:

- additional business rules
- approval workflows
- annotation and audit context

---

## Pattern: permission mediation

A hook can say "this should be asked" rather than directly allowed or denied.

```rust
use halter_hooks::{HookResponse, PermissionDecision};

let response = HookResponse::passthrough()
    .with_permission(PermissionDecision::Ask)
    .with_system_message("Escalated operation requires explicit approval.");
```

This is appropriate for:

- privileged shell commands
- high-risk network operations
- writes outside the main workspace
- long-running or destructive subprocesses

---

## Pattern: redact or suppress noisy output

For verbose tools, a post-tool hook can request output suppression.

```rust
use halter_hooks::HookResponse;

let response = HookResponse::passthrough()
    .with_suppress_output(true);
```

That is useful when:

- output contains secrets
- output is huge and not valuable to the transcript
- you want to preserve audit metadata without rendering raw payloads

---

## Pattern: transform input/output

Hooks can also rewrite data.

Examples:

- normalize shell commands before execution
- sanitize arguments
- rewrite generated paths
- wrap command output with metadata
- redact tokens and keys before transcript inclusion

Use this carefully. Transformative hooks can be powerful but hard to debug.

---

## Example: repo-local policy stack

A realistic policy arrangement for an enterprise workspace might include:

1. a repo-local pre-tool hook to detect destructive commands
2. a post-tool hook to annotate outputs with ticket or policy IDs
3. a session-start hook to inject organization-wide operating guidelines
4. a permission-request hook to auto-deny network access outside an allowlist
5. a subagent-start hook to clamp allowed task classes for delegated work

This lets you adapt halter to local governance without changing core runtime logic.

---

## User-facing implications in the CLI

Even if you're only using `halter run` or `halter chat`, hooks can materially change behavior.

You may see:

- commands blocked that would otherwise be allowed by tool policy
- injected guidance that changes model behavior
- permission prompts or denials
- notifications emitted at significant lifecycle moments
- different outputs because a hook rewrote or suppressed them

This is expected. Hooks are part of the runtime contract.

---

## Ordering and precedence

The crate is designed to support ordered hook execution and merging.

In practical terms, when building systems on top of this crate:

- keep high-priority hard-safety hooks early and simple
- keep advisory/enrichment hooks separate from deny hooks
- avoid having multiple hooks compete to rewrite the same field
- document your hook stack clearly, especially if both scripts and SDK hooks are active

---

## Error handling

Important failure classes include:

- malformed hook payloads
- merge conflicts between incompatible outputs
- unavailable external hook commands
- serialization/deserialization errors
- unexpected runtime hook panics or adapter failures

If a hook architecture is mission-critical, test it like code, not like config.

Recommended practices:

- keep hook outputs deterministic
- prefer explicit block/allow decisions over ambiguous transformations
- log enough metadata to understand why a hook fired
- avoid hidden side effects in hooks

---

## Testing strategy

If you embed halter and rely on hooks, test at three layers.

### Unit tests

Test your hook logic in isolation.

### Merge tests

If multiple hooks apply to the same event, test merged outcomes explicitly.

### Integration tests

Drive the runtime through actual events and assert observed behavior:

- tool call blocked
- permission requested
- context injected
- output suppressed
- session stopped

The `fake` provider from `halter-providers` is useful for fast runtime tests.

---

## When not to use hooks

Hooks are the wrong tool when you simply need:

- a new model provider → use `halter-providers`
- a new tool → use `halter-tools`
- durable session persistence → use `halter-session`
- custom resource loading → use the `halter` resource/compiler layer

Use hooks when the problem is policy, interception, annotation, or workflow control.

---

## Recommended design guidelines

- Keep hooks narrow and event-specific.
- Prefer explicit hard blocks for truly unsafe operations.
- Prefer system messages and additional context for guidance.
- Use permission decisions for operations requiring human escalation.
- Treat output rewriting as a high-power feature and keep it well-tested.
- Avoid business logic that depends on undocumented payload shapes.

---

## Related docs

- `../halter/README.md` — builder APIs for installing plugin and SDK hooks
- `../halter-runtime/README.md` — where hook dispatch is invoked during execution
- `../halter-tools/README.md` — common policy targets for pre/post tool hooks
- `../halter-cli/README.md` — how hook effects surface to end users