or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

agent-definitions.mdagents.mdclient.mdconfiguration-options.mdcontent-blocks.mdcore-query-interface.mdcustom-tools.mderror-handling.mderrors.mdhook-system.mdhooks.mdindex.mdmcp-config.mdmcp-server-configuration.mdmessages-and-content.mdmessages.mdoptions.mdpermission-control.mdpermissions.mdquery.mdtransport.md
COMPLETION_SUMMARY.md

core-query-interface.mddocs/

0

# Core Query Interface

1

2

The core query interface provides two interaction patterns for communicating with Claude: a simple `query()` function for stateless interactions and a `ClaudeSDKClient` class for bidirectional streaming with state management.

3

4

## Capabilities

5

6

### Simple Query Function

7

8

The `query()` function provides the simplest way to interact with Claude for one-shot or unidirectional streaming queries.

9

10

```python { .api }

11

async def query(

12

*,

13

prompt: str | AsyncIterable[dict[str, Any]],

14

options: ClaudeAgentOptions | None = None,

15

transport: Transport | None = None,

16

) -> AsyncIterator[Message]:

17

"""

18

One-shot or unidirectional streaming query to Claude.

19

20

Args:

21

prompt: User message string or async iterable of message dicts

22

options: Configuration options (default: ClaudeAgentOptions())

23

transport: Custom transport implementation (default: subprocess CLI)

24

25

Returns:

26

AsyncIterator yielding Message objects

27

28

Raises:

29

CLINotFoundError: When Claude Code CLI is not found

30

CLIConnectionError: When unable to connect to CLI

31

ProcessError: When CLI process fails

32

"""

33

```

34

35

**Parameters:**

36

37

- `prompt` (str | AsyncIterable[dict[str, Any]]): The prompt to send to Claude. Can be a string for single-shot queries or an AsyncIterable[dict] for streaming mode with continuous interaction. In streaming mode, each dict should have the structure:

38

```python

39

{

40

"type": "user",

41

"message": {"role": "user", "content": "..."},

42

"parent_tool_use_id": None,

43

"session_id": "..."

44

}

45

```

46

47

- `options` (ClaudeAgentOptions | None): Configuration options. If None, defaults to `ClaudeAgentOptions()`. Use this to configure tools, permissions, working directory, models, and more.

48

49

- `transport` (Transport | None): Custom transport implementation. If provided, this will be used instead of the default subprocess CLI transport. The transport will be automatically configured with the prompt and options.

50

51

**Returns:**

52

53

AsyncIterator[Message] that yields messages from the conversation, including:

54

- `UserMessage`: Messages from the user

55

- `AssistantMessage`: Messages from Claude

56

- `SystemMessage`: System-level messages

57

- `ResultMessage`: Final result with cost and usage information

58

- `StreamEvent`: Raw stream events from the API

59

60

**When to use query():**

61

- Simple one-off questions ("What is 2+2?")

62

- Batch processing of independent prompts

63

- Code generation or analysis tasks

64

- Automated scripts and CI/CD pipelines

65

- When you know all inputs upfront

66

- Stateless operations

67

68

**When to use ClaudeSDKClient instead:**

69

- Interactive conversations with follow-ups

70

- Chat applications or REPL-like interfaces

71

- When you need to send messages based on responses

72

- When you need interrupt capabilities

73

- Long-running sessions with state

74

75

**Usage Example - Simple Query:**

76

77

```python

78

import anyio

79

from claude_agent_sdk import query, AssistantMessage, TextBlock

80

81

async def main():

82

# Simple question

83

async for message in query(prompt="What is 2 + 2?"):

84

if isinstance(message, AssistantMessage):

85

for block in message.content:

86

if isinstance(block, TextBlock):

87

print(block.text)

88

89

anyio.run(main)

90

```

91

92

**Usage Example - With Options:**

93

94

```python

95

from claude_agent_sdk import query, ClaudeAgentOptions

96

97

async def main():

98

options = ClaudeAgentOptions(

99

allowed_tools=["Read", "Write", "Bash"],

100

permission_mode="acceptEdits",

101

cwd="/path/to/project",

102

system_prompt="You are an expert Python developer"

103

)

104

105

async for message in query(

106

prompt="Create a hello.py file that prints Hello World",

107

options=options

108

):

109

print(message)

110

111

anyio.run(main)

112

```

