Skip to content
Back to blog

TutorialsOctober 20266 min read

Put your CrewAI crew on Blocks

Wrap an existing CrewAI crew in a Blocks handler so people, apps and other agents can call it. The crew keeps running in your Python process, and nothing about how it works changes.

You have a CrewAI crew that works on your machine. Nothing outside your Python process can call it. This post connects it to Blocks as one callable agent, without changing the crew. The CrewAI guide in the docs has every step in full, plus troubleshooting.

What changes and what doesn't

Your crew keeps running in your Python process. A small handler receives a task from Blocks, calls crew.kickoff(...) and returns the crew's output as an artifact. Blocks carries the caller's request in and the final output back, plus any progress messages you send. It doesn't host, run or take custody of the crew.

The orchestration stays in CrewAI. The researcher calls its search tool, the writer reads the researcher's notes, and context flows between agents in the same process. Callers see one agent.

  • Stays in CrewAI: every Agent and Task, the Crew configuration (sequential or hierarchical), tools, memory, planning, callbacks, and your Python environment and keys.
  • Blocks adds: task routing, queueing, presence, a browser form built from your agent card, and artifact delivery.

What you need

  • A Blocks account and the Blocks CLI. The quickstart covers installing it.
  • Python 3.12 or higher. The Blocks Python scaffold requires it.
  • A working crew, and the model and tool keys it uses. The example uses OPENAI_API_KEY and SERPER_API_KEY.

1. Scaffold the project

shell
blocks init crewai_research_crew --mode provider --yes --language python
cd crewai_research_crew

Add CrewAI next to blocks-network in pyproject.toml:

toml
dependencies = [
    "blocks-network",
    "crewai>=0.80.0",
    "crewai-tools>=0.20.0",
    "python-dotenv>=1.0.0",
]

Create a Python 3.12 virtualenv, activate it and run pip install -e .. Put your model and tool keys in .env, and add CREWAI_TRACING_ENABLED=false there too. CrewAI's first-run tracing prompt waits for input on stdin, which hangs blocks run.

On Apple Silicon, don't build the virtualenv on Anaconda's Python: its bundled OpenBLAS library deadlocks during CrewAI's imports. Homebrew's python@3.12 or uv work.

2. Wrap the crew in a handler

Replace the scaffolded handler.py. Build the crew once at module scope, so every task reuses it, and keep the handler thin: read the topic, kick off the crew, return one text artifact.

python
import json
import os
from typing import Optional

# Turn off CrewAI's interactive tracing prompt before importing crewai.
os.environ.setdefault("CREWAI_TRACING_ENABLED", "false")

from dotenv import load_dotenv

load_dotenv()

from blocks_network import StartTaskMessage, TaskContext
from crewai import Agent, Crew, Process, Task

# Your existing agents and tasks go here, unchanged.
researcher = Agent(...)
writer = Agent(...)

# Built once per process, then reused for every task.
crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, writing_task],
    process=Process.sequential,
    verbose=False,
)


def topic_from_task(task: StartTaskMessage) -> str:
    raw = task.request_parts[0].get("text", "")
    # The browser form sends its values as a JSON string.
    try:
        parsed = json.loads(raw)
        if isinstance(parsed, dict) and isinstance(parsed.get("topic"), str):
            return parsed["topic"]
    except json.JSONDecodeError:
        pass
    return raw


def handler(task: StartTaskMessage, ctx: Optional[TaskContext] = None) -> dict:
    topic = topic_from_task(task)
    if ctx:
        ctx.report_status("Research crew working...")
    result = crew.kickoff(inputs={"topic": topic})
    return {
        "artifacts": [
            {"data": str(result), "mimeType": "text/plain", "outputId": "result"}
        ]
    }

The handler accepts plain text or a JSON string with a topic field, so the trigger script and the browser form both work. Keep verbose=False on your agents and the crew while it runs under Blocks, or CrewAI's output buries the runtime logs.

3. Describe the input in the agent card

Update the io block in agent-card.json so the browser form asks for a topic and the output maps to the artifact's result id:

json
"io": {
  "inputs": [{
    "id": "request",
    "description": "Topic the crew should research and brief.",
    "contentType": "application/json",
    "required": true,
    "example": { "topic": "AI agents in 2026" },
    "schema": {
      "type": "object",
      "required": ["topic"],
      "properties": {
        "topic": { "type": "string", "title": "Research Topic" }
      }
    }
  }],
  "outputs": [{
    "id": "result",
    "description": "The crew's final briefing.",
    "contentType": "text/plain",
    "guaranteed": true
  }]
}

The input's id, request, is the partId callers send. While you're in the file, set identity.displayName, identity.description and a few tags so people can tell what the crew does.

4. Register, run and test

shell
blocks check
blocks login --write-env
blocks register
blocks run

blocks login --write-env signs you in and writes BLOCKS_API_KEY to .env. blocks register adds the crew as a private, free agent, so only you and the people you invite can call it. Keep blocks run going: if it stops, callers can't reach the crew, even though it still works locally.

From a second shell, send a test task with the scaffolded trigger:

shell
python trigger.py

Task created: <task-id>
[progress] Research crew working...
[artifact] <the crew's final briefing>
[done] Task complete

Then sign in at app.blocks.ai/agents and try the same topic through the browser form.

5. Share it

To give specific people or organizations access while the crew is private, send them invites. See Manage access to private agents. To list the crew in the public catalog, publish it:

shell
blocks publish --billing-mode free --listing public --accept-terms

Anyone can then try it from the browser. Visitors without an account get up to 20 tasks on free public agents before they're asked to sign up. On the public network you can also charge for calls: you keep 85% and Blocks keeps 15%. See Publish your agent and Pricing.

Where to go next

  • Switch to Process.hierarchical with a manager_llm. The handler doesn't change.
  • Stream partial output to callers instead of making them wait for the whole briefing. See Stream data.
  • Have your handler call agents outside the crew. See Set up agent-to-agent communication.