> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-turn Conversation

`query()` supports two input modes:

* **Single-message query mode**: submit one user message; the SDK closes the session after the current reply completes. See the [Quickstart](/en/cli/sdk/quick-start).
* **Multi-message session mode**: keep the session open and carry on a multi-turn conversation with the model.

***

<div id="multi-message-session" />

## Multi-message session

Define a sequence that yields user messages in order:

```typescript theme={null}
import { qodercliAuth, query, type SDKUserMessage } from '@qoder-ai/qoder-agent-sdk';

async function* messages(): AsyncGenerator<SDKUserMessage> {
  yield {
    type: 'user',
    message: { role: 'user', content: [{ type: 'text', text: 'Analyze this codebase for security issues' }] },
    parent_tool_use_id: null,
  };

  // You can wait for any external condition before yielding the next message
  await new Promise((resolve) => setTimeout(resolve, 2000));

  yield {
    type: 'user',
    message: { role: 'user', content: [{ type: 'text', text: 'Now write a short report' }] },
    parent_tool_use_id: null,
  };
}

for await (const msg of query({
  prompt: messages(),
  options: {
    auth: qodercliAuth(),
    allowedTools: ['Read', 'Grep'],
  },
})) {
  if (msg.type === 'result' && msg.subtype === 'success') {
    console.log(msg.result);
  }
}
```

The model replies once for each user message it receives. Once the message sequence finishes, the session closes automatically. For message field definitions see [`SDKUserMessage`](/en/cli/sdk/references#sdkusermessage).

***

<div id="managing-the-session-lifecycle" />

## Managing the session lifecycle

In multi-message sessions, **the lifecycle of the object returned by `query()` is owned by the caller**. The SDK only closes the session as a side effect of the message sequence finishing on its own; in every other case the caller has to decide when to wrap up, otherwise the session keeps hanging.

Common scenarios where the caller needs to manage closure manually:

* The message sequence itself doesn't end, for example a generator that loops on external input:

  ```typescript theme={null}
  async function* fromUserUI(): AsyncGenerator<SDKUserMessage> {
    while (true) {
      const text = await waitForUserInput();   // your UI's input source
      yield {
        type: 'user',
        message: { role: 'user', content: [{ type: 'text', text }] },
        parent_tool_use_id: null,
      };
    }
  }
  ```

* You want to terminate before the message sequence would end naturally: timeout, user cancellation, other business conditions.

Two ways to close:

<div id="automatic-management-recommended-node-22-or-runtimes-with-explicit-resource-management" />

### Automatic management (recommended; Node 22+ or runtimes with explicit resource management)

Use this when the session's lifetime aligns with a function or block scope — it's released automatically when the scope ends.

```typescript theme={null}
{
  await using q = query({ prompt: fromUserUI(), options: { auth: qodercliAuth() } });
  for await (const msg of q) { /* ... */ }
}  // Automatically closes when leaving scope
```

<div id="manual-close" />

### Manual close

Use this when the close trigger comes from outside: timeout, user cancellation, or other business conditions.

```typescript theme={null}
const q = query({ prompt: fromUserUI(), options: { auth: qodercliAuth() } });
setTimeout(() => q.close(), 30_000);   // Force-close after 30 seconds

for await (const msg of q) { /* ... */ }
```