113

114

**Usage Example - Streaming Mode:**

115

116

```python

117

async def prompts():

118

yield {"type": "user", "message": {"role": "user", "content": "Hello"}}

119

yield {"type": "user", "message": {"role": "user", "content": "How are you?"}}

120

121

# All prompts are sent, then all responses received

122

async for message in query(prompt=prompts()):

123

print(message)

124

```

125

126

### Interactive Client

127

128

The `ClaudeSDKClient` class provides bidirectional streaming for interactive conversations with full state management.

129

130

```python { .api }

131

class ClaudeSDKClient:

132

"""

133

Bidirectional streaming client for interactive conversations.

134

135

Key features:

136

- Bidirectional: Send and receive messages at any time

137

- Stateful: Maintains conversation context across messages

138

- Interactive: Send follow-ups based on responses

139

- Control flow: Support for interrupts and session management

140

"""

141

142

def __init__(

143

self,

144

options: ClaudeAgentOptions | None = None,

145

transport: Transport | None = None,

146

):

147

"""

148

Initialize client.

149

150

Args:

151

options: Configuration options (default: ClaudeAgentOptions())

152

transport: Custom transport implementation

153

"""

154

155

async def connect(

156

self, prompt: str | AsyncIterable[dict[str, Any]] | None = None

157

) -> None:

158

"""

159

Establish connection and optionally send initial prompt.

160

161

Args:

162

prompt: Initial prompt string, message stream, or None for empty connection

163

164

Raises:

165

CLIConnectionError: When unable to connect

166

ValueError: When can_use_tool is used incorrectly

167

"""

168

169

async def query(

170

self, prompt: str | AsyncIterable[dict[str, Any]], session_id: str = "default"

171

) -> None:

172

"""

173

Send a message to Claude in streaming mode.

174

175

Args:

176

prompt: String message or async iterable of message dicts

177

session_id: Session identifier for the conversation

178

179

Raises:

180

CLIConnectionError: When not connected

181

"""

182

183

async def receive_messages(self) -> AsyncIterator[Message]:

184

"""

185

Receive all messages until connection closes.

186

187

Yields:

188

Message: Each message received from Claude

189

190

Raises:

191

CLIConnectionError: When not connected

192

"""

193

194

async def receive_response(self) -> AsyncIterator[Message]:

195

"""

196

Receive messages until ResultMessage is received.

197

198

This is a convenience method that automatically terminates after

199

receiving a ResultMessage, which indicates the response is complete.

200

201

Yields:

202

Message: Each message including the final ResultMessage

203

204

Raises:

205

CLIConnectionError: When not connected

206

"""

207

208

async def interrupt(self) -> None:

209

"""

210

Send interrupt signal to stop current operation.

211

212

Only works in streaming mode.

213

214

Raises:

215

CLIConnectionError: When not connected

216

"""

217

218

async def set_permission_mode(self, mode: str) -> None:

219

"""

220

Change permission mode during conversation.

221

222

Args:

223

mode: Permission mode ("default", "acceptEdits", "plan", "bypassPermissions")

224

225

Raises:

226

CLIConnectionError: When not connected

227

"""

228

229

async def set_model(self, model: str | None = None) -> None:

230

"""

231

Switch AI model during conversation.

232

233

Args:

234

model: Model identifier or None for default

235

236

Raises:

237

CLIConnectionError: When not connected

238

"""

239

240

async def get_server_info(self) -> dict[str, Any] | None:

241

"""

242

Get server capabilities and information.

243

244

Returns:

245

Server info dict with commands and capabilities, or None

246

247

Raises:

248

CLIConnectionError: When not connected

249

"""

250

251

async def disconnect(self) -> None:

252

"""

253

Close connection and clean up resources.

254

"""

255

256

async def __aenter__(self) -> ClaudeSDKClient:

257

"""Context manager entry - automatically connects."""

258

259

async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:

260

"""Context manager exit - automatically disconnects."""

261

```

262

263

**Usage Example - Basic Interactive Session:**

264

265

```python

266

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

267

268

async def main():

269

options = ClaudeAgentOptions(

270

allowed_tools=["Read", "Write"],

271

permission_mode="acceptEdits"

272

)

273

274

async with ClaudeSDKClient(options=options) as client:

275

# First query

276

await client.query("Create a todo.txt file")

277

async for msg in client.receive_response():

278

print(msg)

279

280

# Follow-up query in same session

281

await client.query("Add 'Buy groceries' to the file")

282

async for msg in client.receive_response():

283

print(msg)

284

285

anyio.run(main)

286

```

