Instrument a CrewAI Crew (5-minute quickstart)
Add Dobby governance + observability to any locally-running CrewAI crew (pip install crewai) with one import and one handler. No agent code changes.
pip install "dobby-collector[crewai]" + 4 lines of Python at process startup. Every crew.kickoff() becomes one Dobby workload_run with full tool calls + LLM spans + W3C traceparent context. Verified against crewai==1.14.4.When to use the SDK vs other modes
| If your crew... | Pick | Why |
|---|---|---|
| Runs on app.crewai.com (Cloud / Enterprise) | CrewAI Cloud connector | Server-side webhook delivery — no code changes |
| Runs locally in Python (your laptop, K8s, CI, Lambda) | DobbyCrewAIHandler (this page) | Captures every span/tool/LLM call without modifying agent code |
| Calls OpenAI/Anthropic directly (any framework) | LLM Gateway | Synchronous policy enforcement on every LLM call (Inline mode) |
| Uses MCP tool servers | MCP Gateway | Tool-level governance + audit (Hybrid mode) |
The SDK and the LLM/MCP Gateways aren't mutually exclusive — combine for defense-in-depth. SDK gives you span-level workload runs; gateways give you synchronous policy enforcement.
1. Get a connector + bearer token
- Open the Dobby dashboard and pick a tenant.
- Click Workloads → Connect → Dobby SDK. Pick "Python (paste credentials)".
- Copy the
connector_id(wc_...) andapi_key(dsdk_...) — the api_key is shown ONCE, store it in your secrets manager immediately.
2. Install
pip install "dobby-collector[crewai]"The [crewai] extra pulls crewai >= 1.0 as a transitive dep. If you already have CrewAI installed in your venv, the base dobby-collector package is enough. Verified against crewai==1.14.4.
3. Instrument your crew
from crewai import Agent, Crew, Process, Task
from crewai.tools import tool
from dobby_collector import init
from dobby_collector.integrations.crewai import DobbyCrewAIHandler
# 1. Initialise the SDK once at process startup
init(
api_key="dsdk_...", # from your Dobby Workloads page
connector_id="wc_...", # ditto
base_url="https://dobby-ai.com",
framework="crewai", # optional, stamped on every batch envelope
)
# 2. Register the handler ONCE — it auto-listens on the global event bus
handler = DobbyCrewAIHandler() # auto_run=True by default
# 3. Your existing CrewAI code runs unchanged
@tool("get_current_date")
def get_current_date() -> str:
"""Returns the current ISO-8601 UTC date."""
from datetime import datetime, timezone
return datetime.now(timezone.utc).date().isoformat()
researcher = Agent(
role="Researcher",
goal="Fetch today's date using the tool",
backstory="Precise, tool-only.",
tools=[get_current_date],
)
writer = Agent(role="Writer", goal="Summarise the date in 1 sentence", backstory="Concise.")
research_task = Task(
description="Call get_current_date and return the ISO string.",
expected_output="An ISO-8601 date string.",
agent=researcher,
)
summarise_task = Task(
description="Write 'Today is <weekday> <month> <day>, <year>.'",
expected_output="A single sentence.",
agent=writer,
context=[research_task],
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, summarise_task],
process=Process.sequential,
)
result = crew.kickoff() # every span, LLM call, tool call → Dobby automatically- One init call per process. Idempotent — safe to call multiple times, returns the existing instance.
- One handler instance globally. Registers itself on the global
crewai_event_bussingleton in__init__— no manual wire-up. - Auto run boundaries.
auto_run=True(default) wraps eachcrew.kickoff()in start_run/end_run. SetDobbyCrewAIHandler(auto_run=False)if you manage runs yourself. - Thread-safe + non-blocking. Events buffer in-process; a background thread ships them every 10s (or sooner if buffer fills). The handler NEVER raises into your crew code — failures are logged and swallowed.
4. Production: use env vars
# Production: read from env vars instead of hardcoding
export DOBBY_API_KEY="dsdk_..."
export DOBBY_CONNECTOR_ID="wc_..."
export DOBBY_BASE_URL="https://dobby-ai.com"
# Then in code:
from dobby_collector import init
init() # auto-reads DOBBY_* env vars5. Verify it worked
Within ~10 seconds of crew.kickoff() completing, a new run appears on Workloads → Runs. Click into it to see the per-event timeline (task spans, agent execution, every LLM + tool call). For programmatic verification in BigQuery:
SELECT
external_run_id,
status,
JSON_VALUE(metadata_json, '$.framework') AS framework, -- "crewai"
JSON_VALUE(metadata_json, '$.sdk_version') AS sdk_version, -- "0.4.1" or newer
JSON_VALUE(metadata_json, '$.traceparent') AS traceparent, -- W3C
ARRAY_LENGTH(JSON_QUERY_ARRAY(metadata_json, '$.tool_calls')) AS tool_calls,
ARRAY_LENGTH(JSON_QUERY_ARRAY(metadata_json, '$.llm_calls')) AS llm_calls,
ARRAY_LENGTH(JSON_QUERY_ARRAY(metadata_json, '$.agent_steps')) AS agent_steps
FROM `workload_runs`
WHERE connector_id = 'wc_...'
ORDER BY started_at DESC
LIMIT 5Expected on a successful crew: framework="crewai", sdk_version="0.4.0" (or current), traceparent populated, tool_calls > 0 and llm_calls > 0.
What the handler captures
| CrewAI event | Dobby SdkEvent | Becomes… |
|---|---|---|
| CrewKickoffStartedEvent | run.started | Opens a workload_run |
| CrewKickoffCompletedEvent / Failed | run.completed / run.failed | Closes the run + triggers compliance scan |
| TaskStartedEvent / Completed / Failed | span.started / span.completed | Span timeline entry |
| AgentExecutionStartedEvent / Completed | span.started / span.completed | Span timeline entry |
| LLMCallStartedEvent / Completed | llm.start / llm.completion | llm_calls[] with model, prompt, usage, response |
| ToolUsageStartedEvent / Finished | tool.start / tool.end | tool_calls[] with name, args, output |
Troubleshooting
I called crew.kickoff() but no run shows up in Dobby
Most common cause: the SDK's background sender thread gets killed before it flushes. Call dobby_collector.shutdown() before process exit for clean drain — or rely on the built-in atexit handler if your process exits via normal Python return.
Short-lived processes (CI / Lambda): set flush_interval_seconds=2.0 on init() for faster flushes; the DLQ at ~/.dobby-collector/dlq.sqlite persists pending batches across crashes.
Network blocked: the SDK ships to {base_url}/api/v1/webhooks/workloads/{connector_id}. Check egress from your runtime to dobby-ai.com on port 443.
ImportError: DobbyCrewAIHandler requires crewai>=1.0
You installed dobby-collector without the [crewai] extra AND your project doesn't already have CrewAI. Run pip install "dobby-collector[crewai]" or pip install crewai.
I'm on CrewAI < 1.0 — does the handler work?
No. The CrewAIEventsBus + BaseEventListener contract this handler relies on stabilised in crewai >= 1.0. Earlier versions used different callback patterns. Either pin a newer CrewAI release, or fall back to the manual @track / span() / start_run() API of the base SDK.
I see runs, but tool_calls or llm_calls is 0
Make sure you're on dobby-collector ≥ 0.4.1. A bug in v0.4.0 (LLM_END = "llm.end" mismatched the server normalizer's expected "llm.completion") caused llm_calls to always render as 0. Upgrade with pip install -U dobby-collector.
If you're on the latest version and still see 0: check that your agents actually invoked tools (some prompts cause CrewAI to skip tools entirely) and that the LLM call completed without erroring out.
Related
- CrewAI Cloud / Enterprise connector — managed platform, webhook delivery, no SDK
- Distributed tracing (W3C traceparent) — how the SDK auto-emits W3C context
- Python Gateway SDK — synchronous LLM/MCP policy enforcement
- CrewAI docs — upstream framework reference
connector_id in the subject line for faster triage.