Case Study AI / SaaS Agentic Multi-tenant

Bevra: an AI-augmented LinkedIn operating system

Multi-tenant SaaS on top of an agentic content-generation engine. The two systems talk over a small webhook contract. This page walks through what's on each side, the seam between them, and the patterns that transfer to an enterprise build. Live product at bevra.app. Telegram bot at @BevraContentAdvisorBot.

FastAPI React + Vite Postgres / Supabase Claude Sonnet 4.6 Claude Haiku 4.5 n8n + Claude Haiku 4.5 pgvector Fly.io
Bevra AI Studio: Today's Suggested Post from the Next Post Engine (angle, why-now, draft outline, sources)
Bevra's AI Studio. The "Today's Suggested Post" panel renders the structured output the Next Post Engine returns over the webhook: angle, why-now, draft outline, and grounded sources.

The problem

A solo LinkedIn operator runs three jobs off a stack of Notion, ChatGPT, Buffer, and spreadsheets. They track relationships in a funnel. They find high-signal posts and threads to engage with. They produce content on a schedule. Each job lives in a different tool. The switching cost is what kills output.

The same fragmentation exists in enterprise settings where a customer-facing operator has to unify a CRM, a content workflow, and a generation loop. This case study is written for teams scoping that kind of system: agents, tool use, multi-turn workflows, evals.

Approach

Bevra is the operator surface. It holds the relationship funnel, keyword-driven post discovery, saved-post collections, an on-demand post-angle generator, and an ICP-scoring pass that ranks contacts against a prose ICP definition. Every LLM call inside Bevra is single-shot, structured-JSON, and gated by per-tenant token quotas.

Content generation is offloaded to the Next Post Engine, a separate self-hosted agentic backend. The engine owns retrieval, ranking, judgement, and human approval. Bevra fetches the approved suggestion over a webhook and renders it in its AI Studio panel. Each side ships and scales independently.

Architecture

Bevra SaaS React + Vite FastAPI Postgres / Supabase Claude Sonnet 4.6 Claude Haiku 4.5 Supabase JWT Per-tenant quotas Stripe billing Webhook seam X-API-Key GET /webhook/next-post POST /webhook/link POST /webhook/plan Next Post Engine n8n AI Agent node Claude Haiku 4.5 Sources: HN / RSS / Telegram pgvector retrieval Synthesis → Judge → Eval Gate threshold 7.5, drop below HITL via Telegram approval Langfuse · LangSmith evals Fly.io
GET /webhook/next-post?external_user_id=… returns the latest approved suggestion for a tenant. Always 200; missing suggestion becomes {available:false}.
POST /webhook/link binds a Bevra tenant to a Telegram chat_id using a one-time code.
POST /webhook/plan mirrors plan changes from Bevra when Stripe fires.

Where the agentic capability lives

Enterprise readers scoping a similar build need to know which parts are agentic and which parts are straight LLM calls. Bevra and the Next Post Engine split cleanly on that boundary.

Inside Bevra: LLM-augmented, single-shot

Every Claude call inside Bevra is a single messages.create with a system prompt and one user turn. Claude Sonnet 4.6 powers on-demand post-angle generation in AI Studio. Claude Haiku 4.5 scores contacts 0–100 against a user-defined prose ICP. Outputs are structured JSON parsed with a tolerant regex extractor. Per-tenant Anthropic token quotas (100k / 1M / 10M by plan) gate every call at request time.

Bevra ICP scoring: a contact ranked against the user's ICP definition
Haiku 4.5 scores every contact 0–100 against the operator's ICP definition and shows the rationale. One LLM call per contact, batched.

Inside the Next Post Engine: fully agentic, multi-turn

The agent lives here. An n8n AI Agent node drives Claude Haiku 4.5 through a structured-output parser. A producer run flows through synthesis, per-idea retrieval, judge, deterministic eval gate, human approval. When the operator taps Skip on Telegram, the next run's retrieval steers away from what was rejected. An hourly dispatcher fans out to the tenants whose local hour matches their digest time. Each producer run takes 30 to 90 seconds and writes a row into app.runs with counts, scores, tokens, and errors.

Agent tools

