Skip to content

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.

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.91
severity: critical, p=0.64
deployment_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, 2

TypeScript: 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.

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: 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.

  • 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`).
  • explain is 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. think is 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.

© 2026 Tarkova Private Limited.