Hooks¶
Hooks around tool, model and node calls, the built-in hooks and content guards. Concepts: Hooks, Guardrails, Team budgets.
agent_adapter.hooks¶
Hooks: code that runs before, after and on errors of tool, LLM and flow-node calls.
DEFAULT_REDACT = ('key', 'token', 'secret', 'password', 'authorization', 'cookie')
module-attribute
¶
CallableGuard
¶
Bases: ContentGuard
Wraps fn(stage, payload): True passes; False, a reason or a GuardVerdict says no.
ContentGuard
¶
GuardVerdict
dataclass
¶
A guard's answer: allowed, or a reason and (to redact) the rewritten payload.
kinds labels what was found (email, card) and count how many: they go into the
guardrail.redacted event, so they must never hold the found values.
LLMInvocation
dataclass
¶
A model call as hooks see it.
NemoGuard
¶
NodeInvocation
dataclass
¶
A flow node run as hooks see it.
PIIGuard
¶
Bases: TextGuard
Blocks, redacts or approves payloads holding personal data or secrets (regexes only).
PresidioGuard
¶
Bases: TextGuard
Presidio as a guard; pass your own analyzer and anonymizer engines or get defaults.
Rate
dataclass
¶
At most calls calls per per seconds, with bursts of up to calls.
ShortCircuit
dataclass
¶
Returned by a before_* / on_*_error hook: skip the call and use value.
ToolInvocation
dataclass
¶
A tool call as hooks see it; call.arguments are validated, LLM-facing, JSON mode.
hook_name(hook)
¶
The hook's name, or its class name.
redact(value, patterns=DEFAULT_REDACT, *, match=None)
¶
A copy of value whose secret mapping keys are ***.
By default a key is secret when it contains any pattern (case-insensitive); match,
e.g. is_secret_name, decides instead.
run_after(hooks, method, inv, value)
async
¶
Thread value through each hook; a None return keeps the current value.
run_before(hooks, method, inv)
async
¶
Call method on each hook; the first ShortCircuit wins and stops the phase.
run_on_error(hooks, method, inv, error)
async
¶
Offer error to each hook; the first ShortCircuit recovers and stops the phase.
Also exported here, documented under the canonical path: BudgetHook, GuardrailHook, Hook, LoggingHook, RateLimitHook.
agent_adapter.hooks.builtin¶
Built-in hooks: logging, budget, guardrails and rate limits.
BudgetScope = Literal['run', 'thread', 'global', 'principal', 'user', 'team']
module-attribute
¶
GuardrailCheck = Callable[[str, Any], bool | str | Awaitable[bool | str]]
module-attribute
¶
OnExceed = Literal['warn', 'stop']
module-attribute
¶
REDACTED = '***'
module-attribute
¶
Stage = Literal['args', 'result', 'llm_request', 'llm_response']
module-attribute
¶
Also exported here, documented under the canonical path: BudgetHook, DEFAULT_REDACT, GuardrailHook, LoggingHook, Rate, RateLimitHook, redact.
agent_adapter.hooks.builtin.budgets¶
Budget counters shared across processes: the BudgetStore port and its backends.
PERIODS = ('day', 'month')
module-attribute
¶
Per = Literal['day', 'month']
module-attribute
¶
BudgetStatus
dataclass
¶
BudgetStore
¶
Bases: Protocol
Spend counters shared by every hook that uses the store; reserve is atomic.
A hold (reserve) counts against the limit until it is settled with the real cost,
released, or hold_s passes (a crashed caller's hold frees itself). expire_at (epoch
seconds) is when the whole counter may be dropped, its window having ended; None
leaves the counter's current expiry as it is.
Backend errors propagate, so a budget check fails closed.
BudgetUsage
dataclass
¶
A counter's settled spend and its live holds (USD).
committed_usd
property
¶
Spent plus held: what a new reservation is checked against.
MemoryBudgetStore
¶
A BudgetStore in this process (threads and event loops share it), the default.
Keeps at most max_keys live counters; ended windows are dropped first, and a new key
past that is refused (fails closed) rather than forgetting another key's spend.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
release(key, reservation_id)
async
¶
Drop the hold; nothing was spent.
reserve(key, reservation_id, amount_usd, *, limit_usd, hold_s, expire_at)
async
¶
Hold amount_usd when it fits limit_usd (None: always); one atomic step.
settle(key, reservation_id, cost_usd, *, expire_at)
async
¶
Drop the hold and add the real cost (even when the hold already expired).
usage(key)
async
¶
The counter's spend and live holds; zero for an unknown or expired key.
RedisBudgetStore
¶
A BudgetStore on Redis (agent-adapter[redis]), shared by every worker and host.
client_factory() returns a redis.asyncio.Redis (one per event loop). Each operation
is one Lua script (Redis 5 or later), so the check and the hold are atomic across
processes; hold expiry uses the Redis clock. A windowed counter expires with its window.
A counter's three keys share a hash tag (one Cluster slot). Backend errors propagate.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
keys_of(key)
¶
The counter, holds and expiry keys of one budget counter.
release(key, reservation_id)
async
¶
Drop the hold; nothing was spent.
reserve(key, reservation_id, amount_usd, *, limit_usd, hold_s, expire_at)
async
¶
Hold amount_usd when it fits limit_usd (None: always), in one script.
settle(key, reservation_id, cost_usd, *, expire_at)
async
¶
Drop the hold and add the real cost (even when the hold already expired).
usage(key)
async
¶
The counter's spend and live holds; zero for an unknown or ended key.
Reserved
dataclass
¶
The outcome of reserve: accepted or not, and the usage it was checked against.
SQLAlchemyBudgetStore
¶
A BudgetStore on a SQLAlchemy async engine (agent-adapter[sqlalchemy]).
engine is a URL ("postgresql+asyncpg://...") or a callable returning an
AsyncEngine; one engine is made per event loop. reserve locks the counter row with
an UPDATE (READ COMMITTED on Postgres; SQLite's write lock) before it checks and
holds, so workers never hold past the limit together. metadata holds both tables.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad |
MissingDependency
|
SQLAlchemy is not installed. |
aclose()
async
¶
Dispose of the running loop's engine.
release(key, reservation_id)
async
¶
Drop the hold; nothing was spent.
reserve(key, reservation_id, amount_usd, *, limit_usd, hold_s, expire_at)
async
¶
Hold amount_usd when it fits limit_usd (None: always), under a row lock.
settle(key, reservation_id, cost_usd, *, expire_at)
async
¶
Drop the hold and add the real cost (even when the hold already expired).
usage(key)
async
¶
The counter's spend and live holds; zero for an unknown or ended key.
SQLiteBudgetStore
¶
A BudgetStore in a SQLite file (WAL), safe across threads, loops and processes.
Each call runs in a worker thread on its own connection. reserve and settle hold
BEGIN IMMEDIATE, so the check and the hold are one step for every process; timeout
is how long one waits for the file lock (seconds). Ended windows and expired holds are
deleted as new reservations arrive.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad |
release(key, reservation_id)
async
¶
Drop the hold; nothing was spent.
reserve(key, reservation_id, amount_usd, *, limit_usd, hold_s, expire_at)
async
¶
Hold amount_usd when it fits limit_usd (None: always), under the file lock.
settle(key, reservation_id, cost_usd, *, expire_at)
async
¶
Drop the hold and add the real cost (even when the hold already expired).
usage(key)
async
¶
The counter's spend and live holds; zero for an unknown or ended key.
Window
dataclass
¶
One UTC budget window: label names it in keys; end is when it resets (epoch s).
principal_of(ctx)
¶
Who a call is charged to: the authenticated principal's id, else ctx.user_id.
window_at(per, now)
¶
The window holding now: a UTC day, a UTC month, or all time for per=None.
agent_adapter.hooks.builtin.guards¶
Guard hooks: built-in PII and secret checks, any guard service, Presidio and NeMo Guardrails.
Action = Literal['block', 'redact', 'approve']
module-attribute
¶
GuardFn = Callable[[str, Any], GuardResult | Awaitable[GuardResult]]
module-attribute
¶
NO_REPLACEMENT = _Unset()
module-attribute
¶
PII_KINDS = ('email', 'phone', 'card', 'iban', 'secret')
module-attribute
¶
PIIMatch
dataclass
¶
One finding: its kind and where it sits in the text.
TextGuard
¶
Bases: ContentGuard
A guard that reviews each text in a payload; redaction swaps each text for its rewrite.
as_verdict(answer)
¶
A guard function's answer as a GuardVerdict.
find_pii(text, kinds=PII_KINDS, *, patterns=None)
¶
Every finding in text of the given kinds (plus patterns), in order, not overlapping.
iban_ok(text)
¶
True when an IBAN-like id passes its mod-97 check.
luhn_ok(digits)
¶
True when a digit string passes the Luhn checksum used by payment cards.
redact_pii(text, matches, placeholder='[{kind}]')
¶
text with each match replaced by placeholder ({kind} is the upper-case kind).
Also exported here, documented under the canonical path: CallableGuard, ContentGuard, GuardVerdict, NemoGuard, PIIGuard, PresidioGuard.