Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
context-isolation.test.ts16.2 kB
/** * Context Isolation Tests * * Verifies that worker agents receive <10% of PM context size * while retaining all necessary fields for task execution */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { createMockGraphManager } from './helpers/mockGraphManager.js'; import { ContextManager } from '../src/managers/ContextManager.js'; import type { PMContext } from '../src/types/context.types.js'; describe('Context Isolation (Integration)', () => { let graphManager: any; let contextManager: ContextManager; beforeEach(async () => { // Use in-memory mock GraphManager for fast, isolated tests graphManager = createMockGraphManager(); await graphManager.initialize(); await graphManager.clear('ALL'); // Small delay for database clear await new Promise(resolve => setTimeout(resolve, 50)); contextManager = new ContextManager(graphManager); }); afterEach(async () => { if (graphManager) { await graphManager.clear('ALL'); await graphManager.close(); } }); describe('Context Filtering', () => { it('should filter PM context to worker context with 90%+ reduction', () => { // Create large PM context const pmContext: PMContext = { taskId: 'task-1', title: 'Implement authentication service', requirements: 'Create JWT-based auth with refresh tokens', description: 'Detailed implementation of authentication', files: Array.from({ length: 50 }, (_, i) => `src/file-${i}.ts`), dependencies: ['task-0', 'task-2', 'task-3'], status: 'pending', priority: 'high', research: { alternatives: [ 'Option 1: Use Passport.js', 'Option 2: Custom JWT implementation', 'Option 3: OAuth2 with third-party provider' ], references: [ 'https://jwt.io/introduction', 'https://auth0.com/docs', 'https://www.passportjs.org/' ], notes: [ 'Passport.js seems heavy for our use case', 'Custom JWT gives us more control', 'Need to consider refresh token rotation' ], estimatedComplexity: 'Medium-High' }, fullSubgraph: { nodes: Array.from({ length: 20 }, (_, i) => ({ id: `node-${i}`, type: 'concept' })), edges: Array.from({ length: 30 }, (_, i) => ({ id: `edge-${i}`, type: 'depends_on' })) }, planningNotes: [ 'Start with basic JWT implementation', 'Add refresh token logic', 'Implement token blacklist for logout', 'Add rate limiting' ], architectureDecisions: [ 'Use Redis for token blacklist', 'Store refresh tokens in database', 'Implement sliding session expiry' ], allFiles: Array.from({ length: 100 }, (_, i) => `src/all-files/file-${i}.ts`) }; const workerContext = contextManager.filterForAgent(pmContext, 'worker'); const metrics = contextManager.calculateReduction(pmContext, workerContext); // Verify 90%+ reduction expect(metrics.reductionPercent).toBeGreaterThanOrEqual(90); expect(metrics.originalSize).toBeGreaterThan(metrics.filteredSize); // Verify essential fields retained expect(workerContext.taskId).toBe('task-1'); expect(workerContext.title).toBe('Implement authentication service'); expect(workerContext.requirements).toBe('Create JWT-based auth with refresh tokens'); // Verify PM-specific fields removed expect((workerContext as any).research).toBeUndefined(); expect((workerContext as any).fullSubgraph).toBeUndefined(); expect((workerContext as any).planningNotes).toBeUndefined(); expect((workerContext as any).architectureDecisions).toBeUndefined(); expect((workerContext as any).allFiles).toBeUndefined(); // Verify file list limited (default max 10) expect(workerContext.files?.length).toBeLessThanOrEqual(10); }); it('should filter PM context to QC context with requirements', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Test task', requirements: 'Original requirements text', research: { alternatives: ['Alt 1', 'Alt 2'], notes: ['Note 1'] } }; const qcContext = contextManager.filterForAgent(pmContext, 'qc') as import('../src/types/context.types.js').QCContext; // QC should have original requirements expect(qcContext.originalRequirements).toBe('Original requirements text'); // But not have PM research expect((qcContext as any).research).toBeUndefined(); }); it('should return full context for PM agent', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Test task', requirements: 'Requirements', research: { alternatives: ['Alt 1', 'Alt 2'] }, planningNotes: ['Note 1', 'Note 2'] }; const result = contextManager.filterForAgent(pmContext, 'pm'); // PM should get everything expect(result).toEqual(pmContext); expect((result as PMContext).research).toBeDefined(); expect((result as PMContext).planningNotes).toBeDefined(); }); it('should preserve error context for worker retries', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Retry task', requirements: 'Fix authentication bug', errorContext: { previousAttempt: 1, errorMessage: 'JWT verification failed', suggestedFix: 'Check token expiry logic' } }; const workerContext = contextManager.filterForAgent(pmContext, 'worker'); // Error context should be preserved for retries expect(workerContext.errorContext).toBeDefined(); expect(workerContext.errorContext?.previousAttempt).toBe(1); expect(workerContext.errorContext?.errorMessage).toBe('JWT verification failed'); expect(workerContext.errorContext?.suggestedFix).toBe('Check token expiry logic'); }); it('should limit file list size to prevent context bloat', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Task with many files', requirements: 'Process all files', files: Array.from({ length: 100 }, (_, i) => `file-${i}.ts`) }; // Default limit is 10 const workerContext1 = contextManager.filterForAgent(pmContext, 'worker'); expect(workerContext1.files?.length).toBe(10); // Custom limit const workerContext2 = contextManager.filterForAgent(pmContext, 'worker', { agentId: 'worker-1', agentType: 'worker', maxFiles: 5 }); expect(workerContext2.files?.length).toBe(5); }); it('should limit dependencies list', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Task with many deps', requirements: 'Complete after all dependencies', dependencies: Array.from({ length: 20 }, (_, i) => `task-${i}`) }; // Default limit is 5 const workerContext = contextManager.filterForAgent(pmContext, 'worker'); expect(workerContext.dependencies?.length).toBe(5); // Custom limit const workerContext2 = contextManager.filterForAgent(pmContext, 'worker', { agentId: 'worker-1', agentType: 'worker', maxDependencies: 3 }); expect(workerContext2.dependencies?.length).toBe(3); }); it('should handle missing fields gracefully', () => { const minimalContext: PMContext = { taskId: 'task-1', title: 'Minimal task', requirements: 'Do something' }; const workerContext = contextManager.filterForAgent(minimalContext, 'worker'); expect(workerContext.taskId).toBe('task-1'); expect(workerContext.title).toBe('Minimal task'); expect(workerContext.requirements).toBe('Do something'); expect(workerContext.files).toBeUndefined(); expect(workerContext.dependencies).toBeUndefined(); }); it('should verify all required fields present in worker context', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Complete task', requirements: 'Build feature X', description: 'Detailed description', files: ['file1.ts', 'file2.ts'], dependencies: ['task-0'], status: 'in_progress', priority: 'high' }; const workerContext = contextManager.filterForAgent(pmContext, 'worker'); // All required fields for task execution expect(workerContext.taskId).toBeDefined(); expect(workerContext.title).toBeDefined(); expect(workerContext.requirements).toBeDefined(); expect(workerContext.description).toBeDefined(); expect(workerContext.files).toBeDefined(); expect(workerContext.dependencies).toBeDefined(); expect(workerContext.status).toBeDefined(); expect(workerContext.priority).toBeDefined(); }); }); describe('Context Size Metrics', () => { it('should calculate context size in bytes', () => { const smallContext: PMContext = { taskId: 'task-1', title: 'Small task', requirements: 'Simple requirement' }; const largeContext: PMContext = { ...smallContext, research: { alternatives: Array.from({ length: 100 }, (_, i) => `Alternative ${i} with lots of text describing the approach in detail`), notes: Array.from({ length: 50 }, (_, i) => `Note ${i}`) } }; const smallWorker = contextManager.filterForAgent(smallContext, 'worker'); const largeWorker = contextManager.filterForAgent(largeContext, 'worker'); const smallMetrics = contextManager.calculateReduction(smallContext, smallWorker); const largeMetrics = contextManager.calculateReduction(largeContext, largeWorker); // Larger context should show bigger reduction expect(largeMetrics.originalSize).toBeGreaterThan(smallMetrics.originalSize); expect(largeMetrics.reductionPercent).toBeGreaterThan(smallMetrics.reductionPercent); }); it('should track fields removed and retained', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Task', requirements: 'Req', research: { notes: ['Note 1'] }, planningNotes: ['Planning note'] }; const workerContext = contextManager.filterForAgent(pmContext, 'worker'); const metrics = contextManager.calculateReduction(pmContext, workerContext); expect(metrics.fieldsRemoved).toContain('research'); expect(metrics.fieldsRemoved).toContain('planningNotes'); expect(metrics.fieldsRetained).toContain('taskId'); expect(metrics.fieldsRetained).toContain('title'); expect(metrics.fieldsRetained).toContain('requirements'); }); it('should validate context reduction meets requirements', () => { const pmContext: PMContext = { taskId: 'task-1', title: 'Task', requirements: 'Requirements', research: { alternatives: Array.from({ length: 100 }, (_, i) => `Long alternative description ${i}`), notes: Array.from({ length: 50 }, (_, i) => `Long note ${i}`) } }; const workerContext = contextManager.filterForAgent(pmContext, 'worker'); const validation = contextManager.validateContextReduction(pmContext, workerContext); expect(validation.valid).toBe(true); // At least 90% reduction expect(validation.metrics.reductionPercent).toBeGreaterThanOrEqual(90); }); }); describe('Graph Integration', () => { it('should fetch and filter task context from graph', async () => { // Create task in graph (flatten nested objects to JSON strings for Neo4j) const taskNode = await graphManager.addNode('todo', { title: 'Implement feature', requirements: 'Build auth system', description: 'Complete authentication', files: ['auth.ts', 'jwt.ts'], dependencies: ['task-0'], research: JSON.stringify({ notes: ['Use JWT', 'Add refresh tokens'] }), planningNotes: ['Start with basic auth', 'Add OAuth later'] }); // Get filtered context for worker const { context: workerContext } = await contextManager.getFilteredTaskContext( taskNode.id, 'worker' ); expect(workerContext.taskId).toBe(taskNode.id); expect(workerContext.title).toBe('Implement feature'); expect(workerContext.requirements).toBe('Build auth system'); expect((workerContext as any).research).toBeUndefined(); expect((workerContext as any).planningNotes).toBeUndefined(); // Filtered out for workers }); it('should fetch subgraph for PM agent', async () => { // Create task network const task1 = await graphManager.addNode('todo', { title: 'Task 1', requirements: 'Req 1' }); const task2 = await graphManager.addNode('todo', { title: 'Task 2', requirements: 'Req 2' }); await graphManager.addEdge(task1.id, task2.id, 'depends_on', {}); // Get context for PM (includes subgraph) const result = await contextManager.getFilteredTaskContext(task1.id, 'pm'); const pmContext = result.context as PMContext; expect(pmContext.taskId).toBe(task1.id); expect(pmContext.fullSubgraph).toBeDefined(); expect(pmContext.fullSubgraph?.nodes.length).toBeGreaterThan(0); }); it('should handle missing task gracefully', async () => { await expect( contextManager.getFilteredTaskContext('non-existent-task', 'worker') ).rejects.toThrow('Task not found'); }); it('should handle large PM context with significant reduction', async () => { // Create task with substantial data (flatten nested objects for Neo4j) const largeTask = await graphManager.addNode('todo', { title: 'Large task', requirements: 'Process all data', files: Array.from({ length: 100 }, (_, i) => `large-file-${i}.ts`), research: JSON.stringify({ alternatives: Array.from({ length: 20 }, (_, i) => `Alternative ${i}: ${'Lorem ipsum dolor sit amet '.repeat(5)}` ), notes: Array.from({ length: 15 }, (_, i) => `Note ${i}: ${'Detailed note content '.repeat(5)}` ) }), planningNotes: Array.from({ length: 10 }, (_, i) => `Planning note ${i}: ${'Plan details '.repeat(3)}` ) }); const { context: workerContext } = await contextManager.getFilteredTaskContext( largeTask.id, 'worker' ); const pmResult = await contextManager.getFilteredTaskContext(largeTask.id, 'pm'); const pmContextFull = pmResult.context as PMContext; const metrics = contextManager.calculateReduction(pmContextFull, workerContext); // Should achieve 90%+ reduction with large context expect(metrics.reductionPercent).toBeGreaterThanOrEqual(90); // Verify significant size difference (PM context is much larger) expect(metrics.originalSize).toBeGreaterThan(metrics.filteredSize * 5); expect(metrics.filteredSize).toBeLessThan(metrics.originalSize * 0.1); }); }); describe('Context Scope Management', () => { it('should return correct scope for each agent type', () => { const pmScope = contextManager.getScope('pm'); const workerScope = contextManager.getScope('worker'); const qcScope = contextManager.getScope('qc'); expect(pmScope.type).toBe('pm'); expect(pmScope.allowedFields).toContain('*'); expect(workerScope.type).toBe('worker'); expect(workerScope.allowedFields).toContain('taskId'); expect(workerScope.allowedFields).toContain('requirements'); expect(qcScope.type).toBe('qc'); expect(qcScope.allowedFields).toContain('originalRequirements'); }); }); });

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/orneryd/Mimir'

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