The first chat I ran on Sadyr's live server gave me an answer I trusted, a confidence score above threshold, and a booking confirmation for a meeting that didn't exist. No email, no calendar invite, no choice of time — just "You're booked."
The scheduler was a mock. It had passed every test because I'd written the tests against the same mock interface, and both the real adapter and the fake one returned the right shaped object. The gate looked at structure, not reality, and my first production visitor got a promise the system couldn't keep.
I fixed it the same day: a scheduler that isn't real never confirms a meeting, and nobody gets booked without leaving an email. Booking now runs through HubSpot Meetings and offers real open slots in production; the first real booking is still waiting on a CRM permission fix, so it's not called live yet.
That incident is why Sadyr's architecture rule became: own the center; wrap, rent, or swap the edges.
The layer that makes it yours
Sadyr is a revenue agent. It answers pre-sales questions from approved sources only, qualifies the visitor, and hands a sales rep a briefed lead. Most website chatbots will answer anything, which is the problem — a revenue agent that invents a price or a policy is worse than no agent.
The part that makes Sadyr what it is — qualification, routing, scoring, policy enforcement, audit logging — lives in a small kernel. The CRM, the scheduler, the chat channel and the knowledge store sit behind adapters, each with a working mock. HubSpot is the first adapter, not the spine.
That architecture is deliberate. The business logic owns the decision. The edges — where the meeting gets written, where the lead lands, which embedding service scores the documents — are implementation details behind an interface.
Blueprint to production in twelve days: first commit 18 June 2026, live 30 June, 63 commits on eight working days. Claude Code writes the code, tests and documentation; I direct, decide and validate. It's three apps (an API, an operator console, and a website widget) and 17 packages, carrying 2,816 automated tests recorded from the README and not re-run for this description.
About the same time I was building that architecture into Sadyr, OpenAI (11 March 2025) and Google Cloud (24 March and 9 April 2025) were converging publicly on orchestration as its own layer — what would later be called a harness — around the model. I didn't invent the pattern; I built around the same insight while the industry was still naming it.
Why it matters that one platform can serve two demos
Sadyr runs two working demonstrations on the production platform, both anonymized. One is a plan explainer for an insurance provider, built on sample program content. The other is a rulebook assistant for a national amateur sports league's managers, built on 98 real documents behind a sign-in.
The public insurance explainer and the gated managers' assistant differ only by configuration. Documents carry an audience tag, surfaces carry a policy, and public surfaces decline cleanly on manager-only content. One codebase, one deployed API, two completely different products.
That only works because the platform owns the policy layer and the adapters are swappable. The insurance demo uses lenient retrieval settings for sample content. The league demo uses stricter thresholds and enforces sign-in. Both run on the same kernel, the same tests, and the same production infrastructure.
When a prospect asks whether Sadyr can handle their use case, I don't rebuild. I write a configuration file.
What broke when the mock and the real thing looked the same
The answer gate that broke on the first live run had passed 2,816 tests. It checked a retrieval confidence score against a threshold — sensible — and divided the score by the number of words in the question.
Against the mock adapter, which returned a raw count of matched words, that looked right. Real embeddings return scores around 0.4 to 0.6. After dividing by word count, those became 0.03 to 0.05, well under the 0.62 threshold, and every multi-word question abstained.
Found on the first live run; fixed the same day. Fifteen lines added, six removed.
The lesson I kept: a mock that passes structure checks isn't the same as a mock that behaves like the real system under the conditions that matter. The gate should have tested confidence semantics, not just the shape of the return value.
How Content Ops uses the same rule
Content Ops is the console that runs Adroit's knowledge and content. It has discovery notes, an open-questions tracker, a knowledge base where documents move through an editorial lifecycle with version history, a content plan built from keyword data, and a drafting pipeline that writes from approved sources.
The approved knowledge base publishes one-way into Sadyr, so the revenue agent answers from the same documents that passed editorial review.
The first time Content Ops ran at real concurrency — rewriting Adroit's own live website — it produced three production defects in one night. All three traced to the same habit in agent-written code: writing to a database table by deleting the whole table and re-inserting every row. Harmless at one job at a time; disastrous when two jobs run together.
The runs table failed within seconds. Fixed that evening.
The artifacts table carried the same pattern. I found it through a postmortem-export tool I'd built hours earlier that same night — it summarized 227 failure entries into one screen: 29 duplicate-key errors and 151 stopped projects. Fixed the same night.
The jobs-table polling surfaced later: the content-plan screen asked for each of roughly 92 cards separately every 10 seconds, and each request read the whole jobs table. Found in a later log upload: 311 of 323 API errors were pool timeouts. Fixed with one batched request.
The postmortem exporter paid for itself before midnight on the night it shipped. Agent-written code can carry one bad habit across several unrelated tables, so finding it once doesn't mean it's gone.
Content Ops now runs at version 5.12 in production, with 839 TypeScript tests and 57 Python tests passing, measured at commit f861f98 and not re-run for the current version. It drafted 92 of 95 planned pages in two days of batch runs. Seventy-four of those pages were updated in place and 17 created, live 23 September 2026.
The knowledge base, the editorial workflow and the quality gates are owned. The runner that executes the drafts, the database that stores them, and the LLM that does the writing sit behind adapters with mocks, so when a faster model ships or a cheaper runner appears, I swap the edge and the center doesn't change.
The rule for other builders
Own the part that makes your system yours — the policy, the qualification logic, the retrieval semantics, the approval gates — and treat everything else as a rented or swappable edge.
Put the CRM behind an adapter. Put the scheduler behind an adapter. Put the embeddings service, the LLM provider, and the chat widget behind adapters. Write a mock for each one that behaves like the real thing under the conditions your business logic cares about, not just one that returns the right shaped object.
When you build it that way, your tests run fast on mocks by default, your production dependencies are swappable without touching the kernel, and the part you actually care about — the decisions, the policy, the thing that makes your product different from everyone else's — stays in your control.
HubSpot, ChatGPT, Claude, your embeddings vendor, your hosting provider — they're all edges. Wrap them, rent them, or swap them when something better ships.
The center is yours.