G Growreplies docs

Architecture

Data model

The Postgres schema, summarized. Every table that holds tenant data has a workspace_id column (directly or transitively via agent_id) and the corresponding model uses BelongsToWorkspace or BelongsToAgent.

Identity

TableNotes
usersEmail + password (bcrypt). 2FA fields. role: customer or super_admin (the PlatformRole enum). default_workspace_id for switcher pinning.
workspacesName + Stripe customer/subscription IDs + plan_id. app_settings-style overrides live in settings JSON column.
workspace_usersPivot. (user_id, workspace_id, role) where role is owner/admin/member.
invitationsEmail + role + token + 7-day expiry. Belongs to a workspace.

Agents & their config

TableNotes
agentsPersona, theme, system_prompt, guardrails, starter_prompts, allowed_origins, confidence_threshold, language_default, auto_index_visited_pages, is_published, published_version_id.
agent_versionsImmutable snapshots created on every Publish. The runtime reads from these, not the agent row.
behavior_rules(agent_id, kind, conditions JSON, action JSON, enabled, priority).
cta_rulesSpecialized behavior rules for clickable calls-to-action.
curated_answers(agent_id, triggers[], answer_md, citation_url).
experimentsA/B configurations on behavior rules.

Knowledge

TableNotes
sources(agent_id, type, status, config JSON, last_synced_at, error). type: url/sitemap/feed/text/notion/google_doc/auto.
documents(source_id, url, title, content, lang). One per crawled page or pasted text.
chunks(document_id, idx, content, token_count). The unit that's embedded.
integration_connections(workspace_id, provider, encrypted_token, scope). Notion, Google Drive, Slack.

Chunk embeddings live only in the vector store (Vectorize / Qdrant) — the Postgres chunks table holds text + metadata, not vectors. Metadata is mirrored as labels on the vector point so retrieval can filter by agent_id.

Conversations & messages

TableNotes
visitors(agent_id, anonymous_id, ip_hash, ua, first_seen_at, last_seen_at, visit_count). The anon_id ties widget reloads to the same row.
conversations(agent_id, visitor_id, page_url, started_at, lang, claimed_by_user_id, claimed_at, cleared_at, is_playground).
messages(conversation_id, role, content, citations JSON, latency_ms, created_at). Roles: user / assistant / human-agent.
leads(agent_id, conversation_id, name, email, phone, fields JSON). Unique on (agent_id, email) for dedup.
content_gaps(agent_id, sample_question, occurrence_count, sample_conversations[]). Created by DetectGapJob when low-confidence is flagged.

Billing

TableNotes
plans(name, slug, monthly_conversations, price_cents, features JSON, is_active, stripe_product_id, stripe_price_id).
plan_subscriptionsCashier subscription mirror. (workspace_id, stripe_subscription_id, stripe_status, ends_at).
usage_events(workspace_id, kind, quantity, occurred_at). Aggregate by month for the quota gate.
app_settingsSingleton row. Stripe / mail / branding overrides + LLM / vector / crawler config not in env. Encrypted casts on every secret.

Operations

TableNotes
audit_logs(actor_id, workspace_id, action, target_type, target_id, metadata, created_at). Every privileged action.
jobs / failed_jobsStandard Laravel queue tables. failed_jobs drives /admin/jobs/failed.
notificationsStandard Laravel notifications.

Indexes that matter

  • conversations(agent_id, visitor_id, started_at desc) — visitor resume lookup runs every init.
  • messages(conversation_id, created_at) — chat history hydration.
  • chunks(document_id) — fast deletes on source removal.
  • usage_events(workspace_id, occurred_at) — quota gate.
  • leads(agent_id, email) — dedup constraint, unique.
  • workspace_users(user_id, workspace_id) — unique pivot.

Encryption at rest

Sensitive columns use Laravel's encrypted cast: integration OAuth tokens, Stripe secrets in app_settings, mail passwords, custom LLM API keys. The encryption key is the standard APP_KEY — back it up the same way you back up the database.

Soft deletes

Most tables hard-delete on cascade. The exceptions:

  • plans — never destructively deleted (FK from workspaces, FK from invoices). Soft via is_active.
  • invitations — cleared by a daily cleanup job after expiry.