As-built · Bun · Elysia · Bedrock · AWS Marketplace

Three systems,
one knowledge · one pipeline · one market

A retrieval-augmented Knowledge Base, a Sales Intelligence Dashboard on top of it — and now a multi-tenant commerce layer that sells the AI through AWS Marketplace and meters every call with credits. One Bun process integrates AWS, external SaaS, and three UIs.

8ingest sources
3answer surfaces
6report sections
4RAG agents
3planes: AWS · ext · self
58data tables
01 — The big picture

How the systems relate

The RAG knowledge base is the shared foundation. The Sales Dashboard reuses its ingest pipeline and mirror tables, then layers deterministic analytics + LLM agents on top. The newest layer commercializes both: every tenant gets its own workspace, every AI call is credit-metered, and AWS Marketplace owns subscription truth.

Knowledge Base (RAG)

Normalizes 8 sources into S3 + sidecar metadata, indexes them in a Bedrock Knowledge Base, and answers questions through web chat, Slack/Lark bots, and an MCP server.

Sales Intelligence Dashboard

Fuses HubSpot / Fathom / Redmine / Gmail into a daily pipeline report, a grounded sales chat, and the proactive @SilkSales Slack bot. Deterministic first, LLM second.

Commerce layer (SaaS)

Multi-tenant workspaces sold via AWS Marketplace (ResolveCustomer / GetEntitlements / EventBridge). Every AI call passes a credit gate (402) and lands in a usage ledger; a platform admin console manages plans, tenants and the awsDimension mapping.

02 — Where everything lives

System map: AWS vs external vs self-hosted

All retrieval, all AI inference, the corpus, and the subscription market live on AWS. All source data lives externally at third-party SaaS. The self-hosted process (one Bun/Elysia app behind a reverse proxy) is the only bridge between the two — it pulls from external APIs, writes to AWS, and serves three UIs.

// aws — managed services we call

AWS plane

  • S3 — the corpus: one object + .metadata.json sidecar per ingested doc
  • Bedrock KB — vector index + semantic retrieval (S3 Vectors backend, equals/notEquals metadata filters)
  • Bedrock Agents — console RAG agent (InvokeAgent): orchestration + memory + tool binding
  • Bedrock RuntimeConverse for every plain LLM call (classifier, judge, narrators); Nova Pro → Claude
  • Marketplace APIsResolveCustomer · GetEntitlements (us-east-1) · EventBridge lifecycle
// external — third-party sources & channels

External SaaS plane

  • Google — Gmail corpus, Drive mirror, Google SSO (app + sales portal)
  • Slack / Lark — @SilkAI + @SilkSales bots, tracked channels
  • Fathom / Fireflies — meeting transcripts (webhook + daily reconcile)
  • HubSpot — deals · companies · contacts (10-min cron, Private App token)
  • Redmine — projects · issues · members (the "existing customer" anchor)
// self-hosted — our process & DB

Our plane

  • Bun + Elysia — single process behind BASE_URL reverse proxy; all crons, webhooks, orchestration, metering
  • PostgreSQL — mirror tables, credits, subscriptions, usage ledger, traces, settings (58 models)
  • React 19 + AntD — main app (JWT) · sales portal (SSO) · admin console (SUPERADMIN)
  • Docker Compose — local Postgres 13.5 (+ Redis present but not wired)

What each service is doing

The connection graph: external SaaS is pulled in by our API (cron + webhook), normalized to S3 + Postgres, indexed by AWS, then served back out to chat / Slack / Lark / MCP — with the commerce layer gating every AI call.

