Simplify microagents (#8114)

Co-authored-by: Robert Brennan <accounts@rbren.io>
This commit is contained in:
Engel Nyst 2025-04-28 17:00:06 +02:00 committed by GitHub
parent 107789b5a8
commit 1b63633030
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 133 additions and 229 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -1,6 +1,5 @@
---
name: add_openhands_repo_instruction
type: task
version: 1.0.0
author: openhands
agent: CodeActAgent

View File

@ -1,6 +1,5 @@
---
name: address_pr_comments
type: task
version: 1.0.0
author: openhands
agent: CodeActAgent

View File

@ -1,6 +1,5 @@
---
name: get_test_to_pass
type: task
version: 1.0.0
author: openhands
agent: CodeActAgent

View File

@ -1,6 +1,5 @@
---
name: update_pr_description
type: task
version: 1.0.0
author: openhands
agent: CodeActAgent

View File

@ -1,6 +1,5 @@
---
name: update_test_for_new_implementation
type: task
version: 1.0.0
author: openhands
agent: CodeActAgent

View File

@ -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

View File

@ -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():

View File

@ -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',
]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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