Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions lib/crewai/src/crewai/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,47 @@ def validate_async_task_cannot_include_sequential_async_tasks_in_context(
break
return self

@model_validator(mode="after")
def validate_context_no_circular_dependencies(self) -> Self:
"""Validates that task context dependencies do not contain cycles."""
task_ids = {id(task) for task in self.tasks}
visiting: set[int] = set()
visited: set[int] = set()
path: list[Task] = []

def visit(task: Task) -> None:
task_id = id(task)
if task_id in visited:
return
if task_id in visiting:
cycle_start = next(
index
for index, path_task in enumerate(path)
if id(path_task) == task_id
)
cycle_tasks = [*path[cycle_start:], task]
cycle_descriptions = " -> ".join(
cycle_task.description for cycle_task in cycle_tasks
)
raise ValueError(
"Task context dependencies contain a circular dependency: "
f"{cycle_descriptions}."
)

visiting.add(task_id)
path.append(task)
if isinstance(task.context, list):
for context_task in task.context:
if id(context_task) in task_ids:
visit(context_task)
path.pop()
visiting.remove(task_id)
visited.add(task_id)

for task in self.tasks:
visit(task)
return self

@model_validator(mode="after")
def validate_context_no_future_tasks(self) -> Self:
"""Validates that a task's context does not include future tasks."""
Expand Down
31 changes: 31 additions & 0 deletions lib/crewai/tests/test_crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,37 @@ def test_context_no_future_tasks(researcher, writer):
Crew(tasks=[task1, task2, task3, task4], agents=[researcher, writer])


def test_context_no_circular_dependencies(researcher, writer):
task1 = Task(
description="Task 1",
expected_output="output",
agent=researcher,
)
task2 = Task(
description="Task 2",
expected_output="output",
agent=researcher,
)
task3 = Task(
description="Task 3",
expected_output="output",
agent=researcher,
)

task1.context = [task2]
task2.context = [task3]
task3.context = [task1]

with pytest.raises(
ValueError,
match=re.escape(
"Task context dependencies contain a circular dependency: "
"Task 1 -> Task 2 -> Task 3 -> Task 1."
),
):
Crew(tasks=[task1, task2, task3], agents=[researcher, writer])


def test_crew_config_with_wrong_keys():
no_tasks_config = json.dumps(
{
Expand Down