| name | twinmind-reference-architecture |
| description | Implement TwinMind reference architecture with best-practice project layout.
Use when designing new TwinMind integrations, reviewing project structure,
or establishing architecture standards for meeting AI applications.
Trigger with phrases like "twinmind architecture", "twinmind best practices",
"twinmind project structure", "how to organize twinmind", "twinmind layout".
|
| allowed-tools | Read, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
TwinMind Reference Architecture
Overview
Production-ready architecture patterns for TwinMind meeting AI integrations.
Prerequisites
- Understanding of layered architecture
- TwinMind API knowledge
- TypeScript project setup
- Testing framework configured
Project Structure
my-twinmind-project/
├── src/
│ ├── twinmind/
│ │ ├── client.ts # Singleton client wrapper
│ │ ├── config.ts # Environment configuration
│ │ ├── types.ts # TypeScript types
│ │ ├── errors.ts # Custom error classes
│ │ └── handlers/
│ │ ├── webhooks.ts # Webhook handlers
│ │ └── events.ts # Event processing
│ ├── services/
│ │ └── meeting/
│ │ ├── index.ts # Service facade
│ │ ├── transcription.ts # Transcription service
│ │ ├── summary.ts # Summary generation
│ │ ├── actions.ts # Action item extraction
│ │ └── cache.ts # Caching layer
│ ├── integrations/
│ │ ├── calendar/ # Calendar sync
│ │ ├── slack/ # Slack notifications
│ │ ├── linear/ # Task management
│ │ └── email/ # Follow-up emails
│ ├── api/
│ │ ├── routes/
│ │ │ ├── meetings.ts # Meeting endpoints
│ │ │ ├── transcripts.ts # Transcript endpoints
│ │ │ └── webhooks.ts # Webhook endpoint
│ │ └── middleware/
│ │ ├── auth.ts # Authentication
│ │ ├── rateLimit.ts # Rate limiting
│ │ └── validation.ts # Request validation
│ ├── jobs/
│ │ ├── sync.ts # Background sync job
│ │ ├── cleanup.ts # Data cleanup job
│ │ └── reports.ts # Report generation
│ └── utils/
│ ├── audio.ts # Audio processing
│ ├── logging.ts # Structured logging
│ └── metrics.ts # Prometheus metrics
├── tests/
│ ├── unit/
│ │ └── twinmind/
│ ├── integration/
│ │ └── twinmind/
│ └── e2e/
│ └── meeting-flow.test.ts
├── config/
│ ├── twinmind.development.json
│ ├── twinmind.staging.json
│ └── twinmind.production.json
└── docs/
├── ARCHITECTURE.md
└── RUNBOOK.md
Layer Architecture
┌─────────────────────────────────────────────────────┐
│ API Layer │
│ (Controllers, Routes, Webhooks) │
├─────────────────────────────────────────────────────┤
│ Service Layer │
│ (Business Logic, Orchestration) │
├─────────────────────────────────────────────────────┤
│ TwinMind Layer │
│ (Client, Types, Error Handling) │
├─────────────────────────────────────────────────────┤
│ Integration Layer │
│ (Calendar, Slack, Linear, Email) │
├─────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (Cache, Queue, Monitoring) │
└─────────────────────────────────────────────────────┘
Key Components
Step 1: Client Wrapper
import axios, { AxiosInstance } from 'axios';
import { TwinMindConfig, loadConfig } from './config';
import { TranscriptCache } from '../services/meeting/cache';
import { MetricsCollector } from '../utils/metrics';
export class TwinMindService {
private client: AxiosInstance;
private cache: TranscriptCache;
private metrics: MetricsCollector;
private config: TwinMindConfig;
constructor(config?: TwinMindConfig) {
this.config = config || loadConfig();
this.client = axios.create({
baseURL: this.config.baseUrl,
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
: ,
},
: ..,
});
. = (..);
. = ();
.();
}
(): {
....( {
..(, { : config. });
config;
});
....(
{
..(
,
response..?.
);
response;
},
{
..(, {
: error.?. || ,
});
error;
}
);
}
(: , ?: ): <> {
..(
,
..(
,
..(, { : audioUrl, ...options })
)
);
}
(: ): <> {
..(
,
..(, { : transcriptId })
);
}
(: , ?: ): <[]> {
..(, { : { : query, ...options } });
}
}
: | = ;
(): {
(!instance) {
instance = ();
}
instance;
}
Step 2: Service Layer
import { getTwinMindService } from '../../twinmind/client';
import { TranscriptionService } from './transcription';
import { SummaryService } from './summary';
import { ActionItemService } from './actions';
import { CalendarIntegration } from '../../integrations/calendar';
import { SlackIntegration } from '../../integrations/slack';
export interface MeetingResult {
transcriptId: string;
transcript: Transcript;
summary: Summary;
actionItems: ActionItem[];
participants: Participant[];
}
export class MeetingService {
private twinmind = getTwinMindService();
private transcription = new TranscriptionService();
private summaryService = new SummaryService();
private actionService = new ();
calendar = ();
slack = ();
(
: ,
: = {}
): <> {
calendarEvent = options.
? ..(options.)
: ;
transcript = ..(audioUrl, {
: calendarEvent?. || options.,
: calendarEvent?.,
});
[summary, actionItems] = .([
..(transcript.),
..(transcript.),
]);
participants = .(
transcript,
calendarEvent?.
);
(options.) {
..({
: transcript.,
: summary.,
actionItems,
});
}
{
: transcript.,
transcript,
summary,
actionItems,
participants,
};
}
(
: ,
?: []
): <[]> {
speakers = transcript. || [];
speakers.( ({
: speaker.,
: attendees?.[index] || speaker. || ,
: .(transcript., speaker.),
}));
}
(: [], : ): {
segments
.( s. === speakerId)
.( total + (s. - s.), );
}
}
Step 3: Error Boundary
export class TwinMindError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode?: number,
public readonly retryable: boolean = false,
public readonly originalError?: Error
) {
super(message);
this.name = 'TwinMindError';
}
static fromApiError(error: any): TwinMindError {
const status = error.response?.status;
const message = error.response?.data?.message || error.message;
const code = error.response?.data?.code || 'UNKNOWN';
switch (status) {
case 401:
return (message, , status, );
:
(message, , status, );
:
:
:
(message, , status, );
:
(message, code, status, , error);
}
}
}
wrapWithErrorHandling<T>(
: <T>
): <T> {
().( {
.(error);
});
}
Step 4: Health Check
export interface HealthStatus {
status: 'healthy' | 'degraded' | 'unhealthy';
checks: HealthCheck[];
timestamp: Date;
}
export interface HealthCheck {
name: string;
status: 'pass' | 'warn' | 'fail';
latencyMs?: number;
message?: string;
}
export async function checkHealth(): Promise<HealthStatus> {
const checks: HealthCheck[] = [];
const apiCheck = await checkApiHealth();
checks.push(apiCheck);
const cacheCheck = await checkCacheHealth();
checks.push(cacheCheck);
const dbCheck = await checkDatabaseHealth();
checks.push(dbCheck);
hasFailure = checks.( c. === );
hasWarning = checks.( c. === );
{
: hasFailure ? : hasWarning ? : ,
checks,
: (),
};
}
(): <> {
start = .();
{
service = ();
service.();
{
: ,
: ,
: .() - start,
};
} (: ) {
{
: ,
: ,
: .() - start,
: error.,
};
}
}
Data Flow Diagram
┌───────────────┐
│ Calendar │
│ (Google) │
└───────┬───────┘
│ sync
┌──────────┐ ┌────────▼────────┐
│ Client │─────request────►│ API Gateway │
│ App │◄────response────│ │
└──────────┘ └────────┬────────┘
│
┌────────▼────────┐
│ Meeting │
│ Service │
└────────┬────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
│ Transcription │ │ Summary │ │ Action Items │
│ Service │ │ Service │ │ Service │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
┌────────▼────────┐
│ TwinMind │
│ API │
└────────┬────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
│ Slack │ │ Linear │ │ Email │
│ (notifications)│ │ (tasks) │ │ (follow-ups) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Configuration Management
export interface TwinMindConfig {
apiKey: string;
baseUrl: string;
environment: 'development' | 'staging' | 'production';
timeout: number;
retries: number;
cacheOptions: {
enabled: boolean;
ttlSeconds: number;
};
features: {
diarization: boolean;
autoSummary: boolean;
actionItemExtraction: boolean;
};
}
export function loadConfig(): TwinMindConfig {
const env = process.env.NODE_ENV || 'development';
const envConfig = require(`./twinmind.${env}.json`);
return {
apiKey: process.env.TWINMIND_API_KEY!,
baseUrl: process.env.TWINMIND_API_URL || 'https://api.twinmind.com/v1',
environment: env as any,
: (process.. || ),
: (process.. || ),
...envConfig,
};
}
Output
- Structured project layout
- Client wrapper with caching and metrics
- Service layer with business logic
- Error boundary implemented
- Health checks configured
- Configuration management
Error Handling
| Issue | Cause | Solution |
|---|
| Circular dependencies | Wrong layering | Separate concerns by layer |
| Config not loading | Wrong paths | Verify config file locations |
| Type errors | Missing types | Add TwinMind types |
| Test isolation | Shared state | Use dependency injection |
Resources
Flagship Skills
For multi-environment setup, see twinmind-multi-env-setup.