Docs Β· reference
Firestore Schema Reference
Firestore Schema Reference
Complete Firestore data models and collections for the TeamDay platform.
Collections Overview
TeamDay uses Firebase Firestore as its primary database. The schema follows a hierarchical structure with strong security rules and real-time subscriptions.
| Collection | Purpose | Access Control |
|---|---|---|
organizations | Organization/team management | Members only |
users | User account data | Owner only |
userProfiles | User preferences and settings | Owner only |
character | Agents (stored as characters in Firestore) | Visibility-based |
spaces | Workspaces for agents | Visibility-based |
chats | Conversations | Owner/org members |
tasks | Agent-created tasks | Org members |
executions | Agent execution history | Org members |
missions | Long-running autonomous tasks | Org members |
scheduledTasks | Scheduled agent automations | Org members |
skills | Reusable prompt packages | Visibility-based |
subagents | Specialized agent configs | Visibility-based |
plugins | Configuration packages | Visibility-based |
pluginInstallations | Plugin installs per space | Org members |
mcpInstances | MCP server instances | Org members |
personal_access_tokens | API tokens | Owner only |
invites | Organization invites | Public read |
usageAggregates | Usage metrics | Org members |
creditTransactions | Credit balance history | Org members |
Core Collections
organizations
Organization/team management. Each user belongs to one or more organizations.
interface Organization {
id: string
name: string
logoUrl?: string
ownerId: string // User who created the org
members: string[] // Array of user IDs
subscription?: OrganizationSubscription
createdAt: Timestamp
updatedAt: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string // Encrypted API key
}
Example Document:
{
"id": "org_abc123",
"name": "Acme Corporation",
"logoUrl": "https://storage.googleapis.com/...",
"ownerId": "user_xyz",
"members": ["user_xyz", "user_abc", "user_def"],
"subscription": {
"tier": "pro",
"status": "active"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
members(array-contains) for member queriesownerIdfor owner queries
Subcollections:
gitCredentials/{credentialId}- OAuth tokens for Git integrations
users
User authentication and account data. Managed by Firebase Auth.
interface User {
id: string // Firebase Auth UID
email: string
displayName?: string
photoURL?: string
createdAt: Timestamp
lastSignIn?: Timestamp
}
Security: Owner-only access. No client-side deletion allowed.
Subcollections:
gitCredentials/{credentialId}- Personal Git OAuth tokens
userProfiles
User preferences, settings, and last-used organization.
interface UserProfile {
userId: string
lastUsedOrganizationId?: string
preferences?: {
theme?: 'light' | 'dark' | 'system'
locale?: string
}
createdAt: Timestamp
updatedAt?: Timestamp
// Encrypted fields (server-side only)
encryptedAnthropicApiKey?: string
encryptedClaudeCodeOAuthToken?: string
}
Security: Owner can read/update. Cannot modify encrypted credential fields from client.
character (Agents)
Agents collection (stored as characters in Firestore). These are the AI agents that users create and configure.
type CharacterVisibility = 'private' | 'organization' | 'public' | 'unlisted'
type CharacterCategory = 'marketing' | 'finance' | 'hr' | 'engineering' | 'operations' | 'general'
interface Character {
id: string
name: string
role: string // e.g., "Marketing Assistant"
initialGreeting: string
characterDescription: string // Agent description
system_message: string // System prompt (legacy field name)
systemPrompt?: string // System prompt (preferred alias)
image: string // Avatar URL
color: string // Brand color
// Configuration
model?: string // e.g., 'claude-sonnet-4-6'
visibility: CharacterVisibility // Agent visibility
ownerId: string
organizationId?: string
ownerName?: string
// Resources
files: any[] // Attached files
linksToGetContextFrom: any[] // URLs for context
tools?: string[] | null // Legacy AI tools
advanced_tools?: string[] // MCP tool IDs
skillIds?: string[] // Skill references
subagentIds?: string[] // Subagent references
// Features
suggested_topics?: any[]
suggested_topics_conversation_starters?: string[]
voice?: string // Voice model ID
tags?: string[]
articles?: string[]
// Template system
isTemplate?: boolean // Global template flag
category?: CharacterCategory
featured?: boolean
version?: number
changelog?: string
templateId?: string // Source template ID
instanceCount?: number // For templates
// State
isForkable?: boolean
archived?: boolean
// Statistics
statistics?: {
likes: number
dislikes: number
uniqueChats: number
}
// Timestamps
createdAt: Date | Timestamp
updatedAt?: Date | Timestamp
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
}
Example Document:
{
"id": "char_123",
"name": "Marketing Maven",
"role": "Marketing Assistant",
"initialGreeting": "Hi! I'm here to help with marketing tasks.",
"characterDescription": "Expert in content marketing and social media",
"system_message": "You are a marketing specialist...",
"systemPrompt": "You are a marketing specialist...",
"image": "https://storage.googleapis.com/avatars/marketing.png",
"color": "#FF6B6B",
"model": "claude-sonnet-4-6",
"visibility": "organization",
"ownerId": "user_xyz",
"organizationId": "org_abc123",
"skillIds": ["research-assistant", "technical-writer"],
"advanced_tools": ["mcp_google_analytics"],
"tags": ["marketing", "content"],
"archived": false,
"isTemplate": false,
"createdAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
- Composite:
organizationId+isTemplate+createdAt(desc) ownerId+visibilityisTemplatefor template queries
Visibility Rules:
public: Visible to all authenticated usersorganization: Visible to all org membersprivate: Only owner can seeunlisted: Only owner can see (not in listings)- Templates (
isTemplate: true): Visible to all
spaces
Workspaces where agents operate. Each space has its own file system and git configuration.
type SpaceVisibility = 'private' | 'organization' | 'public'
type SpaceInitializationType = 'empty' | 'readme' | 'git' | 'starterKit'
type SpaceInitializationStatus = 'pending' | 'initializing' | 'ready' | 'failed'
interface Space {
id: string
organizationId: string
name: string
description: string
// Branding
coverImage?: string
gradientColors?: [string, string] // [fromColor, toColor]
coverImageAttribution?: {
source: 'unsplash' | 'upload' | 'url' | 'ai'
photographerName?: string
photographerUsername?: string
unsplashUrl?: string
}
// Access control
visibility: SpaceVisibility
ownerId: string
allowedUserIds?: string[] // For private spaces
// Configuration
assignedAgents?: string[] // Agent IDs (stored as characterIds)
instructions?: string // Fallback instructions
advanced_tools?: string[] // MCP instance IDs
skillIds?: string[] // Skill IDs
subagentIds?: string[] // Subagent IDs
pluginIds?: string[] // Plugin IDs to install
// Git configuration
git?: {
aiSettings?: {
autoCommitOnSessionEnd?: boolean
generateCommitMessages?: boolean
autoResolveConflicts?: boolean
requireApprovalForPush?: boolean
}
permissions?: Record<string, {
readers: string[]
writers: string[]
pushers: string[]
defaultPermission: 'none' | 'read' | 'write' | 'push'
}>
defaultUserId?: string
remotes?: Record<string, string> // Cache only
}
// Initialization
initializationStatus?: SpaceInitializationStatus
initializationType?: SpaceInitializationType
initializationError?: string
// Statistics
chatCount?: number
fileCount?: number
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
deletedAt?: Date | null
isDeleted?: boolean
// Optimistic UI
isOptimistic?: boolean
clientSideId?: string
}
Example Document:
{
"id": "space_456",
"organizationId": "org_abc123",
"name": "Marketing Campaigns",
"description": "Space for marketing campaign work",
"visibility": "organization",
"ownerId": "user_xyz",
"assignedAgents": ["char_123"],
"skillIds": ["research-assistant"],
"gradientColors": ["#3b82f6", "#8b5cf6"],
"initializationStatus": "ready",
"initializationType": "empty",
"chatCount": 5,
"fileCount": 23,
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
Indexes Required:
organizationId+updatedAt(desc)ownerIdfor owner queriesvisibilityfor filtering
chats
Conversations between users and AI agents.
type ChatStatus = 'standby' | 'working' | 'done' | 'needs_attention' | 'error'
interface Chat {
id: string
organizationId: string
spaceId: string
title: string
subtitle?: string
status: ChatStatus
ownerId: string
lastMessage?: string
sessionId?: string // Claude Code session ID
// Session lifecycle
closed: boolean
closedAt?: Date | null
// Visibility
visibility?: 'private' | 'organization' | 'public'
// Timestamps
createdAt: Date
updatedAt: Date
}
Subcollections:
messages/{messageId}- Chat messages (immutable)
Messages Structure:
interface ChatMessage {
id: string
chatId: string
role: 'user' | 'assistant'
content: string
createdAt: Date
sessionId?: string
toolCalls?: Array<{
name: string
input: any
result?: string
}>
}
Indexes Required:
- Composite:
organizationId+spaceId+updatedAt(desc) ownerIdfor user's chatssessionIdfor session queries
Security: Messages are immutable (no update/delete).
tasks
Agent-created tasks for coordination and assignment.
type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'
interface Task {
id: string // Format: task-{timestamp}-{random}
organizationId: string
title: string
description: string
assignedTo?: string | null // Agent ID or user ID
priority: TaskPriority
status: TaskStatus
spaceId?: string | null
createdBy: string | null // Agent ID or user ID that created it
// Timestamps
createdAt: string // ISO 8601
completedAt?: string | null // ISO 8601
}
Indexes Required:
- Composite:
organizationId+status+createdAt(desc) assignedTofor assignment queriesspaceIdfor space tasks
executions
Agent execution tracking and delegation history.
type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
interface Execution {
id: string // Format: exec-{timestamp}-{random}
organizationId: string
agentId: string
chatId: string // Associated chat ID
sessionId: string // Claude Code session ID
userId?: string // User who initiated the execution
status: ExecutionStatus
// Content
message: string // User's message (singular)
result?: string // Agent's response
error?: string // Error if failed
// Agent info
agentName?: string // Agent display name (for notifications)
// Delegation tracking
parentExecutionId?: string // Parent execution ID (if delegated)
delegationDepth: number // Depth in delegation chain
// Timing
startedAt: Date
completedAt?: Date
duration?: number // milliseconds
// Cost tracking
tokensInput?: number
tokensOutput?: number
cost?: number
}
Indexes Required:
- Composite:
organizationId+chatId+startedAt(desc) sessionIdfor session queriesparentExecutionIdfor delegation chains
missions
Long-running autonomous agent tasks.
type MissionStatus = 'pending' | 'running' | 'paused' | 'completed' | 'failed'
type AgentType = 'claude' | 'gemini' | 'codex'
interface Mission {
id: string
userId: string
organizationId: string
spaceId?: string
title: string
goal: string
agentType: AgentType
// Schedule
schedule: {
type: 'once' | 'cron' | 'continuous'
value?: string // Cron expression
lastRun?: number
nextRun?: number
}
// State
state: {
currentStep: string
contextSummary: string
artifacts: string[]
memory: Record<string, any>
}
status: MissionStatus
logs: MissionLogEntry[]
// Timestamps
createdAt: number
updatedAt: number
}
interface MissionLogEntry {
timestamp: number
level: 'info' | 'warn' | 'error'
message: string
metadata?: any
}
Indexes Required:
- Composite:
organizationId+status+createdAt(desc) userIdfor user missionsspaceIdfor space missions
scheduledTasks (Deprecated)
Deprecated: This collection has been superseded by the
missionscollection. New scheduled automations should use missions instead. This collection is maintained for backward compatibility only.
Scheduled agent automations (cron jobs).
interface ScheduledTask {
id: string
organizationId: string
characterId: string // The agent's ID
spaceId?: string // Optional space context
// Task details
name: string
prompt: string // What to ask the agent
schedule: string // Cron expression
// State
isActive: boolean
lastRun?: Date | null
lastRunStatus?: 'success' | 'error' | 'running' | null
lastRunSessionId?: string | null
nextRun?: Date | null
// Metadata
createdAt: Date
createdBy: string
updatedAt?: Date | null
}
Related Collection: scheduledTaskExecutions/{executionId} stores execution history.
Indexes Required:
- Composite:
organizationId+isActive+nextRun(asc) characterIdfor agent schedule queriescreatedByfor creator queries
skills
Reusable prompt packages that extend agent capabilities.
type SkillVisibility = 'private' | 'organization' | 'public'
type SkillCategory = 'research' | 'writing' | 'data' | 'code' | 'marketing' | 'productivity' | 'file-handling' | 'other'
interface Skill {
id: string
// Identity
name: string // kebab-case (e.g., "research-assistant")
displayName: string // Human readable
description: string // When to invoke
// Content
prompt: string // SKILL.md markdown instructions
// Dependencies
builtInTools?: string[] // ["Read", "Write", "Bash", "WebSearch"]
requiredMCPs?: string[] // ["google-analytics", "bigquery"]
// Categorization
category: SkillCategory
tags?: string[]
icon?: string // Emoji or icon name
// Ownership
organizationId: string
ownerId: string
visibility: SkillVisibility
// Marketplace
featured?: boolean
verified?: boolean
usageCount?: number
rating?: number // 1-5
ratingCount?: number
// Versioning
version: number
changelog?: string
// Timestamps
createdAt: Date | Timestamp
updatedAt: Date | Timestamp
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Example Document:
{
"id": "skill_research",
"name": "research-assistant",
"displayName": "Research Assistant",
"description": "Deep research on a topic using web search",
"prompt": "# Research Assistant\n\nYou are a research specialist...",
"category": "research",
"icon": "π",
"builtInTools": ["WebSearch", "WebFetch", "Read", "Write"],
"organizationId": "org_abc123",
"ownerId": "user_xyz",
"visibility": "organization",
"version": 1,
"createdAt": "2024-01-15T10:00:00Z"
}
Indexes Required:
- Composite:
organizationId+visibility+createdAt(desc) category+visibilityfor marketplacefeaturedfor featured skills
plugins
Configuration packages (GitHub repos) that bundle commands, agents, skills, and MCP servers.
type PluginVisibility = 'private' | 'organization' | 'public'
type PluginSourceType = 'github' | 'url' | 'path'
interface Plugin {
id: string
// Basic info
name: string // kebab-case identifier
displayName: string
description: string
icon?: string
// Source
source: {
source: PluginSourceType
repo?: string // owner/repo (GitHub)
url?: string // Git URL
path?: string // Local path
ref?: string // branch/tag/commit
}
// Manifest (cached from repo)
manifest?: {
name: string
description: string
version: string
author?: {
name: string
email?: string
url?: string
}
homepage?: string
repository?: string
license?: string
keywords?: string[]
category?: string
// Component flags
commands?: boolean | string[]
agents?: boolean | string[]
skills?: boolean | string[]
hooks?: boolean | string[]
mcpServers?: boolean | string[]
}
// Ownership
organizationId: string
ownerId: string
visibility: PluginVisibility
// Stats
installCount?: number
starCount?: number
// Verification
verified?: boolean
featured?: boolean
// Timestamps
createdAt: Date
updatedAt: Date
// Soft delete
isDeleted?: boolean
deletedAt?: Date | null
}
Related Collection: pluginInstallations/{installationId} tracks space installations.
Indexes Required:
- Composite:
organizationId+visibility+createdAt(desc) visibilityfor marketplacefeaturedfor featured plugins
personal_access_tokens
API tokens for programmatic access.
interface PersonalAccessToken {
id: string // Token ID (not the token itself)
uid: string // Owner user ID
organizationId: string
name: string // User-defined name
// Token hash (actual token never stored)
tokenHash: string
// Usage tracking
lastUsedAt?: Date | null
createdAt: Date
expiresAt?: Date | null
}
Security: Server-side managed. Users can only read/delete their own tokens.
Indexes Required:
- Composite:
uid+organizationId tokenHashfor authentication (unique)
invites
Organization invitation management.
interface Invite {
id: string
organizationId: string
email: string
role: 'member' | 'admin'
invitedBy: string // User ID
status: 'pending' | 'accepted' | 'expired'
expiresAt: Date
createdAt: Date
}
Security: Public read (needed for invite links). Only org owners can create/update/delete.
Server-Side Only Collections
These collections have no client access and are managed exclusively via Admin SDK:
authStates
Authentication tier caching for performance.
cli_pending_auth
Temporary OAuth codes for CLI authentication.
cli_refresh_tokens
Refresh tokens for CLI tool.
partner_connections
RSA keys for partner authentication (encrypted private keys).
partner_organizations
Links partners to organizations.
usageLogs
Detailed per-request usage logs.
Read-Only Collections (Client)
usageAggregates
Aggregated usage metrics. Structure: usageAggregates/{period}/data/{orgId}_{dateKey}
interface UsageAggregate {
organizationId: string
period: 'daily' | 'monthly'
dateKey: string // YYYY-MM-DD
// Metrics
totalRequests: number
totalTokensInput: number
totalTokensOutput: number
totalCost: number
// Breakdowns
byAgent: Record<string, {
requests: number
tokensInput: number
tokensOutput: number
cost: number
}>
}
Security: Org members can read. Writing is server-side only.
creditTransactions
Credit balance history.
interface CreditTransaction {
id: string
organizationId: string
type: 'purchase' | 'usage' | 'refund' | 'grant'
amount: number // Positive or negative
balance: number // Running balance
description: string
metadata?: any
createdAt: Date
}
Security: Org members can read. Writing is server-side only.
Indexes Summary
Required Composite Indexes
// Organizations
organizations: members (array-contains)
// Agents (character collection)
character: organizationId + isTemplate + createdAt (desc)
character: ownerId + visibility
// Spaces
spaces: organizationId + updatedAt (desc)
// Chats
chats: organizationId + spaceId + updatedAt (desc)
// Tasks
tasks: organizationId + status + createdAt (desc)
// Executions
executions: organizationId + chatId + startedAt (desc)
// Missions
missions: organizationId + status + createdAt (desc)
// Scheduled Tasks
scheduledTasks: organizationId + isActive + nextRun (asc)
// Skills
skills: organizationId + visibility + createdAt (desc)
skills: category + visibility
// Plugins
plugins: organizationId + visibility + createdAt (desc)
// Personal Access Tokens
personal_access_tokens: uid + organizationId
Single-Field Indexes
Most createdAt, updatedAt, ownerId, visibility fields are auto-indexed by Firestore.
Security Rules Summary
Access Patterns
Owner-only:
users- Only owner can read/updateuserProfiles- Only owner can read/updatepersonal_access_tokens- Owner can read/delete
Org Member Access:
organizations- Members can readspaces- Filtered by visibilitychats- Owner or org memberstasks- Org membersexecutions- Org membersmissions- Org membersscheduledTasks- Org members
Visibility-Based:
character(agents) - Public/org/private/unlistedskills- Public/org/privateplugins- Public/org/private
Server-Only:
authStatescli_pending_authcli_refresh_tokenspartner_connectionspartner_organizationsusageLogs
Read-Only (Client):
usageAggregates- Org members readcreditTransactions- Org members read
Encrypted Fields
These fields are encrypted server-side and cannot be modified from client:
organizations.encryptedAnthropicApiKeyuserProfiles.encryptedAnthropicApiKeyuserProfiles.encryptedClaudeCodeOAuthToken
Client updates attempting to modify these fields will be rejected by security rules.
Best Practices
Firestore Constraints
- No Undefined Values: Firestore doesn't allow
undefined. Usenullor omit the field.
// β Bad
await updateDoc(docRef, { optional: undefined })
// β
Good
await updateDoc(docRef, { optional: value ?? null })
// β
Better - omit undefined
const cleanData = Object.fromEntries(
Object.entries(data).filter(([_, v]) => v !== undefined)
)
- Use Server Timestamps: Always use
serverTimestamp()for consistency.
import { serverTimestamp } from 'firebase/firestore'
await addDoc(collection, {
...data,
createdAt: serverTimestamp()
})
- Cleanup Subscriptions: Always unsubscribe from real-time listeners.
const unsubscribe = onSnapshot(query, snapshot => { })
onUnmounted(() => {
if (unsubscribe) unsubscribe()
})
Soft Deletes
Most collections use soft deletes for recoverability:
// Soft delete
await updateDoc(docRef, {
isDeleted: true,
deletedAt: new Date()
})
// Restore
await updateDoc(docRef, {
isDeleted: false,
deletedAt: null
})
Optimistic Updates
For better UX, the app uses optimistic updates for creates:
// Add optimistic item immediately
optimisticItems.value.push(newItem)
// Create in Firestore
const docRef = await addDoc(collection, data)
// Snapshot listener updates with real data
Migration Notes
Legacy Fields
Some collections have deprecated fields maintained for backward compatibility:
Agents (character collection):
system_messageβ UsesystemPrompt(preferred)skills(inline) β UseskillIds(references)toolsβ Useadvanced_tools
UserProfiles:
anthropicApiKey(plaintext) β UseencryptedAnthropicApiKeyclaudeCodeOAuthToken(plaintext) β UseencryptedClaudeCodeOAuthToken
Data Migration
When migrating data, always:
- Create new fields alongside old ones
- Update application code to read from both
- Background job migrates data
- Remove old field references after migration complete
- Keep deprecated fields documented for 6 months minimum