agent-client-protocol 2.1.0

Core protocol types and traits for the Agent Client Protocol
Documentation
//! Creating and managing sessions for multi-turn conversations.
//!
//! A **session** represents a multi-turn conversation with an agent. Within a
//! session, you can send prompts, receive responses, and the agent maintains
//! context across turns.
//!
//! The examples below use the stable protocol v1 `SessionBuilder` and
//! `ActiveSession`. With the `unstable_protocol_v2` feature, callbacks created
//! through `Client.v2()` receive `V2ConnectionTo` and its `build_session*`,
//! `V2SessionBuilder`, `resume_session*`, `V2ResumeSessionBuilder`, and
//! command-only `V2Session` APIs. With `unstable_session_fork`, it also exposes
//! `fork_session*` and `V2ForkSessionBuilder`. The v2 resume and fork helpers
//! return builders and do not publish their requests until `start_session` or
//! `on_proxy_session_start` is called. V2 prompt responses acknowledge
//! acceptance independently; receive session-wide updates and interactive
//! requests through typed connection handlers.
//!
//! # Creating a Session
//!
//! Use the session builder to create a new session:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.builder().connect_with(transport, async |cx| {
//! cx.build_session_cwd()?          // Use current working directory
//!     .block_task()                // Mark as blocking
//!     .run_until(async |session| {
//!         // Use the session here
//!         Ok(())
//!     })
//!     .await?;
//! # Ok(())
//! # }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! Or specify a custom working directory:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.builder().connect_with(transport, async |cx| {
//! cx.build_session("/path/to/project")
//!     .block_task()
//!     .run_until(async |session| { Ok(()) })
//!     .await?;
//! # Ok(())
//! # }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Restoring a Session
//!
//! Stable protocol v1 direct clients can turn `session/load` and
//! `session/resume` into a [`RestoredSession`](crate::RestoredSession) without
//! manually installing session handlers. It contains both the [`ActiveSession`]
//! and the complete operation-specific response:
//!
//! ```no_run
//! # use agent_client_protocol::{Client, ConnectTo};
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.builder().connect_with(transport, async |cx| {
//! let restored = cx
//!     .load_session("session-1", "/path/to/project")
//!     .block_task()
//!     .start_session()
//!     .await?;
//! let (mut session, load_response) = restored.into_parts();
//! println!("load response: {load_response:?}");
//! session.send_prompt("Continue where we left off")?;
//! # Ok(())
//! # }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! Use `load_session` only when the agent advertises the top-level
//! `loadSession` capability. Use `resume_session` to continue without replay
//! when the agent advertises `sessionCapabilities.resume`.
//! The matching `load_session_from` and `resume_session_from` helpers preserve
//! requests assembled elsewhere. Non-blocking callers can use
//! `on_session_start` instead of `block_task().start_session()`.
//!
//! For `session/load`, the SDK acknowledges its local route before publishing
//! the request, so history updates sent before the load response remain queued
//! on the returned session. An error removes the provisional route before
//! later traffic is dispatched. Dropping an in-flight blocking start
//! immediately deactivates the provisional route and triggers the standard
//! [`SentRequest`](crate::SentRequest) drop-time cancellation behavior.
//!
//! # Sending Prompts
//!
//! Inside `run_until`, you get an [`ActiveSession`] that lets you interact
//! with the agent:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.builder().connect_with(transport, async |cx| {
//! # cx.build_session_cwd()?.block_task()
//! .run_until(async |mut session| {
//!     // Send a prompt
//!     session.send_prompt("What is 2 + 2?")?;
//!
//!     // Read the complete response as a string
//!     let response = session.read_to_string().await?;
//!     println!("{}", response);
//!
//!     // Send another prompt in the same session
//!     session.send_prompt("And what is 3 + 3?")?;
//!     let response = session.read_to_string().await?;
//!
//!     Ok(())
//! })
//! # .await?;
//! # Ok(())
//! # }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Adding MCP Servers
//!
//! You can attach MCP (Model Context Protocol) servers to a session to provide
//! tools to the agent:
//!
//! MCP attachment requires the `unstable_mcp_over_acp` feature. Standalone MCP
//! servers remain available without it. Draft protocol v2 per-session
//! attachment uses `V2SessionBuilder::with_mcp_server` for new sessions or
//! `V2ResumeSessionBuilder::with_mcp_server` for resumed sessions. With
//! `unstable_session_fork`, `V2ForkSessionBuilder::with_mcp_server` provides the
//! same attachment for forked sessions. These APIs additionally require
//! `unstable_protocol_v2`. The SDK installs the routes and initially polls the
//! runners before publishing the setup request, so the agent can use them
//! during setup or resume replay. Successful attachments remain active for the
//! connection lifetime; setup failures, including an error response after
//! cancellation, clean up the pending attachment.
//!
//! ```ignore
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # use agent_client_protocol::mcp_server::McpServer;
//! # use agent_client_protocol_rmcp::McpServerExt;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # let my_mcp_server = McpServer::<Agent, _>::builder("tools").build();
//! # Client.builder().connect_with(transport, async |cx| {
//! cx.build_session_cwd()?
//!     .with_mcp_server(my_mcp_server)?
//!     .block_task()
//!     .run_until(async |session| { Ok(()) })
//!     .await?;
//! # Ok(())
//! # }).await?;
//! # Ok(())
//! # }
//! ```
//!
//! See the cookbook for detailed MCP server examples.
//!
//! # Non-Blocking Session Start
//!
//! If you're inside an `on_receive_*` callback and need to start a session,
//! use `on_session_start` instead of `block_task().run_until()`:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # use agent_client_protocol::schema::v1::NewSessionRequest;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! Client.builder()
//!     .on_receive_request(async |req: NewSessionRequest, responder, cx| {
//!         cx.build_session_from(req)
//!             .on_session_start(async |session| {
//!                 // Handle the session
//!                 Ok(())
//!             })?;
//!         Ok(())
//!     }, agent_client_protocol::on_receive_request!())
//! #   .connect_with(transport, async |_| Ok(())).await?;
//! # Ok(())
//! # }
//! ```
//!
//! When the session response is routed during its original dispatch, session
//! routing is installed before later messages are dispatched. The callback is
//! invoked in a spawned task, so no user callback code has that ordering
//! guarantee and the callback can wait for session traffic. A response
//! interceptor that retains and routes the response later cannot retroactively
//! order setup before messages already processed. See [Ordering](super::ordering)
//! for details.
//!
//! For a draft v2 proxy, use `V2SessionBuilder::on_proxy_session_start` or
//! `V2ResumeSessionBuilder::on_proxy_session_start` instead. The feature-gated
//! `V2ForkSessionBuilder` exposes the same helper. Each forwards the complete
//! operation-specific response and then spawns the callback with an
//! `OpenedV2Session`, so the callback keeps both the command-only session
//! handle and that exact response:
//!
//! ```rust,ignore
//! Proxy.v2()
//!     .on_receive_request_from(
//!         Client,
//!         async |request: schema::v2::NewSessionRequest, responder, cx| {
//!             cx.build_session_from(request)
//!                 .on_proxy_session_start(responder, async |opened| {
//!                     let (session, setup_response) = opened.into_parts();
//!                     track_session(session.session_id(), setup_response);
//!                     Ok(())
//!                 })
//!         },
//!         agent_client_protocol::on_receive_request!(),
//!     );
//! ```
//!
//! For `session/new` and feature-gated `session/fork`, the builder installs
//! routing with the newly allocated response session ID before later inbound
//! traffic is dispatched. For `session/resume`, the builder installs and
//! acknowledges session routing
//! before publishing the downstream request, allowing replay updates to be
//! forwarded before the resume response. The downstream request inherits
//! upstream cancellation. An unsuccessful downstream response drops pending
//! routing and MCP attachment; successful setup keeps those routes for the
//! connection lifetime. The cancellation signal itself remains advisory while
//! the helper awaits that response. User work runs outside the ordering
//! barrier. V2 session updates and interactive requests remain independent
//! traffic handled by typed connection callbacks.
//!
//! # Next Steps
//!
//! - [Callbacks](super::callbacks) - Handle incoming requests
//! - [Ordering](super::ordering) - Understand when to use `block_task` vs `on_*`
//!
//! [`ActiveSession`]: crate::ActiveSession