PostgreSQL 16 with the pgvector extension. SQLAlchemy 2.0 async ORM. 45 models live in agnt-backend/app/db/models.py— a single file so the whole schema is greppable in one place.
Engine configuration
The async engine is created in app/db/session.py. It runs with a large pool because scheduler jobs, webhook handlers, and the A2A public API all contend for connections.
Alembic with sequential numbered migrations. Every migration file in agnt-backend/alembic/versions/ is named NNN_description.py. Upgrade paths are chained via down_revision. Run alembic upgrade head after every pull.
All models
Models are grouped by domain for navigation. Line numbers are pinned to the current app/db/models.py.
Identity & channels
Model
Line
Purpose
User
34
Primary user row. Holds city, language, cp_agent_id, credits.
Claim requests for venues listed publicly but not yet owned.
VenueChunk
460
pgvector chunks of venue knowledge for RAG retrieval.
Cities & lists
Model
Line
Purpose
City
470
Supported cities: Canggu, Seminyak, Ubud, etc.
CityAmbassador
487
Local ambassadors promoting AGNT in a given city.
UserList
444
User-curated venue lists (wishlists, favourites).
Agents & A2A
Model
Line
Purpose
A2AEnvelope
525
Local mirror of an outbound A2A envelope. Status transitions via ENVELOPE_TRANSITIONS.
AgentCard
708
Well-known AgentCard published by each agent on the network.
CardResponse
729
Per-user responses to generated shareable cards.
Commerce & accounting
Model
Line
Purpose
CommerceLedger
549
Fee collection ledger. Pairs with Stripe events.
B2BOnboardingSession
501
Multi-step venue onboarding state machine.
AccountingDocument
678
Invoices, receipts, credit notes for business clients.
AccountingLedgerEntry
692
Double-entry accounting lines for each document.
Social graph
Model
Line
Purpose
UserFollowsTaste
566
Taste-based follow relationships between users.
PushSubscription
577
Web Push subscription state.
Observability
Model
Line
Purpose
AnalyticsEvent
591
Generic analytics sink for product events.
ConversationSnapshot
601
Point-in-time snapshots of a conversation for replay or audit.
AffiliateClick
617
Click-through events on affiliate links.
ErrorLog
743
Structured error log with context metadata.
Dupe search & diet
Model
Line
Purpose
PriceWatcher
628
Standing price-watch jobs on Shopee/Tokopedia/Lazada products.
FoodDiary
643
Calorie scan history per user with macro breakdowns.
Transport
Model
Line
Purpose
TransportBooking
661
Lalamove courier bookings with quote + confirmation state.
Fleet memory
Model
Line
Purpose
ProjectMemory
761
Project-scoped memory entries for the agent fleet.
CRM
Model
Line
Purpose
CrmContact
787
Venue-side contact records.
CrmDeal
810
Sales pipeline deals with value and stage.
CrmNote
834
Free-form notes attached to contacts or deals.
CrmActivity
846
Activity timeline entries (call, email, meeting).
Knowledge graph
Model
Line
Purpose
EntityNode
868
Typed entity node for the AGNT knowledge graph.
EntityEdge
884
Directed edges between entity nodes with a relation type.
Conventions
UUIDs as primary keys, generated server-side via uuid_generate_v7() from the pgcrypto-compatible extension.
Timestamps are timestamptz with server_default=func.now().
Status columns have both an application-level state machine in a2a_enums.py and a DB CHECK constraint from migration 016 onwards. Application validation runs first; the DB check is belt-and-braces.
Money is stored as Numeric(10, 2) in the display table and as Integer cents in high-throughput ledgers.
Locking & concurrency
Two patterns matter when touching money or credits:
credit_manager.py always uses SELECT ... FOR UPDATE on the user row before adjusting balance.
commerce.py uses Stripe idempotency keys on every payment operation and, for critical money jobs, takes a Postgres advisory lock as a fallback to the Redis-based scheduler lock.