Skip to content

Схема БД

Постійна форма даних 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

prisma
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

prisma
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

prisma
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

prisma
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])
}

(Машину станів див. у Життєвому циклі воркера.)

Решта, за шарами

ШарМоделі (ескіз)
userApiKey { teamId, hash, scopes } · Invite { teamId, email, role, token, expiresAt } · Role
systemUsage { teamId, agentId?, kind, amount, unit, ts } · Setting { scope, key, value } · Notification { userId, type, readAt } · File { teamId, agentId?, storageKey, name, size, mime, tags }
adminAuditLog { actorId, action, target, meta, ts } · FeatureFlag { key, enabled, scope }
runtimeTask { teamId, agentId, kind, status, payload, attempts } · Event { sessionId?, type, data } (переважно стримиться через Redis; персиститься лише для аудиту)
agentMemory { agentId, kind, content, embeddingRef? } · KnowledgeBase { agentId, workspace }Document · Chat { agentId, channel }Message { chatId, role, content } · Channel · Integration · Skill · Cron · Access
billingSubscription { teamId, priceId, status, currentPeriodEnd } · ProductPrice · Invoice ─ Payment · PaymentMethod · WebhookEvent

Де живе нереляційний стан (не в цій схемі)

  • Вектори + граф знань → Postgres pgvector + Apache AGE, власник — LightRAG за workspace (один на KnowledgeBase). Див. LightRAG.
  • Черга + pub/sub + кешRedis (BullMQ-задачі, стрим events). Не durable-правда.
  • Об'єктне сховище → файли/артефакти за посиланням; рядок File тримає storageKey, байти лежать у S3/R2.

Див. також