Skip to main content

HookRunner

Struct HookRunner 

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

Runs every registered interceptor and configured hook against a tool call.

Implementations§

Source§

impl HookRunner

Source

pub fn new(workspace: impl Into<PathBuf>, hooks: Vec<HookSpec>) -> Self

Source

pub fn with_interceptor(self, interceptor: impl Interceptor + 'static) -> Self

Adds an in-process participant, after any already registered.

Appends rather than replaces, and the order of the calls is the order they are consulted in — see the module docs for where that sits relative to subprocess hooks, and why.

RuntimeBuilder::with_interceptor is how a host normally reaches this; the constructor is here for a host building a runner for a runtime of its own.

Source

pub fn with_reporter( self, report: impl Fn(&str) + Send + Sync + 'static, ) -> Self

Redirects failure reports somewhere other than stderr.

A broken participant is an operator’s problem, not the model’s, so it is said out loud by default — including when OnFailure::Allow means the turn carries on, which is the case where nothing else would ever mention it. A host that owns its own logging replaces the destination; it cannot remove it.

Source

pub fn is_empty(&self) -> bool

Whether this runner would consult anybody at all.

Source

pub async fn decide_async(&self, call: &HookCall) -> HookOutcome

Consults every applicable subprocess hook, in order, until one refuses.

Never fails: every way a hook can go wrong ends as a HookOutcome carrying words, because an error here would reach the model as a bare blocked call with the reason thrown away.

Consults everybody: in-process interceptors first, then subprocess hooks.

Both are awaited on the caller’s own runtime. A hook is a subprocess, and since mentra 0.24 spawning and waiting for one is an async primitive rather than blocking work, so the spawn_blocking this used to route hooks through has gone with the thread it needed — and with it the one thing that thread could not do: a turn cancelled mid-hook now drops the future and kills the program, where a blocking wait abandoned the thread to it. That holds on every runtime flavor, current_thread included.

Source

pub async fn review_async( &self, call: &HookCall, output: Value, is_error: bool, ) -> HookOutcome

Consults everybody about a call that has already run.

The after-the-call twin of decide_async, down to the order and the threading: only the request differs, and the answers that request admits. output is the result as JSON — a text result is a JSON string, a structured one is itself — and is_error is the tool’s own verdict, which a replacement may leave alone or overturn.

Answers HookOutcome::Allow when nobody objected, which for a result means keep.

Trait Implementations§

Source§

impl Clone for HookRunner

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

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

Performs copy-assignment from source. Read more
Source§

impl Debug for HookRunner

Source§

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

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

impl ExecutionHookParticipant for HookRunner

One participant, both seams — which is how basis registers a workspace’s chain and the shape upstream added in 0.26 for exactly this.

The two legacy impls below say the same things to mentra’s two older registries, and they are kept because they are the only door a mentra::test::MockRuntime has (its builder takes a PreExecutionHook and a PostExecutionHook and nothing mixed — upstream gap), and because HookRunner is public and a host on those seams may still be using them. All four methods route through decide_async and review_async, so there is one chain and one set of semantics however a runner is installed.

What the mixed registration buys, and why basis’s own open uses it: one ExecutionHookRegistration rather than two independent guards, whose snapshot is taken once and retained across both sides of a call — so a workspace cannot be consulted before a tool and absent after it, which two separately dropped guards permit. And a rewrite’s attribution survives: mentra threads the reason a Modify carried into the refusal that a rejected rewrite earns (invalid JSON, a schema violation, a parallel-lane category flip), where the legacy seam drops it. Threading it into a policy or authorizer denial used to be a separate upstream gap (mentra#57); mentra 0.27 closed it, so a refusal of a rewritten write now speaks in the policy’s words and names the hook ahead of them — "mixed execution hooks (execution hook '<chain>': hook '<name>': <reason>) rewrote this call; the rewritten call then failed: <policy's own words>". See basis/tests/hooks/guarded.rs for what a caller actually reads once mentra’s own 200-byte cap on a ToolCompleted summary is applied — the policy’s words are often the part that no longer fits.

Source§

fn before<'life0, 'life1, 'async_trait>( &'life0 self, context: &'life1 PreExecutionContext, ) -> Pin<Box<dyn Future<Output = Result<BeforeDecision, RuntimeError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Never returns Err, for pre_tool_execution’s reason: an error would reach the model as a blocked call with the reason thrown away.

Source§

fn after<'life0, 'life1, 'async_trait>( &'life0 self, context: &'life1 PostExecutionContext, ) -> Pin<Box<dyn Future<Output = Result<AfterDecision, RuntimeError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

A refusal arrives as a Replace rather than an AfterDecision::Deny, and deliberately: mentra prefixes a Deny with its own “denied by execution hook” wording, and after a tool has run the strongest thing left is what the model reads — which basis’s chain has already worded, naming the participant that objected. Overwriting the result with that text and is_error: true is the same answer post_tool_execution gives, in the same words.

Source§

fn name(&self) -> &str

Source§

impl PostExecutionHook for HookRunner

Source§

fn post_tool_execution<'life0, 'life1, 'async_trait>( &'life0 self, context: &'life1 PostExecutionContext, ) -> Pin<Box<dyn Future<Output = Result<ResultDecision, RuntimeError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Never returns Err, for the reason pre_tool_execution does not: an error here fails the turn, and a guard’s opinion is worth more to whoever reads the transcript than a stack of runtime errors is.

A refusal — a participant’s deny, or a broken one that denies on failure — arrives as the reason in place of the output, is_error: true. That is the strongest thing left after a tool has run: the side effects are done, and AgentEvent::ToolExecutionFinished has already carried the real result to every subscriber, so what a refusal can still govern is what the model reads. A guard that broke while checking an output for credentials has not established that there were none in it.

Source§

impl PreExecutionHook for HookRunner

Source§

fn pre_tool_execution<'life0, 'life1, 'async_trait>( &'life0 self, context: &'life1 PreExecutionContext, ) -> Pin<Box<dyn Future<Output = Result<HookDecision, RuntimeError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Never returns Err.

mentra turns a hook error into a bare blocked-tool result, which throws the reason away; every outcome here is a HookDecision instead, so whatever happened reaches both the model and the audit trail as words.

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + 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: Sized + 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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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 = !

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

fn try_from(value: U) -> Result<T, !>

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