Skip to main content

FoundationDB Implementation Patterns for V5

Created: 2025-10-14 Purpose: Extract V4 FoundationDB patterns and adapt for V5 persistent workspace sessions with cross-session intelligence Source: V4 DATABASE-MODELS (SESSION, TENANT, CONVERSATION, USER)


🎯 V5 Requirements vs V4 Patterns

Requirements

  1. Full DB Separation: Complete tenant isolation at database level
  2. Persistent Sessions: workspace state survives restarts (files, editor, terminal, conversations)
  3. Session-to-Session Advice: Cross-session queries, relationships, and intelligence

Key Insight: V5 Needs TWO Session Types

V4 has: Auth Sessions (JWT tokens) V5 needs: Auth Sessions + workspace Sessions (new concept)

┌─────────────────────────────────────────────────────┐
│ V5 Session Architecture │
├─────────────────────────────────────────────────────┤
│ │
│ AuthSession workspaceSession │
│ ├─ JWT tokens ├─ workspace_path │
│ ├─ token_family ├─ active_files │
│ ├─ refresh_token ├─ editor_state │
│ ├─ expires_at ├─ terminal_state │
│ └─ ip_address ├─ conversation_id │
│ ├─ parent_session_id │
│ (handles auth) └─ related_sessions[] │
│ │
│ (handles persistence) │
└─────────────────────────────────────────────────────┘

📋 Pattern 1: Multi-Tenant Key Structure

V4 Pattern (from all models)

{tenant_id}/{entity_type}/{entity_id}

Examples from V4:

# Users
/{tenant_id}/users/{user_id}

# Sessions (auth)
/{tenant_id}/sessions/{session_id}

# Conversations
/{tenant_id}/conversations/{conversation_id}

# Audit logs
/{tenant_id}/audit/{audit_id}

V5 Adaptation

Use full tenant isolation for:

# Core entities
/{tenant_id}/users/{user_id}
/{tenant_id}/tenants/{tenant_id} # Self-reference

# Auth sessions (V4 pattern)
/{tenant_id}/auth_sessions/{session_id}

# workspace sessions (NEW for V5)
/{tenant_id}/workspace_sessions/{session_id}

# Conversations (V4 pattern)
/{tenant_id}/conversations/{conversation_id}

# Audit logs
/{tenant_id}/audit/{audit_id}

