intutic_clawde.gate.adapters.crewai
CrewAI adapter: install() registers a before_tool_call hook.
CrewAI (v1.15.3+) exposes two documented interception surfaces for a tool call: the legacy register_before_tool_call_hook(fn) and the newer @on(InterceptionPoint.PRE_TOOL_CALL) decorator. install() below uses the legacy registry — it is the simpler, framework-recommended entry point for "add one global hook" and both dialects share the same underlying dispatcher (crewai.hooks.dispatch), so there is nothing the decorator form buys here that the registry form does not.
The veto mechanism, CONFIRMED live, not assumed. The plan that scoped this adapter flagged CrewAI's exact veto shape as unconfirmed — "return False", "raise", or "mutate a .blocked/.cancel field" were all on the table pending a real install. crewai==1.15.16 was installed in a scratch venv (PyPI was reachable; a Python 3.11 toolchain was available for its tiktoken/pyo3 native build) and its source read directly:
crewai/hooks/tool_hooks.py—register_before_tool_call_hook's own docstring: "Return False to block tool execution / Return True or None to allow execution."crewai/hooks/dispatch.py—before_tool_call_reducerturns aFalsereturn intoraise HookAborted(...)internally;run_before_tool_call_hookscatches that and returnsTrue(blocked) to the caller.
So the mechanism is exactly "return False to block" — no exception needs to cross this adapter's own boundary, and no context field needs mutating.
A second, less convenient finding from that same reading: CrewAI's dispatcher fails OPEN on a crash. crewai/hooks/dispatch.py's _invoke_hook catches HookAborted specially but SWALLOWS every other exception a hook raises (verbose-only warning, then treats the call as un-modified — i.e. ALLOWED); confirmed empirically, not just read, by registering a hook that raises RuntimeError and observing run_before_tool_call_hooks return False (not aborted). A naive adapter that let Gate.guard()'s own exceptions (or a "no gate configured" misconfiguration) propagate would have every one of those silently discarded by CrewAI and the call would run unguarded — exactly the fail-OPEN failure mode every other tier in this codebase is built to avoid (see gateBody.ts's SHELL_FAIL_CLOSED prelude). _make_hook below therefore catches every exception, not only IntuticGateRefusal, and returns False (block) for any of them — CrewAI cannot be trusted to fail closed on this adapter's behalf, so the adapter does it itself.
Optional import: importing this module never fails even without crewai installed. Only calling install() requires it — pip install intutic-clawde[crewai].
Functions
install()
def install(*, gate: Optional[Gate] = None) -> NoneRegister a global before_tool_call hook that refuses denied calls.
Usage:
from intutic_clawde.gate.adapters.crewai import install
install() # uses the process-wide gate from intutic_clawde.gate.install()
# or, with an explicit gate:
install(gate=my_gate)Idempotent-adjacent: CrewAI's registry has no built-in dedupe, so calling install() twice registers the hook twice (each firing independently but non-conflicting — the reducer is a boolean OR of every hook's verdict). Call once, at process start.