Skip to main content

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

Aspectv1.0 (Ephemeral)v2.0 (Persistent)
Compute Model4 separate ephemeral sandboxes1 unified persistent workspace
Container Lifetime30-minute timeout8-hour renewable sessions
StorageR2 snapshotsGCS FUSE mount + R2 mirror
State ManagementDurable Objects onlySQLite cluster + JSONL
LLM CoordinationExternal routingIn-workspace orchestrator
Cold Start5-10 seconds0 seconds (always-warm)

1.2 Technology Stack

LayerTechnologyVersionPurpose
FrontendNext.js14.xReact framework with App Router
React18.xUI library
TypeScript5.xType safety
Tailwind CSS3.xStyling
Zustand4.xState management
Socket.io Client4.xWebSocket communication
EdgeCloudflare Workers-API Gateway
Durable Objects-Workspace registry & locks
CDN + WAF-Edge caching & security
ComputeCloudflare Containers-Persistent workspaces
GKE Autopilot-Alternative k8s option
Docker24.xContainer runtime
StorageGCS-Primary object storage
gcsfuse1.xFUSE mount for containers
R2-Hot mirror cache
DatabaseSQLite3.x6-database cluster
JSONL-Append-only session logs
AI/MLMulti-agent orchestrator-In-workspace coordination
Provider adapters-Claude, Gemini, Codex, Kimi
DevOpsWrangler3.xWorkers deployment
GitHub Actions-CI/CD
Terraform1.xInfrastructure 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

Componentv1.0v2.0Migration
Session API/sessions/*/workspaces/*URL rewrite
State StoreDurable Objects onlySQLite + JSONLData export/import
File StorageR2 snapshotsGCS FUSE mountrclone sync
WebSocketPer-sessionPer-workspaceReconnection logic
Agent RoutingExternalIn-workspaceArchitecture change

Document Status: Draft - Ready for review
Next Steps: Implement prototype, load testing, cost validation