How It Works
Every time you call agent.ask(), Mango runs a structured loop that combines memory retrieval, LLM reasoning, and tool execution.
The agent loop
User question
│
▼
┌─────────────────────────────────────────────┐
│ MANGO AGENT │
│ │
│ 1. Inject training examples (gold-standard) │
│ 2. Pre-inject memory examples into prompt │
│ 3. Select relevant schema (per-query) │
│ 4. LLM decides which tools to call │
│ 5. Auto-inject schema before run_mql (*) │
│ 6. Validate MQL before execution │
│ 7. Execute tools against MongoDB │
│ 8. Auto-retry on fixable errors (max 2x) │
│ 9. Stream natural language answer │
│ 10. Auto-save successful queries to memory │
└─────────────────────────────────────────────┘
│
▼
SSE stream → your frontend
(*) Schema is injected automatically before each run_mql call so the LLM always has accurate field names and types at query time, even if the prompt was built earlier.
The loop runs until the LLM produces a final answer with no further tool calls, or until max_iterations is reached (safety cap, default: 8).
Memory retrieval
Before the first LLM call, Mango searches the vector store for questions semantically similar to the current one. Matches above the similarity threshold are injected into the prompt as examples. Verified training examples (loaded via train) get their own section and are marked as safe to reuse directly; auto-saved interactions are framed as reference data:
## VERIFIED TRAINING EXAMPLES — use these directly without additional exploration.
Q: How many users signed up last month?
Tool: run_mql | Args: {"operation": "count", "collection": "users", ...}
## Similar past interactions
Q: How many users signed up in January?
Tool: run_mql | Args: {"operation": "count", "collection": "users", ...}
Result: 1,247 users signed up in January 2026.
This gives the LLM concrete examples of correct queries for your specific database, dramatically improving accuracy on repeated or similar questions.
Schema context
agent.setup() introspects your database once (when introspect=True or a schema is passed) and builds the static part of the system prompt (role, rules, database name). The schema itself is injected per turn, in the dynamic part of the prompt: all collection names are always listed, and full details are shown only for the collections most relevant to the question (see Schema). Each collection's details include:
- Document count
- Field names, types, presence frequencies and sample values for categorical fields
- Index information
- Detected cross-collection references
The LLM uses this context to generate queries with the correct field names and operators — without guessing.
Tools available
Mango ships with a set of read-only tools the LLM can call:
| Tool | What it does |
|---|---|
list_collections | List all collections (grouped when there are more than 50) |
search_collections | Find collections by name pattern |
describe_collection | Full schema for a specific collection |
inspect_field | Distinct values and type distribution of a single field |
collection_stats | Document count for every collection, largest first (optional top_n) |
run_mql | Execute find, aggregate, count, or distinct |
These six are returned by build_mongo_tools(). Optional tools such as save_text_memory (SaveTextMemoryTool) and explain_query (ExplainQueryTool) must be registered manually — see Tools.
Safety
- Read-only by design.
run_mqlonly acceptsfind,aggregate,count, anddistinct. Any attempt to run write operations is rejected at the tool level with aValidationError. - Allowlist, not blocklist. The permitted operations are explicitly whitelisted, not everything-except-writes.
- No raw query passthrough. The LLM cannot execute arbitrary strings — all queries go through the
QueryRequestdataclass.
Guardrails
run_mqlrows are capped at 100 (max_rows); a terminal$limitis appended to aggregation pipelines so they never stream unbounded results.- Every query runs with
maxTimeMS= 30 s (MongoRunner(max_time_ms=...)). - A forbidden-operator denylist (
$where,$out,$merge,$function, …) and a pipeline-stage allowlist are enforced inside the runner itself, even withvalidate=False. - String literals are coerced to
datetime/ObjectIdwhen the introspected field type makes it unambiguous. - Value-grounding hints tell the LLM the exact encodings of categorical fields (casing, spelling) before it queries.
- Per-user access control (collection scoping, row filters, masking) via middlewares — see Access Control.
AgentResponse
ask() returns an AgentResponse with everything you need:
@dataclass
class AgentResponse:
answer: str # the natural language answer
tool_calls_made: list[str] # which tools were called
input_tokens: int # total input tokens used
output_tokens: int # total output tokens used
iterations: int # number of LLM calls in this turn
memory_hits: int # how many memory examples were injected
retries_made: int # how many tool calls were retried after a fixable error