Skip to content

docs(query): document real-time input/output interleaving - #1179

Open
GautamSharma99 wants to merge 1 commit into
anthropics:mainfrom
GautamSharma99:fix/1169-document-query-streaming
Open

docs(query): document real-time input/output interleaving#1179
GautamSharma99 wants to merge 1 commit into
anthropics:mainfrom
GautamSharma99:fix/1169-document-query-streaming

Conversation

@GautamSharma99

Copy link
Copy Markdown

Summary

Align the public query() documentation with its actual concurrent streaming lifecycle and supported capabilities.

  • explain that async prompt input and response processing run concurrently
  • remove the send-all-then-receive promise
  • distinguish a single-call input stream from ClaudeSDKClient control methods
  • document that query() supports SDK MCP tools and hooks
  • clarify that can_use_tool requires an async-iterable prompt
  • add a deterministic regression proving output can arrive before input exhaustion

Problem

The query() docstring described async-iterable operation as unidirectional and stated that all prompts were sent before any responses were received. The implementation does the opposite: it starts Query.stream_input(prompt) in a background task and immediately consumes Query.receive_messages().

The README also said that ClaudeSDKClient additionally enabled custom tools and hooks. InternalClient already extracts SDK MCP servers, converts hooks, and sends both through the initialize control request for query().

These inaccuracies could lead callers to make incorrect assumptions about producer timing, backpressure, exception ordering, and which API they need for in-process extensions.

Documentation changes

Concurrent single-call lifecycle

The docstring now describes an async prompt iterable as the input source for one call while responses are processed concurrently. Input and output may interleave; the iterable does not need to be exhausted before the first response is yielded.

The wording avoids conflating concurrency with the stateful client API:

  • query() owns one connection lifecycle
  • the supplied iterable is its only input channel once the call starts
  • it has no separate query() or interrupt() method
  • ClaudeSDKClient remains the right choice for explicit connection management, first-class follow-up calls, interrupts, and long-running interactive sessions

Tools and hooks

The README and docstring now state that both public entry points support Python SDK MCP tools and hooks through ClaudeAgentOptions.

The streaming requirement is documented precisely: SDK MCP tools and hooks work through the internal control protocol, while a can_use_tool callback specifically requires an AsyncIterable prompt rather than a string prompt.

Behavioral regression

The new test uses synchronization events instead of sleeps:

  1. the producer yields its first prompt and blocks before the second
  2. the transport waits until that first prompt is written
  3. the transport emits an assistant response while the producer is still blocked
  4. the public query() iterator yields that response
  5. only then does the consumer release the producer to finish and receive the result

This fails under the documented send-all-then-receive model and proves the current concurrent implementation deterministically.

Runtime compatibility

No runtime behavior changes. This PR corrects the public contract and locks the existing scheduling behavior with a regression test.

Validation

  • uv run --extra dev pytest -q — 1292 passed, 5 skipped
  • uv run --extra dev ruff check src tests — passed
  • uv run --extra dev ruff format --check src tests — passed
  • uv run --extra dev mypy src — passed

Fixes #1169

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

query() documentation incorrectly promises send-all-then-receive streaming semantics

1 participant