Skip to main content
A typed path is an agent where every step that changes the call is a typed tool call, and code says what comes next. The model reads what the caller meant. Code checks it, saves it and speaks the next line.
grade.py
The model sends one value it must choose, a grade of four. Code knows which exercise is waiting, records the result, picks the next exercise and says it. The caller hears the next line with no second model request.

Why type the path

  1. Fewer tokens. A tool result with a ToolReply asks the model for no reply. Most caller turns cost one model request: the one that reads the caller.
  2. The same call every time. A receipt, a read-back or a question said by code has the same words on every call, and on both frameworks.
  3. The model cannot invent an outcome. A booking, a claim or a promise exists only when a tool returned it. The model can only ask for it.
  4. An open standard. The path is data: Pydantic types, task rows and reply rows. Another runner, a test or a tool can read it without a model.

Who decides what

The rules the examples follow

Require what the call cannot run without. Values the call starts with, like an account id or a customer’s name, are call_start values marked required, with a type. A dispatch that lacks one, or sends a wrong one, never reaches the greeting. See Start values. Type every argument. A tool’s parameters are Pydantic types with a description the model reads. A wrong value is refused before the tool runs, and the reason goes back to the model so it can ask again.
state.py
A type also says how code speaks a value. Spelled is said a character at a time, BirthDate with its year, Phone in its groups, a date from today. See Value types. Kestrel types the policy number’s spelling. Faro types a grade, a level and a learning goal as Literals. A rule about the request, like Wasla’s payment window, is not a type: it is the tool’s, as below. Let the result speak. Give each result a ToolReply. The line can read {{result.<field>}} and State values. A value is said by its JSON Schema format, so a date reads as a day. See Save a result and speak it. Hide the words a reply says from the model with hidden=("said",) on the tool. A model that reads a line may say it again in its own words. Let the result save. An Assign row saves a result field into the State. The next tool reads it as a locked argument, so the model never carries it. Nosh saves the places it offered, and place_details refuses any id that was not offered. Lock what the caller does not choose. Account ids, the call’s id, the report so far and the exercise waiting are locked. The model never sees them and cannot change them. Read back before an effect. A tool that changes something has a FinishRule with a confirm line. Code says it, and the tool runs only on the caller’s yes to the same input. See Ask before a closing tool runs. Open each step in code. A step that opens with an OpeningTool, or with opening: listen and an announce line, says its first line with no model request. A group’s arguments carry what the caller already said into the first step, so its read-back can play at once. Decide in the tool, say it in code. An argument type says what the caller meant: a date, a phone number, a spelled id. A rule about the request, like “not in the future”, belongs to the tool. The tool returns a typed outcome, a Literal field, and a reply on the tool turns each outcome into a line. Code says it, and the turn costs one model request: the call. A validator that raises would cost two more: one for the model to say the error, and a finish check after it. See Replies on the tool itself. A tool that reads its input back for a yes checks the rule in a precheck, so it answers before the read-back. See Check a rule before the tool runs.
claims.py
Refuse in code when the model must decide. Raise ToolError with a message that names what to do next. The model reads it and chooses what to ask.
finder.py

What it costs

Each example’s scenarios count the model requests of every caller turn. These are the whole scripted calls, typed by the caller, on the real model. A request above one per turn is a turn where the model asked for a tool, read the refusal, and asked again, or where a step opened with a model reply.

Where the model still talks

  1. A question off the path, like “Do you take cards?” Keep these short in the prompt.
  2. A goodbye. Or give the end tool an announce line with announce_wait=True, and the model says nothing.
  3. A step whose first line depends on what was said. Prefer an OpeningTool when a tool can compute it.

Troubleshooting

The model says the line code already said

The prompt tells the model to say something a reply also says. Fix: tell the model in the tool’s docstring that the application says the result. The model reads the docstring as the tool’s description.

A turn makes two requests where you expected one

The tool was refused, or no ToolReply matched its result. Fix: give every result shape a reply, including the “not found” one. Read the refusal from the tool.refused event. See Watch tool calls.

Where to go next

Nosh: a tools-only finder

The smallest typed path: three tools and their replies.

Reply and confirm in code

Every rule of ToolReply, FinishRule and OpeningTool.