---
title: "One platform, two opposite audiences"
url: "https://toddpaulbrownjr.com/writing/one-platform-two-audiences/"
author: "Todd Paul Brown Jr."
description: "Two working Sadyr demos — public insurance explainer and gated sports rulebook assistant — run on one platform with opposite audiences and no re-architecting."
kind: "article"
updated: "2026-09-26T02:50:36+00:00"
---

# One platform, two opposite audiences

I built two working demonstrations of Sadyr — the revenue agent — on the same production platform, for opposite audiences, with no re-architecting. One is a public plan explainer for an insurance provider, answering questions from 95 approved documents through a visitor chat widget. The other is a sign-in-gated rulebook assistant for a national amateur sports league's managers, serving 98 documents with audience restrictions: 89 public, 9 managers-only. Both run on the same API process at the same time.

The insurance demo uses sample program content — fictional plans and coverage details, disclosed as such on the page. The sports league demo uses real rulebook documents. Same platform, same Content Ops knowledge pipeline, opposite tone and access rules.

This is what happens when you own the center.

## Why it matters that both run together

Sadyr is built on one principle: own the center; wrap, rent or swap the edges. A small kernel owns the business logic — qualification, routing, scoring, policy, audit. The CRM, the scheduler, the chat channel and the knowledge store sit behind adapters, each with a mock. HubSpot is the first adapter, not the spine. Each client is one configuration file.

That architecture is why standing up the gated audience didn't require a second platform or a fork of the codebase. It required a data model change — an `audience` field on each document — and a configuration block: `surfaces` per client. The sports league has four surfaces total: three public (one each for prospects, teams and athletes) and one gated for managers, requiring visitor Google sign-in.

Every active client's visitor chat is mounted at its own path in one process — what I call the chat fleet. Tenant isolation is verified live: one client's widget answers only from its own knowledge base and abstains on another client's questions. The insurance widget has never seen the league's rulebook; the league's public surfaces have never seen manager-only content.

The lesson I drew: if a knowledge platform needs a schema change and a new codebase every time an audience is added, the platform boundary is in the wrong place. The audience control belongs on the document, not on the deployment.

## How the gated demo works

The league's manager surface answered real ruling questions from manager-only handbook content with titled sources. The public surfaces abstained cleanly. No manager-only content leaked across the boundary, verified end to end with test conversations.

The retrieval filter runs server-side: when a visitor hits a public surface, Sadyr passes `audience: "all"` to the knowledge adapter. Documents tagged `"managers"` never enter the embedding search. When a signed-in manager visits the gated surface, the filter passes `audience: ["all", "managers"]` and both pools are searched.

The sign-in itself is Google OAuth, handled by the chat widget. The manager clicks "Sign in with Google," the widget sends the credential to the API, and the API verifies it against a list of approved email domains. If the domain matches, the session gets manager-level access. If it doesn't, the visitor stays on the public surface.

That approved-domain list lives in the client's configuration file, next to the surfaces block. Adding a new allowed domain is a one-line config change, not a code deployment.

## What broke and how I fixed it

The first time I tested the manager surface, I forgot to tag the manager-only documents. All 98 documents in the league's knowledge base were tagged `audience: "all"` by default. The public surfaces answered manager questions correctly because there was no boundary to enforce.

I caught it in staging by asking a manager question on the public prospects surface and getting a confident answer with a titled source from the manager handbook. I added the `audience` column to the documents table, ran a migration to default every row to `"all"`, manually tagged the 9 manager documents, re-synced the knowledge base, and tested again. The public surface abstained; the manager surface answered. Total time from discovery to fix: about 90 minutes.

The second thing that broke was the sign-in redirect. The widget sent the visitor to Google, Google sent them back to the widget with a credential, but the widget lost the original chat context and opened a new conversation instead of resuming the old one. The visitor saw "You're signed in" and then had to repeat their question.

I fixed it by storing the conversation ID in session storage before the redirect and restoring it on return. The widget now picks up exactly where the visitor left off, signed-in session attached. That fix was 11 lines added, 3 removed.

## The insurance demo, and why the content is sample

The insurance demo runs on 95 approved documents synced one-way from Content Ops into Sadyr, embedded, and served through a branded public demo page. The plan and coverage content is explicitly sample — fictional program content created to show how the agent works, not real insurance products. The demo page discloses that.

Why sample content? Because real insurance program documentation carries regulatory weight, and a demo agent that invents or misquotes a coverage limit is worse than no agent. The sample content lets prospects see Sadyr answer plan questions with titled sources and abstain cleanly on out-of-scope topics, without the legal exposure of real product content on a public demo.

The knowledge pipeline is the same as the league demo: Content Ops drafts the pages, I approve them, they sync to Sadyr, Sadyr embeds them and serves them through the chat widget. The only difference is that the insurance demo's pages were written as fictional plans rather than real ones.

The demo's retrieval is verified end to end: I asked it questions about the sample plans, and it cited the correct documents by title. I asked it questions outside the knowledge base, and it abstained. The confidence gate — the retrieval score threshold that decides whether Sadyr is confident enough to answer — is set deliberately lenient for the demo, because a prospect testing the widget expects more answers than a production deployment would give.

## When this converged with the industry

I built the first version of the audience filter in May 2026, about the same time Google Cloud Next (9 April 2025) announced ADK and Agent Engine and said "Every enterprise will soon rely on multi-agent systems." The gated demo went live in June 2026, a few months after OpenAI released the Responses API and Agents SDK (11 March 2025), citing customers struggling with custom orchestration logic.

The architecture I landed on — tenant isolation, per-client configuration files, audience control on the document rather than the deployment — is the same orchestration layer Google Cloud described in March 2025 as "a new paradigm for LLM-powered applications to handle more complex tasks." I wasn't inventing it; I was building around the same architectural insight the industry was converging on while it was still naming the abstraction.

## What owning the center lets you do

Both demos run on Sadyr's production API, which went live 30 June 2026. Blueprint to production in twelve days: first commit 18 June, 63 commits on eight working days. Claude Code wrote the code, tests and documentation; I directed, decided and validated. The platform carries 2,816 automated tests, recorded from the Sadyr README and not re-run for this article.

The insurance demo and the league demo are both working demonstrations on that production platform. Neither has paying customers yet, so the status word is demo, not sold.

The point of owning the center is that adding a new audience — employees, partners, authenticated customers, regional variations — doesn't require a second platform. It requires a configuration block and a database column. The CRM adapter, the knowledge adapter and the chat widget are already built to handle it.

That's the trade: more engineering up front to own the center, and then every new client or audience is configuration, not code.
