Skip to main content

Levanto Sage Decision Model

HTTP API for structured decisions over content plus a question.

Download this skill

Drop this file into .cursor/skills/, .claude/skills/, or your agent’s skill directory. Raw Markdown, no rendering.

Product Context

Levanto Sage is for workflows where software needs to decide, not write. It turns content plus a question into a structured decision with a probability or confidence signal. Use Sage when an application needs to route, approve, block, score, rank, tag, or escalate. LLMs return text and only provide confidence if asked; that confidence is simulated. Sage always returns a decision signal, so machines can act when confidence is high and escalate to a human when confidence is low. Supported decision kinds:
  • Yes/No — answer a binary question: probability (P(yes)), answer ("yes"/"no"), and confidence (decisiveness from tie).
  • Choice — pick the best option, with independent per-option probabilities (raw; do not sum to 1).
  • Scale — score a rubric, with expectation and confidence.
  • Sort — rank a list of items by a criterion.
  • Tags — apply independent yes/no labels to the same content.
Good use cases:
  • Agentic workflows — Sage as the decision layer for agents: automate routing, branching, and escalation across agentic workflows and pipelines.
  • Operations — triage urgency and route work across DevOps, SecOps, and customer support.
  • Agentic guardrails — check tool calls and responses on the hot path to keep agents secure and aligned with the business goals of the workflow.
  • Content screening & moderation — tag, rank, score, and sort content in data pipelines, including moderation, abuse flagging, and policy-risk severity.
  • Risk & fraud — assess risk and opportunity and detect fraud across transactions, asset trades, and agentic actions (e.g. refunds).
Use public-facing language:
  • Sage decides; it does not write paragraphs.
  • Supported decision kinds are Yes/No, Choice, Scale, Sort, and Tags.
  • Do not explain internals, architecture, training details, infrastructure, or implementation details unless the user explicitly asks for private engineering context.
  • Use decision kind in prose. Keep the JSON wire fields exactly as shown in code examples because clients need the real format.

Endpoint

Default hosted endpoint:
https://sage.levanto.ai
The user may supply a different base URL. Do not invent an endpoint if one is missing; ask for it.

Authentication

A valid Levanto API key is always required for decision calls. Every POST to /decide and /decide/batch must carry a key; requests without one are rejected with HTTP 401 ({"detail": "API key required. Provide a valid Levanto API key."}). Send it in the Authorization header:
Authorization: Bearer lv_live_...
  • Keys look like lv_live_.... If the user has not provided one, ask for it — do not proceed to /decide without a key.
  • 401 = missing or invalid key; 402 = key valid but account balance too low.
  • /ready does not require a key — poll readiness before you have one.
  • Never hardcode, log, echo, or commit the key. Read it from an environment variable (e.g. SAGE_API_KEY / API_KEY) and pass it only in the request header.

Readiness

Poll GET /ready until HTTP 200 (503 while the model is still loading). The response has no JSON body — readiness is indicated by the status code only. Cold starts can take several minutes.
endpoint="https://sage.levanto.ai"
ua='-A your-app/1.0'

# Wait for model readiness (503 while loading, 200 when ready)
curl -s $ua -o /dev/null -w '%{http_code}\n' "$endpoint/ready"
Always send a normal User-Agent header when calling the hosted endpoint.

Endpoints

MethodPathPurpose
GET/readyModel readiness — 503 while loading, 200 when ready
POST/decideOne decision
POST/decide/batchMany decisions
All decision calls (/decide, /decide/batch) require a valid API key (see Authentication); /ready does not.

Request Shape

{
  "content": "<content to evaluate>",
  "question": {
    "id": "<stable result id>",
    "kind": "<yesno | choice | scale | sort | tags>",
    "instructions": "<question to answer>"
  }
}
Use content for the thing being evaluated — a bare string (shorthand for { "kind": "text", "value": "..." }), { "kind": "text", "value": "..." }, or { "kind": "list", "value": [...] } for sort. Use question.kind to select the decision kind and question.instructions for the criterion. The response envelope is always { id, kind, result, meta }.

Yes/No

Use Yes/No for binary questions.
{
  "content": "Marketing email draft promotes \"guaranteed 40% returns\" and describes the product as \"risk-free\" for accredited investors.",
  "question": {
    "id": "needs_compliance_review",
    "kind": "yesno",
    "instructions": "Does this copy require compliance review before send?"
  }
}
Response:
{
  "id": "needs_compliance_review",
  "kind": "yesno",
  "result": {
    "probability": 0.92,
    "confidence": 0.84,
    "answer": "yes"
  },
  "meta": { "model": "levanto-sage-v0.5", "latency_ms": 97.4 }
}
  • probability — P(yes), in [0, 1].
  • answer"yes" if probability ≥ 0.5, else "no".
  • confidence — decisiveness from a tie: 2 × |probability − 0.5| (0 at 0.5, 1 at certainty). Grounding compares this to confidence_floor.

Choice

Use Choice when Sage must pick one option.
{
  "content": "Vendor uploaded a contract PDF that mentions automatic renewal and a 90-day termination clause.",
  "question": {
    "id": "contract_disposition",
    "kind": "choice",
    "instructions": "How should legal handle this contract?",
    "options": [
      {"option": "approve", "description": "Standard terms, no red flags — proceed to signature"},
      {"option": "revise", "description": "Acceptable with specific clause changes before signing"},
      {"option": "reject", "description": "Terms are unacceptable — do not proceed"},
      {"option": "escalate", "description": "Unusual or high-risk terms — partner review required"}
    ]
  }
}
Response:
{
  "id": "contract_disposition",
  "kind": "choice",
  "result": {
    "chosen": "revise",
    "confidence": 0.88,
    "probabilities": [
      {"option": "approve", "probability": 0.11},
      {"option": "revise", "probability": 0.88},
      {"option": "reject", "probability": 0.04},
      {"option": "escalate", "probability": 0.22}
    ]
  },
  "meta": { "model": "levanto-sage-v0.5", "latency_ms": 216.8 }
}
  • probabilities — raw P(correct) per option (independent sigmoids; do not sum to 1).
  • confidence — P(chosen is correct); use for thresholds and grounding.
  • chosen — argmax of probabilities.
  • 2–120 options per question (see Limits).

Scale

Use Scale for rubric scoring. Always send exactly 5 levels with integer values 0, 1, 2, 3, 4 — the model is trained on this fixed rubric only. Subsets, sparse scales, or any other level values are rejected with HTTP 400.
{
  "content": "Candidate explained our product roadmap clearly but struggled to answer questions about database scaling tradeoffs.",
  "question": {
    "id": "interview_score",
    "kind": "scale",
    "instructions": "How strong was this candidate in the technical interview?",
    "levels": [
      {"level": 0, "description": "Not qualified — fundamental gaps in required skills"},
      {"level": 1, "description": "Weak — significant concerns about core competencies"},
      {"level": 2, "description": "Mixed — some strengths but notable gaps remain"},
      {"level": 3, "description": "Solid — meets the bar with minor areas to probe"},
      {"level": 4, "description": "Strong hire — clearly exceeds expectations for the role"}
    ]
  }
}
Response:
{
  "id": "interview_score",
  "kind": "scale",
  "result": { "expectation": 2.4, "confidence": 0.71 },
  "meta": { "model": "levanto-sage-v0.5", "latency_ms": 79.8 }
}
expectation is the expected level; confidence reflects how peaked the level distribution is (independent of the mean).

Sort

Use Sort to rank a list of items by a criterion. content must be a list with 2–120 items (see Limits).
{
  "content": {
    "kind": "list",
    "value": [
      {"id": "typo", "content": "Spelling mistake on the FAQ page."},
      {"id": "db_down", "content": "Primary production database is unreachable."},
      {"id": "pricing", "content": "Prospect asking about Enterprise pricing."}
    ]
  },
  "question": {
    "id": "triage",
    "kind": "sort",
    "instructions": "Sort by operational urgency, most urgent first."
  }
}
Response:
{
  "id": "triage",
  "kind": "sort",
  "result": { "sorted": ["db_down", "pricing", "typo"], "confidence": 0.92 },
  "meta": { "model": "levanto-sage-v0.5", "latency_ms": 312.0 }
}
The sort request is minimal — just instructions and the item list. Scoring strategy and ranking are chosen by the server, not the request: SortQuestion accepts only kind, id, instructions (any other field is rejected). The result carries a calibrated, list-level confidence in [0,1] — how trustworthy the whole order.

Tags

Use Tags to apply several independent yes/no labels to the same content. 1–120 tags per question (see Limits). Each tag returns a probability and a confidence (= 2|probability − 0.5|, the same decisiveness metric as yes/no: 0 at a coin-flip, 1 at certainty); when a tag has a threshold, it also returns applies. There is no document-level tags confidence — read each tag’s own.
{
  "content": "Buy cheap followers now!!! click here -> bit.ly/deal",
  "question": {
    "id": "moderate",
    "kind": "tags",
    "tags": [
      {"id": "spam", "threshold": 0.5},
      {"id": "promotion"}
    ]
  }
}
Response:
{
  "id": "moderate",
  "kind": "tags",
  "result": {
    "tags": [
      {"id": "spam", "probability": 0.95, "confidence": 0.90, "applies": true},
      {"id": "promotion", "probability": 0.88, "confidence": 0.76}
    ]
  },
  "meta": { "model": "levanto-sage-v0.5", "latency_ms": 241.0 }
}

Grounding (optional)