Source ingest

Hacker News + RSS + public Telegram channels via a Telethon MTProto ingester. Per-tenant source config.

pgvector retrieval

Gemini embeddings over headline + why_now. Dedup on re-sync. Retrieval steers away from suggestions the operator has skipped.

Synthesis chain

Structured-output LLM pass turning raw items into candidate post ideas (headline, angle, why-now, draft outline, sources, self-score).

Judge chain

Per-idea grounded LLM pass. Scores each candidate 1–10 against the tenant's own accepted / declined / profile evidence retrieved from pgvector, and returns the retrieval IDs it cited.

Eval Gate

Deterministic Code node. Takes the judge's score and applies penalties: -5 for ungrounded sources, -1.5 for hype or trend-inflation phrasing, -5 when the judge cites only rejected past evidence. Drops anything below 7.5/10. Dropped ideas never reach Telegram.

HITL approval

Surviving candidates DM the operator on Telegram with [✅ Approve] / [❌ Skip] inline buttons. The callback handler is idempotent on status='pending'.

Next Post Engine: Telegram approval buttons on a suggestion
The approval surface is an inline Telegram callback. The same shape ports to Slack, Teams, or an in-app inbox.

Observability & evals

Every producer execution writes a row into app.runs: item counts, judge scores, tokens spent, errors. Langfuse hosts per-tenant dashboards that correlate judge scores with the operator's approve/skip decision. A weekly n8n cron refreshes them.

Prompt and system changes are validated offline before they merge. A LangSmith experiment runs the pipeline against the golden dataset (next-post-golden_v1) with named evaluators: citation grounding (citation_alignment, avg_citation_alignment, n_citations_total), agreement with human labels (matches_human_label, pearson_r), and structured-output validity (wrapper). Each candidate change becomes an experiment tagged against a baseline; the bar charts show whether the change helped, hurt, or moved nothing. A parallel Python harness (evals/run_eval.py) runs the same deterministic criteria from evals/criteria.py. That file also defines the runtime Eval Gate's penalties. It exits non-zero on regression so it can gate CI.

LangSmith next-post-golden_v1 experiments with citation_alignment, matches_human_label, pearson_r evaluators
LangSmith holds the golden dataset (next-post-golden_v1) and the experiment history. Each bar is one experiment run against the same 34 examples with a tagged baseline for comparison.

Multi-tenant & billing patterns

Every request carries a Supabase JWT (ES256) verified via JWKS in a FastAPI dependency. Every row in Postgres is keyed on user_id. A Stripe webhook drives UserSubscription; only active and trialing count as paid. Per-plan caps run on both apify_actor_runs and anthropic_tokens. check_anthropic_quota gates every LLM call at request time. record_anthropic_usage reads the real response.usage object and increments per-tenant counters.

Reliability & failure isolation

The webhook contract is 200-only, with an {available:false} sentinel when nothing is ready. When the engine is unreachable, Bevra shows an empty AI Studio panel instead of an error. The ICP-scoring loop isolates failures per contact. One bad LLM response never poisons the batch. URL canonicalization plus an Alembic backfill migration healed cross-source duplicates without downtime. The Telegram approval callback is idempotent on status='pending'. A re-fired callback never double-approves a suggestion.

What an enterprise team can reuse

The webhook seam

Three endpoints (GET /latest, POST /link, POST /plan) let the SaaS front-end and the agentic backend ship on separate cadences without coupling their release cycles.

Two eval surfaces, one criteria file

A deterministic runtime gate keeps bad candidates off the human's plate. An offline LangSmith harness runs the same criteria over a golden set so prompt changes can be scored before they ship. Both read the same criteria.py. Retrofit is more expensive than build.

The HITL surface

An inline Telegram callback is the whole approval UI. The same interaction shape ports to Slack, Teams, or an in-app inbox without touching the agent.

Quota + billing at call time

Token gates at request time. Real usage read back from the provider's response.usage. Paid-plan enforcement runs inline with the request, not on a nightly reconciliation.


Comparing notes

Get in touch if you're scoping an agentic system with tool use, multi-turn workflows, and evals, and any of the patterns above are worth pressure-testing.