Hooks¶
techrevati.runtime.hooks ¶
Hooks — interceptor chain that mutates model, tool, and handoff boundaries.
Hooks differ from EventSink (which observes only): each hook may
inspect and rewrite the data flowing through a turn, a tool call, or a
handoff.
The orchestrator runs hooks left-to-right around every run_turn /
arun_turn, run_tool / arun_tool, and handoff call, so a
later hook always sees the output of the previous one.
The two built-in transformations are:
-
before_model / before_tool — receive a mutable
HookContext; mutatectx.prompt(model) orctx.args(tool) in place. Callers expose the mutable container via theprompt_ctx=/args_ctx=kwargs on the session methods; if not supplied, the orchestrator synthesizes a no-op context so hooks still fire. -
after_model / after_tool — receive the model/tool result and return a (possibly new) replacement value. Returning
Noneis taken literally — return the original value to leave it unchanged.
Hooks are intentionally caller-supplied, not "smart" defaults: the
runtime ships three reference implementations (RedactPIIHook,
LogModelIOHook, TokenBudgetCheckHook) and applications compose
their own. Heavy/IO-bound work belongs in AsyncHook so it does not
block the event loop.
This module is zero-dep. Built-ins use stdlib only.
HookContext
dataclass
¶
HookContext(
role,
phase,
model="",
prompt=None,
tool="",
args=dict(),
extra=dict(),
)
Mutable context passed through the hook chain.
The same instance flows left-to-right through the chain; later hooks
see earlier hooks' mutations. The orchestrator does not introspect
any field except for logging — caller controls the shape of
prompt and args.
Fields
role: the session role (AgentSession.role).
phase: the session phase (AgentSession.phase).
model: the model name passed to run_turn / arun_turn.
Empty string for tool hooks.
prompt: opaque caller-supplied model input. Hooks redact / log /
mutate this in place; the caller's coro_factory closes over
it so the model call sees the post-hook value.
tool: the tool name for run_tool / arun_tool. Empty
string for model hooks.
args: caller-supplied tool input dict. Hooks may mutate keys.
extra: free-form dict for caller-defined keys (e.g. correlation
id, trace context).
Hook ¶
Bases: Protocol
Sync interceptor.
Implementations override any subset of the five lifecycle methods —
the dispatcher uses hasattr to skip unimplemented ones, so a
hook that only cares about after_tool need not provide stubs.
The name attribute labels the hook in logs and events; default
to the class name if not set explicitly.
AsyncHook ¶
Bases: Protocol
Async sibling of Hook.
Async sessions dispatch both — sync hooks run inline, async hooks are awaited. Optional methods (override the ones you need):
async def abefore_model(self, ctx: HookContext) -> Noneasync def aafter_model(self, ctx: HookContext, result: Any) -> Anyasync def abefore_tool(self, ctx: HookContext) -> Noneasync def aafter_tool(self, ctx: HookContext, result: Any) -> Anyasync def abefore_handoff(self, ctx: HookContext) -> None
RedactPIIHook ¶
RedactPIIHook(
*,
patterns=None,
replacement="[REDACTED]",
name="redact_pii",
)
Redact PII patterns from text-bearing ctx.prompt before model calls.
Operates on:
ctx.promptwhen it is astr— replaces matches withreplacement(default"[REDACTED]").ctx.promptwhen it is adict— walks string leaves.ctx.promptwhen it is a list of message dictionaries — walks each message'scontentfield.
Other shapes are skipped silently — caller can supply a custom hook
when the schema is exotic. The hook is best-effort: it is a
first-line defense, not a substitute for a dedicated PII scrubber
(see docs/patterns/hooks.md for the discussion).
Also redacts after_model(result) when the result is a string,
so log sinks downstream cannot leak content the upstream redactor
caught coming in.
LogModelIOHook ¶
LogModelIOHook(
*,
logger=None,
level=logging.INFO,
include_prompt=False,
include_result=False,
max_chars=4000,
name="log_model_io",
)
Log model call metadata, with opt-in payload logging.
Defaults to logger.info at the techrevati.runtime.hooks logger;
pass logger= to redirect. Prompt and result payloads are disabled by
default so production deployments do not accidentally leak model inputs or
outputs. Set include_prompt=True and/or include_result=True only
after redaction and retention policy are in place.
The hook truncates payloads above max_chars (default 4000) and
suffixes "…(truncated)" so a runaway 100k-token blob does not
flood the log pipeline.
HookBudgetExceededError ¶
HookBudgetExceededError(*, estimated, limit, model)
Bases: Exception
Raised by TokenBudgetCheckHook when an estimate exceeds the cap.
Catchable inside arun_turn so the recovery loop can react (e.g.
smaller context budget). Use GovernancePlane with
MaxBudgetLimit(on_breach="terminate") if you want a hard stop
that bypasses recovery.
TokenBudgetCheckHook ¶
TokenBudgetCheckHook(
*,
token_limit,
estimator=None,
name="token_budget_check",
)
Pre-flight token-budget guard.
Estimates input tokens for the configured model using a caller-
supplied estimator (e.g. lambda p: len(str(p)) // 4 as a coarse
fallback, or a real tokenizer). Raises HookBudgetExceededError
if the estimate exceeds token_limit.
This is an alternative to UsageLimits for the case where the
budget needs to be enforced before the model call rather than
after the response comes back. The two compose: keep the
pre-flight check tight (cheap, catches obvious mistakes) and let
UsageLimits enforce the cumulative quota across many turns.