A staging consultation calculated the chart and then returned nothing but the
ensureFinalResponseText fallback. The model had made four calls to
run-jyotish-consultation, and two of them never reached a calculation: they set
both domains and theme, which canonicalDomainPlan rejects at execution. The
schema declared those two fields as independent optionals, the description never
mentioned the constraint, and the instructions actively told the model to use
theme for a single-domain retry. Each attempt therefore bought a rule the
contract never stated, and because the throw happens before the step-recording
try/catch, it left no trace in the receipt either.
Make the constraint unrepresentable instead of enforced. The model-facing schema
keeps only question and domains, so Mastra refuses the pair before the tool body
runs; the description states the single-array contract, and the instruction that
advertised theme is gone. canonicalDomainPlan still resolves the single-value
form for callers that build a plan without that schema, and is now exported so
that path has its own tests.
maxSteps and the abort timeout bound the same run but were hard-coded apart. One
calculation takes about 20s against a 110s budget, so time is the binding
constraint and three failed calculations exhaust it whatever the step count. The
budget only has to cover the longest useful shape, so it moves to 8 beside the
timeout with that reasoning recorded, and the recorded step list is sized to
match so an exhausted run cannot truncate its own evidence.
Step exhaustion was only ever inferable by counting events, since finishReason
was recorded nowhere and progressive-disclosure reads never reach the public
stream. Capture it as a closed enum plus a step count, normalizing anything
unrecognized, and log both as controlled fields. Neither may enter the client
receipt, whose step schema is strict and would fail a successful run.
Co-authored-by: Cursor <cursoragent@cursor.com>
Every workflow fault except abort and timeout collapsed into the single
calculation_failed code, and the upstream message was discarded, so a failing
run left no evidence of whether the API rejected the call or returned a payload
that missed the response contract.
Classify failures into a closed vocabulary carried on ConsultationWorkflowError
and record it as the failureCode of the runtime step. The observability tool
call schema gains one controlled optional field; upstream error text stays out
of logs, as that contract requires. Forward request_id to the API so a run can
be aligned with its access log.
Build public receipts from an explicit allowlist. The internal failure code
must not reach the client contract, whose step schema is strict and would
otherwise reject a successful run.
Co-authored-by: Cursor <cursoragent@cursor.com>
The runtime contract gated on total consultation tool attempts, so a single
transient calculation failure raised the counter past one and made the run
unrecoverable even after a later attempt succeeded. The already-computed chart
was discarded and the request ended as runtime_contract_incomplete.
Count only successful workflow executions against the single-calculation
boundary. Failed attempts stay in the attempt counter for observability. The
request-scoped cache still retains the successful promise, so a request can
never run more than one billable calculation.
Also record the staging-first branch delivery convention in AGENTS.md.
Co-authored-by: Cursor <cursoragent@cursor.com>