How the OpenHuman Configurable Hooks System (`hooks.json`) Intercepts Tool Calls and Prompts

The OpenHuman configurable hooks system uses a declarative hooks.json file to register external scripts that intercept, modify, or block tool invocations and agent prompts through ToolHook and PostTurnHook implementations injected into the core runtime.

The hooks.json configuration file provides a user-friendly interface for extending the behavior of the tinyhumansai/openhuman framework without modifying internal source code. By mapping specific event types to executable scripts, developers can audit, mutate, or reject operations at critical points in the agent execution pipeline.

Hook Architecture and Core Components

The configurable hooks system bridges external scripts with OpenHuman’s internal runtime through four primary components working in concert.

hooks.json Configuration

The hooks.json file serves as the declarative entry point, mapping event names such as "tool_call" and "post_turn" to script paths, arguments, and environment variables. This file lives alongside the main OpenHuman configuration and is validated at startup by the hook bridge to ensure all referenced executables exist and event types are supported.

Bridge Implementation

Located in src/openhuman/hooks/bridge.rs, the hook bridge reads the JSON configuration and instantiates internal ToolHook and PostTurnHook structures. These implementations act as adapters that translate internal Rust events into JSON payloads streamed to external scripts via stdin, then interpret exit codes to determine execution flow.

Core Registration

During runtime initialization, src/openhuman/core/runtime/builder.rs registers the bridge’s hooks via CoreBuilder::tool_hook() and CoreBuilder::post_turn_hook(). This registration ensures that every subsequent tool invocation and turn completion automatically flows through the external scripts defined in hooks.json.

Decision Logic

The src/openhuman/hooks/ops.rs module defines the decision enums and execution logic that govern hook behavior. When a script returns exit code 0, the operation is allowed; exit code 2 triggers a denial that aborts the current operation; any other code represents an error that is logged and treated as a failure.

How Tool Call Interception Works

When the agent produces a tool call, the core invokes ToolHook::decide() before executing the underlying function.

The bridge looks up the "tool_call" event in hooks.json, serializes the tool invocation payload (including tool name and arguments), and spawns the configured script with this data on stdin. The script’s response determines the next phase:

  • Allow (exit 0): The core proceeds with the original or modified arguments.
  • Deny (exit 2): The core aborts the tool execution and returns an error to the model.
  • Modify: The script may output a JSON payload on stdout containing mutated arguments; the bridge merges these changes before allowing the call.
  • Ask: The script can request user confirmation before proceeding.

Because hooks are gate-closing, a single denial short-circuits the entire operation, preventing accidental execution of privileged actions.

Post-Turn Prompt Interception

After the core generates a response, the PostTurnHook::run() method triggers to process the completed turn.

This hook receives the full transcript of the conversation turn as a JSON payload, enabling scripts to audit the exchange, inject additional context, or rewrite the assistant’s final message.

If the script exits with code 0 and outputs modified JSON on stdout, the bridge replaces the original response with the processed version before it reaches the user. Exit code 2 causes the entire turn to be discarded, effectively blocking the prompt from being delivered.

Configuring hooks.json for Your Use Case

The configuration file structure is straightforward, requiring only the event type and script path. Each entry may optionally specify environment variables and arguments.

{
  "tool_call": {
    "script": "./scripts/my_tool_hook.sh",
    "env": { "HOOK_MODE": "audit" }
  },
  "post_turn": {
    "script": "./scripts/append_footer.sh",
    "env": {}
  }
}

The tool_call entry executes for every tool invocation, while post_turn runs after each agent response cycle. You may define one or both hooks depending on your interception requirements.

Implementing Custom Hook Scripts

Hook scripts can be written in any language, provided they consume JSON from stdin and return appropriate exit codes.

Audit-Only Tool Hook

This bash script logs tool names for compliance without blocking execution:

#!/usr/bin/env sh

# Read the tool payload from stdin

payload=$(cat)

# Extract tool name

tool_name=$(echo "$payload" | jq -r '.name')
echo "Audit: tool $tool_name called" >> /tmp/hook_audit.log

# Exit 0 to allow the call unchanged

exit 0

Argument Modification Hook

To modify tool arguments, output the modified JSON structure:

#!/usr/bin/env sh

# Read input and modify the URL argument

cat > /tmp/hook_input.json
jq '.args.url = "https://api.example.com/modified"' /tmp/hook_input.json
exit 0

Post-Turn Content Injection

This script appends a disclaimer to every assistant response:

#!/usr/bin/env sh

# Receive full turn transcript

turn=$(cat)

# Append disclaimer to last message

updated=$(echo "$turn" | jq '.messages[-1].content += "\n\n---\n*This response was processed by a custom hook.*"')
echo "$updated"
exit 0

Integration with the OpenHuman Core Runtime

The hook system integrates tightly with the core initialization sequence. When constructing a new Core instance, the CoreBuilder pattern in src/openhuman/core/runtime/builder.rs accepts optional tool_hook and post_turn_hook implementations.

By passing the bridge instances created from hooks.json, the builder ensures that all agent tool executions and turn completions route through the external scripts. This design allows the hooks system to operate as a cross-cutting concern, applying consistent policies across different agent configurations without requiring changes to individual tool implementations or prompt templates.

Summary

  • The hooks.json file provides a declarative interface for registering external scripts that intercept tool calls and prompt generation in OpenHuman.
  • Exit code 0 allows the operation, exit code 2 denies it, and other codes indicate errors.
  • The src/openhuman/hooks/bridge.rs module implements ToolHook and PostTurnHook traits that serialize events to external scripts.
  • Tool call hooks receive payloads on stdin and can modify arguments by outputting JSON, enabling runtime mutation of API requests.
  • Post-turn hooks process the complete conversation transcript after response generation, supporting content rewriting and compliance auditing.
  • Registration occurs during core construction in src/openhuman/core/runtime/builder.rs, ensuring universal coverage across the runtime.

Frequently Asked Questions

What exit codes should my hook scripts return to OpenHuman?

Your hook scripts must return specific exit codes to communicate decisions: exit code 0 signals that the operation should proceed (optionally with modified data output to stdout), exit code 2 explicitly denies or blocks the operation, and any other exit code represents an error that OpenHuman will log and treat as a failure condition.

Can hooks modify the arguments of a tool call before execution?

Yes, tool call hooks can intercept and mutate arguments. When the script exits with code 0, any JSON output written to stdout is parsed by the bridge in src/openhuman/hooks/bridge.rs and merged into the original tool call payload, allowing you to override specific parameters like URLs or filtering criteria before the tool executes.

Where does OpenHuman register the hooks system during startup?

The hooks are registered during the core initialization phase in src/openhuman/core/runtime/builder.rs. The CoreBuilder accepts hook implementations through CoreBuilder::tool_hook() and CoreBuilder::post_turn_hook() methods, which permanently bind the external script invocations defined in hooks.json to the runtime’s execution pipeline.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →