AI agents
An agent that judges in free text (“this looks like a billing issue, fairly sure”) can’t be
routed on or audited. Give it Curva as a tool and it gets typed answers with calibrated
probabilities instead, and an abstain flag when it should hand over to a person.
Every way in goes through a Curva server, so answers are cached, calibrated by feedback and audit-logged:
| Agent setup | Use |
|---|---|
| MCP clients (Claude Desktop, Claude Code, Cursor and other MCP-capable agents) | curva mcp, see MCP server |
| Any tool-calling API or agent framework, in Python | curva.integrations: the tool’s JSON Schema, description and decide function |
| Any language | /openapi.json: generate a client, or hand the operation to your framework |
Nothing extra to install: pip install curva-ai includes all of it.
MCP agents
Section titled “MCP agents”For agents that speak the Model Context Protocol, run curva mcp next to (or pointing at) your
Curva server; it exposes a decide tool:
claude mcp add curva -- curva mcp --url http://127.0.0.1:7777Setup for other clients is in MCP server.
Tool calling in Python
Section titled “Tool calling in Python”Three plain helpers, no framework required:
from curva import Curva, Choice, Noulfrom curva.integrations import decide, description, input_schema
questions = { "department": Choice("Which team should handle this", ["billing", "technical", "sales"], min_confidence=0.8), "refund": Noul("The customer explicitly asks for a refund"),}
schema = input_schema(questions) # JSON Schema of the tool's arguments (only `state` here)text = description(questions) # tells the model when to call the toolresult = decide(Curva(), {"ticket": "..."}, questions, project="support") # JSON text for the agentRegister schema and text as a tool with your model provider or framework, and call decide
when the model uses it. With fixed questions (above, recommended for production) you decide
what gets asked and the agent only passes the state. Call input_schema() and description()
without questions to let the agent write its own questions in the /v1/decide shape: flexible,
but every new wording starts a fresh calibration.
decide returns compact JSON:
{"decision_id": "dec_…", "answers": { "department": {"choice": "billing", "probabilities": {"billing": 0.97, "technical": 0.02, "sales": 0.01, "none_of_these": 0.0}, "confidence": 0.97, "abstain": false, "calibrated": false}, "refund": {"noul": 0.93, "calibrated": false}}}Curva errors (a 422 for a malformed question, a 429) come back as text instead of raising, so the agent can fix the call or wait.
Close the loop
Section titled “Close the loop”Keep the decision_id from the tool output. When the outcome is known, send it back:
Curva().feedback(decision_id, "department", "billing")From 30 labels per question, the agent’s answers are calibrated whenever that makes them more accurate. See Calibrate with feedback.