Why tenant prefix?

  • Complete data isolation
  • Efficient range scans: /{tenant_id}/* gets all tenant data
  • Multi-region replication by tenant
  • Tenant deletion = single range clear

📋 Pattern 2: Secondary Indexes

V4 Pattern

# Primary
/{tenant_id}/sessions/{session_id} -> Session

# Secondary indexes
/{tenant_id}/sessions_by_user/{user_id} -> [session_ids]
/{tenant_id}/sessions_by_family/{token_family} -> [session_ids]
/{tenant_id}/active_sessions -> [session_ids]

V5 workspace Session Indexes

# Primary
/{tenant_id}/workspace_sessions/{session_id} -> workspaceSession

# By user
/{tenant_id}/workspace_sessions_by_user/{user_id} -> [session_ids]

# By workspace path (for finding existing sessions)
/{tenant_id}/sessions_by_workspace/{workspace_path} -> [session_ids]

# Recent sessions (for quick access)
/{tenant_id}/recent_sessions/{user_id} -> [session_ids] # sorted by last_accessed

# Session relationships (for cross-session queries)
/{tenant_id}/session_children/{parent_session_id} -> [child_session_ids]
/{tenant_id}/session_related/{session_id} -> [related_session_ids]

# By conversation (link sessions to AI chat)
/{tenant_id}/sessions_by_conversation/{conversation_id} -> [session_ids]

📋 Pattern 3: JWT Token Family System

V4 Pattern (from SESSION-MODEL)

pub struct AuthSession {
pub session_id: Uuid,
pub user_id: Uuid,
pub tenant_id: Uuid,
pub token_family: Uuid, // Links all tokens in refresh chain
pub created_at: DateTime<Utc>,
pub last_accessed: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
pub refresh_expires_at: Option<DateTime<Utc>>,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub is_active: bool,
}

Token Rotation Flow:

  1. Login → Create token_family UUID
  2. Issue access token (1 hour) + refresh token (7 days)
  3. Refresh → New tokens with same token_family
  4. Old tokens blacklisted
  5. Logout → Blacklist entire token_family

V5 Usage

Keep V4 AuthSession pattern unchanged for authentication.

Add link to workspaceSession:

pub struct AuthSession {
// ... V4 fields ...
pub workspace_session_id: Option<Uuid>, // Link to active workspace
}

📋 Pattern 4: workspace Session (NEW for V5)

Extended from V4 Session + Conversation

pub struct workspaceSession {
// Identity (V4 pattern)
pub session_id: Uuid,
pub tenant_id: Uuid,
pub user_id: Uuid,
pub name: String, // "Backend API Refactor"

// workspace State (PERSISTENT - NEW)
pub workspace_path: String, // "/projects/my-app"
pub active_files: Vec<String>, // ["src/main.rs", "cargo.toml"]
pub editor_state: editorState, // Cursor positions, tabs, selections
pub terminal_state: terminalState, // Command history, cwd
pub breakpoints: Vec<Breakpoint>, // Debug breakpoints
pub watch_expressions: Vec<String>, // Debug watches

// Conversation Context (from V4 CONVERSATION)
pub conversation_id: Uuid, // Link to Conversation model
pub context_files: Vec<String>, // Files in AI context
pub agent_memory: SessionMemory, // Agent's memory for this session

// Session Relationships (CROSS-SESSION - NEW)
pub parent_session_id: Option<Uuid>, // Forked from?
pub related_sessions: Vec<Uuid>, // Related work sessions
pub tags: Vec<String>, // "backend", "auth", "refactor"

// Metadata
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_accessed_at: DateTime<Utc>,
pub is_active: bool,
}

// Supporting structs
pub struct editorState {
pub open_tabs: Vec<editorTab>,
pub active_tab_index: usize,
pub layout: String, // "split-horizontal", "split-vertical", etc.
}

pub struct editorTab {
pub file_path: String,
pub cursor_line: u32,
pub cursor_column: u32,
pub scroll_position: u32,
pub selections: Vec<Selection>,
}

pub struct terminalState {
pub cwd: String,
pub command_history: Vec<String>, // Last 100 commands
pub environment_vars: HashMap<String, String>,
}

pub struct SessionMemory {
pub facts: Vec<String>, // Things agent learned
pub decisions: Vec<Decision>, // Decisions made
pub context_summary: String, // Auto-generated summary
}

📋 Pattern 5: Cross-Session Intelligence (NEW for V5)

Use Case: "Based on my last backend session, help me with..."

Query Pattern:

// Get user's recent backend sessions
async fn get_related_sessions(
tenant_id: &Uuid,
user_id: &Uuid,
tags: &[String],
limit: usize
) -> Result<Vec<workspaceSession>> {
// Scan: /{tenant_id}/workspace_sessions_by_user/{user_id}
// Filter: tags contain any of tags
// Sort: by last_accessed_at desc
// Limit: limit
}

// Build cross-session context for llm
async fn build_cross_session_context(
current_session: &workspaceSession,
related_sessions: &[workspaceSession]
) -> Result<CrossSessionContext> {
let mut context = CrossSessionContext::new();

// Current session facts
context.add_facts(&current_session.agent_memory.facts);

// Related session decisions
for session in related_sessions {
context.add_decisions(&session.agent_memory.decisions);
context.add_files(&session.active_files);
}

// Conversation history from all sessions
for session in related_sessions {
let conversation = get_conversation(&session.conversation_id).await?;
context.add_conversation_summary(conversation.summary);
}

Ok(context)
}

Relationship Types

1. Parent-Child (Forked Sessions)

// User forks session to try different approach
let child_session = current_session.fork("Try async approach");
child_session.parent_session_id = Some(current_session.session_id);

// Later: "Show me what I tried in the other approach"
let parent = get_session(&child_session.parent_session_id.unwrap()).await?;

2. Related Sessions (Same Topic)

// Auto-link sessions with similar tags/workspace
let related = find_sessions_by_tags(
tenant_id,
user_id,
&["backend", "auth"]
).await?;

current_session.related_sessions = related.iter().map(|s| s.session_id).collect();

3. Conversation-Linked Sessions

// Multiple sessions discussing same problem
let sessions = get_sessions_by_conversation(tenant_id, conversation_id).await?;
// "We discussed this in 3 different sessions, here's what we learned..."

📋 Pattern 6: Tenant Model (V4 Full Pattern)

From V4 TENANT-MODEL

pub struct Tenant {
pub tenant_id: Uuid,
pub name: String, // "Acme Corp" or "John's workspace"
pub domain: Option<String>, // Custom domain
pub plan: TenantPlan, // Free/Starter/Pro/Enterprise
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub settings: TenantSettings,
pub metadata: Option<serde_json::Value>,
}

pub enum TenantPlan {
Free, // 5 users, 10 workspaces
Starter, // 10 users, 25 workspaces
Professional, // 50 users, 100 workspaces
Enterprise // Unlimited
}

pub struct TenantSettings {
pub max_users: i32, // -1 = unlimited
pub max_workspaces: i32,
pub max_agents: i32,
pub features: Vec<String>, // ["basic", "api", "advanced"]
}

V5 Usage

Use full V4 pattern for multi-tenant B2B architecture.

Add workspace limits:

pub struct TenantSettings {
// V4 fields
pub max_users: i32,
pub max_agents: i32,

// V5 workspace limits
pub max_workspace_sessions: i32,
pub max_storage_gb: i32,
pub features: Vec<String>,
}

📋 Pattern 7: Conversation Model (V4 Pattern + Enhancements)

From V4 CONVERSATION-MODEL

pub struct Conversation {
pub id: Uuid,
pub tenant_id: Uuid,
pub user_id: Uuid,
pub session_id: String, // V4: generic session_id
pub ai_provider: String, // "claude", "gemini", etc.
pub title: String, // Auto-generated or user-set
pub description: Option<String>,
pub tags: Vec<String>,
pub filename: String, // Export filename
pub path: String, // GCS/S3 path
pub status: ConversationStatus, // Active/Archived/Deleted
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub message_count: Option<u32>,
pub token_count: Option<u32>,
}

V5 Enhancement

Link to workspaceSession instead of generic session_id:

pub struct Conversation {
// V4 fields...
pub workspace_session_id: Option<Uuid>, // Link to workspace session

// Additional cross-session fields
pub related_conversations: Vec<Uuid>, // Conversations in related sessions
pub summary: String, // AI-generated summary for cross-session context
}

📋 Pattern 8: Repository Implementation

Base Repository Pattern (from V4)

use foundationdb::*;
use std::sync::Arc;

pub struct FdbRepository {
db: Arc<Database>,
}

impl FdbRepository {
pub fn new(db: Arc<Database>) -> Self {
Self { db }
}

// Helper: Build tenant-prefixed key
fn build_key(&self, tenant_id: &Uuid, parts: &[&str]) -> Vec<u8> {
let mut key = format!("/{}", tenant_id);
for part in parts {
key.push('/');
key.push_str(part);
}
key.into_bytes()
}

// Helper: Serialize value
fn serialize<T: serde::Serialize>(&self, value: &T) -> Result<Vec<u8>> {
serde_json::to_vec(value).map_err(|e| /* ... */)
}

