Introduction: From One-Shot to Multi-Turn Conversations

Welcome to the next step of your mastery journey! You have learned how to enable built-in tools that transform your agent from a text responder into an active assistant capable of searching the web, reading and writing files, and executing commands. However, all your interactions so far have treated each query() call as an independent request. This stateless approach works perfectly for one-off tasks, but it has a significant limitation — the agent has no memory of previous interactions.

In this lesson, you'll learn how to move beyond single queries to multi-turn conversations by capturing and reusing session IDs. The TypeScript SDK maintains conversation context through session identifiers that you extract from query responses and pass to subsequent queries using the resume option. You'll learn how to capture session IDs from the message stream, use them to continue conversations, and observe how context preservation enables sophisticated workflows that build on previous work.

Session-Based Conversations: Stateless vs Stateful

The query() function creates a new session for every call by default, which means each interaction is completely independent. When you call query() with a prompt, the SDK starts a fresh agent session, processes your request, returns the response stream, and then the session context is lost unless you explicitly capture and reuse its session ID. This stateless behavior is simple and efficient for one-off tasks, but it prevents the agent from building on previous work or answering follow-up questions.

By capturing the session ID from a query's response and passing it to subsequent queries using the resume option, you can maintain a persistent conversation. When you resume a session, the agent remembers everything from previous messages in that session. This stateful behavior enables natural conversations in which you can ask follow-up questions, provide clarifications, or build complex workflows that span multiple exchanges.

Featurequery() without resumptionquery() with session resumption
Session behaviorCreates a new session for each call; no memory of previous interactions.Maintains a persistent session across multiple turns — full context is preserved.
Memory and contextNo memory between calls — each query starts fresh with no knowledge of prior exchanges.Remembers everything from previous messages in the same session, enabling natural follow-ups.
Use caseOne-off tasks: single prompt → single response → done. Best for independent requests.Multi-turn conversations: follow-ups, clarifications, building on previous work. Best for chatbots.
Session managementAutomatic — session is created and used with each call. No session ID handling needed.Manual — requires capturing session ID from init message and passing it via option.
Capturing Session IDs from Message Streams

To maintain conversation context across multiple queries, you need to capture the session ID from the message stream. The SDK includes this information in a special system message with subtype: "init" that appears early in the stream.

The code demonstrates how to capture the session ID while processing the message stream. Before handling assistant messages, we check if the current message is a system message with subtype: "init". When we find this special initialization message, we extract its session_id property and store it in a variable. This session ID is the key to resuming the conversation later — you'll pass it to subsequent query() calls using the resume option.

Once you've captured the session ID and processed all messages, you can use that ID to continue the conversation with follow-up queries that maintain full context.

Building a Response Display Helper

Since we'll need to receive and display responses multiple times throughout our conversation, let's create a reusable helper function that encapsulates this pattern and captures the session ID for us.

The helper function takes the receive-and-display logic we just wrote and packages it into a reusable function. It accepts the AsyncIterable<SDKMessage> returned by query(), iterates over all messages to display them with formatting, and returns the captured session ID. Now, instead of writing the full for await...of loop and message handling every time we want to see the agent's response, we can simply call await displayResponse(stream) and get back the session ID for later use. This makes our conversation code cleaner and more maintainable. Now let's use this helper to conduct an actual multi-turn conversation.

Conducting a Multi-Turn Conversation

With the helper function in place, we can now conduct a complete multi-turn conversation that demonstrates context preservation. Let's configure our options once and reuse them across queries, then send a first query and capture its session ID.

The code creates an Options object with configuration that will be reused across multiple queries — model selection, turn limits, allowed tools, and working directory. Then it calls query() with a prompt asking the agent to search for information about session IDs in the TypeScript SDK. The query() function returns an async iterable stream, which we pass to our displayResponse() helper function. The helper processes all messages, displays them with formatting, and returns the captured session ID that we'll need for the next query.

This first query establishes a new session, and the session ID we captured is the key to continuing the conversation. Let's see what the agent produces in response to this first query.

Observing the First Response

When the agent processes the first query, it searches the web for information and provides a structured explanation of how session IDs work in the TypeScript SDK.

The agent explains its plan, uses the WebSearch tool to find information, and then provides a structured explanation of how session IDs work. This response becomes part of the session's context, which means the agent will remember it when processing the next query. Now let's send a second query that builds on this information to demonstrate context preservation.

Resuming Sessions with Follow-Up Queries

The real power of session IDs becomes apparent when you send a second query that references information from the first interaction. By passing the captured session ID using the resume option, the agent can understand contextual references like "what you just found."

The code sends a second query that asks the agent to edit a file and add information about "what you just found." Notice how this query uses the phrase "you just found" — this only makes sense if the agent remembers the first query and its results. The key is the resume: sessionId option, which tells the SDK to continue the previous conversation rather than starting a new one. We spread the original options object (...options) to maintain the same configuration, then add the resume property with the captured session ID.

If you had made two separate query() calls without using resume, the second query would have failed because the agent wouldn't have known what information to include. The session ID is what connects these two queries into a single conversation. Let's examine the agent's response to see how it uses the remembered context.

Observing Context Preservation

When the agent processes the second query, it demonstrates a clear understanding of the previous conversation by using the information it found earlier to complete the file editing task.

The agent reads the existing file, then uses the Edit tool to add a new section based on the information it found in the first query. Notice how the agent's explanation in the final response references the same concepts it discovered during the web search — session creation, session resumption, context preservation, and the example pattern. This demonstrates that the agent successfully maintained context across both queries and used information from the first interaction to complete the second task.

The session ID acts as a bridge between queries, allowing the agent to access the full conversation history and build on previous work. This pattern of capturing session IDs and using the resume option is the foundation for building sophisticated conversational workflows in TypeScript.

Summary

You have now learned how to move beyond single queries to multi-turn conversations using session IDs in the TypeScript SDK. By capturing session IDs from the system init message in query response streams, storing them for later use, and passing them to subsequent query() calls using the resume option, you can build agents that maintain context across multiple interactions. The example demonstrated how context preservation enables natural follow-up queries in which the agent remembers previous work and builds on it to complete complex tasks, opening the door to sophisticated conversational workflows that would be impossible with stateless queries.

The key pattern to remember is: call query() to start a conversation, iterate over the returned stream to capture the session ID from the system init message, then call query() again with resume: sessionId in the options to continue the conversation. This simple but powerful approach gives you full control over conversation flow while maintaining the functional, stream-based API design of the TypeScript SDK.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal