Workflows in one call
Some questions only make sense once others are answered: how severe an incident is depends on
which system failed, and whether to roll back depends on both. Give a question depends_on and
Curva answers the whole graph in one /v1/decide request, stage by stage, with every
dependent question seeing the answers it depends on.
Incident triage
Section titled “Incident triage”system first; then severity and deployment_related, which both need it; then rollback,
which needs those two.
{ "state": {"alert": "p99 latency 4s on orders-db since 14:02", "last_deploy": "orders-api v212 at 13:58"}, "questions": { "system": {"type": "choice", "instructions": "Which system is failing?", "options": {"database": "", "api": "", "network": ""}}, "severity": {"type": "score", "instructions": "How severe is the incident?", "levels": ["minor", "major", "critical"], "depends_on": ["system"]}, "deployment_related": {"type": "noul", "instructions": "Did a recent deployment cause it?", "depends_on": ["system"]}, "rollback": {"type": "noul", "instructions": "Should the last deployment be rolled back now?", "depends_on": ["severity", "deployment_related"], "think": true} }}This runs as three stages, one model call each:
| Stage | Questions | Sees |
|---|---|---|
| 0 | system |
the state |
| 1 | severity, deployment_related |
the state and system |
| 2 | rollback |
the state, system, severity and deployment_related |
The stage-2 prompt carries the earlier answers in a fenced block, with their probabilities:
<earlier_answers>system: database, p=0.91severity: critical, p=0.64deployment_related: yes, p=0.83</earlier_answers>Answers come back in your question order, each with the stage that answered it.
latency_ms and cost_usd add up every call (calls in the same stage run concurrently), and the
decision is audited once.
From Python:
from curva import Curva, Choice, Noul, Score
d = Curva().decide(alert, { "system": Choice("Which system is failing?", ["database", "api", "network"]), "severity": Score("How severe is the incident?", ["minor", "major", "critical"]).depends("system"), "deployment_related": Noul("Did a recent deployment cause it?").depends("system"), "rollback": Noul("Should the last deployment be rolled back now?", think=True) .depends("severity", "deployment_related"),})d["rollback"].noul, d["rollback"].stage # 0.78, 2TypeScript: dependsOn(noul("Roll back now?", { think: true }), "severity", "deployment_related").
A receipt: extraction and decisions together
Section titled “A receipt: extraction and decisions together”Dependencies mix freely with extraction and images:
d = curva.decide({"employee": "E-114"}, { "merchant": Text("Merchant name?", max_length=80), "total": Number("Total paid?", min=0), "category": Choice("Expense category?", ["meals", "travel", "equipment", "other"]), "reimbursable": Noul("Reimbursable under a 50 EUR meals policy?").depends("category", "total"),}, images=["receipt.jpg"])merchant, total and category are read in one call; reimbursable follows with
category: meals, p=0.93 and total: 42.5, p=0.88 in view.
Branching on an answer
Section titled “Branching on an answer”A when field written @<key> reads that question’s answer instead of the state, and makes it
a dependency. The value it compares is the option key (choice), true/false (noul, by
P(yes) ≥ 0.5), the most likely level’s index (score), the list of selected keys (multi), or the
extracted value:
"db_owner": {"type": "text", "instructions": "Which team owns the database?", "when": {"@system": "database"}},"page_oncall": {"type": "noul", "instructions": "Page the on-call engineer?", "depends_on": ["db_owner"]}When system is not database, db_owner is {"skipped": true}, and so is page_oncall:
a skipped dependency skips its dependents. Rule conditions can read @key answers the same way,
and a dependency answered by a rule counts as answered, with no model call for it.
think on one question
Section titled “think on one question”think: true on a question lets the model reason before answering that one, in a verbal call
of its own, while the rest of its stage answers fast in the normal call. Both calls run
concurrently. The request-level think still makes every question think, in one call.
Rules of the graph
Section titled “Rules of the graph”- Stage 0 holds every question without dependencies; a question runs one stage after the latest of its dependencies. At most 8 stages.
- A dependent question sees the answers of its direct and transitive dependencies. One call shows one block for all of its questions.
- Unknown keys, a question depending on itself, and cycles get 422; the message names the cycle
(
`a` → `b` → `a`). explainis not supported with dependencies (422).- Calibration: a dependent question is stored like any other, but its fingerprint includes its
dependency keys, so the same wording asked with and without earlier answers in view is
calibrated separately.
thinkis not part of the fingerprint. - With the cache-friendly layout (
config: "curva-1.2.0"), each stage’s prompt starts with its questions, then the earlier answers, then the state, so repeated decisions share the longest possible prefix.

