Skip to main content
Glama
adr-018-atomic-tools-architecture.md10.2 kB
# ADR-018: Atomic Tools Architecture **Status**: Accepted **Date**: 2025-12-16 **Deciders**: Development Team **Related**: ADR-003 (Memory-Centric Architecture), ADR-005 (Testing Strategy), Issue #311 (Test Infrastructure Improvements) ## Context The current tool architecture creates significant testing and performance challenges: ### Problems with Current Architecture 1. **Complex Orchestrator Classes**: Tools depend on orchestrator classes (`ResearchOrchestrator`, `KnowledgeGraphManager`) that create deep dependency chains 2. **Test Complexity**: Deep dependency trees require extensive ESM mocking with `jest.unstable_mockModule()`, resulting in: - 50+ lines of mock setup per test file - 300-400 line test files - 37+ timeout failures in CI - 850+ second test suite execution time 3. **Token Overhead**: Orchestrators execute sequentially, adding 2-8 seconds per call and 5,000-6,000 tokens per session 4. **Architecture Misalignment**: Orchestrators conflict with CE-MCP directive-based architecture (ADR-014) ### Test Suite Metrics (Before) | Metric | Current State | Impact | |--------|--------------|--------| | Test suite time | 850+ seconds | Developer productivity loss | | Timeout failures | 37+ tests | CI reliability issues | | ESM mock setup | 50+ lines/test | High maintenance burden | | Test file size | 300-400 lines | Reduced readability | | Mock chains | Deep dependency trees | Brittle tests | ## Decision We will adopt an **Atomic Tools Architecture** with **Dependency Injection** pattern: ### Core Principles 1. **Atomic Tools**: Each tool is self-contained with minimal external dependencies 2. **Dependency Injection**: External dependencies are injected as parameters with sensible defaults 3. **Resource-Based State**: Complex state managers (like `KnowledgeGraphManager`) converted to MCP Resources 4. **Direct Execution**: Tools call utilities directly instead of through orchestrator layers ### Architecture Pattern **Old Pattern (Orchestrator-based)**: ```typescript // Tool with deep dependencies import { ResearchOrchestrator } from '../utils/research-orchestrator.js'; import { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js'; export async function myTool(args: ToolArgs) { const orchestrator = new ResearchOrchestrator(args.projectPath, 'docs/adrs'); const result = await orchestrator.answerResearchQuestion(args.query); // ... complex logic with multiple orchestrator calls } // Test requires complex mocking beforeAll(async () => { await setupESMMocks({ '../../src/utils/research-orchestrator.js': { ResearchOrchestrator: mockClass }, '../../src/utils/tree-sitter-analyzer.js': { TreeSitterAnalyzer: mockClass }, '../../src/utils/file-system.js': { analyzeProjectStructure: mock, findRelatedCode: mock }, '../../src/utils/adr-discovery.js': { discoverAdrsInDirectory: mock }, // ... 50+ lines of mocks }); const module = await import('../../src/tools/my-tool.js'); }); ``` **New Pattern (Atomic with DI)**: ```typescript // Tool with dependency injection import { findFiles, readFile } from '../utils/file-system.js'; interface ToolDependencies { fs?: { findFiles?: typeof findFiles; readFile?: typeof readFile; }; } export async function myTool( args: ToolArgs, deps: ToolDependencies = {} ) { // Use injected dependencies or real implementations const findFilesImpl = deps.fs?.findFiles ?? findFiles; const readFileImpl = deps.fs?.readFile ?? readFile; const files = await findFilesImpl(args.projectPath, '*.ts'); const content = await readFileImpl(files[0]); // ... direct logic without orchestrator return formatMCPResponse(result); } // Test with simple DI test('myTool finds files', async () => { const mockFs = { findFiles: jest.fn().mockResolvedValue(['file1.ts']), readFile: jest.fn().mockResolvedValue('content'), }; const result = await myTool( { projectPath: '/test', query: 'auth' }, { fs: mockFs } ); expect(result.content[0].text).toContain('file1.ts'); }); ``` ### Resource Pattern for State **Old Pattern (Stateful Manager)**: ```typescript const kgManager = new KnowledgeGraphManager(); const snapshot = await kgManager.loadKnowledgeGraph(); const results = await kgManager.queryKnowledgeGraph("what ADRs exist?"); ``` **New Pattern (Resource + Simple Tool)**: ```typescript // Read graph - zero token cost via MCP Resource const graph = await readResource('knowledge://graph'); const adrs = graph.nodes.filter(n => n.type === 'adr'); // Modify graph - use CRUD tool await callTool('update_knowledge', { operation: 'add_entity', entity: 'adr-019', entityType: 'adr', metadata: { title: 'New Decision' } }); ``` ## Target State (After Implementation) | Metric | Before | After | Improvement | |--------|--------|-------|-------------| | Test suite time | 850s | <60s | 93% faster | | Timeout failures | 37+ | ~0 | Eliminated | | ESM mock setup | 50+ lines | 5-10 lines | 80-90% less | | Test file size | 300-400 lines | 50-100 lines | 70-80% smaller | | Mock chains | Deep trees | Flat DI | Simplified | ## Implementation Strategy ### Phase 1: Foundation 1. ✅ Document atomic tools pattern (this ADR) 2. Create atomic tool template with DI examples 3. Update test documentation 4. Mark `ResearchOrchestrator` as deprecated ### Phase 2: High-Priority Tool Migration Convert tools with heaviest test burden: 1. `review-existing-adrs-tool.ts` - Currently uses ResearchOrchestrator 2. `adr-suggestion-tool.ts` - Currently uses ResearchOrchestrator 3. `environment-analysis-tool.ts` - Currently uses ResearchOrchestrator 4. Remaining tools as needed ### Phase 3: Test Infrastructure Cleanup 1. Simplify `esm-mock-helper.ts` (reduce to basic DI support) 2. Remove orchestrator mock factories 3. Update test setup documentation ### Phase 4: Validation 1. Full test suite passes in <60 seconds 2. Zero timeout failures in CI 3. Test coverage maintained or improved (≥85%) 4. CI reliability >99% ### Phase 5: Deprecation (Future) - v3.0.0: Mark orchestrators as deprecated - v4.0.0: Remove orchestrator classes entirely ## Migration Guide ### For Tool Developers When creating new tools: ```typescript // ✅ DO: Use dependency injection export async function newTool( args: ToolArgs, deps: { fs?: typeof import('../utils/file-system.js'); ai?: ReturnType<typeof getAIExecutor>; } = {} ) { const fs = deps.fs ?? await import('../utils/file-system.js'); const ai = deps.ai ?? getAIExecutor(); // ... implementation } // ❌ DON'T: Create orchestrator instances export async function oldTool(args: ToolArgs) { const orchestrator = new ResearchOrchestrator(args.projectPath); const result = await orchestrator.answerResearchQuestion(args.query); } ``` ### For Test Writers ```typescript // ✅ DO: Simple DI mocking test('tool processes files', async () => { const mockFs = { readFile: jest.fn().mockResolvedValue('content') }; const result = await myTool( { projectPath: '/test' }, { fs: mockFs } ); expect(mockFs.readFile).toHaveBeenCalledWith('/test/file.ts'); }); // ❌ DON'T: Complex ESM module mocking beforeAll(async () => { await setupESMMocks({ '../../src/utils/research-orchestrator.js': MockFactories.createResearchOrchestrator(), '../../src/utils/tree-sitter-analyzer.js': MockFactories.createTreeSitterAnalyzer(), // ... 50 more lines }); }); ``` ### For Existing Tools Tools can be migrated incrementally: 1. Add optional `deps` parameter with defaults 2. Replace direct instantiation with injected dependencies 3. Update tests to use DI instead of ESM mocks 4. Remove orchestrator dependencies Example PR: [See Phase 2 implementations] ## Consequences ### Positive 1. **Dramatic Test Speed Improvement**: 850s → <60s (93% faster) 2. **Eliminated Timeout Failures**: 37+ failures → ~0 3. **Simplified Test Code**: 50+ lines mock setup → 5-10 lines 4. **Improved Maintainability**: Smaller, clearer test files (50-100 lines vs 300-400) 5. **Better Tool Isolation**: Each tool is independently testable 6. **Zero Token Overhead**: No sequential orchestrator calls 7. **Alignment with CE-MCP**: Direct execution matches directive-based architecture 8. **Easy Onboarding**: New developers can understand and test tools quickly ### Negative 1. **Migration Effort**: Requires updating existing tools and tests 2. **Breaking Changes**: Tools with orchestrator dependencies need refactoring 3. **Coordination Required**: Multiple tools need to be migrated systematically ### Neutral 1. **Internal Orchestrators**: Can still be used internally for complex workflows 2. **Backward Compatibility**: Old tools continue working during migration 3. **Gradual Rollout**: Can be implemented incrementally ## Validation ### Success Criteria 1. ✅ Full test suite completes in <60 seconds 2. ✅ Zero timeout failures in CI pipeline 3. ✅ New tools can be tested in <20 lines of setup 4. ✅ Test coverage maintained at ≥85% 5. ✅ CI pipeline reliability >99% ### Monitoring Track metrics after each phase: - Test suite execution time - Number of timeout failures - Average test file size - Average mock setup lines per test - Test coverage percentage - CI success rate ### Review Points - After Phase 2: Review first 3 tool migrations - After Phase 3: Validate test infrastructure improvements - After Phase 4: Final validation against success criteria ## References - **Issue #311**: Test Infrastructure Improvements (parent EPIC) - **Issues #334-337**: Current test failures - **ADR-003**: Memory-Centric Architecture - **ADR-005**: Testing and Quality Assurance Strategy - **ADR-014**: CE-MCP Architecture - **docs/knowledge-graph-resource-tool.md**: Resource pattern example ## Notes - This ADR supersedes the implicit orchestrator-based architecture - Existing tools can continue using orchestrators during migration period - The knowledge graph has already been converted to resource pattern (see docs/knowledge-graph-resource-tool.md) - ResearchOrchestrator marked as deprecated in v2.x, removed in v4.0.0

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tosin2013/mcp-adr-analysis-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server