ServicePlaneRole in the architectureData direction
S3awsCorpus store — normalized content + metadata sidecar; data source for the Bedrock KB; powers the (currently hidden) knowledge-base file tree.API → S3 → KB
Bedrock Knowledge BaseawsVector index + semantic retrieval. Only filter vocabulary is equals/notEquals — tenant / sensitivity / source / month filters are pre-computed at ingest.S3 → KB → all surfaces
Bedrock AgentsawsConsole-defined RAG agent (InvokeAgent) — owns orchestration, session memory, tool binding + KB filter for chat / Slack / Lark / MCP retrieval.API ⇄ agent ⇄ KB
Bedrock Runtime (Converse)awsEvery plain LLM call: chat time-intent classifier, eval judge, sales narrators (dealRisk / upsell / execAction / narrative), query rewrite. Default Nova Pro → Claude when unlocked.API → Converse
AWS MarketplaceawsSubscription commerce. ResolveCustomer at registration; GetEntitlements (us-east-1) is the entitlement truth; EventBridge lifecycle events arrive at our webhook. Events are triggers only — truth is always re-derived.AWS → webhook → subscriptions
Gmail / DriveexternalEmail + document corpus (3-day cron + push webhook; Drive mirror + extract). Google also provides SSO for the main app and sales portal.Google → API (OAuth)
SlackexternalTwo apps: @SilkAI (KB Q&A, tracked channels, 10-min poll) and @SilkSales (separate app, own signing secret, mid-day push + DM). Signature-verified webhooks.API ⇄ Slack
LarkexternalKB Q&A bot + tracked chats; mirrors the Slack integration surface.API ⇄ Lark
Fathom · FirefliesexternalMeeting transcripts (webhook + a daily reconciliation sweep that retracts deleted recordings from S3 / DB / KB).SaaS → API → S3 + DB
HubSpotexternalCRM: deals / companies / contacts (10-min cron). Needs a Private App token — the whole sales layer runs on empty data until one is set.HubSpot → API
RedmineexternalProjects / issues / members; matched to HubSpot companies to decide "existing customer" for upsell.Redmine → API
Bun + Elysia APIselfThe only compute. 14 crons, 6 webhooks, RAG orchestration, sales aggregation, credit gate, marketplace, admin console. Exposed at /api behind the reverse proxy.hub — all planes
PostgreSQLselfMirror + dedup tables (deterministic queries), subscriptions / credits / usage ledger / traces, settings. Tenant-scoped fail-closed.API ⇄ DB
React apps ×3selfMain app (JWT), sales portal (Google SSO, separate token), admin console (SUPERADMIN, no tenant scope). All typed end-to-end via Eden Treaty.Browser → API

Architect's note: there is deliberately no cross-account AWS infrastructure beyond what's listed — no Lambda, no SQS, no API Gateway of our own. The Elysia process is the single integration point: it owns every credential, every webhook verifier, and every cron. AWS holds the intelligence; the process holds the integration.

02 — Data flow

Ingest → Index → Answer

Every source normalizes content into an S3 object plus a .metadata.json sidecar. One Bedrock Knowledge Base indexes them; three surfaces retrieve with the same filter vocabulary. Every run is also traced (ai_query_traces: intent → retrieval config → context → answer).

Sources
Gmail
3-day cron + webhook
Drive
file mirror + extract
Redmine
projects · issues
Slack / Lark
tracked channels
Fathom · Fireflies
meeting transcripts
HubSpot
10-min cron
Store
S3 Bucket
object + .metadata.json
PostgreSQL
mirror + dedup rows
Index
Bedrock KB
S3 Vectors · embeddings + equals-filter metadata
Ingestion coalescer
1 job at a time · debounce + 409 retry
Answer
Web Chat
stream + citations + trace
Slack / Lark
classifier → agent
MCP Server
4 tools · API key or OAuth
03 — Core idea

Two data planes, kept in parallel

Every source writes both representations. The structured plane makes the next cron tick cheap and powers deterministic queries; the semantic plane powers free-text retrieval.

// semantic

S3 object + metadata sidecar

Indexed by the Bedrock Knowledge Base for free-text retrieval across chat, Slack/Lark and MCP. The sidecar carries the filterable metadata (type, source, month, year…) plus the governance keys tenant_id / client_id / sensitivity.

// structured

Postgres mirror tables

Dedup ("have I seen this id?"), sync bookkeeping, and deterministic queries. A row's existence is what lets the next tick skip already-ingested ids — and what the Sales system reads.

04 — Anatomy

Every source module looks the same

controller.ts

HTTP routes — status, manual "sync now", account CRUD, webhook (signature-verified).

service.ts

Fetch + normalize logic for that source's external API.

ingest.ts

The run loop — fetch → filter → write S3 (+meta) → upsert Postgres → trigger KB.

cron.ts

Interval scheduler wired into main.ts; runs wrapped by runSyncTask.

05 — The contract

Metadata schema & S3-Vectors limits

The store supports only equals / notEquals — no ranges. That single constraint shapes the whole metadata design.

SourceKey filterable metadataNote
sharedmonth year updated_at tenant_idPre-computed at ingest — the equals-only workaround for date ranges. tenant_id is ANDed into every retrieval (fail-closed tenancy).
meetingtype title recording_id date is_externalFathom = type:meeting, Fireflies = type:fireflies.
slacksource channel client_id is_privateclient_id enables strict per-customer scoping.
redminesource project_name ticket_id statusOmits month/year to stay under the ~10-attribute cap.
emailsource account from_addr subject directionAttachments inherit the parent's metadata.

