diff --git a/docs/i18n/pt-BR/docusaurus-plugin-content-docs/current/usage/prompting/microagents-public.md b/docs/i18n/pt-BR/docusaurus-plugin-content-docs/current/usage/prompting/microagents-public.md index 33af533e0d..5969cfdb8f 100644 --- a/docs/i18n/pt-BR/docusaurus-plugin-content-docs/current/usage/prompting/microagents-public.md +++ b/docs/i18n/pt-BR/docusaurus-plugin-content-docs/current/usage/prompting/microagents-public.md @@ -4,7 +4,7 @@ Microagentes públicos são diretrizes especializadas acionadas por palavras-chave para todos os usuários do OpenHands. Eles são definidos em arquivos markdown no diretório -[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge). +[`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge). Microagentes públicos: - Monitoram comandos recebidos em busca de suas palavras-chave de acionamento. @@ -15,7 +15,7 @@ Microagentes públicos: ## Microagentes Públicos Atuais Para mais informações sobre microagentes específicos, consulte seus arquivos de documentação individuais no -diretório [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/). +diretório [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/). ### Agente GitHub **Arquivo**: `github.md` @@ -59,7 +59,7 @@ yes | npm install package-name ## Contribuindo com um Microagente Público Você pode criar seus próprios microagentes públicos adicionando novos arquivos markdown ao -diretório [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/). +diretório [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/). ### Melhores Práticas para Microagentes Públicos @@ -81,7 +81,7 @@ Antes de criar um microagente público, considere: #### 2. Crie o Arquivo -Crie um novo arquivo markdown em [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/) +Crie um novo arquivo markdown em [`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/) com um nome descritivo (por exemplo, `docker.md` para um agente focado em Docker). Atualize o arquivo com o frontmatter necessário [de acordo com o formato exigido](./microagents-overview#microagent-format) diff --git a/docs/modules/usage/prompting/microagents-public.md b/docs/modules/usage/prompting/microagents-public.md index 5f8fa2d62c..73106d2e45 100644 --- a/docs/modules/usage/prompting/microagents-public.md +++ b/docs/modules/usage/prompting/microagents-public.md @@ -32,7 +32,7 @@ Before creating a global microagent, consider: #### 2. Create File Create a new Markdown file with a descriptive name in the appropriate directory: -[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge) +[`microagents/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents) #### 3. Testing the Global Microagent diff --git a/microagents/README.md b/microagents/README.md index 16abed7137..f3d8da8b12 100644 --- a/microagents/README.md +++ b/microagents/README.md @@ -15,11 +15,11 @@ This directory (`OpenHands/microagents/`) contains shareable microagents that ar Directory structure: ``` OpenHands/microagents/ -├── knowledge/ # Keyword-triggered expertise -│ ├── git.md # Git operations -│ ├── testing.md # Testing practices -│ └── docker.md # Docker guidelines -└── tasks/ # Interactive workflows +├── # Keyword-triggered expertise +│ ├── git.md # Git operations +│ ├── testing.md # Testing practices +│ └── docker.md # Docker guidelines +└── # These microagents are always loaded ├── pr_review.md # PR review process ├── bug_fix.md # Bug fixing workflow └── feature.md # Feature implementation @@ -37,8 +37,7 @@ your-repository/ └── .openhands/ └── microagents/ └── repo.md # Repository-specific instructions - └── knowledges/ # Private micro-agents that are only available inside this repo - └── tasks/ # Private micro-agents that are only available inside this repo + └── ... # Private micro-agents that are only available inside this repo ``` @@ -47,7 +46,6 @@ your-repository/ When OpenHands works with a repository, it: 1. Loads repository-specific instructions from `.openhands/microagents/repo.md` if present 2. Loads relevant knowledge agents based on keywords in conversations -3. Enable task agent if user select one of them ## Types of Microagents @@ -68,7 +66,7 @@ Key characteristics: - **Reusable**: Knowledge can be applied across multiple projects - **Versioned**: Support multiple versions of tools/frameworks -You can see an example of a knowledge-based agent in [OpenHands's github microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/github.md). +You can see an example of a knowledge-based agent in [OpenHands's github microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/github.md). ### 2. Repository Agents @@ -86,22 +84,6 @@ Key features: You can see an example of a repo agent in [the agent for the OpenHands repo itself](https://github.com/All-Hands-AI/OpenHands/blob/main/.openhands/microagents/repo.md). -### 3. Task Agents - -Task agents provide interactive workflows that guide users through common development tasks. They: -- Accept user inputs -- Follow predefined steps -- Adapt to context -- Provide consistent results - -Key capabilities: -- **Interactive**: Guide users through complex processes -- **Validating**: Check inputs and conditions -- **Flexible**: Adapt to different scenarios -- **Reproducible**: Ensure consistent outcomes - -Example workflow: -You can see an example of a task-based agent in [OpenHands's pull request updating microagent](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/tasks/update_pr_description.md). ## Contributing @@ -113,13 +95,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati - Common problem solutions - General development guidelines -2. **Task Agents** - When you have: - - Repeatable workflows - - Multi-step processes - - Common development tasks - - Standard procedures -3. **Repository Agents** - When you need: +2. **Repository Agents** - When you need: - Project-specific guidelines - Team conventions and practices - Custom workflow documentation @@ -134,14 +111,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati - Use file patterns when relevant - Keep knowledge general and reusable -2. **For Task Agents**: - - Break workflows into clear steps - - Validate user inputs - - Provide helpful defaults - - Include usage examples - - Make steps adaptable -3. **For Repository Agents**: +2. **For Repository Agents**: - Document clear setup instructions - Include repository structure details - Specify testing and build procedures @@ -152,9 +123,8 @@ You can see an example of a task-based agent in [OpenHands's pull request updati ### Submission Process 1. Create your agent file in the appropriate directory: - - `knowledge/` for expertise (public, shareable) - - `tasks/` for workflows (public, shareable) - - Note: Repository agents should remain in their respective repositories' `.openhands/microagents/` directory + - `microagents/` for expertise (public, shareable) + - Note: Repository-specific agents should remain in their respective repositories' `.openhands/microagents/` directory 2. Test thoroughly 3. Submit a pull request to OpenHands diff --git a/microagents/knowledge/add_agent.md b/microagents/add_agent.md similarity index 96% rename from microagents/knowledge/add_agent.md rename to microagents/add_agent.md index db71f3f0c5..56c5a49eab 100644 --- a/microagents/knowledge/add_agent.md +++ b/microagents/add_agent.md @@ -38,4 +38,4 @@ For detailed information, see: - [Microagents Overview](https://docs.all-hands.dev/modules/usage/prompting/microagents-overview) - [Microagents Syntax](https://docs.all-hands.dev/modules/usage/prompting/microagents-syntax) -- [Example GitHub Microagent](https://github.com/All-Hands-AI/OpenHands/blob/main/microagents/knowledge/github.md) +- [Example GitHub Microagent](https://github.com/All-Hands-AI/OpenHands/blob/main/microagents/github.md) diff --git a/microagents/tasks/add_openhands_repo_instruction.md b/microagents/add_openhands_repo_instruction.md similarity index 99% rename from microagents/tasks/add_openhands_repo_instruction.md rename to microagents/add_openhands_repo_instruction.md index 3a4a670846..da0ac754c9 100644 --- a/microagents/tasks/add_openhands_repo_instruction.md +++ b/microagents/add_openhands_repo_instruction.md @@ -1,6 +1,5 @@ --- name: add_openhands_repo_instruction -type: task version: 1.0.0 author: openhands agent: CodeActAgent diff --git a/microagents/tasks/address_pr_comments.md b/microagents/address_pr_comments.md similarity index 97% rename from microagents/tasks/address_pr_comments.md rename to microagents/address_pr_comments.md index db7d406e39..2f1b3c35e1 100644 --- a/microagents/tasks/address_pr_comments.md +++ b/microagents/address_pr_comments.md @@ -1,6 +1,5 @@ --- name: address_pr_comments -type: task version: 1.0.0 author: openhands agent: CodeActAgent diff --git a/microagents/knowledge/docker.md b/microagents/docker.md similarity index 100% rename from microagents/knowledge/docker.md rename to microagents/docker.md diff --git a/microagents/knowledge/flarglebargle.md b/microagents/flarglebargle.md similarity index 100% rename from microagents/knowledge/flarglebargle.md rename to microagents/flarglebargle.md diff --git a/microagents/tasks/get_test_to_pass.md b/microagents/get_test_to_pass.md similarity index 98% rename from microagents/tasks/get_test_to_pass.md rename to microagents/get_test_to_pass.md index 9f2097c875..8f2f0e2e69 100644 --- a/microagents/tasks/get_test_to_pass.md +++ b/microagents/get_test_to_pass.md @@ -1,6 +1,5 @@ --- name: get_test_to_pass -type: task version: 1.0.0 author: openhands agent: CodeActAgent diff --git a/microagents/knowledge/github.md b/microagents/github.md similarity index 100% rename from microagents/knowledge/github.md rename to microagents/github.md diff --git a/microagents/knowledge/gitlab.md b/microagents/gitlab.md similarity index 100% rename from microagents/knowledge/gitlab.md rename to microagents/gitlab.md diff --git a/microagents/knowledge/kubernetes.md b/microagents/kubernetes.md similarity index 100% rename from microagents/knowledge/kubernetes.md rename to microagents/kubernetes.md diff --git a/microagents/knowledge/npm.md b/microagents/npm.md similarity index 100% rename from microagents/knowledge/npm.md rename to microagents/npm.md diff --git a/microagents/knowledge/pdflatex.md b/microagents/pdflatex.md similarity index 100% rename from microagents/knowledge/pdflatex.md rename to microagents/pdflatex.md diff --git a/microagents/knowledge/security.md b/microagents/security.md similarity index 100% rename from microagents/knowledge/security.md rename to microagents/security.md diff --git a/microagents/knowledge/swift-linux.md b/microagents/swift-linux.md similarity index 100% rename from microagents/knowledge/swift-linux.md rename to microagents/swift-linux.md diff --git a/microagents/tasks/update_pr_description.md b/microagents/update_pr_description.md similarity index 98% rename from microagents/tasks/update_pr_description.md rename to microagents/update_pr_description.md index 509256ef6c..ee7d054516 100644 --- a/microagents/tasks/update_pr_description.md +++ b/microagents/update_pr_description.md @@ -1,6 +1,5 @@ --- name: update_pr_description -type: task version: 1.0.0 author: openhands agent: CodeActAgent diff --git a/microagents/tasks/update_test_for_new_implementation.md b/microagents/update_test_for_new_implementation.md similarity index 98% rename from microagents/tasks/update_test_for_new_implementation.md rename to microagents/update_test_for_new_implementation.md index c694d8009e..c558506520 100644 --- a/microagents/tasks/update_test_for_new_implementation.md +++ b/microagents/update_test_for_new_implementation.md @@ -1,6 +1,5 @@ --- name: update_test_for_new_implementation -type: task version: 1.0.0 author: openhands agent: CodeActAgent diff --git a/openhands/integrations/gitlab/gitlab_service.py b/openhands/integrations/gitlab/gitlab_service.py index 01a2dc402c..875c3006d6 100644 --- a/openhands/integrations/gitlab/gitlab_service.py +++ b/openhands/integrations/gitlab/gitlab_service.py @@ -109,7 +109,7 @@ class GitLabService(BaseGitService, GitService): raise self.handle_http_error(e) async def execute_graphql_query( - self, query: str, variables: dict[str, Any]|None = None + self, query: str, variables: dict[str, Any] | None = None ) -> Any: """ Execute a GraphQL query against the GitLab GraphQL API diff --git a/openhands/memory/memory.py b/openhands/memory/memory.py index 85576a1d0a..0042af2831 100644 --- a/openhands/memory/memory.py +++ b/openhands/memory/memory.py @@ -249,7 +249,7 @@ class Memory: """ Loads microagents from the global microagents_dir """ - repo_agents, knowledge_agents, _ = load_microagents_from_dir( + repo_agents, knowledge_agents = load_microagents_from_dir( GLOBAL_MICROAGENTS_DIR ) for name, agent in knowledge_agents.items(): diff --git a/openhands/microagent/__init__.py b/openhands/microagent/__init__.py index 2fa14b7627..110da6d38d 100644 --- a/openhands/microagent/__init__.py +++ b/openhands/microagent/__init__.py @@ -2,18 +2,15 @@ from .microagent import ( BaseMicroagent, KnowledgeMicroagent, RepoMicroagent, - TaskMicroagent, load_microagents_from_dir, ) -from .types import MicroagentMetadata, MicroagentType, TaskInput +from .types import MicroagentMetadata, MicroagentType __all__ = [ 'BaseMicroagent', 'KnowledgeMicroagent', 'RepoMicroagent', - 'TaskMicroagent', 'MicroagentMetadata', 'MicroagentType', - 'TaskInput', 'load_microagents_from_dir', ] diff --git a/openhands/microagent/microagent.py b/openhands/microagent/microagent.py index aef2e245b8..c1b18ca794 100644 --- a/openhands/microagent/microagent.py +++ b/openhands/microagent/microagent.py @@ -23,11 +23,23 @@ class BaseMicroagent(BaseModel): @classmethod def load( - cls, path: Union[str, Path], file_content: str | None = None + cls, + path: Union[str, Path], + microagent_dir: Path | None = None, + file_content: str | None = None, ) -> 'BaseMicroagent': - """Load a microagent from a markdown file with frontmatter.""" + """Load a microagent from a markdown file with frontmatter. + + The agent's name is derived from its path relative to the microagent_dir. + """ path = Path(path) if isinstance(path, str) else path + # Calculate derived name from relative path if microagent_dir is provided + # Otherwise, we will rely on the name from metadata later + derived_name = None + if microagent_dir is not None: + derived_name = str(path.relative_to(microagent_dir).with_suffix('')) + # Only load directly from path if file_content is not provided if file_content is None: with open(path) as f: @@ -59,18 +71,33 @@ class BaseMicroagent(BaseModel): subclass_map = { MicroagentType.KNOWLEDGE: KnowledgeMicroagent, MicroagentType.REPO_KNOWLEDGE: RepoMicroagent, - MicroagentType.TASK: TaskMicroagent, } - if metadata.type not in subclass_map: - raise ValueError(f'Unknown microagent type: {metadata.type}') - agent_class = subclass_map[metadata.type] + # Infer the agent type: + # 1. If triggers exist -> KNOWLEDGE + # 2. Else (no triggers) -> REPO + inferred_type: MicroagentType + if metadata.triggers: + inferred_type = MicroagentType.KNOWLEDGE + else: + # No triggers, default to REPO unless metadata explicitly says otherwise (which it shouldn't for REPO) + # This handles cases where 'type' might be missing or defaulted by Pydantic + inferred_type = MicroagentType.REPO_KNOWLEDGE + + if inferred_type not in subclass_map: + # This should theoretically not happen with the logic above + raise ValueError(f'Could not determine microagent type for: {path}') + + # Use derived_name if available (from relative path), otherwise fallback to metadata.name + agent_name = derived_name if derived_name is not None else metadata.name + + agent_class = subclass_map[inferred_type] return agent_class( - name=metadata.name, + name=agent_name, content=content, metadata=metadata, source=str(path), - type=metadata.type, + type=inferred_type, ) @@ -118,23 +145,14 @@ class RepoMicroagent(BaseMicroagent): def __init__(self, **data): super().__init__(**data) if self.type != MicroagentType.REPO_KNOWLEDGE: - raise ValueError('RepoMicroagent must have type REPO_KNOWLEDGE') - - -class TaskMicroagent(BaseMicroagent): - """Microagent specialized for task-based operations.""" - - def __init__(self, **data): - super().__init__(**data) - if self.type != MicroagentType.TASK: - raise ValueError('TaskMicroagent must have type TASK') + raise ValueError( + f'RepoMicroagent initialized with incorrect type: {self.type}' + ) def load_microagents_from_dir( microagent_dir: Union[str, Path], -) -> tuple[ - dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent], dict[str, TaskMicroagent] -]: +) -> tuple[dict[str, RepoMicroagent], dict[str, KnowledgeMicroagent]]: """Load all microagents from the given directory. Note, legacy repo instructions will not be loaded here. @@ -150,9 +168,8 @@ def load_microagents_from_dir( repo_agents = {} knowledge_agents = {} - task_agents = {} - # Load all agents from .openhands/microagents directory + # Load all agents from microagents directory logger.debug(f'Loading agents from {microagent_dir}') if microagent_dir.exists(): for file in microagent_dir.rglob('*.md'): @@ -161,15 +178,13 @@ def load_microagents_from_dir( if file.name == 'README.md': continue try: - agent = BaseMicroagent.load(file) + agent = BaseMicroagent.load(file, microagent_dir) if isinstance(agent, RepoMicroagent): repo_agents[agent.name] = agent elif isinstance(agent, KnowledgeMicroagent): knowledge_agents[agent.name] = agent - elif isinstance(agent, TaskMicroagent): - task_agents[agent.name] = agent logger.debug(f'Loaded agent {agent.name} from {file}') except Exception as e: raise ValueError(f'Error loading agent from {file}: {e}') - return repo_agents, knowledge_agents, task_agents + return repo_agents, knowledge_agents diff --git a/openhands/microagent/types.py b/openhands/microagent/types.py index 8542faafa5..fab42b7e08 100644 --- a/openhands/microagent/types.py +++ b/openhands/microagent/types.py @@ -8,7 +8,6 @@ class MicroagentType(str, Enum): KNOWLEDGE = 'knowledge' REPO_KNOWLEDGE = 'repo' - TASK = 'task' class MicroagentMetadata(BaseModel): @@ -19,11 +18,3 @@ class MicroagentMetadata(BaseModel): version: str = Field(default='1.0.0') agent: str = Field(default='CodeActAgent') triggers: list[str] = [] # optional, only exists for knowledge microagents - - -class TaskInput(BaseModel): - """Input parameter for task-based agents.""" - - name: str - description: str - required: bool = True diff --git a/openhands/runtime/base.py b/openhands/runtime/base.py index 0168e39280..ceb04f407a 100644 --- a/openhands/runtime/base.py +++ b/openhands/runtime/base.py @@ -457,7 +457,9 @@ class Runtime(FileEditRuntimeMixin): self.log('info', 'openhands_instructions microagent loaded.') loaded_microagents.append( BaseMicroagent.load( - path='.openhands_instructions', file_content=obs.content + path='.openhands_instructions', + microagent_dir=None, + file_content=obs.content, ) ) @@ -483,16 +485,13 @@ class Runtime(FileEditRuntimeMixin): # Clean up the temporary zip file zip_path.unlink() # Load all microagents using the existing function - repo_agents, knowledge_agents, task_agents = load_microagents_from_dir( - microagent_folder - ) + repo_agents, knowledge_agents = load_microagents_from_dir(microagent_folder) self.log( 'info', - f'Loaded {len(repo_agents)} repo agents, {len(knowledge_agents)} knowledge agents, and {len(task_agents)} task agents', + f'Loaded {len(repo_agents)} repo agents and {len(knowledge_agents)} knowledge agents', ) loaded_microagents.extend(repo_agents.values()) loaded_microagents.extend(knowledge_agents.values()) - loaded_microagents.extend(task_agents.values()) shutil.rmtree(microagent_folder) return loaded_microagents diff --git a/tests/runtime/test_microagent.py b/tests/runtime/test_microagent.py index b9e0fd02ab..02a178d84f 100644 --- a/tests/runtime/test_microagent.py +++ b/tests/runtime/test_microagent.py @@ -7,7 +7,7 @@ from conftest import ( _load_runtime, ) -from openhands.microagent import KnowledgeMicroagent, RepoMicroagent, TaskMicroagent +from openhands.microagent import KnowledgeMicroagent, RepoMicroagent def _create_test_microagents(test_dir: str): @@ -48,22 +48,6 @@ Repository-specific test instructions. """ (microagents_dir / 'repo.md').write_text(repo_agent) - # Create test task agent in a nested directory - task_dir = microagents_dir / 'tasks' / 'nested' - task_dir.mkdir(parents=True, exist_ok=True) - task_agent = """--- -name: test_task -type: task -version: 1.0.0 -agent: CodeActAgent ---- - -# Test Task - -Test task content -""" - (task_dir / 'task.md').write_text(task_agent) - # Create legacy repo instructions legacy_instructions = """# Legacy Instructions @@ -88,26 +72,20 @@ def test_load_microagents_with_trailing_slashes( a for a in loaded_agents if isinstance(a, KnowledgeMicroagent) ] repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)] - task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)] # Check knowledge agents assert len(knowledge_agents) == 1 agent = knowledge_agents[0] - assert agent.name == 'test_knowledge_agent' + assert agent.name == 'knowledge/knowledge' assert 'test' in agent.triggers assert 'pytest' in agent.triggers # Check repo agents (including legacy) assert len(repo_agents) == 2 # repo.md + .openhands_instructions repo_names = {a.name for a in repo_agents} - assert 'test_repo_agent' in repo_names + assert 'repo' in repo_names assert 'repo_legacy' in repo_names - # Check task agents - assert len(task_agents) == 1 - agent = task_agents[0] - assert agent.name == 'test_task' - finally: _close_test_runtime(runtime) @@ -131,26 +109,20 @@ def test_load_microagents_with_selected_repo(temp_dir, runtime_cls, run_as_openh a for a in loaded_agents if isinstance(a, KnowledgeMicroagent) ] repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)] - task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)] # Check knowledge agents assert len(knowledge_agents) == 1 agent = knowledge_agents[0] - assert agent.name == 'test_knowledge_agent' + assert agent.name == 'knowledge/knowledge' assert 'test' in agent.triggers assert 'pytest' in agent.triggers # Check repo agents (including legacy) assert len(repo_agents) == 2 # repo.md + .openhands_instructions repo_names = {a.name for a in repo_agents} - assert 'test_repo_agent' in repo_names + assert 'repo' in repo_names assert 'repo_legacy' in repo_names - # Check task agents - assert len(task_agents) == 1 - agent = task_agents[0] - assert agent.name == 'test_task' - finally: _close_test_runtime(runtime) @@ -184,14 +156,12 @@ Repository-specific test instructions. a for a in loaded_agents if isinstance(a, KnowledgeMicroagent) ] repo_agents = [a for a in loaded_agents if isinstance(a, RepoMicroagent)] - task_agents = [a for a in loaded_agents if isinstance(a, TaskMicroagent)] assert len(knowledge_agents) == 0 assert len(repo_agents) == 1 - assert len(task_agents) == 0 agent = repo_agents[0] - assert agent.name == 'test_repo_agent' + assert agent.name == 'repo' finally: _close_test_runtime(runtime) diff --git a/tests/unit/test_memory.py b/tests/unit/test_memory.py index 1c5776c621..60bd3359c9 100644 --- a/tests/unit/test_memory.py +++ b/tests/unit/test_memory.py @@ -152,8 +152,9 @@ async def test_memory_with_microagents(): # from the global directory that's in the repo assert len(memory.knowledge_microagents) > 0 - # We know 'flarglebargle' exists in the global directory - assert 'flarglebargle' in memory.knowledge_microagents + # Check for the derived name 'flarglebargle' + derived_name = 'flarglebargle' + assert derived_name in memory.knowledge_microagents # Create a microagent action with the trigger word microagent_action = RecallAction( @@ -187,7 +188,8 @@ async def test_memory_with_microagents(): assert source == EventSource.ENVIRONMENT assert observation.recall_type == RecallType.KNOWLEDGE assert len(observation.microagent_knowledge) == 1 - assert observation.microagent_knowledge[0].name == 'flarglebargle' + # Check against the derived name + assert observation.microagent_knowledge[0].name == derived_name assert observation.microagent_knowledge[0].trigger == 'flarglebargle' assert 'magic word' in observation.microagent_knowledge[0].content @@ -280,8 +282,9 @@ async def test_memory_with_agent_microagents(): # from the global directory that's in the repo assert len(memory.knowledge_microagents) > 0 - # We know 'flarglebargle' exists in the global directory - assert 'flarglebargle' in memory.knowledge_microagents + # Check for the derived name 'flarglebargle' + derived_name = 'flarglebargle' + assert derived_name in memory.knowledge_microagents # Create a microagent action with the trigger word microagent_action = RecallAction( @@ -315,7 +318,8 @@ async def test_memory_with_agent_microagents(): assert source == EventSource.ENVIRONMENT assert observation.recall_type == RecallType.KNOWLEDGE assert len(observation.microagent_knowledge) == 1 - assert observation.microagent_knowledge[0].name == 'flarglebargle' + # Check against the derived name + assert observation.microagent_knowledge[0].name == derived_name assert observation.microagent_knowledge[0].trigger == 'flarglebargle' assert 'magic word' in observation.microagent_knowledge[0].content diff --git a/tests/unit/test_microagent_no_header.py b/tests/unit/test_microagent_no_header.py index d7942834e2..584919763a 100644 --- a/tests/unit/test_microagent_no_header.py +++ b/tests/unit/test_microagent_no_header.py @@ -9,8 +9,8 @@ def test_load_markdown_without_frontmatter(): content = '# Test Content\nThis is a test markdown file without frontmatter.' path = Path('test.md') - # Load the agent from content - agent = BaseMicroagent.load(path, content) + # Load the agent from content using keyword argument + agent = BaseMicroagent.load(path=path, file_content=content) # Verify it's loaded as a repo agent with default values assert isinstance(agent, RepoMicroagent) @@ -28,8 +28,8 @@ def test_load_markdown_with_empty_frontmatter(): ) path = Path('test.md') - # Load the agent from content - agent = BaseMicroagent.load(path, content) + # Load the agent from content using keyword argument + agent = BaseMicroagent.load(path=path, file_content=content) # Verify it's loaded as a repo agent with default values assert isinstance(agent, RepoMicroagent) @@ -52,8 +52,8 @@ name: custom_name This is a test markdown file with partial frontmatter.""" path = Path('test.md') - # Load the agent from content - agent = BaseMicroagent.load(path, content) + # Load the agent from content using keyword argument + agent = BaseMicroagent.load(path=path, file_content=content) # Verify it uses provided name but default values for other fields assert isinstance(agent, RepoMicroagent) @@ -79,8 +79,8 @@ version: 2.0.0 This is a test markdown file with full frontmatter.""" path = Path('test.md') - # Load the agent from content - agent = BaseMicroagent.load(path, content) + # Load the agent from content using keyword argument + agent = BaseMicroagent.load(path=path, file_content=content) # Verify all provided values are used assert isinstance(agent, RepoMicroagent) diff --git a/tests/unit/test_microagent_utils.py b/tests/unit/test_microagent_utils.py index ce52c7a5f8..dd379644df 100644 --- a/tests/unit/test_microagent_utils.py +++ b/tests/unit/test_microagent_utils.py @@ -5,14 +5,12 @@ from pathlib import Path import pytest -from openhands.core.exceptions import MicroagentValidationError from openhands.microagent import ( BaseMicroagent, KnowledgeMicroagent, MicroagentMetadata, MicroagentType, RepoMicroagent, - TaskMicroagent, load_microagents_from_dir, ) @@ -26,9 +24,10 @@ def test_legacy_micro_agent_load(tmp_path): legacy_file = tmp_path / '.openhands_instructions' legacy_file.write_text(CONTENT) - micro_agent = BaseMicroagent.load(legacy_file) + # Pass microagent_dir (tmp_path in this case) to load + micro_agent = BaseMicroagent.load(legacy_file, tmp_path) assert isinstance(micro_agent, RepoMicroagent) - assert micro_agent.name == 'repo_legacy' + assert micro_agent.name == 'repo_legacy' # Legacy name is hardcoded assert micro_agent.content == CONTENT assert micro_agent.type == MicroagentType.REPO_KNOWLEDGE @@ -39,10 +38,9 @@ def temp_microagents_dir(): with tempfile.TemporaryDirectory() as temp_dir: root = Path(temp_dir) - # Create test knowledge agent + # Create test knowledge agent (type inferred from triggers) knowledge_agent = """--- -name: test_knowledge_agent -type: knowledge +# type: knowledge version: 1.0.0 agent: CodeActAgent triggers: @@ -56,10 +54,9 @@ Testing best practices and guidelines. """ (root / 'knowledge.md').write_text(knowledge_agent) - # Create test repo agent + # Create test repo agent (type inferred from lack of triggers) repo_agent = """--- -name: test_repo_agent -type: repo +# type: repo version: 1.0.0 agent: CodeActAgent --- @@ -70,33 +67,19 @@ Repository-specific test instructions. """ (root / 'repo.md').write_text(repo_agent) - # Create test task agent - task_agent = """--- -name: test_task -type: task -version: 1.0.0 -agent: CodeActAgent ---- - -# Test Task - -Test task content -""" - (root / 'task.md').write_text(task_agent) - yield root def test_knowledge_agent(): """Test knowledge agent functionality.""" + # Note: We still pass type to the constructor here, as it expects it. + # The loader infers the type before calling the constructor. agent = KnowledgeMicroagent( name='test', content='Test content', - metadata=MicroagentMetadata( - name='test', type=MicroagentType.KNOWLEDGE, triggers=['test', 'pytest'] - ), + metadata=MicroagentMetadata(name='test', triggers=['test', 'pytest']), source='test.md', - type=MicroagentType.KNOWLEDGE, + type=MicroagentType.KNOWLEDGE, # Constructor still needs type ) assert agent.match_trigger('running a test') == 'test' @@ -107,42 +90,20 @@ def test_knowledge_agent(): def test_load_microagents(temp_microagents_dir): """Test loading microagents from directory.""" - repo_agents, knowledge_agents, task_agents = load_microagents_from_dir( - temp_microagents_dir - ) + repo_agents, knowledge_agents = load_microagents_from_dir(temp_microagents_dir) - # Check knowledge agents + # Check knowledge agents (name derived from filename: knowledge.md -> 'knowledge') assert len(knowledge_agents) == 1 - agent = knowledge_agents['test_knowledge_agent'] - assert isinstance(agent, KnowledgeMicroagent) - assert 'test' in agent.triggers + agent_k = knowledge_agents['knowledge'] + assert isinstance(agent_k, KnowledgeMicroagent) + assert agent_k.type == MicroagentType.KNOWLEDGE # Check inferred type + assert 'test' in agent_k.triggers - # Check repo agents + # Check repo agents (name derived from filename: repo.md -> 'repo') assert len(repo_agents) == 1 - agent = repo_agents['test_repo_agent'] - assert isinstance(agent, RepoMicroagent) - - # Check task agents - assert len(task_agents) == 1 - agent = task_agents['test_task'] - assert isinstance(agent, TaskMicroagent) - - -def test_invalid_agent_type(temp_microagents_dir): - """Test loading agent with invalid type.""" - invalid_agent = """--- -name: test_invalid -type: invalid -version: 1.0.0 -agent: CodeActAgent ---- - -Invalid agent content -""" - (temp_microagents_dir / 'invalid.md').write_text(invalid_agent) - - with pytest.raises(MicroagentValidationError): - BaseMicroagent.load(temp_microagents_dir / 'invalid.md') + agent_r = repo_agents['repo'] + assert isinstance(agent_r, RepoMicroagent) + assert agent_r.type == MicroagentType.REPO_KNOWLEDGE # Check inferred type def test_load_microagents_with_nested_dirs(temp_microagents_dir): @@ -151,8 +112,7 @@ def test_load_microagents_with_nested_dirs(temp_microagents_dir): nested_dir = temp_microagents_dir / 'nested' / 'dir' nested_dir.mkdir(parents=True) nested_agent = """--- -name: nested_knowledge_agent -type: knowledge +# type: knowledge version: 1.0.0 agent: CodeActAgent triggers: @@ -165,25 +125,25 @@ Testing nested directory loading. """ (nested_dir / 'nested.md').write_text(nested_agent) - repo_agents, knowledge_agents, task_agents = load_microagents_from_dir( - temp_microagents_dir - ) + repo_agents, knowledge_agents = load_microagents_from_dir(temp_microagents_dir) - # Check that we can find the nested agent - assert len(knowledge_agents) == 2 # Original + nested - agent = knowledge_agents['nested_knowledge_agent'] - assert isinstance(agent, KnowledgeMicroagent) - assert 'nested' in agent.triggers + # Check that we can find the nested agent (name derived from path: nested/dir/nested.md -> 'nested/dir/nested') + assert ( + len(knowledge_agents) == 2 + ) # Original ('knowledge') + nested ('nested/dir/nested') + agent_n = knowledge_agents['nested/dir/nested'] + assert isinstance(agent_n, KnowledgeMicroagent) + assert agent_n.type == MicroagentType.KNOWLEDGE # Check inferred type + assert 'nested' in agent_n.triggers def test_load_microagents_with_trailing_slashes(temp_microagents_dir): """Test loading microagents when directory paths have trailing slashes.""" # Create a directory with trailing slash - knowledge_dir = temp_microagents_dir / 'knowledge/' + knowledge_dir = temp_microagents_dir / 'test_knowledge/' knowledge_dir.mkdir(exist_ok=True) knowledge_agent = """--- -name: trailing_knowledge_agent -type: knowledge +# type: knowledge version: 1.0.0 agent: CodeActAgent triggers: @@ -196,12 +156,15 @@ Testing loading with trailing slashes. """ (knowledge_dir / 'trailing.md').write_text(knowledge_agent) - repo_agents, knowledge_agents, task_agents = load_microagents_from_dir( + repo_agents, knowledge_agents = load_microagents_from_dir( str(temp_microagents_dir) + '/' # Add trailing slash to test ) - # Check that we can find the agent despite trailing slashes - assert len(knowledge_agents) == 2 # Original + trailing - agent = knowledge_agents['trailing_knowledge_agent'] - assert isinstance(agent, KnowledgeMicroagent) - assert 'trailing' in agent.triggers + # Check that we can find the agent despite trailing slashes (name derived from path: test_knowledge/trailing.md -> 'test_knowledge/trailing') + assert ( + len(knowledge_agents) == 2 + ) # Original ('knowledge') + trailing ('test_knowledge/trailing') + agent_t = knowledge_agents['test_knowledge/trailing'] + assert isinstance(agent_t, KnowledgeMicroagent) + assert agent_t.type == MicroagentType.KNOWLEDGE # Check inferred type + assert 'trailing' in agent_t.triggers