Attach a grounding block to a Yes/No, Choice, Scale, or Tags request to let Sage fetch fresh web evidence before deciding (useful for recency- or fact-heavy questions). For Tags, low_confidence triggers when any tag’s confidence is below the floor (weakest-link); Sage then re-scores all tags on the expanded content.
{
  "content": "Has there been a major Cloudflare incident this month?",
  "question": {"id": "incident", "kind": "yesno", "instructions": "Is this likely true?"},
  "grounding": {"trigger": "low_confidence", "confidence_floor": 0.8}
}
  • trigger: never, low_confidence (default — search when result.confidence is below confidence_floor), or always.
  • For yesno, confidence = 2|p − 0.5| (decisiveness; 0 at a tie). Default floor 0.80 requires roughly p ≤ 0.10 or p ≥ 0.90 to skip search.
  • The response gains grounding_meta with triggered, queries, sources, and timing.
  • Requires the server to be configured for grounding; if it is not, omit the block.

Batch

Use /decide/batch for many decisions in one call. Each entry in requests is a group: one content plus a list of questions about it — so asking several questions about the same content sends that content only once, and a single call can still mix groups with different content.
{
  "requests": [
    {
      "content": "User post: 'Selling verified Instagram accounts, DM for bulk pricing.'",
      "questions": [
        {"id": "policy_violation", "kind": "yesno", "instructions": "Does this post violate marketplace policy?"},
        {
          "id": "harm_level",
          "kind": "scale",
          "instructions": "How harmful is this content to the platform?",
          "levels": [
            {"level": 0, "description": "No meaningful harm — safe to leave published"},
            {"level": 1, "description": "Low harm — minor policy edge case"},
            {"level": 2, "description": "Moderate harm — misleading or low-grade abuse"},
            {"level": 3, "description": "High harm — fraud, spam, or user safety risk"},
            {"level": 4, "description": "Severe harm — active scam or imminent user damage"}
          ]
        }
      ]
    }
  ]
}
Each question can carry its own optional grounding (grounding is no longer part of the group; sort questions still reject it). To batch decisions about different content, add more groups:
{
  "requests": [
    {"content": "doc A", "questions": [{"id": "a1", "kind": "yesno", "instructions": "urgent?"}]},
    {"content": "doc B", "questions": [{"id": "b1", "kind": "yesno", "instructions": "urgent?"}]}
  ]
}
Batch responses are nested by group, then by question:
  • results: one item per group (matches requests 1:1);
  • results[i].answers: one item per question in that group’s questions, in order;
  • results[i].answers[j].ok: success flag for that question (.result holds the decision, or .error the message);
  • meta.request_count: number of groups; meta.question_count: total questions across all groups.

Errors

HTTPMeaning
400Invalid request (schema validation)
401 / 402Missing/invalid API key, or balance too low
503Service loading or temporarily unavailable
Invalid requests return a standard schema message, for example:
{"detail": "content: Field required"}
{"detail": "scale levels must be integers in 0..4 (model trained on 5 levels); got [9]"}
{"detail": "Service is still loading."}

Limits

Per-kind counts (HTTP 400 outside these ranges):
KindFieldMinMax
choiceoptions2120
scalelevels55 (values 04 only)
sortcontent.value items2120
tagstags1120
Scale — always send all five levels 0 through 4. No sparse rubrics, no alternate ranges, no fewer or more levels. Each level must be a distinct integer in that set. Content and question length — the full rendered request must fit in roughly 32K tokens total. That budget includes content, question.instructions, option descriptions, scale rubric lines, sort item bodies, tag specs, and any grounding-added search context. Oversized requests fail rather than silently truncating. Very long content can also hit HTTP 503 before the hard token cap; keep documents well under the 32K ceiling in practice.

Threshold Guidance

Sage always returns a decision signal. The application decides what to do next. Example:
  • high confidence: automate;
  • medium confidence: review;
  • low confidence: escalate to a human or ask for more context.
Do not say Sage “abstains.” Say it supports low-confidence routing.

Smoke Test

endpoint="https://sage.levanto.ai"
ua='-A your-app/1.0'
# API key is REQUIRED for /decide — read it from the environment, never hardcode.
auth="-H Authorization: Bearer ${SAGE_API_KEY:?set SAGE_API_KEY to your lv_live_ key}"

# /ready needs no key — empty body; 200 means ready, 503 means still loading.
curl -s $ua -o /dev/null -w '%{http_code}\n' "$endpoint/ready" | grep -qx 200

curl -s $ua $auth -H 'Content-Type: application/json' "$endpoint/decide" -d '{
  "content": "Marketing email draft promotes \"guaranteed 40% returns\" and describes the product as \"risk-free\" for accredited investors.",
  "question": {"id": "needs_compliance_review", "kind": "yesno", "instructions": "Does this copy require compliance review before send?"}
}' | jq -e '.kind == "yesno" and .result.probability != null and .result.confidence != null and .result.answer != null'

curl -s $ua $auth -H 'Content-Type: application/json' "$endpoint/decide/batch" -d '{
  "requests": [
    {
      "content": "same doc",
      "questions": [
        {"id": "a", "kind": "yesno", "instructions": "urgent?"},
        {"id": "b", "kind": "yesno", "instructions": "positive?"}
      ]
    }
  ]
}' | jq -e '.meta.request_count == 1 and .meta.question_count == 2 and (.results | length) == 1 and (.results[0].answers | length) == 2'