Skip to main content
A turn is more than the user’s message and your agent’s reply. In between, your agent reasons, calls tools and reads what they return. This page shows how to send those steps to Synap, what Synap does with each one, and why each is worth the extra call. Send traces when your agent uses tools or plans across steps, and runs as a long-lived process that can hold the gRPC stream. If your agent answers in one model call with no tools, its turns are enough; see Conversations and transcripts. For history that already exists, see Import existing history.

Why traces help

Synap’s anticipation agent prepares the context your agent will need on its next turn. Without traces it sees only the edges of a turn: what the user asked and what your agent replied. With them it also sees how the reply was reached: what your agent decided to check, which tool it called with which arguments, and what came back. The next question usually follows from that. A user who has been told their order shipped asks when it arrives, and the agent that saw the order lookup can prepare for that question before it is asked.

Before you start

You need an open stream. There is no REST method for traces, so they can only travel on the stream that instance.listen() opens. Open it once per process. Each trace carries the same ids as the turn it belongs to. Synap files a trace under its conversation, and drops a trace that has no conversation_id.
Your agent also reports its turns with send_message: a user_message when the user speaks and an assistant_message after it replies. The reply is what makes Synap act on the traces, so do not skip it.

Send each kind of trace

Reasoning

Send one reasoning step per call with record_thinking. This is the only trace that shows intent: a tool call says what your agent looked at, and a reasoning step says why. step_index orders the steps within a turn, and thought_type labels the kind of step, for example "plan", "reflect", "tool_decision" or "self_correction".
Send what your model provider gives you. Many providers return a summary of the reasoning rather than the reasoning itself, and some return nothing. A turn without reasoning is an ordinary turn; do not fill it with text you wrote yourself, because Synap would treat it as your agent’s thinking.

A tool call

Report each tool your agent invokes with record_tool_call, before you run it. The tool name and its arguments say what your agent is about to need, which is often what the user asks about next. Give each call a tool_call_id and pass the same id with its result, so a turn with several tools in flight still pairs each result with the call that asked for it.
Use this method rather than send_message(event_type="tool_call"). Every event carries both an event type and a role, and this method sets the two together, so they always agree.

A tool’s result

Report what the tool returned with record_tool_result. The result is the material your agent built its reply from, so it is the strongest clue to what the user will ask next. The SDK also keeps it in a local archive so that history search can find it after a compaction.
The result travels in its own field, not as message content, because it is not something a person said. A string is sent as itself; anything else is sent as JSON.
A tool result is usually your customer’s data: an order, an account, a record. It leaves your process when you send it. Send what your agent needs in order to be understood, not the whole row. Synap’s anticipation agent reads only the first 2,000 characters of a result, and each event on the stream must fit in 4 MB.

One turn, end to end

A small helper that wraps every tool call keeps the call, the result and their shared id together, so no tool runs unreported.

What Synap does with each

All three kinds of trace go to the same place: the working thread that Synap’s anticipation agent keeps for the conversation.
  • They do not wake it. A trace arrives while your agent is still working on the turn, and a bundle pushed then would arrive after your agent had already fetched. So traces join the thread, and the anticipation agent reads them when your assistant_message ends the turn.
  • They do not become memory. Only user_message and assistant_message events are stored as conversation turns. Reasoning, tool calls and tool results are never compacted and never extracted into long-term memory. When your agent’s reply states something a tool returned, that reply is stored as a turn like any other. For a fact that must be remembered on its own, use memories.create.
  • Their words are short-lived. The thread keeps the conversation’s 80 most recent events. It expires 30 minutes after the conversation’s last event, and is deleted when the session ends.
  • Only their shape is kept longer. Synap’s anticipation debugging records keep the outline of each step for 30 days: the tool name, the call id, step_index, thought_type, and how many characters each field held. They do not keep the words of your reasoning, your tool arguments or your tool results.

The local turn archive

The SDK keeps an archive of each conversation’s turns on the machine it runs on: the turns your process recorded, and earlier turns it fetched from Synap. record_tool_result adds the tool’s result to this archive before it sends the event, by calling archive_tool_result. The archived copy is never sent anywhere. The reason is what a compaction leaves out. A compaction summarises what people said; the exact values a tool returned, such as an order number, a date or an amount, are the details a summary drops. Once a conversation has compacted, conversation.context.get_context_for_prompt searches the earlier turns in this archive (by default for the latest user message) and appends the best matches word for word after the recent messages, in history_excerpts. Archived tool results are part of that search, so a figure a tool returned many turns ago can still reach the prompt. A result is archived as a turn with the role tool, written as [tool result: lookup_order] followed by the result as compact JSON (or the string itself), cut at 16,000 characters.
The archive is a copy on your machine, so erasing a person in Synap cannot reach it. cache.clear_user() does not touch it either, because the archive is organised by conversation, not by user. When you erase a person, remove the local copy too: shut the SDK down, then delete turns.db and its turns.db-wal and turns.db-shm files from the cache directory.

Without a stream

record_thinking, record_tool_call and record_tool_result raise ListeningNotActiveError when listen() was never called or stop_listening() has run. While the stream is reconnecting they do not raise: events are buffered (up to 100 events or five minutes) and replayed when it returns. See Event delivery. record_tool_result archives the result before it sends, so the local copy is kept even when the call raises. A long-running process that writes its turns over REST with record_message and never opens a stream can still keep tool results searchable by archiving them directly:
Synap’s framework integrations follow this rule for you: with an open stream they report reasoning, tool calls and tool results; without one they skip the reports and still archive tool results locally.

Common mistakes

  • A trace without conversation_id. Synap cannot place it in a conversation and drops it. Use the same UUID as the turn.
  • No assistant_message. The anticipation agent reads the traces when the reply arrives. A turn that never reports its reply leaves the traces unread.
  • Tool events built by hand with send_message. You have to set the event type, the role and the tool fields yourself, and keep them consistent. record_tool_call and record_tool_result set all of them for you.
  • Several tools in flight without tool_call_id. Three calls and three results arrive with nothing saying which result answers which call.
  • The whole record as a result. Send the fields your agent used. The anticipation agent reads the first 2,000 characters, and the rest only adds data that leaves your process.
  • Expecting a trace to become a memory. Traces are hints for the next turn. Store lasting facts as turns or with memories.create.
  • Opening a stream per request to send traces. A serverless handler exits before a bundle can arrive. See gRPC vs REST.