Backend Integration

This page is the end-to-end recipe for putting Mango behind your application: your users log in to your app, ask questions, and get answers scoped to what they are allowed to see.

The one rule: Mango never authenticates. It receives a user object you already trust and enforces the permissions you attach to it. See Access Control for the model.

1. Build the agent once

The agent is a long-lived object: build it at application startup, not per request.

# mango_setup.py
import os
from mango import MangoAgent
from mango.tools import ToolRegistry, build_mongo_tools, SaveTextMemoryTool
from mango.integrations.anthropic import AnthropicLlmService
from mango.integrations.mongodb import MongoRunner
from mango.integrations.chromadb import ChromaAgentMemory

llm = AnthropicLlmService(model="claude-sonnet-4-6", api_key=os.environ["ANTHROPIC_API_KEY"])

db = MongoRunner(max_time_ms=30_000)          # server-side time budget per query
db.connect(os.environ["MONGODB_URI"])         # use a MongoDB user with the `read` role

memory = ChromaAgentMemory(persist_dir="/var/lib/mango/memory")

tools = ToolRegistry()
for tool in build_mongo_tools(db):
    tools.register(tool)
tools.register(SaveTextMemoryTool(memory))

agent = MangoAgent(
    llm_service=llm,
    tool_registry=tools,
    db=db,
    agent_memory=memory,
    introspect=True,      # schema-aware prompting; cached with a TTL
)
agent.setup()

2. Describe your users to Mango

Pick the object your framework already gives you for the current user and write the role logic as functions of it:

# mango_access.py
from mango.middleware import CollectionAccess, RowFilter, RedactFields, DenyTools, Budget, AuditLog

ROLE_COLLECTIONS = {
    "support": ["users", "orders", "returns", "support_tickets"],
    "finance": ["orders", "payments", "ledger_entries", "merchants"],
    "admin": "*",
}
PII = ["users.email", "users.phone", "users.addresses", "payments.details"]

def install_access_control(agent):
    agent.use(CollectionAccess(lambda u: ROLE_COLLECTIONS[u.role]))
    agent.use(RowFilter("users", lambda u: {} if u.role == "admin" else {"country": u.country}))
    agent.use(RedactFields(lambda u: [] if u.role == "admin" else PII))
    agent.use(DenyTools(lambda u: [] if u.role == "admin" else [
        "save_text_memory",                                  # tool
        "memory_train", "memory_import", "memory_export",    # REST endpoints
    ]))
    agent.use(Budget(max_turns_per_day=300))
    agent.use(AuditLog())

u here is whatever your user_for returns (next step). Attributes like role and country are yours.

3. Wire authentication

FastAPI β€” mount the router into your app

mango_router returns an APIRouter. user_for is the only contact point with your auth: it receives the request and returns your user object, or raises HTTPException(401).

# main.py
from fastapi import FastAPI, HTTPException, Request
from mango.servers.fastapi import mango_router
from mango_setup import agent
from mango_access import install_access_control

app = FastAPI()
install_access_control(agent)

def user_for(request: Request):
    user = request.state.user            # set by your own auth middleware / dependency
    if user is None:
        raise HTTPException(status_code=401)
    return user

app.include_router(mango_router(agent, user_for=user_for), prefix="/mango")

user_for may be async. Three common shapes:

# JWT (any library that verifies the token for you)
async def user_for(request: Request):
    token = request.headers.get("Authorization", "").removeprefix("Bearer ").strip()
    try:
        claims = verify_jwt(token)      # your verifier: signature, issuer, audience, expiry
    except InvalidToken:
        raise HTTPException(status_code=401)
    return {"id": claims["sub"], "role": claims["role"], "country": claims.get("country", "")}

# Behind an API gateway that already authenticated (Kong, Envoy, Cloudflare Access…)
def user_for(request: Request):
    if request.headers.get("X-Gateway-Secret") != settings.GATEWAY_SECRET:
        raise HTTPException(status_code=401)
    return {"id": request.headers["X-User-Id"], "role": request.headers["X-User-Role"]}

# Service-to-service with a static API key
def user_for(request: Request):
    if not secrets.compare_digest(request.headers.get("X-API-Key", ""), settings.MANGO_KEY):
        raise HTTPException(status_code=401)
    return {"id": "reporting-job", "role": "finance"}

Give your user object an id (or sub / email / username). Mango uses it to bind sessions to users and to key budgets and audit records.

Standalone server

If Mango is its own service, the same callback goes to MangoFastAPIServer:

from mango.servers.fastapi import MangoFastAPIServer

MangoFastAPIServer(agent, user_for=user_for, cors_origins="https://app.example.com").run(port=8000)

Without user_for the server runs open and logs a warning at startup β€” fine on localhost, never on a network.

Not FastAPI? Call the agent directly

The HTTP layer is a thin adapter. From Django, Flask, a Celery worker or a CLI you call the agent yourself and pass the user:

# Django (async view)
async def ask(request):
    body = json.loads(request.body)
    response = await agent.new_session().ask(body["question"], user=request.user)
    return JsonResponse({"answer": response.answer, "tools": response.tool_calls_made})

new_session() is cheap and gives each conversation its own history; keep a map conversation_id β†’ agent if you want follow-up questions.

4. Talk to it from the frontend

const res = await fetch('/mango/ask/stream', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
  body: JSON.stringify({ question, session_id }),   // session_id: null on the first turn
})
for await (const event of sse(res)) {               // see FastAPI Server for a reader
  if (event.type === 'session')      session_id = event.session_id
  if (event.type === 'tool_result' && !event.success) showBlocked(event.preview)
  if (event.type === 'answer')       render(event.text)
  if (event.type === 'error')        showError(event.code === 'access_denied' ? event.message : 'Something went wrong')
}

5. Memory in a multi-user deployment

  • save_text_memory, delete_last_memory_entry and the /memory/* endpoints write to a store shared by everyone. Restrict them to trusted roles with DenyTools.
  • Memory examples are filtered by the caller's visible collections before they enter the prompt, so a support agent never sees a finance query as an example.
  • One agent = one database = one memory directory. Do not point two databases at the same persist_dir.

6. Production checklist

  1. MongoDB user with the read role. Mango blocks writes at the query level ($out, $merge, server-side JavaScript, admin stages, at any depth) and logs a warning if the connected user can write β€” but the database role is the first ring, Mango is the second.
  2. user_for on every deployment that is reachable from a network.
  3. Scope collections with CollectionAccess or MongoRunner(allowed_collections=[…]), and mask PII with RedactFields. Sampled documents and query results are sent to the LLM provider.
  4. CORS: set cors_origins / MANGO_CORS_ORIGINS to your frontend origin(s); the default * disables credentials as the CORS spec requires.
  5. Reverse proxy with TLS and rate limiting in front. Mango caps question length (4 000 chars), session id format and import size (1 000 entries) but does not rate-limit.
  6. Time and size budgets: MongoRunner(max_time_ms=…) bounds every query server-side; run_mql caps rows at 100 by default.
  7. Audit: keep AuditLog() on and ship the mango.audit logger to your log pipeline.
  8. Sessions: tune session_ttl_seconds / max_sessions on the router to your traffic; sessions live in process memory.