CODITECT Development Studio - Technical Design Document (TDD) v2.0
Version: 2.0.0
Date: 2026-01-31
Status: Draft
Changes: Unified persistent workspace, GCS FUSE storage, SQLite cluster, in-workspace multi-LLM coordination
1. Technical Overview
1.1 Architecture Evolution
| Aspect | v1.0 (Ephemeral) | v2.0 (Persistent) |
|---|---|---|
| Compute Model | 4 separate ephemeral sandboxes | 1 unified persistent workspace |
| Container Lifetime | 30-minute timeout | 8-hour renewable sessions |
| Storage | R2 snapshots | GCS FUSE mount + R2 mirror |
| State Management | Durable Objects only | SQLite cluster + JSONL |
| LLM Coordination | External routing | In-workspace orchestrator |
| Cold Start | 5-10 seconds | 0 seconds (always-warm) |
1.2 Technology Stack
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend | Next.js | 14.x | React framework with App Router |
| React | 18.x | UI library | |
| TypeScript | 5.x | Type safety | |
| Tailwind CSS | 3.x | Styling | |
| Zustand | 4.x | State management | |
| Socket.io Client | 4.x | WebSocket communication | |
| Edge | Cloudflare Workers | - | API Gateway |
| Durable Objects | - | Workspace registry & locks | |
| CDN + WAF | - | Edge caching & security | |
| Compute | Cloudflare Containers | - | Persistent workspaces |
| GKE Autopilot | - | Alternative k8s option | |
| Docker | 24.x | Container runtime | |
| Storage | GCS | - | Primary object storage |
| gcsfuse | 1.x | FUSE mount for containers | |
| R2 | - | Hot mirror cache | |
| Database | SQLite | 3.x | 6-database cluster |
| JSONL | - | Append-only session logs | |
| AI/ML | Multi-agent orchestrator | - | In-workspace coordination |
| Provider adapters | - | Claude, Gemini, Codex, Kimi | |
| DevOps | Wrangler | 3.x | Workers deployment |
| GitHub Actions | - | CI/CD | |
| Terraform | 1.x | Infrastructure as code |
2. System Interfaces
2.1 Core Type Definitions
// Tenant Context (unchanged)
interface TenantContext {
orgId: string;
teamId: string;
userId: string;
projectId?: string;
quotas: QuotaConfig;
permissions: string[];
}
// Workspace (v2.0 - replaces Session)
interface Workspace {
id: string;
tenant: TenantContext;
status: 'provisioning' | 'running' | 'hibernating' | 'hibernated' | 'terminating';
containerId: string;
websocketUrl: string;
gcsMountPath: string;
createdAt: Date;
expiresAt: Date;
lastActivityAt: Date;
agents: AgentState[];
databases: DatabaseConfig;
}
// Agent State (v2.0 - in-workspace)
interface AgentState {
id: 'claude' | 'gemini' | 'codex' | 'kimi';
status: 'idle' | 'loading' | 'executing' | 'waiting' | 'error';
currentTask?: Task;
lockedFiles: string[];
metrics: AgentMetrics;
}
// Task (v2.0)
interface Task {
id: string;
type: 'code' | 'analyze' | 'review' | 'test' | 'chat';
agent: AgentId;
prompt: string;
context?: {
files?: string[];
codebase?: boolean;
session?: boolean;
};
priority: number;
createdAt: Date;
}
// Database Cluster (v2.0)
interface DatabaseConfig {
sessions: SQLiteConnection;
messages: SQLiteConnection;
artifacts: SQLiteConnection;
parsedSessions: SQLiteConnection;
agentMetrics: SQLiteConnection;
workspaceIdx: SQLiteConnection;
}
// Chat Message (enhanced for multi-agent)
interface ChatMessage {
id: string;
workspaceId: string;
agentId: AgentId;
role: 'user' | 'assistant' | 'system' | 'tool';
content: string;
timestamp: Date;
metadata: {
tokens?: { input: number; output: number };
latency?: number;
cost?: number;
tools?: ToolCall[];
fileEdits?: FileEdit[];
};
}
// File with Lock (v2.0)
interface FileNode {
path: string;
name: string;
type: 'file' | 'directory' | 'symlink';
size?: number;
modifiedAt: Date;
lockedBy?: AgentId; // v2.0 - agent lock indicator
lockType?: 'read' | 'write';
children?: FileNode[];
}
// Session JSONL Event (v2.0)
interface SessionEvent {
timestamp: string;
eventType: 'file_edit' | 'agent_message' | 'tool_call' | 'checkpoint' | 'agent_state_change';
agentId: AgentId;
payload: unknown;
}
3. Frontend Architecture
3.1 Component Hierarchy (v2.0)
app/
├── layout.tsx # Root layout with providers
├── page.tsx # Main workspace view
├── viz/
│ └── page.tsx # Fullscreen visualization
├── api/
│ └── [...route]/
│ └── route.ts # API proxy
│
├── components/
│ ├── layout/
│ │ ├── app-shell.tsx # Main app container
│ │ ├── sidebar.tsx # Navigation + agent status
│ │ └── header.tsx # Top header with workspace info
│ │
│ ├── chat/
│ │ ├── chat-panel.tsx # Main chat container
│ │ ├── message-list.tsx # Scrollable messages
│ │ ├── message-item.tsx # Individual message with agent attribution
│ │ ├── chat-input.tsx # Message input with agent targeting
│ │ ├── agent-selector.tsx # Multi-agent picker
│ │ └── agent-activity.tsx # Real-time agent status panel (v2.0)
│ │
│ ├── workspace/
│ │ ├── file-tree.tsx # Explorer with lock indicators (v2.0)
│ │ ├── file-editor.tsx # Monaco/CodeMirror
│ │ ├── agent-panel.tsx # Agent status dashboard (v2.0)
│ │ ├── timeline.tsx # Session replay from SQLite (v2.0)
│ │ └── terminal.tsx # Shared terminal
│ │
│ ├── visualization/
│ │ ├── viz-panel.tsx # Visualization container
│ │ ├── workflow-view.tsx # Mermaid diagrams
│ │ ├── architecture-view.tsx # C4 models
│ │ └── cost-dashboard.tsx # Live cost tracking (v2.0)
│ │
│ └── shared/
│ ├── loading.tsx # Loading states
│ ├── error-boundary.tsx # Error handling
│ └── theme-toggle.tsx # Dark/light mode
│
├── lib/
│ ├── store.ts # Zustand store
│ ├── api.ts # API client
│ ├── websocket.ts # WebSocket manager
│ ├── sqlite-client.ts # SQLite browser client (v2.0)
│ └── utils.ts # Utilities
│
└── hooks/
├── use-workspace.ts # Workspace management (v2.0)
├── use-agents.ts # Multi-agent state (v2.0)
├── use-sqlite.ts # SQLite queries (v2.0)
└── use-websocket.ts # Real-time connection
3.2 State Management (v2.0)
// store.ts - v2.0 with multi-agent support
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface WorkspaceState {
// Auth State
user: User | null;
token: string | null;
isAuthenticated: boolean;
// Workspace State (v2.0)
workspace: Workspace | null;
workspaces: Workspace[];
connectionStatus: 'connected' | 'disconnected' | 'reconnecting';
// Multi-Agent State (v2.0)
agents: AgentState[];
activeAgent: AgentId | null;
taskQueue: Task[];
// File State with Locks (v2.0)
fileTree: FileNode | null;
openFiles: string[];
activeFile: string | null;
lockedFiles: Map<string, AgentId>;
// UI State
sidebarOpen: boolean;
activeTab: 'chat' | 'files' | 'agents' | 'timeline';
theme: 'dark' | 'light';
// Chat State
messages: ChatMessage[];
isStreaming: boolean;
// Actions
setWorkspace: (workspace: Workspace) => void;
updateAgentState: (agentId: AgentId, state: Partial<AgentState>) => void;
setFileLock: (filePath: string, agentId: AgentId | null) => void;
addTask: (task: Task) => void;
completeTask: (taskId: string) => void;
}
export const useWorkspaceStore = create<WorkspaceState>()(
persist(
(set, get) => ({
// Initial state...
// v2.0 Actions
updateAgentState: (agentId, state) => set((s) => ({
agents: s.agents.map(a =>
a.id === agentId ? { ...a, ...state } : a
)
})),
setFileLock: (filePath, agentId) => set((s) => {
const locks = new Map(s.lockedFiles);
if (agentId) locks.set(filePath, agentId);
else locks.delete(filePath);
return { lockedFiles: locks };
}),
addTask: (task) => set((s) => ({
taskQueue: [...s.taskQueue, task]
})),
}),
{
name: 'coditect-workspace-v2',
}
)
);
4. Backend Architecture
4.1 Worker API Gateway
// src/worker.ts - v2.0 with workspace management
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { authMiddleware } from './middleware/auth';
import { tenantMiddleware } from './middleware/tenant';
import { authRouter } from './routes/auth';
import { workspacesRouter } from './routes/workspaces'; // v2.0
import { agentsRouter } from './routes/agents'; // v2.0
import { chatRouter } from './routes/chat';
import { filesRouter } from './routes/files';
const app = new Hono<{ Bindings: Env }>();
app.use('*', cors({
origin: ['https://studio.coditect.dev', 'http://localhost:3000'],
credentials: true,
}));
// v2.0: Workspace lifecycle routes
app.route('/auth', authRouter);
app.route('/api/v1/workspaces', workspacesRouter);
app.route('/api/v1/agents', agentsRouter);
app.route('/api/v1/chat', chatRouter);
app.route('/api/v1/files', filesRouter);
export default app;
4.2 Workspace Management (Durable Object v2.0)
// src/durable-objects/workspace-registry.ts
export class WorkspaceRegistry implements DurableObject {
private state: DurableObjectState;
private workspaces: Map<string, WorkspaceState> = new Map();
constructor(state: DurableObjectState) {
this.state = state;
this.state.blockConcurrencyWhile(async () => {
const stored = await this.state.storage.list<WorkspaceState>();
stored.forEach((value, key) => this.workspaces.set(key, value));
});
}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case '/provision':
return this.provisionWorkspace(request);
case '/heartbeat':
return this.workspaceHeartbeat(request);
case '/hibernate':
return this.hibernateWorkspace(request);
case '/get':
return this.getWorkspace(request);
default:
return new Response('Not found', { status: 404 });
}
}
private async provisionWorkspace(request: Request): Promise<Response> {
const config: WorkspaceConfig = await request.json();
const workspaceId = crypto.randomUUID();
// v2.0: Provision persistent container with GCS mount
const workspace: WorkspaceState = {
id: workspaceId,
tenant: config.tenant,
status: 'provisioning',
gcsPath: `organizations/${config.tenant.orgId}/workspaces/${workspaceId}`,
createdAt: Date.now(),
};
this.workspaces.set(workspaceId, workspace);
await this.state.storage.put(workspaceId, workspace);
// Trigger async container provisioning
this.state.waitUntil(this.spawnContainer(workspaceId, config));
return Response.json({ workspaceId, status: 'provisioning' });
}
private async spawnContainer(workspaceId: string, config: WorkspaceConfig): Promise<void> {
// v2.0: Call Cloudflare Containers API or GKE
const containerConfig = {
image: 'coditect/workspace:v2.0',
resources: { cpu: 2, memory: '4Gi' },
env: {
GCS_BUCKET: 'coditect-workspaces',
GCS_MOUNT_PATH: `/home/developer/projects`,
WORKSPACE_ID: workspaceId,
SQLITE_PATH: `/home/developer/.coditect/databases`,
},
volumes: [{
name: 'gcs-fuse',
gcs: { bucket: 'coditect-workspaces', path: config.tenant.orgId }
}]
};
// Provision and wait for ready
const container = await provisionContainer(containerConfig);
// Update workspace state
const workspace = this.workspaces.get(workspaceId)!;
workspace.status = 'running';
workspace.containerId = container.id;
workspace.websocketUrl = container.wsUrl;
await this.state.storage.put(workspaceId, workspace);
// Initialize SQLite databases
await initializeDatabaseCluster(workspaceId, container);
}
}
4.3 SQLite Database Cluster (v2.0)
// src/databases/cluster.ts
import Database from 'better-sqlite3';
export class DatabaseCluster {
private databases: Map<string, Database> = new Map();
private gcsSyncInterval: NodeJS.Timer;
constructor(private workspaceId: string, private dbPath: string) {
this.initializeDatabases();
this.startGcsSync();
}
private initializeDatabases() {
// 6-database cluster per workspace
const dbConfigs = [
{ name: 'sessions', schema: sessionsSchema },
{ name: 'messages', schema: messagesSchema },
{ name: 'artifacts', schema: artifactsSchema },
{ name: 'parsed_sessions', schema: parsedSessionsSchema },
{ name: 'agent_metrics', schema: agentMetricsSchema },
{ name: 'workspace_idx', schema: workspaceIdxSchema },
];
for (const config of dbConfigs) {
const dbPath = `${this.dbPath}/${config.name}.db`;
const db = new Database(dbPath);
db.exec(config.schema);
db.pragma('journal_mode = WAL'); // Write-Ahead Logging for performance
this.databases.set(config.name, db);
}
}
private startGcsSync() {
// Sync WAL files to GCS every 30 seconds
this.gcsSyncInterval = setInterval(async () => {
for (const [name, db] of this.databases) {
const walPath = `${this.dbPath}/${name}.db-wal`;
await syncToGCS(walPath, `workspaces/${this.workspaceId}/databases/`);
}
}, 30000);
}
// Query interfaces
get sessions() { return this.databases.get('sessions')!; }
get messages() { return this.databases.get('messages')!; }
get artifacts() { return this.databases.get('artifacts')!; }
get parsedSessions() { return this.databases.get('parsed_sessions')!; }
get agentMetrics() { return this.databases.get('agent_metrics')!; }
get workspaceIdx() { return this.databases.get('workspace_idx')!; }
}
// Database Schemas
const sessionsSchema = `
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL,
agent_id TEXT,
status TEXT,
context_json TEXT,
checkpoint_ref TEXT,
created_at INTEGER,
updated_at INTEGER
);
CREATE INDEX idx_workspace ON sessions(workspace_id);
`;
const messagesSchema = `
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
session_id TEXT,
agent_id TEXT,
role TEXT,
content TEXT,
tokens_input INTEGER,
tokens_output INTEGER,
timestamp INTEGER,
FOREIGN KEY (session_id) REFERENCES sessions(id)
);
CREATE INDEX idx_session_agent ON messages(session_id, agent_id);
`;
// ... (additional schemas for artifacts, parsed_sessions, etc.)
4.4 Session JSONL Archive (v2.0)
// src/storage/session-archive.ts
import { createWriteStream } from 'fs';
import { appendFile } from 'fs/promises';
export class SessionJsonlArchive {
private archivePath: string;
private buffer: SessionEvent[] = [];
private flushInterval: NodeJS.Timer;
constructor(workspaceId: string) {
this.archivePath = `/home/developer/.coditect/sessions/${workspaceId}.jsonl`;
this.flushInterval = setInterval(() => this.flush(), 5000);
}
append(event: SessionEvent) {
this.buffer.push(event);
if (this.buffer.length >= 100) {
this.flush();
}
}
private async flush() {
if (this.buffer.length === 0) return;
const lines = this.buffer.map(e => JSON.stringify(e)).join('\n') + '\n';
await appendFile(this.archivePath, lines);
this.buffer = [];
// Sync to GCS
await syncToGCS(this.archivePath, `workspaces/${this.workspaceId}/sessions/`);
}
async *readStream(sessionId?: string): AsyncGenerator<SessionEvent> {
const fileStream = createReadStream(this.archivePath);
const rl = createInterface(fileStream);
for await (const line of rl) {
const event: SessionEvent = JSON.parse(line);
if (!sessionId || event.sessionId === sessionId) {
yield event;
}
}
}
}
5. In-Workspace Agent Orchestrator (v2.0)
5.1 Agent Coordinator
// src/agents/coordinator.ts
export class AgentCoordinator {
private agents: Map<AgentId, BaseAgent> = new Map();
private taskQueue: TaskQueue;
private lockManager: LockManager;
private messageBus: MessageBus;
constructor(private workspace: Workspace) {
this.taskQueue = new TaskQueue();
this.lockManager = new LockManager();
this.messageBus = new MessageBus();
// Initialize all 4 agents
this.agents.set('claude', new ClaudeAgent());
this.agents.set('gemini', new GeminiAgent());
this.agents.set('codex', new CodexAgent());
this.agents.set('kimi', new KimiAgent());
}
async submitTask(task: Task): Promise<void> {
// Add to queue
this.taskQueue.enqueue(task);
// Notify target agent
const agent = this.agents.get(task.agent);
if (agent && agent.status === 'idle') {
await this.executeTask(task);
}
}
private async executeTask(task: Task): Promise<void> {
const agent = this.agents.get(task.agent)!;
// Acquire file locks if needed
if (task.context?.files) {
for (const file of task.context.files) {
await this.lockManager.acquire(file, task.agent, 'write');
}
}
// Update agent state
agent.status = 'executing';
agent.currentTask = task;
// Broadcast state change
this.messageBus.broadcast({
type: 'agent_state_change',
agent: task.agent,
state: 'executing',
task: task.id
});
try {
// Execute
const result = await agent.execute(task);
// Log to SQLite
await this.workspace.databases.agentMetrics.run(
'INSERT INTO tasks (id, agent_id, type, status, duration) VALUES (?, ?, ?, ?, ?)',
[task.id, task.agent, task.type, 'completed', Date.now() - task.createdAt.getTime()]
);
// Archive to JSONL
this.workspace.archive.append({
timestamp: new Date().toISOString(),
eventType: 'task_complete',
agentId: task.agent,
payload: { taskId: task.id, result }
});
} finally {
// Release locks
if (task.context?.files) {
for (const file of task.context.files) {
await this.lockManager.release(file, task.agent);
}
}
agent.status = 'idle';
agent.currentTask = undefined;
// Process next task in queue
const nextTask = this.taskQueue.dequeue(task.agent);
if (nextTask) {
await this.executeTask(nextTask);
}
}
}
}
// Lock Manager
class LockManager {
private locks: Map<string, { agent: AgentId; type: 'read' | 'write' }> = new Map();
async acquire(file: string, agent: AgentId, type: 'read' | 'write'): Promise<boolean> {
const existing = this.locks.get(file);
if (existing) {
if (existing.type === 'write' || type === 'write') {
return false; // Cannot acquire
}
}
this.locks.set(file, { agent, type });
return true;
}
async release(file: string, agent: AgentId): Promise<void> {
const lock = this.locks.get(file);
if (lock && lock.agent === agent) {
this.locks.delete(file);
}
}
}
6. Deployment Architecture
6.1 Infrastructure (Terraform)
# main.tf - v2.0 infrastructure
# GCS Bucket for workspace storage
resource "google_storage_bucket" "workspaces" {
name = "coditect-workspaces"
location = "US"
lifecycle_rule {
condition { age = 30 }
action { type = "SetStorageClass", storage_class = "NEARLINE" }
}
}
# Cloudflare R2 Mirror
resource "cloudflare_r2_bucket" "mirror" {
account_id = var.cloudflare_account_id
name = "coditect-workspace-mirror"
}
# GKE Autopilot (alternative to Cloudflare Containers)
resource "google_container_cluster" "workspaces" {
name = "coditect-workspaces"
location = "us-central1"
enable_autopilot = true
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
}
# Service Account for GCS FUSE
resource "google_service_account" "workspace" {
account_id = "coditect-workspace"
display_name = "CODITECT Workspace Service Account"
}
resource "google_storage_bucket_iam_member" "workspace_access" {
bucket = google_storage_bucket.workspaces.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.workspace.email}"
}
7. Migration Guide (v1.0 → v2.0)
7.1 Data Migration
// migration/v1-to-v2.ts
async function migrateSessionToWorkspace(sessionId: string): Promise<void> {
// 1. Read v1.0 session from R2
const v1Session = await r2.get(`sessions/${sessionId}`);
// 2. Create v2.0 workspace
const workspace = await createWorkspace({
tenant: v1Session.tenant,
gcsPath: `migrated/${sessionId}`,
});
// 3. Copy files from R2 to GCS
await migrateFiles(v1Session.filePrefix, workspace.gcsPath);
// 4. Convert session logs to SQLite + JSONL
await migrateSessionLogs(sessionId, workspace.id);
// 5. Update DNS/URLs
await updateRouting(sessionId, workspace.id);
}
7.2 Breaking Changes
| Component | v1.0 | v2.0 | Migration |
|---|---|---|---|
| Session API | /sessions/* | /workspaces/* | URL rewrite |
| State Store | Durable Objects only | SQLite + JSONL | Data export/import |
| File Storage | R2 snapshots | GCS FUSE mount | rclone sync |
| WebSocket | Per-session | Per-workspace | Reconnection logic |
| Agent Routing | External | In-workspace | Architecture change |
Document Status: Draft - Ready for review
Next Steps: Implement prototype, load testing, cost validation