You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Scope the resolver run-once guarantee to questions, not resolver bodies
Each question is asked exactly once per call, but on the multi-round-trip
form an eliciting resolver's body runs again to consume its answer, and a
resolver that answered without asking may run again whenever the call
resumes. The tutorial info box, its recap bullet, and the refund_desk
caveats now say exactly that, so authors don't hang side effects on a
runs-at-most-once reading that only holds for the synchronous form.
Copy file name to clipboardExpand all lines: docs/tutorial/dependencies.md
+9-7Lines changed: 9 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -122,18 +122,20 @@ That's the right default for a precondition: no answer, no order. When declining
122
122
multi-round-trip `tools/call` - the server returns it, the client's `elicitation_callback`
123
123
answers it, and the `Client` retries the call for you (**[Multi-round-trip requests](../advanced/multi-round-trip.md)**). On
124
124
**2025-11-25** and earlier it is a synchronous elicitation request mid-call. Each question is
125
-
asked exactly once per call; a resolver that answered *without* asking, like `check_stock`,
126
-
may run again when the call resumes after a question. When it resumes, each answer is matched
127
-
back to its question, so an eliciting resolver must derive its question deterministically from
128
-
the tool's arguments and earlier answers - a per-call generated value (a `default_factory` id,
129
-
a timestamp) is re-derived on each round and must not appear in a question the answer is meant
130
-
to bind to.
125
+
asked exactly once per call - a guarantee about the question, not the resolver. In the
126
+
multi-round-trip form an eliciting resolver runs again to consume its answer, so code before
127
+
its `return Elicit(...)` runs on the asking round and again on the answering one; a resolver
128
+
that answered *without* asking, like `check_stock`, may run again whenever the call resumes
129
+
after a question. When it resumes, each answer is matched back to its question, so an
130
+
eliciting resolver must derive its question deterministically from the tool's arguments and
131
+
earlier answers - a per-call generated value (a `default_factory` id, a timestamp) is
132
+
re-derived on each round and must not appear in a question the answer is meant to bind to.
131
133
132
134
## Recap
133
135
134
136
*`Annotated[T, Resolve(fn)]` on a tool parameter: the SDK runs `fn` and injects its return value.
135
137
* A resolved parameter is invisible to the model and cannot be supplied by a client. Values the model must not invent - prices, identities, permissions - belong here.
136
-
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once, however many consumers it has; a resolver that never asked may run again when a call resumes after a question.
138
+
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once per round, however many consumers it has; each question is asked exactly once, an eliciting resolver runs again to consume its answer, and a resolver that never asked may run again when a call resumes.
137
139
* Bad graphs fail at registration with `InvalidSignature`, not mid-call.
138
140
* Return `Elicit(message, Model)` to ask the user, only when you have to. Unwrapped annotations abort on decline; `ElicitationResult[T]` lets the tool branch.
0 commit comments