Skip to content

Multi-Tenant AI BackendProduction

Shared AI Platform Backend

We run the AI features of four client products (a medical platform, a community platform and two storefronts) on one production .NET backend. It routes each task across eight provider types, runs agent-written code in throwaway Docker containers, connects agents to MCP servers, and holds risky tool calls until a person approves them.

  • One AI backend, four products
  • 8 provider types, routed per task
  • Risky tool calls wait for a human
.NET 10EF CoreMicrosoft Agent FrameworkMCP C# SDKAWS BedrockDockerPineconeHangfire

SaaS & customer support · Updated

The challenge

Four products needed clinical-note generation, grounded chat, page design, agentic automation and embedding pipelines. Building that four times would have meant four bills, four sets of prompts, and four places for a tenant's data to leak.

What we built· with the Bracket Mind team

  • Sandboxed code execution: the model writes Python or Node, and it runs in a fresh Docker container — 1 CPU, 512 MB, 256 processes, non-root, no credentials inside — force-removed after the run, with the agent's steps streamed to the browser over SSE
  • Model routing from the database across eight provider types — Bedrock, Azure OpenAI, Anthropic, OpenAI, Gemini, NVIDIA NIM, Hugging Face and any OpenAI-compatible endpoint — failing over to the next ranked model on transient errors
  • Agents tenants extend without a deploy: markdown skills loaded on demand, HTTP tools defined as data, and MCP servers whose tools are discovered at the start of each run
  • Human approval for risky tool calls — write requests and every MCP tool pause the run and ask a person over the same SSE stream; a denial or a five-minute timeout tells the model no
  • User-defined tools locked down against SSRF: hostnames resolved and private, loopback and cloud-metadata addresses refused, redirects never followed, responses capped, credentials encrypted at rest
  • A RAG pipeline — embeddings, Pinecone retrieval, Cohere reranking on Bedrock, then synthesis — behind chat-abuse guardrails covering prompt injection, toxicity, credential probing, IP reputation and persistent bans
  • One AI gateway for all four products: providers, models and per-task model choices set in an admin console without a deploy; every paid call logged with tokens, latency and cost, estimates flagged as estimates; cost reports by product, user, model and task; an operations dashboard for AI spend, errors, job queues and abuse blocks; and a daily check of model rates against published price lists
  • Per-call AI cost attributed to the right tenant, and deny-by-default access to every AI feature, granted per tenant

How it fits together

  1. Four products

    • Medical platform

    • Community platform

    • Two storefronts

  2. One .NET 10 backend

    • AI gateway

      Per-task model choice, cost logged on every paid call, admin console

    • Agents

      Skills, HTTP tools and MCP servers; risky calls wait for a person

    • RAG pipeline

      Pinecone retrieval, Cohere reranking, chat-abuse guardrails

  3. Execution

    • Docker sandbox

      A fresh container per run: 1 CPU, 512 MB, no credentials

    • Background jobs

      Hangfire

  4. Model providers

    • 8 provider types

      Bedrock, Azure OpenAI, Anthropic, OpenAI, Gemini, NVIDIA NIM, Hugging Face, OpenAI-compatible; failover on transient errors

Simplified, and drawn only from this case study: what it shows, not every moving part.

Which engineering decisions shaped it?

  1. A capability map, not Microsoft.Extensions.AI, decides what each provider can do

    Instead of
    Microsoft.Extensions.AI's common chat interface as the one contract for every provider
    Why
    That common surface can't carry prompt caching, extended thinking, streamed tool events or response prefill, which the cache-heavy and agentic features depend on; today only the Bedrock path carries them. Microsoft.Extensions.AI still runs inside every other provider, and the map decides in one place what each one can take, logging any feature it drops instead of losing it silently.
  2. Every MCP tool call waits for a person

    Instead of
    Gating only calls that write, as we do for HTTP tools, or trusting an MCP server once it is registered
    Why
    An MCP server's tool names, descriptions and results are written remotely and can change after registration, so we treat them as untrusted. A denial or a five-minute timeout reaches the model as a refusal it plans around, and a run with no person on the stream gets no MCP tools at all.
  3. MCP results are capped at 24,000 characters

    Instead of
    Passing each result through whole and trusting servers to keep them small
    Why
    An agent loop re-sends every earlier tool result on each round, so one oversized payload is billed again and again: a single 654K-character result cost 2.1M input tokens, about 28 times a normal page-builder run. A capped result is rebuilt to stay valid JSON, with a note telling the model what was cut.
  4. The code sandbox keeps its network on and holds no credentials

    Instead of
    A sandbox with networking switched off
    Why
    The agent writes code to fetch and scrape web pages, which a network-off container can't do. So the boundary is what's inside: no API keys or tokens, and anything that needs a credential runs as a tool in the API process. Each run gets a fresh container that is force-removed afterwards.
  5. Agent runs stop at 200 tool calls, a limit we set ourselves

    Instead of
    The agent framework's default cap of 40 iterations (model round trips) per request
    Why
    A migration makes one create-project call per project, so 40 couldn't finish a 67-project job, and when the framework's cap trips, its closing call is rejected by Bedrock. Our 200-call limit is a cost circuit-breaker for runaway retry loops, and the framework's own cap sits just above it, so ours always fires first.

Why it matters

Four products, one AI backend, no duplicated work — and the client can see what each model and feature costs them instead of getting a surprise bill. If a request ever reaches across tenants it simply 404s, so a healthcare product and a consumer product safely share the same infrastructure.

Our founder's part

Haseeb Attaullah: Backend engineer on the AI layer — the sandboxed code-execution tool, the provider registry and its failover policy, tenant-defined agent skills, HTTP tools and MCP server connections, human approval for risky tool calls, deny-by-default AI task access, cost attribution for agents and background jobs, and encrypted per-tenant API keys. The gateway's routing service, cost reports and dashboard were built by a teammate.

Building something like this?

Tell us what you're building. We reply within 0–4 hours with honest feedback on scope, timeline and whether we're the right fit.

contact

Have a product to ship or an AI feature to get right?

Tell us what you're building. Replies within 0–4 hours, with honest feedback on scope, timeline and whether we're the right fit.

Book a 30-min call