HookDecision

Struct HookDecision 

Source
pub struct HookDecision { /* private fields */ }
Expand description

Decision returned by a hook handler to control agent execution flow.

When a hook returns Some(HookDecision), it takes control of the execution flow. This struct determines whether execution should continue, whether inputs/prompts should be modified, and provides a reason for logging and debugging.

§“First Non-None Wins” Model

The hooks system uses a sequential “first non-None wins” execution model:

  1. Hooks are executed in the order they were registered
  2. Each hook returns Option<HookDecision>:
    • None = “I don’t care, let the next hook decide”
    • Some(decision) = “I’m taking control, stop checking other hooks”
  3. The first hook that returns Some(decision) determines the outcome
  4. Remaining hooks are skipped after a decision is made
  5. If all hooks return None, execution continues normally

This model ensures:

  • Predictable behavior (order matters)
  • Performance (no unnecessary hook executions)
  • Priority (earlier hooks can’t be overridden by later ones)

§Fields

  • continue_execution: If false, abort the current operation (tool execution or prompt processing)
  • modified_input: For PreToolUse hooks - replaces the tool input with this value
  • modified_prompt: For UserPromptSubmit hooks - replaces the user prompt with this value
  • reason: Optional explanation for why this decision was made (useful for debugging/logging)

§Example: Hook Priority Order

use open_agent::{Hooks, PreToolUseEvent, HookDecision};

let hooks = Hooks::new()
    // First hook - security gate (highest priority)
    .add_pre_tool_use(|event| async move {
        if event.tool_name == "dangerous_tool" {
            // This blocks execution - later hooks won't run
            return Some(HookDecision::block("Blocked by security"));
        }
        None // Pass to next hook
    })
    // Second hook - rate limiting
    .add_pre_tool_use(|event| async move {
        // This only runs if first hook returned None
        if over_rate_limit(&event) {
            return Some(HookDecision::block("Rate limit exceeded"));
        }
        None
    })
    // Third hook - logging
    .add_pre_tool_use(|event| async move {
        // This only runs if previous hooks returned None
        println!("Tool {} called", event.tool_name);
        None // Always pass through
    });

fn over_rate_limit(_event: &PreToolUseEvent) -> bool { false }

§Builder Methods

The struct provides convenient builder methods for common scenarios:

  • HookDecision::continue_() - Allow execution to proceed normally
  • HookDecision::block(reason) - Block execution with a reason
  • HookDecision::modify_input(input, reason) - Continue with modified tool input
  • HookDecision::modify_prompt(prompt, reason) - Continue with modified user prompt

Implementations§

Source§

impl HookDecision

Source

pub fn continue_() -> Self

Creates a decision to continue execution normally without modifications.

This is typically used when a hook wants to explicitly signal “continue” rather than returning None. In most cases, returning None is simpler and preferred.

§Example
use open_agent::{PreToolUseEvent, HookDecision};

async fn my_hook(event: PreToolUseEvent) -> Option<HookDecision> {
    // Log the tool use
    println!("Tool called: {}", event.tool_name);

    // Explicitly continue (though returning None would be simpler)
    Some(HookDecision::continue_())
}

Note: Named continue_() with trailing underscore because continue is a Rust keyword.

Source

pub fn block(reason: impl Into<String>) -> Self

Creates a decision to block execution with a reason.

When a hook returns this decision, the current operation (tool execution or prompt processing) is aborted, and the reason is logged.

§Parameters
  • reason: Human-readable explanation for why execution was blocked
§Example
use open_agent::{PreToolUseEvent, HookDecision};

async fn security_gate(event: PreToolUseEvent) -> Option<HookDecision> {
    if event.tool_name == "Bash" {
        if let Some(cmd) = event.tool_input.get("command") {
            if cmd.as_str()?.contains("rm -rf /") {
                return Some(HookDecision::block(
                    "Dangerous recursive delete blocked"
                ));
            }
        }
    }
    None
}
Source

pub fn modify_input(input: Value, reason: impl Into<String>) -> Self

Creates a decision to modify tool input before execution.

Use this in PreToolUse hooks to change the parameters that will be passed to the tool. The tool will execute with the modified input instead of the original.

§Parameters
  • input: The new tool input (as JSON Value) that replaces the original
  • reason: Explanation for why the input was modified
§Example
use open_agent::{PreToolUseEvent, HookDecision};
use serde_json::json;

async fn inject_security_token(event: PreToolUseEvent) -> Option<HookDecision> {
    if event.tool_name == "WebFetch" {
        // Add authentication to all web requests
        let mut modified = event.tool_input.clone();
        modified["headers"] = json!({
            "Authorization": "Bearer secret-token",
            "X-User-ID": "user-123"
        });

        return Some(HookDecision::modify_input(
            modified,
            "Injected authentication headers"
        ));
    }
    None
}
Source

pub fn modify_prompt( prompt: impl Into<String>, reason: impl Into<String>, ) -> Self

Creates a decision to modify the user’s prompt before processing.

Use this in UserPromptSubmit hooks to enhance, sanitize, or transform user input. The agent will process the modified prompt instead of the original.

§Parameters
  • prompt: The new prompt text that replaces the user’s original input
  • reason: Explanation for why the prompt was modified
§Example
use open_agent::{UserPromptSubmitEvent, HookDecision};

async fn add_context(event: UserPromptSubmitEvent) -> Option<HookDecision> {
    // Add system context to every user prompt
    let enhanced = format!(
        "{}\n\n[System Context: You are in production mode. Be extra careful with destructive operations.]",
        event.prompt
    );

    Some(HookDecision::modify_prompt(
        enhanced,
        "Added production safety context"
    ))
}
§Warning

Modifying prompts can be confusing for users if done excessively or without clear communication. Use this feature judiciously and consider logging modifications.

Source

pub fn continue_execution(&self) -> bool

Returns whether execution should continue.

Source

pub fn modified_input(&self) -> Option<&Value>

Returns the modified input, if any.

Source

pub fn modified_prompt(&self) -> Option<&str>

Returns the modified prompt, if any.

Source

pub fn reason(&self) -> Option<&str>

Returns the reason, if any.

Trait Implementations§

Source§

impl Clone for HookDecision

Source§

fn clone(&self) -> HookDecision

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for HookDecision

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for HookDecision

Source§

fn default() -> HookDecision

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more