- After each call, Jev sees the original request and its answer together. A
Noulquestion, which we call the gate, asks whether the request states an important fact about the customer that should be remembered. If P(yes) is at least 0.5, the ticket and Jev’s answer are saved as a memory for that customer. - Before each call, we add the customer’s saved tickets, with Jev’s answers to them, to
the state. We do this in a wrapper,
ask_with_memory(), with the same arguments and response asask(): the TypeSafe Python SDK’sclient.system_one, with the model pinned and the call cached.
ask_with_memory() takes the same arguments as ask() and returns the same
response, you can swap one for the other without changing any other code.
Setup
TYPESAFE_API_KEY. Every API call is cached in
json_cache.json.
Download it into the folder you run this code from to replay the published numbers
instead of calling the API, or leave it out to run everything live.
Numbers below came from jev-1.14.0 on 2026-10-29.
Define the tickets
Two customers, five earlier tickets and three later messages, all written for this cookbook. C-1042 (Acme Corp) runs the self-hosted edition on Windows Server under an Enterprise contract. C-2210 (Example Studio) is on the cloud edition’s self-serve Team plan. Three of the earlier tickets state a lasting fact: an edition, a plan, a contract. Two don’t: a phone at the airport, a public holiday. No later message mentions the customer’s setup, so without memory Jev has to guess it. Each ticket becomes a state with two parts, built byticket_state(): customer, who the
customer is, and ticket, a simplified helpdesk thread. In your app, the state is each
ticket as your helpdesk sends it, and ask_with_memory() adds the customer’s saved
tickets to it.
The split into earlier tickets and later messages is only for this example.
Route a ticket
OneChoice question picks the support team. The none_suitable option gives Jev an
answer for when no team fits, and a ticket with too little context can end up there.
Ask Jev whether the ticket is worth remembering
First, we pair the original state with Jev’s answer inas_memory(). This pair is the
memory the wrapper may save. The request key holds the state from before memories were
added, so old memories never end up nested inside new ones. jev_answered maps each
question to its answer.
A second Jev call reads that pair. The gate asks whether the request
states an important fact about the customer, such as their edition, operating system, plan
or contract. Jev’s answer is there as context, but a fact that appears only in the
answer does not count. Without that rule, a generic answer such as “General product
questions” can outweigh a real fact in the request, and an answer that relied on an
earlier memory can be saved again as if it were new.
Wrap the call so it remembers
We keep memories inMEMORIES, one list per customer ID, and look them up by the ID in
the state.
- Every call makes two sequential requests: the routing request, then the memory gate. The gate judges the ticket together with Jev’s answer, so it runs after the routing request. If your gate ignores the answer, it could instead be a second question in the routing request (see fan-out and parallel questions).
- Memory only grows. Every saved memory goes into every later call for that customer. There is no size limit, no check for relevance and no expiry date here.
- Your questions never mention the memories. The memories go into the state under
earlier_tickets_from_this_customer, and Jev reads the whole state. Nothing inROUTEchanges. - The state needs
customer.id. Plain Jev also accepts a string as the state, but this wrapper needs the ID to find the customer’s list. - A saved answer can repeat a mistake. The gate ignores anything that appears only in Jev’s answer, so an answer can’t get a ticket saved by itself. A saved ticket keeps its answer, though, and every later call for that customer reads it, so a wrong inference can carry forward.
Send the earlier tickets and watch the answers change
Send the five earlier tickets throughask_with_memory(), in the order they arrived.
Before the first ticket, and again after each one, that customer sends two of the later
messages: slow dashboards and 20 more seats. This shows how each saved memory changes
Jev’s answers. The repeated messages go through the wrapper too, so the gate judges them
as well.
Route the same messages for both customers
Each customer now sends the three later messages. Each message goes to plainask() and
to ask_with_memory(), with the same state and the same question.
Open it in the playground
The link holds the exact routing state used for C-1042’s first slow-dashboards call, including its memories at that point, plus the routing question.Next steps
- Choose what your app should remember. Edit
GATEto describe what is worth remembering for your own task. To setREMEMBER_AT, label request-and-answer pairs from your traffic as worth remembering or not, pick the probability that best separates the two groups, and check it on pairs you didn’t use to pick it. Raise it if a misleading memory costs more than a missing one. See Confidence and confidence-based routing. - Choose which memories to add. The wrapper adds every saved memory. A relevance question can select a smaller set once the list gets long.
- Replace outdated memories. Ask whether a new memory replaces an older one, then remove the old entry in code when appropriate.
- Keep memories between runs. Replace
MEMORIESwith a database keyed by customer ID. The in-memory dict disappears when the process exits. - Remember what your app did. Add your reply to the ticket thread as a support message, and the gate judges it along with the customer’s words.

