agent_client_protocol/concepts/sessions.rs
1//! Creating and managing sessions for multi-turn conversations.
2//!
3//! A **session** represents a multi-turn conversation with an agent. Within a
4//! session, you can send prompts, receive responses, and the agent maintains
5//! context across turns.
6//!
7//! # Creating a Session
8//!
9//! Use the session builder to create a new session:
10//!
11//! ```
12//! # use agent_client_protocol::{Client, Agent, ConnectTo};
13//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
14//! # Client.builder().connect_with(transport, async |cx| {
15//! cx.build_session_cwd()? // Use current working directory
16//! .block_task() // Mark as blocking
17//! .run_until(async |session| {
18//! // Use the session here
19//! Ok(())
20//! })
21//! .await?;
22//! # Ok(())
23//! # }).await?;
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! Or specify a custom working directory:
29//!
30//! ```
31//! # use agent_client_protocol::{Client, Agent, ConnectTo};
32//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
33//! # Client.builder().connect_with(transport, async |cx| {
34//! cx.build_session("/path/to/project")
35//! .block_task()
36//! .run_until(async |session| { Ok(()) })
37//! .await?;
38//! # Ok(())
39//! # }).await?;
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! # Sending Prompts
45//!
46//! Inside `run_until`, you get an [`ActiveSession`] that lets you interact
47//! with the agent:
48//!
49//! ```
50//! # use agent_client_protocol::{Client, Agent, ConnectTo};
51//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
52//! # Client.builder().connect_with(transport, async |cx| {
53//! # cx.build_session_cwd()?.block_task()
54//! .run_until(async |mut session| {
55//! // Send a prompt
56//! session.send_prompt("What is 2 + 2?")?;
57//!
58//! // Read the complete response as a string
59//! let response = session.read_to_string().await?;
60//! println!("{}", response);
61//!
62//! // Send another prompt in the same session
63//! session.send_prompt("And what is 3 + 3?")?;
64//! let response = session.read_to_string().await?;
65//!
66//! Ok(())
67//! })
68//! # .await?;
69//! # Ok(())
70//! # }).await?;
71//! # Ok(())
72//! # }
73//! ```
74//!
75//! # Adding MCP Servers
76//!
77//! You can attach MCP (Model Context Protocol) servers to a session to provide
78//! tools to the agent:
79//!
80//! MCP attachment requires the `unstable_mcp_over_acp` feature. Standalone MCP
81//! servers remain available without it.
82//!
83//! ```ignore
84//! # use agent_client_protocol::{Client, Agent, ConnectTo};
85//! # use agent_client_protocol::mcp_server::McpServer;
86//! # use agent_client_protocol_rmcp::McpServerExt;
87//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
88//! # let my_mcp_server = McpServer::<Agent, _>::builder("tools").build();
89//! # Client.builder().connect_with(transport, async |cx| {
90//! cx.build_session_cwd()?
91//! .with_mcp_server(my_mcp_server)?
92//! .block_task()
93//! .run_until(async |session| { Ok(()) })
94//! .await?;
95//! # Ok(())
96//! # }).await?;
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! See the cookbook for detailed MCP server examples.
102//!
103//! # Non-Blocking Session Start
104//!
105//! If you're inside an `on_receive_*` callback and need to start a session,
106//! use `on_session_start` instead of `block_task().run_until()`:
107//!
108//! ```
109//! # use agent_client_protocol::{Client, Agent, ConnectTo};
110//! # use agent_client_protocol::schema::v1::NewSessionRequest;
111//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
112//! Client.builder()
113//! .on_receive_request(async |req: NewSessionRequest, responder, cx| {
114//! cx.build_session_from(req)
115//! .on_session_start(async |session| {
116//! // Handle the session
117//! Ok(())
118//! })?;
119//! Ok(())
120//! }, agent_client_protocol::on_receive_request!())
121//! # .connect_with(transport, async |_| Ok(())).await?;
122//! # Ok(())
123//! # }
124//! ```
125//!
126//! When the session response is routed during its original dispatch, session
127//! routing is installed before later messages are dispatched. The callback is
128//! invoked in a spawned task, so no user callback code has that ordering
129//! guarantee and the callback can wait for session traffic. A response
130//! interceptor that retains and routes the response later cannot retroactively
131//! order setup before messages already processed. See [Ordering](super::ordering)
132//! for details.
133//!
134//! # Next Steps
135//!
136//! - [Callbacks](super::callbacks) - Handle incoming requests
137//! - [Ordering](super::ordering) - Understand when to use `block_task` vs `on_*`
138//!
139//! [`ActiveSession`]: crate::ActiveSession