// Helper: Deserialize value
fn deserialize<T: serde::DeserializeOwned>(&self, data: &[u8]) -> Result<T> {
serde_json::from_slice(data).map_err(|e| /* ... */)
}
}

workspaceSession Repository Example

pub struct workspaceSessionRepository {
base: FdbRepository,
}

impl workspaceSessionRepository {
pub async fn create(&self, session: &workspaceSession) -> Result<()> {
let trx = self.base.db.create_trx()?;

// Primary key
let key = self.base.build_key(
&session.tenant_id,
&["workspace_sessions", &session.session_id.to_string()]
);
let value = self.base.serialize(session)?;
trx.set(&key, &value);

// Index: by user
let user_index_key = self.base.build_key(
&session.tenant_id,
&["workspace_sessions_by_user", &session.user_id.to_string()]
);
let session_ids = self.get_user_session_ids(&session.tenant_id, &session.user_id).await?;
let mut session_ids = session_ids.unwrap_or_default();
session_ids.push(session.session_id);
trx.set(&user_index_key, &self.base.serialize(&session_ids)?);

// Index: by workspace path
let workspace_index_key = self.base.build_key(
&session.tenant_id,
&["sessions_by_workspace", &session.workspace_path]
);
// ... similar indexing ...

trx.commit().await?;
Ok(())
}

pub async fn get_by_id(&self, tenant_id: &Uuid, session_id: &Uuid) -> Result<Option<workspaceSession>> {
let trx = self.base.db.create_trx()?;
let key = self.base.build_key(
tenant_id,
&["workspace_sessions", &session_id.to_string()]
);

match trx.get(&key, false).await? {
Some(value) => Ok(Some(self.base.deserialize(&value)?)),
None => Ok(None)
}
}

pub async fn get_related_sessions(
&self,
tenant_id: &Uuid,
user_id: &Uuid,
tags: &[String],
limit: usize
) -> Result<Vec<workspaceSession>> {
let trx = self.base.db.create_trx()?;

// Get user's session IDs
let user_index_key = self.base.build_key(
tenant_id,
&["workspace_sessions_by_user", &user_id.to_string()]
);

let session_ids: Vec<Uuid> = match trx.get(&user_index_key, false).await? {
Some(value) => self.base.deserialize(&value)?,
None => return Ok(vec![])
};

// Fetch sessions and filter by tags
let mut sessions = Vec::new();
for session_id in session_ids {
if let Some(session) = self.get_by_id(tenant_id, &session_id).await? {
if session.tags.iter().any(|t| tags.contains(t)) {
sessions.push(session);
}
}
}

// Sort by last_accessed_at desc
sessions.sort_by(|a, b| b.last_accessed_at.cmp(&a.last_accessed_at));

// Limit
sessions.truncate(limit);

Ok(sessions)
}
}

