systemPatterns.md•12.7 kB
# System Patterns: ACE MCP Server
## Architectural Patterns
### 1. Three-Component ACE Architecture
```
┌─────────────┐
│ Generator │ → Produces trajectories using playbook
└──────┬──────┘
↓
┌─────────────┐
│ Reflector │ → Analyzes trajectories for insights
└──────┬──────┘
↓
┌─────────────┐
│ Curator │ → Creates delta operations
└──────┬──────┘
↓
┌─────────────┐
│ Playbook │ → Applies deltas, stores bullets
└─────────────┘
```
**Pattern**: Pipeline architecture with clear separation of concerns
**Benefit**: Each component is independently testable and replaceable
### 2. Provider Abstraction Pattern (NEW)
```typescript
// Abstract interface
interface LLMProvider {
chat(messages: Message[]): Promise<string>;
embed(text: string): Promise<number[]>;
}
// Concrete implementations
class OpenAIProvider implements LLMProvider { }
class LMStudioProvider implements LLMProvider { }
// Factory pattern
function createLLMProvider(config: Config): LLMProvider {
return config.provider === 'openai'
? new OpenAIProvider(config.openai)
: new LMStudioProvider(config.lmstudio);
}
```
**Pattern**: Strategy + Factory
**Benefit**: Switch LLM providers without changing core logic
### 3. Delta Update Pattern
```typescript
// Instead of full rewrites
const fullRewrite = {
operation: 'REPLACE',
content: [...1000 bullets] // 100KB
};
// Use incremental deltas
const delta = {
operation: 'ADD',
bullet: { content: '...' } // 100 bytes
};
```
**Pattern**: Event Sourcing (append-only operations)
**Benefit**: Audit trail + minimal data transfer
### 4. Semantic Deduplication Pattern
```typescript
// Before adding new bullet
async function addBullet(bullet: Bullet): Promise<void> {
const similar = await findSimilar(bullet, threshold);
if (similar) {
// Merge: combine helpful_count, keep best wording
similar.helpful_count += bullet.helpful_count;
} else {
// Insert: truly new knowledge
playbook.bullets.push(bullet);
}
}
```
**Pattern**: Content-addressable storage with similarity matching
**Benefit**: Automatic knowledge consolidation
### 5. Context Isolation Pattern
```typescript
// Each context has its own playbook
const contexts = {
'frontend': Playbook,
'backend': Playbook,
'devops': Playbook
};
// No cross-contamination
await generator.generate({
query: 'Create API endpoint',
context_id: 'backend' // Only uses backend bullets
});
```
**Pattern**: Multi-tenancy with namespace isolation
**Benefit**: Strategies don't leak between domains
## Design Patterns
### Repository Pattern (Storage Layer)
```typescript
interface PlaybookRepository {
get(context_id: string): Promise<Playbook>;
save(playbook: Playbook): Promise<void>;
delete(context_id: string): Promise<void>;
list(): Promise<string[]>;
}
class FilePlaybookRepository implements PlaybookRepository {
constructor(private basePath: string) {}
// Implementation with file system
}
```
**Usage**: Abstract storage details from business logic
**Benefit**: Easy to swap JSON files for PostgreSQL later
### Builder Pattern (Delta Operations)
```typescript
class DeltaBuilder {
private operations: DeltaOperation[] = [];
add(content: string): this {
this.operations.push({ operation: 'ADD', bullet: { content } });
return this;
}
update(id: string, changes: Partial<Bullet>): this {
this.operations.push({ operation: 'UPDATE', bullet_id: id, updates: changes });
return this;
}
build(): DeltaOperation[] {
return this.operations;
}
}
// Usage
const deltas = new DeltaBuilder()
.add('New strategy')
.update('b001', { helpful_count: 5 })
.build();
```
**Usage**: Fluent API for creating delta batches
**Benefit**: Readable code, easy composition
### Observer Pattern (Operations Logging)
```typescript
interface OperationObserver {
onDeltaApplied(delta: DeltaOperation): void;
onDeduplication(merged: Bullet, duplicates: Bullet[]): void;
}
class LoggingObserver implements OperationObserver {
onDeltaApplied(delta: DeltaOperation) {
logger.info('Delta applied', { delta });
}
}
class MetricsObserver implements OperationObserver {
onDeltaApplied(delta: DeltaOperation) {
metrics.increment('delta_operations');
}
}
```
**Usage**: Track operations without coupling
**Benefit**: Easy to add monitoring, metrics, audit logs
### Singleton Pattern (Configuration)
```typescript
class Config {
private static instance: Config;
private constructor(private env: NodeJS.ProcessEnv) {}
static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config(process.env);
}
return Config.instance;
}
getLLMProvider(): LLMProviderConfig { }
getStoragePath(): string { }
}
```
**Usage**: Single source of configuration truth
**Benefit**: No prop-drilling, consistent config access
## Error Handling Patterns
### Result Type Pattern
```typescript
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function generate(query: string): Promise<Result<Trajectory>> {
try {
const trajectory = await llm.generate(query);
return { ok: true, value: trajectory };
} catch (error) {
return { ok: false, error: error as Error };
}
}
// Usage
const result = await generate('Create endpoint');
if (result.ok) {
console.log(result.value);
} else {
logger.error(result.error);
}
```
**Usage**: Explicit error handling without exceptions
**Benefit**: Type-safe error propagation
### Circuit Breaker Pattern (LLM Calls)
```typescript
class CircuitBreaker {
private failures = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
throw new Error('Circuit breaker open');
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
}
```
**Usage**: Protect against cascading LLM API failures
**Benefit**: Fast-fail when provider is down
## Data Flow Patterns
### Request Flow (MCP Tool Call)
```
Client (Cursor AI)
↓ JSON-RPC over stdio
MCP Server (tools.ts)
↓ Parse & validate
ACE Component (generator/reflector/curator)
↓ Business logic
LLM Provider (openai/lmstudio)
↓ HTTP request
Storage (playbook.ts)
↓ File I/O
↓ JSON response
Client
```
### Update Flow (Delta Application)
```
Insights (reflector output)
↓
Curator (create deltas)
↓
PlaybookManager (validate)
↓
Deduplicator (check similarity)
↓
BulletStore (persist)
↓
FileSystem (write JSON)
↓
Cache invalidation
```
## Concurrency Patterns
### Async/Await Pattern
```typescript
// All I/O operations are async
async function fullACEWorkflow(query: string): Promise<void> {
const trajectory = await generator.generate(query);
const insights = await reflector.reflect(trajectory);
const deltas = await curator.curate(insights);
await playbookManager.update(deltas);
}
```
**Pattern**: Promise-based async
**Benefit**: Non-blocking I/O, readable code
### Mutex Pattern (Playbook Updates)
```typescript
class PlaybookLock {
private locks = new Map<string, Promise<void>>();
async withLock<T>(context_id: string, fn: () => Promise<T>): Promise<T> {
// Wait for existing lock
await this.locks.get(context_id);
// Create new lock
const lock = fn().finally(() => this.locks.delete(context_id));
this.locks.set(context_id, lock);
return lock;
}
}
```
**Usage**: Prevent concurrent playbook modifications
**Benefit**: Avoid race conditions in multi-user scenarios
## Docker Patterns (NEW)
### Multi-Stage Build Pattern
```dockerfile
# Stage 1: Build
FROM node:lts AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Runtime
FROM node:lts-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
```
**Pattern**: Separate build and runtime environments
**Benefit**: Smaller final image, faster deployments
### Docker Compose Service Pattern
```yaml
services:
ace-server:
build: .
volumes:
- contexts:/app/contexts
environment:
- LLM_PROVIDER=${LLM_PROVIDER}
networks:
- ace-network
ace-dashboard:
build: ./dashboard
ports:
- "3000:80"
depends_on:
- ace-server
networks:
- ace-network
volumes:
contexts:
networks:
ace-network:
```
**Pattern**: Service orchestration with shared resources
**Benefit**: One command to start entire stack
## Testing Patterns
### Mock LLM Pattern
```typescript
class MockLLMProvider implements LLMProvider {
async chat(messages: Message[]): Promise<string> {
return 'Mock response for testing';
}
async embed(text: string): Promise<number[]> {
return Array(384).fill(0); // Mock embedding
}
}
// Usage in tests
const generator = new Generator(new MockLLMProvider());
```
**Pattern**: Dependency injection for testing
**Benefit**: Fast tests without real API calls
### Fixture Pattern
```typescript
const testPlaybook: Playbook = {
context_id: 'test',
bullets: [
{ id: 'b1', content: 'Test strategy 1', helpful_count: 5 },
{ id: 'b2', content: 'Test strategy 2', helpful_count: 3 }
],
metadata: { /* ... */ }
};
describe('Deduplicator', () => {
it('should merge similar bullets', async () => {
const deduplicator = new Deduplicator(testPlaybook);
// Test with known data
});
});
```
**Pattern**: Reusable test data
**Benefit**: Consistent, maintainable tests
## Configuration Patterns
### Environment-Based Config (NEW)
```typescript
interface EnvironmentConfig {
development: Config;
production: Config;
test: Config;
}
function loadConfig(): Config {
const env = process.env.NODE_ENV || 'development';
return configs[env];
}
```
**Pattern**: Environment-aware configuration
**Benefit**: Different settings for dev/prod without code changes
### Validation Pattern
```typescript
import { z } from 'zod';
const ConfigSchema = z.object({
llmProvider: z.enum(['openai', 'lmstudio']),
openai: z.object({
apiKey: z.string().min(1),
model: z.string()
}).optional(),
lmstudio: z.object({
baseUrl: z.string().url(),
model: z.string()
}).optional()
});
function validateConfig(config: unknown): Config {
return ConfigSchema.parse(config);
}
```
**Pattern**: Schema-based validation
**Benefit**: Type-safe config with clear error messages
## Observability Patterns
### Structured Logging Pattern
```typescript
logger.info('ACE generation started', {
context_id: 'backend',
query_length: query.length,
timestamp: Date.now()
});
```
**Pattern**: Key-value logging
**Benefit**: Easy to parse, search, analyze
### Metrics Pattern (Future)
```typescript
metrics.histogram('ace.generation.duration', duration);
metrics.counter('ace.bullets.added').inc();
metrics.gauge('ace.playbook.size', bullets.length);
```
**Pattern**: Prometheus-style metrics
**Benefit**: Performance monitoring, alerting
## Common Anti-Patterns to Avoid
❌ **Full Context Rewrites**: Never replace entire playbook
✅ **Delta Updates**: Always use ADD/UPDATE/DELETE operations
❌ **Hardcoded Provider**: Don't couple to OpenAI
✅ **Provider Abstraction**: Use interface-based design
❌ **Shared State**: Don't share playbook references
✅ **Immutable Updates**: Clone before modifying
❌ **Synchronous I/O**: Don't use `fs.readFileSync`
✅ **Async Operations**: Always use `fs.promises`
❌ **Magic Numbers**: `if (similarity > 0.85)`
✅ **Named Constants**: `if (similarity > config.dedupThreshold)`
## Best Practices Summary
1. **Separation of Concerns**: ACE components are independent
2. **Dependency Injection**: Pass providers, don't create them
3. **Type Safety**: Use TypeScript strictly, no `any`
4. **Error Handling**: Use Result types or try-catch with logging
5. **Configuration**: Environment variables, not hardcoded values
6. **Testing**: Mock external dependencies, use fixtures
7. **Logging**: Structured logs with context
8. **Docker**: Multi-stage builds, health checks, volumes for data
9. **Documentation**: Code comments for "why", not "what"
10. **Performance**: Async by default, cache when appropriate