Skip to content

CrewAI adapter: enforce a governed run's budgets on a crew - #127

Merged
prashar32 merged 2 commits into
mainfrom
feat/crewai-adapter
Jun 14, 2026
Merged

CrewAI adapter: enforce a governed run's budgets on a crew#127
prashar32 merged 2 commits into
mainfrom
feat/crewai-adapter

Conversation

@prashar32

Copy link
Copy Markdown
Owner

Adds a CrewAI framework adapter to the Python SDK so an existing crew can run under a governed run with no code change beyond wiring one callback — same shape as the LangChain / OpenAI-Agents adapters.

What it does

RiskKernelStepCallback is a CrewAI step_callback. Pass it to Agent(step_callback=...) or Crew(step_callback=...):

from riskkernel.adapters.crewai import RiskKernelStepCallback

cb = RiskKernelStepCallback(run)                 # gate_tools=True to add the approval gate
crew = Crew(agents=[...], tasks=[...], step_callback=cb)
crew.kickoff()                                   # raises rk.BudgetExceeded at the loop/time budget
  • One agent step counts as one governed step, so the deterministic loop/time budget halts a runaway crew.
  • With gate_tools=True, each tool call (an AgentAction) routes through the human-approval gate before its step is counted; a denial raises ApprovalDenied and the side effect never runs.
  • The adapter carries no governance logic — the daemon decides (Surface 2).

Why step_callback, not the event bus

CrewAI's event bus runs handlers fire-and-forget in a thread pool / via asyncio.gather(return_exceptions=True), and the agent loop never awaits the returned future — so a BudgetExceeded raised in a bus listener is captured and dropped, and the crew would keep spending. step_callback is called synchronously inside the agent executor's loop and an unknown error is re-raised out of it, so the halt propagates and stops the run (the analogue of LangChain's raise_error). I verified this against real CrewAI: a runaway agent halts at its loop budget out of crew.kickoff(), and CrewAI's own bus is seen swallowing the same exception in its internal tool listener — exactly why the bus is the wrong surface here.

Dependencies / version

crewai is lazily imported (the callback duck-types on the answer object), so the SDK still installs and imports stdlib-only. It's an optional [crewai] extra pinned to >=0.80,<2; the supported range is documented in the module docstring.

Tests

sdks/python/tests/test_crewai.py, runnable on stdlib alone (no crewai required):

  • step ticking (one governed step per call)
  • loop-budget halt is surfaced, not swallowed (the propagation contract)
  • tool gating off by default / on requests approval then ticks / final answer is never gated / denial raises before the step is counted
  • duck-typed AgentAction vs AgentFinish detection
  • a @skipUnless(crewai installed) integration test that drives a real crew with a looping stub LLM and asserts crew.kickoff() halts at the budget

Verified end to end against crewai 1.14.7 in a throwaway venv (integration test passes); the project env stays crewai-free.

README and the root CHANGELOG are updated.

Closes #83

Add a CrewAI adapter so an existing crew can be put under a governed run with
no code change beyond wiring one callback. RiskKernelStepCallback is a CrewAI
step_callback you pass to Agent(step_callback=...) or Crew(step_callback=...):
one agent step counts as one governed step, so the deterministic loop/time
budget halts a runaway crew, and with gate_tools=True each tool call (an
AgentAction) routes through the approval gate before its step is counted.

I went with step_callback rather than the event bus on purpose. The event bus
runs handlers fire-and-forget in a thread pool / via asyncio.gather with
return_exceptions=True, and the agent loop never awaits the returned future, so
a BudgetExceeded raised in a bus listener gets captured and dropped — it would
not halt the crew. step_callback is called synchronously inside the executor's
loop and an unknown error is re-raised out of it, so the halt actually
propagates and stops the run (the analogue of LangChain's raise_error). I
confirmed this against crewai 1.14.7: a runaway agent halts at its loop budget
out of crew.kickoff().

crewai is lazily imported (duck-typed on the answer object), so the SDK still
installs and imports stdlib-only; it's an optional [crewai] extra pinned to
>=0.80,<2. Tests cover the enforcement path on stdlib alone (step ticking,
loop-budget halt surfaced not swallowed, tool gating off/on/denied, action vs
final-answer detection) plus a skip-unless-installed integration test that
drives a real crew to the halt.
@prashar32
prashar32 merged commit e25dcd9 into main Jun 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CrewAI adapter (enforce budgets on a crew via the daemon)

1 participant