Skip to main content
A CrewAI crew runs on AgentOS mostly unchanged. You add a capability handler around kickoff() and point CrewAI’s LLM at the AgentOS gateway. Every model call then goes through the environment’s models and guardrails, and its tokens and cost are recorded on the run. For human approval, use a Flow: the run stops at a step boundary, waits for a person in the Review queue, and the resume continues from the step after it.
Tested with CrewAI 1.15.30 and Python 3.12, using the conformance kit: 25 checks passed, including approve, edit and reject of the Flow’s hold.

The bundle

The example has two capabilities: answer, a crew of two agents, and refund, a Flow that asks a person before a refund above params.refund_limit.
The SDK wheel goes into vendor/, as described in SDKs. Zip the folder’s contents and deploy it (see Deploying).

Wire CrewAI’s model to the gateway

  • provider="openai" makes CrewAI use its OpenAI-compatible client for any model name. The name goes to the gateway exactly as config.models gives it (openai/gpt-4o-mini, anthropic/claude-sonnet-4-5, bedrock-prod/…).
  • default_headers=inv.llm.headers adds the run token, x-agentos-run-token, to every call. The gateway ignores the API key.
  • Build the LLM, agents and crews inside the handler. The run token belongs to one invocation and stops working when it answers.
Don’t use custom_openai=True instead. It strips a leading openai/ from the model name, so a model whose provider slug is openai reaches the gateway as gpt-4o-mini, a name the gateway refuses.

Asking a person from a Flow

A crew’s kickoff() can’t pause: a CrewAI agent’s tool hooks can only allow or block a call, so stopping in the middle means running the whole crew again from the start on the resume. Put the decision at a Flow step boundary instead:
1

The first invocation runs the Flow up to the decision

summarise asks the model for a summary and route sends a refund above the limit to wait_for_review, which only marks the state. The handler then saves the Flow’s state in run state and calls inv.ask(). The run is held and the container may stop.
2

A person decides

The hold shows the order, the amount and the model’s summary in the Review queue, with approve, edit (for example a lower amount) and reject.
3

The resume continues after the decision

The handler loads the saved state, writes the decision into it and runs the Flow again. summarise sees its summary is already there and skips the model, and route now goes to issue_refund or decline. Only the steps after the decision run.
Keep side effects such as the refund after the decision, and key them by run_id. Code before inv.ask() runs again on the resume, so it must be safe to repeat. See Human approvals.

Run it

An edit carries the item’s full data, so send every key the hold asked with. See the Holds API.

Things to know

  • Guardrail blocks are retried. When a guardrail blocks a model call, the gateway answers 400, and CrewAI runs the whole agent step again, up to max_retry_limit more times (2 by default), tools included. Set max_retry_limit=0 on agents whose tools have side effects.
  • Turn off telemetry. The Dockerfile sets CREWAI_DISABLE_TELEMETRY, CREWAI_TRACING_ENABLED=false and OTEL_SDK_DISABLED. Agents reach nothing outside the platform unless network access allows it, so telemetry calls would only fail.
  • Give CrewAI somewhere to write. The container runs as nobody. HOME=/tmp and CREWAI_STORAGE_DIR give CrewAI a writable directory.
  • Allow time to start. CrewAI is heavy: expect a slower start and more memory than a plain agent. Each container gets 1 CPU and 1 GB.

Next steps

Human approvals

Holds, actions, edits and the resume in detail.

Framework examples

The same patterns in other frameworks.