# How Configurable `hooks.json` Scripts Observe and Gate Agent Behavior in OpenHuman

> Discover how OpenHuman's configurable hooks.json scripts observe and gate agent behavior. Inspect tool calls and enforce allow, ask, or deny decisions across multiple layers for robust agent control.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: how-to-guide
- Published: 2026-09-01

---

**OpenHuman uses a layered [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) system that spawns external scripts at critical lifecycle points to inspect tool calls and enforce `allow`, `ask`, or `deny` decisions, merging verdicts across system, user, workspace, and project layers with deny taking precedence over ask over allow.**

The [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) configuration file in OpenHuman provides a declarative, user-authored mechanism to govern autonomous agent behavior. By implementing a four-layer trust model and a process-isolated bridge architecture, the system allows administrators and developers to intercept every tool execution and turn completion without modifying the core Rust codebase. This article examines how the [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) contract is parsed, executed, and enforced according to the source implementation in the `tinyhumansai/openhuman` repository.

## The Three-Stage Hook Architecture

OpenHuman’s hook system operates through three distinct phases: configuration discovery, bridge registration, and runtime enforcement. Each phase is implemented in dedicated modules under `src/openhuman/hooks/`.

### Discovery and Layering

At startup, the core runtime reads [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) from four potential locations—system, user, workspace, and project directories—in order of increasing trust. The logic in [`src/openhuman/hooks/config.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/config.rs) loads each file into a `HooksFile` struct and merges them into a single effective configuration.

The merging algorithm follows a deterministic precedence rule: **deny > ask > allow**. This means a restrictive policy defined in a higher-trust layer (e.g., system-wide) cannot be overridden by a more permissive rule in a lower-trust layer (e.g., a specific project). The resulting `HookOutput` collapses all layered decisions into a single verdict that governs the agent’s next action.

### Bridge Installation

Once configurations are loaded, the `ConfiguredHookBridge` (defined in [`src/openhuman/hooks/bridge.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/bridge.rs)) registers each declared hook with the tinyagents harness via `tinyagents::harness::tool_hook`. The bridge supports two primary hook types:

- **Script hooks**: Spawn external executables as child processes
- **Prompt hooks**: Render templated strings in the user interface

For script hooks, the bridge feeds a JSON envelope describing the current turn context to the script’s **STDIN** and expects a JSON decision object from **STDOUT**. This design ensures complete language agnosticism—hooks can be written in Bash, Python, JavaScript, or any other runtime.

### Enforcement and Decision Merging

When a turn completes or a tool is about to execute, the harness consults the aggregated `HookDecision` enum variants (`Allow`, `Ask`, `Deny`) from [`src/openhuman/hooks/types.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/types.rs). The strictest verdict across all active layers determines the outcome:

1. **Deny**: Immediately aborts the turn or tool execution
2. **Ask**: Pauses execution to prompt the user for confirmation
3. **Allow**: Permits the operation to proceed

Because the bridge is wired directly into the tinyagents dispatcher, this policy enforcement applies uniformly to all agent-tool interactions, including built-in tools like `file_edit`, `shell`, and `prompt`.

## The JSON Envelope and Hook Contract

Each hook script receives a standardized input envelope matching Cursor’s [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) contract. The envelope structure, defined in [`src/openhuman/hooks/types.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/types.rs), contains:

- **`event`**: The lifecycle trigger (e.g., `beforeToolUse`, `afterToolResult`, `sessionStart`)
- **`payload`**: A typed context object such as `ToolPayload` (tool name and arguments), `ShellPayload` (command string), `PromptPayload` (LLM prompt text), or `FileEdit` (file path and diff)

Scripts inspect these fields and emit a JSON response with a `decision` field and optional `reason` string. Any non-zero exit code, timeout, or malformed JSON response is automatically interpreted as a denial, ensuring **fail-closed behavior**.

## Practical Implementation Examples

The following examples demonstrate common governance patterns using the [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) specification.

### Denying Dangerous Tools

Create a [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) that invokes a security script before any tool execution:

```json
{
  "version": 1,
  "enabled": true,
  "hooks": [
    {
      "event": "beforeToolUse",
      "type": "script",
      "command": "/usr/local/bin/deny-dangerous-tool.sh"
    }
  ]
}

```

The corresponding script inspects the payload and blocks destructive operations:

```bash
#!/usr/bin/env bash
read -r envelope
tool=$(echo "$envelope" | jq -r '.payload.tool')

if [[ "$tool" == "file_delete" ]]; then
  echo '{"decision":"deny","reason":"File deletion is blocked by policy"}'
else
  echo '{"decision":"allow"}'
fi

```