287

288

**Usage Example - Dynamic Permission Changes:**

289

290

```python

291

async with ClaudeSDKClient() as client:

292

# Start with default permissions

293

await client.query("Help me analyze this codebase")

294

async for msg in client.receive_response():

295

print(msg)

296

297

# Switch to auto-accept edits

298

await client.set_permission_mode('acceptEdits')

299

await client.query("Now implement the fix we discussed")

300

async for msg in client.receive_response():

301

print(msg)

302

```

303

304

**Usage Example - Interrupt Support:**

305

306

```python

307

async with ClaudeSDKClient() as client:

308

await client.query("Generate 1000 test files")

309

310

# Receive some messages

311

count = 0

312

async for msg in client.receive_messages():

313

print(msg)

314

count += 1

315

if count > 5:

316

# Stop the operation

317

await client.interrupt()

318

break

319

```

320

321

**Usage Example - Model Switching:**

322

323

```python

324

async with ClaudeSDKClient() as client:

325

# Start with default model

326

await client.query("Help me understand this problem")

327

async for msg in client.receive_response():

328

print(msg)

329

330

# Switch to a different model

331

await client.set_model('claude-sonnet-4-5')

332

await client.query("Now implement the solution")

333

async for msg in client.receive_response():

334

print(msg)

335

```

336

337

**Important Notes:**

338

339

- As of v0.0.20, you cannot use a ClaudeSDKClient instance across different async runtime contexts (e.g., different trio nurseries or asyncio task groups)

340

- The client internally maintains a persistent anyio task group that remains active from `connect()` until `disconnect()`

341

- Complete all operations within the same async context where the client was connected

342

- Always use the context manager (`async with`) or manually call `disconnect()` to clean up resources

343

344

### Transport Interface

345

346

The `Transport` class provides an abstract interface for custom transport implementations.

347

348

```python { .api }

349

class Transport:

350

"""

351

Abstract transport interface for custom implementations.

352

353

Note: This is an internal API that may change in future releases.

354

"""

355

356

async def connect(self) -> None:

357

"""Establish connection to Claude Code."""

358

359

async def write(self, data: str) -> None:

360

"""Write data to the transport."""

361

362

def read_messages(self) -> AsyncIterator[dict[str, Any]]:

363

"""Read messages stream from the transport."""

364

365

async def close(self) -> None:

366

"""Close connection and clean up resources."""

367

368

def is_ready(self) -> bool:

369

"""Check if transport is ready for communication."""

370

371

async def end_input(self) -> None:

372

"""Signal end of input stream."""

373

```

374

375

**Usage Example - Custom Transport:**

376

377

```python

378

from claude_agent_sdk import Transport, query

379

380

class MyCustomTransport(Transport):

381

async def connect(self) -> None:

382

# Custom connection logic

383

pass

384

385

async def write(self, data: str) -> None:

386

# Custom write logic

387

pass

388

389

def read_messages(self) -> AsyncIterator[dict[str, Any]]:

390

# Custom read logic

391

pass

392

393

async def close(self) -> None:

394

# Custom cleanup logic

395

pass

396

397

def is_ready(self) -> bool:

398

return True

399

400

async def end_input(self) -> None:

401

pass

402

403

# Use custom transport

404

transport = MyCustomTransport()

405

async for message in query(prompt="Hello", transport=transport):

406

print(message)

407

```

408

409

## Comparison: query() vs ClaudeSDKClient

410

411

| Feature | query() | ClaudeSDKClient |

412

|---------|---------|----------------|

413

| Communication | Unidirectional | Bidirectional |

414

| State | Stateless | Stateful |

415

| Use case | One-shot queries | Multi-turn conversations |

416

| Interrupts | No | Yes |

417

| Dynamic config | No | Yes (permissions, model) |

418

| Follow-ups | No | Yes |

419

| Complexity | Simple | Full-featured |

420

| Context manager | No | Yes |

421

422

Choose `query()` for simple, stateless interactions. Choose `ClaudeSDKClient` for interactive, stateful conversations with full control flow.

423