Схема БД
Постоянная форма данных 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.