Data
Morpheus works across two data planes, and knowing which one a rule reads from explains most of its behaviour.
| Plane | Store | Role |
|---|---|---|
OLTP |
PostgreSQL (Supabase) |
The operational database, shared with the Payability web application. Every write Morpheus makes to its own state lands here. |
OLAP |
BigQuery |
A periodic export of the HubSpot portal plus a mirror of application tables. Every re-engagement segment and priority score is computed here. |
|
Re-engagement handlers read from BigQuery and write to HubSpot; PostgreSQL is consulted only to resolve identity and ownership. BigQuery is an export, so a segment can lag reality by the export interval — a contact who booked a deal five minutes ago may still appear in a segment. |
Pages
| Page | Contents |
|---|---|
The core entities and how they relate |
|
The 13 SQLAlchemy models and their conventions |
|
Every repository method, with signatures |
|
All 20 |
|
Watermarks, cursors, and kill switches |
|
Datasets, export shape, and deduplication |
|
All 11 views, with purpose and consumer |
|
The full authoritative schema |
Schema ownership
The PostgreSQL schema is owned by the web application’s Prisma schema, not by this repository. There are no migrations here.
Consequences:
-
A column Morpheus needs must be added to the Prisma schema first, migrated there, and only then mapped in
morpheus_data_layer. -
The ORM mirrors the schema but does not define it.
ForeignKey("subscriptions.id")is deliberately not declared — that table is not managed here and the constraint is enforced at the database level only. -
Attribute names are camelCase and match the column exactly, so a model is a faithful, greppable reflection of the schema.
The current schema is reproduced verbatim at Prisma schema reference.
Tables Morpheus writes
| Table | Written by |
|---|---|
|
enrichment-scorer (upsert, enrichment, score), morpheus-hubspot ( |
|
enrichment-scorer, enrichment-scraper (BigQuery sync), morpheus-hubspot (ids, lifecycle, heat) |
|
enrichment-scraper (BigQuery sync), morpheus-hubspot (opt-out webhook) |
|
enrichment-scraper (BigQuery sync), phone-agent (Twilio intel), morpheus-hubspot (Five9) |
|
morpheus-hubspot (dialer sync, Five9 sync) |
|
morpheus-hubspot (engagement id, sync status, FK repair) |
|
morpheus-hubspot (engagement sync, task logs), phone-agent (reminders), enrichment-scraper (lifecycle notes) |
|
morpheus-hubspot (create, status), phone-agent (escalation) |
|
morpheus-hubspot ( |
|
enrichment-scraper (cursor), morpheus-hubspot (watermarks), round-robin (cursor) |
|
email-agent (categorisation) |
|
email-agent (category) |
Everything else in the schema — campaigns, sequences, phone numbers, domains, AI agent configuration, profiles — belongs to the web application. Morpheus reads some of it and writes none of it.
Tenancy
Every domain table carries subscriptionId. Production runs a single subscription,
b191a905-5d19-4fa0-941b-d8eb81c0c477. Four of the five functions read it from an environment variable and reject
mismatched events; email-agent derives it from the row it loaded. See
tenant isolation.
Connection handling
All five Lambdas call init(database_url, pool_size=0) at cold start, which selects
SQLAlchemy’s NullPool, and the connection string must be the Supabase Transaction mode
pooler URL (port 6543). The reasoning is in
pooling — it matters enough that getting it wrong
exhausts the database’s connection limit under concurrency.