📋 Pattern 9: Transaction Patterns

V4 Best Practices

1. Read-Modify-Write:

loop {
let trx = db.create_trx()?;

// Read
let value = trx.get(&key, false).await?;

// Modify
let mut data: MyStruct = deserialize(&value)?;
data.update();

// Write
trx.set(&key, &serialize(&data)?);

// Commit with retry
match trx.commit().await {
Ok(_) => break,
Err(e) if e.is_retryable() => continue,
Err(e) => return Err(e.into())
}
}

2. Batch Operations:

async fn update_session_state_batch(
&self,
session: &workspaceSession,
files: Vec<String>,
editor_state: editorState
) -> Result<()> {
let trx = self.db.create_trx()?;

// Update session
session.active_files = files;
session.editor_state = editor_state;
session.updated_at = Utc::now();

let key = self.build_key(&session.tenant_id, &["workspace_sessions", &session.session_id.to_string()]);
trx.set(&key, &self.serialize(session)?);

// Update last_accessed index
let recent_key = self.build_key(&session.tenant_id, &["recent_sessions", &session.user_id.to_string()]);
// ... update index ...

trx.commit().await?;
Ok(())
}

🎯 V5 Implementation Roadmap

Phase 1: Core Models (Week 1)

  1. Tenant - Full V4 pattern with workspace limits
  2. User - V4 pattern with tenant isolation
  3. AuthSession - V4 JWT pattern
  4. workspaceSession - NEW enhanced model

Phase 2: Repositories (Week 2)

  1. Base FdbRepository trait
  2. TenantRepository
  3. UserRepository
  4. AuthSessionRepository
  5. workspaceSessionRepository

Phase 3: Cross-Session Intelligence (Week 3)

  1. Session relationship queries
  2. Cross-session context building
  3. Agent memory integration

Phase 4: Testing & Integration (Week 4)

  1. Repository tests
  2. End-to-end session flow
  3. Frontend integration
  4. Performance optimization

✅ Key Takeaways

  1. Use V4 Tenant Isolation - All keys prefixed with {tenant_id}/
  2. TWO Session Types - AuthSession (JWT) + workspaceSession (persistence)
  3. Secondary Indexes - For all common query patterns
  4. Cross-Session Queries - Via tags, relationships, conversations
  5. Transaction Retry Logic - Always handle FDB conflicts
  6. Batch Updates - Group related changes in one transaction

Next Step: Implement models in backend/src/db/models.rs based on these patterns.

Last Updated: 2025-10-14 Status: Ready for implementation