Data Models
```prisma model User { id String @id @default(uuid()) email String @unique name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
Full Public Reader
Data Models
Database Schema
TrajectoryOS uses Prisma ORM with PostgreSQL (production) or SQLite (development).
Schema File
Location: `services/trajectory-core/prisma/schema.prisma`
Core Models
User
model User {
id String @id @default(uuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
lifestates LifeState[]
projects Project[]
skills SkillEvidence[]
sessions Session[]
}Purpose: User accounts
Key Fields:
- `email` - Authentication identifier (unique)
- `name` - Display name
Relations:
- One user → many life states (time series)
- One user → many projects
- One user → many skill evidence entries
- One user → many interview sessions
---
LifeState
model LifeState {
id String @id @default(uuid())
userId String
timestamp DateTime @default(now())
// Latent vector (stored as JSON)
latentVector Json
// Derived physics variables
thrust Float
alignment Float
gravity Float
mass Float
escapeIndex Float
// Metadata
notes String?
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, timestamp])
}Purpose: Snapshot of life trajectory at a point in time
Key Fields:
- `latentVector` - High-dimensional representation (z_t) stored as JSON array
- `thrust`, `alignment`, `gravity`, `mass` - Derived physics variables
- `escapeIndex` - The critical η metric
- `timestamp` - When this state was computed
Queries:
// Get current state
const current = await prisma.lifeState.findFirst({
where: { userId },
orderBy: { timestamp: 'desc' }
});
// Get history (last 30 days)
const history = await prisma.lifeState.findMany({
where: {
userId,
timestamp: { gte: thirtyDaysAgo }
},
orderBy: { timestamp: 'asc' }
});---
Skill
model Skill {
id String @id @default(uuid())
name String @unique
domain String
description String?
createdAt DateTime @default(now())
// Relations
evidence SkillEvidence[]
edges SkillEdge[] @relation("SkillFrom")
edgesTo SkillEdge[] @relation("SkillTo")
}Purpose: Global skill taxonomy
Key Fields:
- `name` - Skill name (e.g., "Python", "Choreography")
- `domain` - Category (e.g., "Programming", "Creative")
Relations:
- One skill → many evidence entries (from different users)
- One skill → many outgoing edges (skill graph)
- One skill → many incoming edges
Example Skills:
const skills = [
{ name: "Python", domain: "Programming" },
{ name: "Choreography", domain: "Creative" },
{ name: "System Design", domain: "Engineering" },
{ name: "Leadership", domain: "Management" }
];---
SkillEdge
model SkillEdge {
id String @id @default(uuid())
fromSkillId String
toSkillId String
weight Float @default(0.5)
description String?
fromSkill Skill @relation("SkillFrom", fields: [fromSkillId], references: [id], onDelete: Cascade)
toSkill Skill @relation("SkillTo", fields: [toSkillId], references: [id], onDelete: Cascade)
@@unique([fromSkillId, toSkillId])
}Purpose: Directed edges in skill graph (transfer learning)
Key Fields:
- `weight` - Transfer strength (0-1), e.g., Python → JavaScript = 0.7
- `description` - Why this edge exists
Example Edges:
const edges = [
{ from: "Python", to: "JavaScript", weight: 0.7, description: "Programming concepts transfer" },
{ from: "Choreography", to: "Animation", weight: 0.6, description: "Movement principles" },
{ from: "System Design", to: "Architecture", weight: 0.8, description: "Structural thinking" }
];Graph Traversal:
// Get all skills reachable from Python within 2 hops
const reachable = await prisma.$queryRaw`
WITH RECURSIVE skill_path AS (
SELECT to_skill_id, 1 AS depth
FROM skill_edges
WHERE from_skill_id = ${pythonId}
UNION
SELECT e.to_skill_id, p.depth + 1
FROM skill_edges e
JOIN skill_path p ON e.from_skill_id = p.to_skill_id
WHERE p.depth < 2
)
SELECT DISTINCT s.* FROM skills s
JOIN skill_path sp ON s.id = sp.to_skill_id
`;---
SkillEvidence
model SkillEvidence {
id String @id @default(uuid())
userId String
skillId String
timestamp DateTime @default(now())
// Evidence data
level Float // Estimated level (0-10)
confidence Float // Confidence (0-1)
source String // "interview", "artifact", "echelon", etc.
sourceRef String? // Reference to source
notes String?
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
skill Skill @relation(fields: [skillId], references: [id], onDelete: Cascade)
@@index([userId, skillId, timestamp])
}Purpose: Observations of user skill levels
Key Fields:
- `level` - Estimated competency (0-10 scale)
- `confidence` - How certain we are (0-1)
- `source` - Where evidence came from
- `sourceRef` - Link to interview transcript, document ID, etc.
Source Types:
| Source | Description | Typical Confidence |
|--------|-------------|-------------------|
| `interview` | User stated in conversation | 0.5-0.7 |
| `artifact` | Code, document analysis | 0.7-0.9 |
| `echelon` | Embodied flow state | 0.8-1.0 |
| `external` | Resume, portfolio | 0.4-0.6 |
Bayesian Update:
// When new evidence arrives
const evidence = await prisma.skillEvidence.create({
data: {
userId,
skillId,
level: 7.5,
confidence: 0.8,
source: "interview",
sourceRef: sessionId,
notes: "Built 3 production apps in Python"
}
});
// Trigger Bayesian update in Python model
await fetch('http://skill-model:5001/update', {
method: 'POST',
body: JSON.stringify({ userId, skillId, evidence })
});---
Project
model Project {
id String @id @default(uuid())
userId String
name String
description String?
status String @default("active")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Project metadata
embedding Json? // Vector embedding
metadata Json? // Flexible metadata
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, status])
}Purpose: User's projects and commitments
Key Fields:
- `status` - "active", "paused", "completed", "archived"
- `embedding` - Semantic embedding for alignment computation
- `metadata` - Flexible JSON for tags, links, time allocation, etc.
Metadata Example:
{
"tags": ["web-dev", "side-project"],
"hoursPerWeek": 10,
"priority": "high",
"repository": "https://github.com/user/project",
"relatedSkills": ["Python", "React", "System Design"]
}Alignment Computation:
// Get all active projects
const projects = await prisma.project.findMany({
where: { userId, status: 'active' }
});
// Compute alignment (cosine similarity of embeddings)
const alignment = await fetch('http://alignment-model:5002/compute', {
method: 'POST',
body: JSON.stringify({ projects })
});---
Session
model Session {
id String @id @default(uuid())
userId String
type String
startTime DateTime @default(now())
endTime DateTime?
status String @default("active")
// Session data
transcript String?
artifacts Json?
metadata Json?
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, startTime])
}Purpose: Interview and generation sessions
Key Fields:
- `type` - "interview", "background_generation", etc.
- `transcript` - Full conversation history
- `artifacts` - Generated content (plans, insights)
- `metadata` - Additional context
Transcript Format (JSON):
{
"messages": [
{ "role": "agent", "content": "Tell me about your recent work.", "timestamp": "..." },
{ "role": "user", "content": "I've been building a choreography app.", "timestamp": "..." },
{ "role": "agent", "content": "What technologies are you using?", "timestamp": "..." }
]
}---
Memory
model Memory {
id String @id @default(uuid())
userId String?
content String
embedding Json
source String
sourceRef String?
timestamp DateTime @default(now())
metadata Json?
@@index([userId, timestamp])
}Purpose: Semantic memory for RAG (Retrieval-Augmented Generation)
Key Fields:
- `content` - Text snippet
- `embedding` - Vector for similarity search
- `source` - "interview", "document", "plan", etc.
- `userId` - Optional (some memories are global knowledge)
Usage:
// Store memory from interview
await prisma.memory.create({
data: {
userId,
content: "User is interested in computational choreography and has experience with Python and creative coding.",
embedding: await embed(content),
source: "interview",
sourceRef: sessionId
}
});
// Later, retrieve relevant memories
const query = "What are my main interests?";
const queryEmbedding = await embed(query);
const relevant = await vectorStore.search(queryEmbedding, {
filter: { userId },
limit: 5
});---
Domain Types (TypeScript)
Location: `services/trajectory-core/src/domain/types.ts`
LifePhysics
export interface LifePhysics {
thrust: number;
alignment: number;
gravity: number;
mass: number;
escapeIndex: number;
}SkillBelief
export interface SkillBelief {
skillId: string;
mean: number; // Posterior mean level
variance: number; // Posterior variance
confidence: number; // Confidence interval width
utilization: number; // How actively used (0-1)
lastEvidence: Date;
}EchelonSummary
export interface EchelonSummary {
sessionId: string;
userId: string;
startTime: Date;
endTime: Date;
// Aggregated metrics
avgTension: number;
avgDrift: number;
avgMomentum: number;
avgPhase: number;
flowScore: number;
// Detected episodes
flowEpisodes: Array<{ start: Date; end: Date }>;
breakdownPeriods?: Array<{ start: Date; end: Date }>;
// Context tagging
activity?: string;
projectIds?: string[];
skillIds?: string[];
notes?: string;
}---
Indexes & Performance
Critical Indexes
// Already defined in models above:
@@index([userId, timestamp]) // LifeState, Session, Memory
@@index([userId, skillId, timestamp]) // SkillEvidence
@@index([userId, status]) // ProjectQuery Patterns
Most Common:
1. Get current state: `LifeState.findFirst(userId, orderBy: timestamp desc)`
2. Get skill evidence: `SkillEvidence.findMany(userId, skillId)`
3. Get active projects: `Project.findMany(userId, status: active)`
Optimization:
- Index covers all common queries
- No full table scans
- Pagination for large result sets
---
Migrations
Create Initial Schema
cd services/trajectory-core
pnpm prisma migrate dev --name initAdd New Model
1. Edit `schema.prisma`
2. Run migration:
pnpm prisma migrate dev --name add_new_modelDeploy to Production
pnpm prisma migrate deploy---
Data Lifecycle
User Onboarding
1. Create User record
2. Run initial interview
3. Create SkillEvidence from interview
4. Compute initial LifeState
5. Store in databaseOngoing Updates
Daily:
- Background analysis job
- Recompute LifeState
- Store new snapshot
Weekly:
- Prune old LifeState snapshots (keep daily aggregates)
- Cleanup completed sessionsUser Deletion (GDPR)
1. Mark user as deleted
2. Cascade delete all related records
3. Anonymize memories (remove userId)
4. Export user data before deletion (if requested)---
Next: See [API Reference](../api/README.md) for endpoint documentation.
Promotion Decision
Promote into a technical note or architecture paper with implementation anchors.
Source Anchor
Comp-Core/backend/cc-trajectory/docs/architecture/data-models.md
Detected Structure
Method · Evaluation · References · Code Anchors · Architecture