From 68d0f2a1cc1685c06099ce137e160864b852ad89 Mon Sep 17 00:00:00 2001 From: Fiorella Aguirrezabala Date: Wed, 11 Feb 2026 15:43:07 -0300 Subject: [PATCH] fix(migrations): correct operation ordering and uncomment ExampleTool in startproject template Migration operations were generated with lessons/FAQs before agents, causing "Lesson did not include an id" errors on first migrate. The startproject template also had ExampleTool commented out while startagent referenced it, breaking the default setup flow. Closes #2 --- cogsol/core/migrations.py | 2 +- cogsol/management/commands/makemigrations.py | 5 +- cogsol/management/commands/startproject.py | 35 +++--- tests/test_agents.py | 108 ++++++++++++++++++- 4 files changed, 130 insertions(+), 20 deletions(-) diff --git a/cogsol/core/migrations.py b/cogsol/core/migrations.py index f538420..75afa3a 100644 --- a/cogsol/core/migrations.py +++ b/cogsol/core/migrations.py @@ -166,7 +166,7 @@ def diff_states( ) else: # Cognitive API entities (agents) - for entity in ["retrieval_tools", "tools", "lessons", "faqs", "fixed_responses", "agents"]: + for entity in ["retrieval_tools", "tools", "agents", "lessons", "faqs", "fixed_responses"]: operations.extend( _diff_bucket( entity, diff --git a/cogsol/management/commands/makemigrations.py b/cogsol/management/commands/makemigrations.py index b77ae4b..2674713 100644 --- a/cogsol/management/commands/makemigrations.py +++ b/cogsol/management/commands/makemigrations.py @@ -4,7 +4,10 @@ from pathlib import Path from typing import Any -from cogsol import __version__ +try: + from cogsol import __version__ +except ImportError: + __version__ = "unknown" from cogsol.core import migrations as migutils from cogsol.core.loader import collect_content_definitions, collect_definitions from cogsol.management.base import BaseCommand diff --git a/cogsol/management/commands/startproject.py b/cogsol/management/commands/startproject.py index 865315f..d06742a 100644 --- a/cogsol/management/commands/startproject.py +++ b/cogsol/management/commands/startproject.py @@ -33,23 +33,24 @@ def main(): TOOLS_PY = """\ from cogsol.tools import BaseTool, tool_params -# -# class ExampleTool(BaseTool): -# description = "Demo tool that echoes the provided text." -# -# @tool_params( -# text={"description": "Text to echo", "type": "string", "required": True}, -# count={"description": "Times to repeat", "type": "integer", "required": False}, -# ) -# def run(self, chat=None, data=None, secrets=None, log=None, text: str = "", count: int = 1): -# \"\"\" -# text: Text to echo back. -# count: Times to repeat the text. -# \"\"\" -# message = " ".join([text] * max(1, int(count))) -# # chat/data/secrets/log are available per platform docs -# response = message -# return response + + +class ExampleTool(BaseTool): + description = "Demo tool that echoes the provided text." + + @tool_params( + text={"description": "Text to echo", "type": "string", "required": True}, + count={"description": "Times to repeat", "type": "integer", "required": False}, + ) + def run(self, chat=None, data=None, secrets=None, log=None, text: str = "", count: int = 1): + \"\"\" + text: Text to echo back. + count: Times to repeat the text. + \"\"\" + message = " ".join([text] * max(1, int(count))) + # chat/data/secrets/log are available per platform docs + response = message + return response """ SEARCHES_PY = """\ diff --git a/tests/test_agents.py b/tests/test_agents.py index 0536554..146d4cb 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -8,7 +8,13 @@ from cogsol.agents import BaseAgent, genconfigs, optimizations from cogsol.core.loader import collect_definitions from cogsol.core.migrations import diff_states, empty_state -from cogsol.db.migrations import AlterField, CreateRetrievalTool +from cogsol.db.migrations import ( + AlterField, + CreateAgent, + CreateLesson, + CreateRetrievalTool, + CreateTool, +) class TestBaseAgent: @@ -141,6 +147,106 @@ def test_faq_change_creates_faq_alter(self): assert agent_faq_ops == [] +class TestOperationOrdering: + """Tests for correct dependency ordering of migration operations.""" + + def test_agent_created_before_lessons(self): + """CreateAgent should appear before CreateLesson in migration operations.""" + with tempfile.TemporaryDirectory() as tmpdir: + project_path = Path(tmpdir) + agents_path = project_path / "agents" + agent_pkg = agents_path / "assistant" + agent_pkg.mkdir(parents=True) + + (agents_path / "__init__.py").write_text("", encoding="utf-8") + (agents_path / "tools.py").write_text("", encoding="utf-8") + (agent_pkg / "__init__.py").write_text("", encoding="utf-8") + (agent_pkg / "agent.py").write_text( + """ +from cogsol.agents import BaseAgent + + +class GreetingLesson: + name = "Greeting" + content = "Always greet the user." + + +class AssistantAgent(BaseAgent): + system_prompt = "You are a helpful assistant." + lessons = [GreetingLesson()] + + class Meta: + name = "AssistantAgent" + chat_name = "Assistant" +""", + encoding="utf-8", + ) + + defs = collect_definitions(project_path, "agents") + ops = diff_states(empty_state(), defs, app="agents") + + create_agent_indices = [i for i, op in enumerate(ops) if isinstance(op, CreateAgent)] + create_lesson_indices = [i for i, op in enumerate(ops) if isinstance(op, CreateLesson)] + + assert create_agent_indices, "Expected at least one CreateAgent operation" + assert create_lesson_indices, "Expected at least one CreateLesson operation" + assert max(create_agent_indices) < min( + create_lesson_indices + ), "CreateAgent operations must come before CreateLesson operations" + + def test_tools_created_before_agents(self): + """CreateTool should appear before CreateAgent in migration operations.""" + with tempfile.TemporaryDirectory() as tmpdir: + project_path = Path(tmpdir) + agents_path = project_path / "agents" + agent_pkg = agents_path / "assistant" + agent_pkg.mkdir(parents=True) + + (agents_path / "__init__.py").write_text("", encoding="utf-8") + (agents_path / "tools.py").write_text( + """ +from cogsol.tools import BaseTool + + +class MyTool(BaseTool): + name = "my_tool" + description = "A test tool." + parameters = [] + + def run(self, **kwargs): + return "ok" +""", + encoding="utf-8", + ) + (agent_pkg / "__init__.py").write_text("", encoding="utf-8") + (agent_pkg / "agent.py").write_text( + """ +from cogsol.agents import BaseAgent + + +class AssistantAgent(BaseAgent): + system_prompt = "You are a helpful assistant." + + class Meta: + name = "AssistantAgent" + chat_name = "Assistant" +""", + encoding="utf-8", + ) + + defs = collect_definitions(project_path, "agents") + ops = diff_states(empty_state(), defs, app="agents") + + create_tool_indices = [i for i, op in enumerate(ops) if isinstance(op, CreateTool)] + create_agent_indices = [i for i, op in enumerate(ops) if isinstance(op, CreateAgent)] + + assert create_tool_indices, "Expected at least one CreateTool operation" + assert create_agent_indices, "Expected at least one CreateAgent operation" + assert max(create_tool_indices) < min( + create_agent_indices + ), "CreateTool operations must come before CreateAgent operations" + + class TestRetrievalTools: """Tests for retrieval tool definitions."""