Hard cap: ~10 filterable attributes per chunk. A field is promoted to metadata only if it's filtered on; everything else stays inside the embedded body text. That's why duration_minutes lives in the markdown, not the sidecar. Governance keys (tenant_id, client_id, sensitivity) always take precedence over content keys.

06 — Answer surfaces

Three ways in, one filter vocabulary

Four personas route by classifier — knowledge · customer · generative · ops — plus the sales intent when the KB filter preset points at sales data.

Web Chat

Two backends: bedrock (InvokeAgent — full orchestration + memory) and stream (Retrieve + ConverseStream, real token deltas). Citations dedupe to inline [1] refs; every run writes a trace.

Slack / Lark

A cheap Converse call classifies the question to an enabled agent, then InvokeAgent answers scoped to the current channel. Suffixed with — answered by <byline>.

MCP Server

search_knowledge, get_customer_brief, get_meeting_summary, list_recent_docs — for external MCP clients (Claude Desktop, etc.), authenticated by static per-tenant key or OAuth (RFC 7591 DCR + .well-known discovery).

KB filter preset → RetrievalFilter slackScope · larkScope meeting · fireflies · redmine hubspot · email · drive stored on slack_agents.kbFilter

Status: the knowledge-base page and /api/s3 tree API are temporarily hidden (commit 0ea0633) — KB content is currently consumed through Chat and the Agents pages. A migration to drop S3 / Bedrock KB / Bedrock Agents in favor of self-controlled pgvector RAG + agentic loop is proposed but not implemented (docs/2026-08-16_migration-remove-bedrock-kb-agents.md).

06 — Foundation

Shared tech stack

One Bun process. Elysia API with end-to-end type safety via Eden Treaty; React 19 + Ant Design front-end; Amazon Bedrock for all inference; AWS Marketplace for commerce.

Elysia · Bun

Single-process API, /api prefix, public + 3 guarded groups (auth / salesAuth / adminAuth).

Prisma · Postgres

58 models: mirror tables, sync bookkeeping, subscriptions, credits, ledger, traces, settings.

Amazon Bedrock

Retrieve · InvokeAgent · Converse. Default Nova Pro → Claude when unlocked. Marketplace = ResolveCustomer / GetEntitlements / EventBridge.

React 19 · AntD ×3

Main app + sales portal + admin console; Eden Treaty end-to-end types; Zustand; violet _sales/ui kit.

07 — Persistence

Data model at a glance

58 Prisma models across three groups: the RAG mirror + governance, the Sales analytics, and the commerce/platform core.

RAG / ingestHolds
email_messagesGmail dedup + S3 key + direction
drive_filesMirrored Drive files
redmine_*projects · issues · members · roles · users
slack_messages · tracked_channelsTracked-channel messages
lark_messages · tracked_chatsTracked-chat messages
fathom / fireflies_meetingsTranscripts + attendees
hubspot_*accounts · companies · contacts · deals
slack_agentsPersonas + kbFilter presets (4 intents + sales)
slack_bots · lark_botsPer-account bot credentials + signing secrets
tenant_settingsPer-tenant switches (sensitivity privacy filter…)
sync_tasks · itemsPer-run bookkeeping
eval_test_cases · reports · resultsAgent regression tests (daily sweep)
projects · accounts · webhooks · mcp_keysSource accounts + webhook secrets + MCP keys
Sales-specificHolds
hubspot_deal_snapshotsOne row per deal per day
sales_reportsDaily report payload + deltas
risk_flagsDeal-risk agent output
exec_actionsRanked action list
action_resolutionsAction ack / status tracking
upsell_cardsUpsell opportunities
reps · sales_settingsAE roster + quota targets + cached pipeline config
silksales_sent_alertsPush dedup (mid-day alerts)
Commerce / platformHolds
tenants · usersWorkspaces + members (roles, Google SSO)
plans · subscriptionsPlan catalog + awsDimension mapping; AWS-linked subs
tenant_credits · credit_transactionsBalance + full credit ledger
billing_settingsCredit-per-token rates, search cost, period
marketplace_customersAWS account id → tenant mapping (licenseArn, productCode)
ai_usage_logs · ai_query_tracesMetered AI ledger + per-query traces
tenant_mcp_keys · tenant_webhooksMCP access + per-tenant webhook config
oauth_clients · auth_codes · tokensOAuth for external MCP clients (global)
admin_users · admin_audit_logsSUPERADMIN console + audit trail