Схема БД
Постійна форма даних Agentfy.ai 2.0. Prisma — це репозиторій (окремого repository-шару немає); одна база Postgres, моделі згруповані за тими ж шарами-слайсами.
api/prisma/schema.prisma закомічений, і це згенерований файл — так написано в його ж першому рядку. Кожен слайс тримає свої моделі в <slice>.prisma у себе в корені, а схема збирається з них, і звідси випливають дві речі, які варто знати до будь-якої правки: міняти треба файл слайсу, а не збірку; і генерувати треба командою bun run prisma:generate, бо голий npx prisma generate нічого не збирає і виходить нулем на тому, що вже лежить на диску, віддаючи клієнт, який не знає вашої нової моделі. Сніпети нижче ілюстративні; джерело правди — файли слайсів.
Конвенції
- ID у форматі
{slice}-{uuid}(agent-3f9c…,team-a17f…), генеруються вmapper.toCreateслайса — читабельно й самоописово. - Мультитенантність: майже кожен рядок несе
teamId(team= тенант / магазин). Рядки agent-домену несутьagentId, який резолвиться в team. - Таймстемпи:
createdAt/updatedAtу всіх моделей; soft-delete (deletedAt) лише там, де важлива історія. - JSON для відкритих форм:
configагента,payloadзадачі,settingsканалу — типізовані в коді,Jsonу БД. - Секрети ніколи не в плейнтексті — див. envelope-зашифровану модель
Secretнижче та Секрети агента.
Сутності за шарами
user Team ─┬─ UserTeam ─ User apiKey · invite · role
└─ (planId ← billing)
agent Agent ─┬─ Memory ← agent = рядок, який І Є агент
├─ KnowledgeBase ─ Document
├─ Chat ─ Message
├─ Channel · Integration · Skill · Cron · Access
└─ Secret (envelope-зашифрований)
runtime Task ─ AgentRuntimeSession ─ Event ← ефемерне виконання
system Usage · Setting · Notification · File ← спільні сервіси
admin AuditLog · FeatureFlag
billing Subscription ─ Price ─ Product · Invoice ─ Payment · PaymentMethod · WebhookEventУсе тримається на Team (тенант) згори та Agent (ідентичність агента) посередині.
Ключові моделі (ілюстративно)
Тенантність — user
model Team {
id String @id // team-{uuid}
name String
planId String? // written by billing; read by system/setting (inverted dep)
members UserTeam[]
agents Agent[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id String @id // user-{uuid}
email String @unique
name String?
teams UserTeam[]
createdAt DateTime @default(now())
}
model UserTeam { // membership + role
id String @id
userId String
teamId String
role String // owner | admin | member
user User @relation(fields: [userId], references: [id])
team Team @relation(fields: [teamId], references: [id])
@@unique([userId, teamId])
}Агент — agent
model Agent {
id String @id // agent-{uuid} — this row IS the agent
teamId String
name String
status String @default("active") // active | disabled | archived
type String @default("standard") // standard | concierge — призначає лише сервер
soul String? // SOUL.md · USER.md · HEARTBEAT.md — власний текст агента,
user String? // дослівно; їде в експорт і повертається байт у байт
heartbeat String?
config Json @default("{}") // agent.config.json рантайму
runtimeProfile String @default("none") // чи можна йому руки і скільки машини
promptLogEnabled Boolean @default(false) // записувати зібрані промпти цього агента
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([teamId])
}Усе, що називає agentId, іде каскадом від цього рядка — листування, пам'ять, навички, секрети, конфігурація автозапуску, історія роботи. Каскад — це один оператор в одній транзакції, тому видалення, обірване посередині, не видаляє нічого; явне прибирання семи таблиць може зупинитися між будь-якими двома з них і лишити рівно тих сиріт, заради яких каскад і заведено. Єдине, що поза ним, — VectorEmbedding: у нього за задумом немає зовнішнього ключа, і його прибирають явно.
Що означає кожна колонка — див. Склад.
Envelope-зашифровані секрети — agent/secret
model Secret {
id String @id // secret-{uuid}
agentId String
name String // e.g. OPENCART_API_KEY
ciphertext Bytes // AES-256-GCM payload (the wrapped value)
iv Bytes // per-record nonce
authTag Bytes // GCM auth tag (AEAD)
keyVersion Int // which KEK wrapped the DEK
agent Agent @relation(fields: [agentId], references: [id])
createdAt DateTime @default(now())
@@unique([agentId, name])
}KEK ніколи не покидає api; под воркера не бачить плейнтекст. Див. Секрети агента.
Ефемерне виконання — runtime
model AgentRuntimeSession {
id String @id // session-{uuid}
agentId String
taskId String?
status String // pending|starting|running|idle|stopping|stopped|failed
failureReason String? // причина відмови: dial_timeout | handshake_timeout |
// ready_timeout | session_mismatch | channel_version |
// heartbeat_lost
runtimeType String // none | light | browser | heavy | warm
cpuLimit String?
memLimit String?
storageLimit String?
ttlSeconds Int
k8sJobName String?
namespace String?
workerUrl String?
logsUrl String?
startedAt DateTime?
lastActivityAt DateTime?
stoppedAt DateTime?
@@index([agentId, status])
}(Машину станів див. у Життєвому циклі воркера.)
Решта, за шарами
| Шар | Моделі (ескіз) |
|---|---|
| user | ApiKey { teamId, hash, scopes } · Invite { teamId, email, role, token, expiresAt } · Role |
| system | Usage { teamId, agentId?, kind, amount, unit, ts } · Setting { scope, key, value } · Notification { userId, type, readAt } · File { teamId, agentId?, storageKey, name, size, mime, tags } |
| admin | AuditLog { actorId, action, target, meta, ts } · FeatureFlag { key, enabled, scope } |
| runtime | Task { teamId, agentId, kind, status, payload, attempts } · Event { sessionId?, type, data } (переважно стримиться через Redis; персиститься лише для аудиту) |
| agent | Memory { agentId, kind, content, embeddingRef? } · KnowledgeBase { agentId, workspace } ─ Document · Chat { agentId, channel } ─ Message { chatId, role, content } · Channel · Integration · Skill · Cron · Access |
| billing | Subscription { teamId, priceId, status, currentPeriodEnd } · Product ─ Price · Invoice ─ Payment · PaymentMethod · WebhookEvent |
Де живе нереляційний стан (не в цій схемі)
- Вектори + граф знань → Postgres pgvector + Apache AGE, власник — LightRAG за
workspace(один наKnowledgeBase). Див. LightRAG. - Черга + pub/sub + кеш → Redis (BullMQ-задачі, стрим
events). Не durable-правда. - Об'єктне сховище → файли/артефакти за посиланням; рядок
FileтримаєstorageKey, байти лежать у S3/R2.
Див. також
- Шари-слайси · Розбір слайсів — де сидить слайс кожної моделі.
- Модель рантайму — як
Task/AgentRuntimeSessionрухаються за турн. - Секрети агента — схема envelope-шифрування за
Secret.