A CrewAI scheduling agent that can't double-book.
Give a CrewAI agent a calendar tool by subclassing crewai.tools.BaseTool over the agentdraft Python SDK. The crew checks availability and commits bookings through AgentDraft's coordination layer, so when a second agent races your crew for the same slot the conflict is resolved at the storage layer and exactly one writer wins — the loser gets a sentence it can act on, not a double-booked human.
Updated
pip install agentdraft crewai
There is no separate agentdraft-crewai package — CrewAI tools are small enough to own in your own codebase, and the framework-agnostic agentdraft SDK already carries the auth, retries, and conflict semantics. Set AGENTDRAFT_API_KEY to an avs_live_… agent key from the dashboard.
from typing import Any, Optional
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
from agentdraft import Client, Conflict
class BookingInput(BaseModel):
start: str = Field(description="Meeting start, ISO 8601")
end: str = Field(description="Meeting end, ISO 8601")
title: Optional[str] = Field(default=None, description="Short meeting title")
class AgentDraftBookingTool(BaseTool):
name: str = "agentdraft_commit_booking_safe"
description: str = (
"Commit a final booking through AgentDraft. If a higher-priority "
"agent already holds the slot this returns 'OUTRANKED' instead of "
"raising — propose a different time rather than retrying."
)
args_schema: type[BaseModel] = BookingInput
client: Any = None
def _run(self, start: str, end: str, title: Optional[str] = None) -> str:
try:
booking = self.client.bookings.commit(
start=_parse(start), end=_parse(end), title=title,
)
except Conflict as exc:
# A lost race is an expected outcome, not a failure.
return (
f"OUTRANKED — agent_id={exc.winning_agent_id} "
f"(priority={exc.winning_agent_priority}) holds this slot. "
"Call agentdraft_get_availability and propose another time."
)
return f"Committed booking {booking.booking_id}."The companion tool wraps client.availability.list(...) the same way, and returns text rather than objects so the model can read it:
class AvailabilityInput(BaseModel):
start: str = Field(description="Start of the search window, ISO 8601")
end: str = Field(description="End of the search window, ISO 8601")
duration_minutes: int = Field(default=30, description="Meeting length")
class AgentDraftAvailabilityTool(BaseTool):
name: str = "agentdraft_get_availability"
description: str = (
"Get the calendar slots open to this agent between two times. "
"Call this before proposing or committing a meeting."
)
args_schema: type[BaseModel] = AvailabilityInput
client: Any = None
def _run(self, start: str, end: str, duration_minutes: int = 30) -> str:
slots = self.client.availability.list(
start=_parse(start), end=_parse(end),
duration_minutes=duration_minutes,
)
if not slots:
return "No open slots in that window. Propose a different range."
lines = "\n".join(
f"- {s.start.isoformat()} to {s.end.isoformat()}" for s in slots
)
return f"{len(slots)} open slot(s):\n{lines}"_parse is a three-line helper that swaps a trailing Z for +00:00 before datetime.fromisoformat — an LLM will emit the Z form almost every time, and Python won't accept it before 3.11. The same two tools and a runnable Crew ship as examples/crewai_quickstart.py in the agentdraft source distribution.
from crewai import Agent, Crew, Task
from agentdraft import Client
client = Client() # reads AGENTDRAFT_API_KEY
tools = [
AgentDraftAvailabilityTool(client=client),
AgentDraftBookingTool(client=client),
]
scheduler = Agent(
role="Scheduler",
goal="Book meetings without ever double-booking the human",
backstory="You share a calendar with other autonomous agents.",
tools=tools,
)
task = Task(
description="Book a 30-minute call tomorrow at 2pm UTC.",
expected_output="The booking id, or the alternate slot you chose.",
agent=scheduler,
)
Crew(agents=[scheduler], tasks=[task]).kickoff()Because it doesn't work, despite how often it's repeated — and we repeated it here too until we tested it. CrewAI validates Agent(tools=…) against its own crewai.tools.BaseTool, so a langchain_core.tools.BaseTool is rejected by Pydantic before the agent ever runs:
ValidationError: 2 validation errors for Agent tools.0 Input should be a valid dictionary or instance of BaseTool [type=model_type, input_value=AvailabilityTool(...), input_type=AvailabilityTool]
The documented bridge doesn't span the gap either. Tool.from_langchain(tool) requires a callable .func attribute, which only function-derived tools (StructuredTool, @tool) carry — a hand-written BaseTool subclass implements _run instead and raises ValueError: The provided tool must have a callable 'func' attribute.
Verified against crewai 1.15.20 and langchain-core 1.6.2 on 2026-09-07. AutoGen is the opposite case — its LangChainToolAdapter does accept our agentdraft-langchain tools, so that path stays supported.
A CrewAI tool that calls Google Calendar directly works fine for one crew. The moment a second agent — another crew, a founder's assistant, a sales bot — writes to the same calendar, the calendar API has no notion of "the other agent that was about to write here." Both writes succeed and the human gets overlapping events. That's the multi-agent calendar collision .
Routing the booking through AgentDraft resolves the race before it reaches the calendar: one TransactWriteItems with a conditional write per time bucket, so exactly one writer wins deterministically by agent priority. Only the winner reaches Google Calendar. The loser gets a typed 409 naming the winner, its priority, and the audit reference — which the tool above turns into the OUTRANKED sentence the LLM reads.
Frequently asked
Can CrewAI use LangChain tools directly?
No. CrewAI validates an agent's tools list against its own crewai.tools.BaseTool, so passing a langchain_core.tools.BaseTool raises a Pydantic ValidationError. Tool.from_langchain() only converts tools that expose a callable .func, which excludes any hand-written BaseTool subclass. Subclass CrewAI's own BaseTool instead — it's about twenty lines over the underlying SDK. Checked against crewai 1.15.20 on 2026-09-07.
Is there an official agentdraft-crewai package?
No, and deliberately so. A CrewAI tool is a thin subclass over the agentdraft Python SDK, and owning those twenty lines in your own repo means your tool descriptions can be tuned to your crew's prompts. Copy examples/crewai_quickstart.py and edit it.
What happens when two CrewAI agents book the same slot?
AgentDraft's conflict engine resolves the race at the storage layer with a single conditional write per time bucket. Exactly one agent commits; the other's tool call returns an OUTRANKED message naming the winning agent and its priority, so the LLM proposes a different time instead of retrying a slot it cannot win.
Do the tools work inside a hierarchical or async Crew?
Yes. The tools hold one agentdraft.Client and make a single HTTP call per invocation, so they're safe to share across agents in a Crew regardless of process. Conflict resolution happens server-side, so two agents in the same crew racing each other resolve exactly as two agents in different crews would.
- LangChain calendar tools — the packaged BaseTool set for LangChain, LangGraph, and AutoGen.
- Calendar API for AI agents — the HTTP surface these tools call.
- How a deterministic conflict engine resolves 8,217 collisions — what runs underneath the booking tool.
- The concurrency benchmark — reproducible numbers for the engine under concurrent writes.