CrewAI adapter: enforce a governed run's budgets on a crew - #127
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
RiskKernelStepCallbackis a CrewAIstep_callback. Pass it toAgent(step_callback=...)orCrew(step_callback=...):gate_tools=True, each tool call (anAgentAction) routes through the human-approval gate before its step is counted; a denial raisesApprovalDeniedand the side effect never runs.Why
step_callback, not the event busCrewAI'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 aBudgetExceededraised in a bus listener is captured and dropped, and the crew would keep spending.step_callbackis 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'sraise_error). I verified this against real CrewAI: a runaway agent halts at its loop budget out ofcrew.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
crewaiis 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):AgentActionvsAgentFinishdetection@skipUnless(crewai installed)integration test that drives a real crew with a looping stub LLM and assertscrew.kickoff()halts at the budgetVerified 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