When the agent attempts to execute `file_delete`, the bridge receives the denial verdict from STDOUT and aborts the turn before any file system modification occurs.

### Interactive User Prompts

For operations requiring human oversight, use a prompt-type hook to render dynamic confirmation dialogs:

```json
{
  "version": 1,
  "enabled": true,
  "hooks": [
    {
      "event": "beforeToolUse",
      "type": "prompt",
      "command": "Execute {{tool}} with arguments {{args}}? (yes/no)"
    }
  ]
}

```

The bridge template-replaces `{{tool}}` and `{{args}}` with the actual invocation details, displays the prompt in the UI, and maps "yes" responses to `allow` while treating all other input as `deny`.

### Embedded Rust Hooks

For performance-critical or sandboxed environments, [`src/openhuman/hooks/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/ops.rs) exposes a programmatic API to register native Rust closures as hooks without spawning external processes:

```rust
use openhuman::hooks::{self, HookDecision, HookInput};

fn deny_network(_: HookInput) -> HookDecision {
    HookDecision::Deny("Network tools disabled in sandbox".into())
}

// During core bootstrap:
hooks::ops::register_hook("beforeToolUse", deny_network);

```

This approach eliminates process overhead while maintaining the same `HookInput` and `HookDecision` contract used by external scripts.

## Security Model and Layered Trust

The OpenHuman hook architecture implements several defense-in-depth mechanisms to prevent policy bypass:

- **Working Directory Isolation**: Each script executes with its containing [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) directory as the working directory, preventing unintended access outside the policy scope.
- **Deterministic Precedence**: The `HookOutput::merge` logic in [`src/openhuman/hooks/config.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/config.rs) strictly applies the deny-ask-allow hierarchy, ensuring local configurations cannot weaken global security policies.
- **Fail-Closed Execution**: Any script failure—whether timeout, crash, or invalid JSON—results in an implicit denial, guaranteeing that infrastructure failures cannot accidentally grant unauthorized access.

## Summary

- **Four-layer discovery**: OpenHuman reads [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) from system, user, workspace, and project directories, merging them with deny-precedence logic implemented in [`src/openhuman/hooks/config.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/config.rs).
- **Bridge architecture**: The `ConfiguredHookBridge` in [`src/openhuman/hooks/bridge.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/bridge.rs) spawns scripts as isolated child processes, passing context via STDIN and reading decisions from STDOUT.
- **Uniform enforcement**: All tool interactions route through the tinyagents harness, which consults aggregated `HookDecision` enums from [`src/openhuman/hooks/types.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/types.rs) to determine whether to proceed, prompt, or abort.
- **Fail-closed design**: Malformed responses or execution failures default to denial, ensuring policy integrity even during system degradation.
- **Dual API**: External scripts and embedded Rust hooks share the same contract, allowing flexibility between administrative policy scripts and compile-time security controls.

## Frequently Asked Questions

### What file paths does OpenHuman search for [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json)?

OpenHuman searches four specific locations in order of increasing priority: the system-wide configuration directory, the user’s home directory, the current workspace directory, and the specific project root. The merging logic in [`src/openhuman/hooks/config.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/config.rs) processes these layers sequentially, ensuring that higher-trust configurations override lower-trust ones according to the deny-ask-allow precedence rules.

### Can [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) scripts be written in languages other than Bash?

Yes. The `ConfiguredHookBridge` executes the `command` specified in [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) as a generic child process, feeding the JSON envelope to STDIN and expecting a JSON response on STDOUT. This architecture supports Python, JavaScript, compiled binaries, or any executable capable of parsing JSON and emitting the required decision format.

### What happens if a hook script crashes or times out?

If a script exits with a non-zero status code, exceeds the execution timeout, or returns malformed JSON, OpenHuman treats the result as an implicit `deny` decision. This fail-closed behavior, implemented in [`src/openhuman/hooks/bridge.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/hooks/bridge.rs), ensures that infrastructure failures or script bugs cannot inadvertently permit unauthorized tool executions.

### How do I block specific tools like `shell` or `file_edit` for certain projects?

Create a project-level [`hooks.json`](https://github.com/tinyhumansai/openhuman/blob/main/hooks.json) with a `beforeToolUse` script hook that inspects the `.payload.tool` field in the JSON envelope. If the tool name matches your restricted list (e.g., `shell`, `file_edit`), emit `{"decision":"deny"}`. Because project-level configurations layer above workspace and user configurations, these restrictions apply only to that specific project without affecting global settings.