Dobby
Docs/SDK/CrewAI (OSS)

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.

TL;DR: 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.
Are you on CrewAI Cloud? Customers running crews on app.crewai.com (managed platform / Enterprise) do NOT need this SDK. Webhook delivery to Dobby is server-side — see CrewAI Cloud connector docs instead. Use the SDK when your crew runs in your own Python process (local dev, CI, K8s job, Lambda, etc.).

When to use the SDK vs other modes

If your crew...PickWhy
Runs on app.crewai.com (Cloud / Enterprise)CrewAI Cloud connectorServer-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 GatewaySynchronous policy enforcement on every LLM call (Inline mode)
Uses MCP tool serversMCP GatewayTool-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

  1. Open the Dobby dashboard and pick a tenant.
  2. Click Workloads → Connect → Dobby SDK. Pick "Python (paste credentials)".
  3. Copy the connector_id (wc_...) and api_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_bus singleton in __init__ — no manual wire-up.
  • Auto run boundaries. auto_run=True (default) wraps each crew.kickoff() in start_run/end_run. Set DobbyCrewAIHandler(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 vars

5. 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 5

Expected 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 eventDobby SdkEventBecomes…
CrewKickoffStartedEventrun.startedOpens a workload_run
CrewKickoffCompletedEvent / Failedrun.completed / run.failedCloses the run + triggers compliance scan
TaskStartedEvent / Completed / Failedspan.started / span.completedSpan timeline entry
AgentExecutionStartedEvent / Completedspan.started / span.completedSpan timeline entry
LLMCallStartedEvent / Completedllm.start / llm.completionllm_calls[] with model, prompt, usage, response
ToolUsageStartedEvent / Finishedtool.start / tool.endtool_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

Need help? Reach out at [email protected] or open a ticket from your Dobby dashboard. Include your connector_id in the subject line for faster triage.
Dobby AI Platform - AI Agents That Execute Real Work With Full Control