1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//! 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 `v1::SessionBuilder` and
//! `v1::ActiveSession`. With the `unstable_protocol_v2` feature, callbacks created
//! through `Client.v2()` receive `v2::ConnectionTo` and its `build_session*`,
//! `v2::SessionBuilder`, `resume_session*`, `v2::ResumeSessionBuilder`, and
//! command-only `v2::ActiveSession` APIs. With `unstable_session_fork`, it also exposes
//! `fork_session*` and `v2::ForkSessionBuilder`. 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. Every setup method hands
//! back a [`SessionSetup`]: the session plus the complete response of the
//! request that set it up:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.v1().connect_with(transport, async |cx| {
//! cx.build_session_cwd()? // Use current working directory
//! .block_task() // Mark as blocking
//! .run_until(async |setup| {
//! // Use `setup.session` and `setup.response` 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.v1().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 [`SessionSetup`] without manually installing session
//! handlers. It contains both the [`ActiveSession`] and the complete
//! operation-specific response:
//!
//! ```no_run
//! # use agent_client_protocol::{Client, ConnectTo};
//! use agent_client_protocol::v1::SessionSetup;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.v1().connect_with(transport, async |cx| {
//! let SessionSetup {
//! mut session,
//! response: load_response,
//! ..
//! } = cx
//! .load_session("session-1", "/path/to/project")
//! .block_task()
//! .start_session()
//! .await?;
//! 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`, the [`SessionSetup`] holds an [`ActiveSession`] that
//! lets you interact with the agent:
//!
//! ```
//! # use agent_client_protocol::{Client, Agent, ConnectTo};
//! use agent_client_protocol::v1::SessionSetup;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # Client.v1().connect_with(transport, async |cx| {
//! # cx.build_session_cwd()?.block_task()
//! .run_until(async |SessionSetup { 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(())
//! # }
//! ```
//!
//! The `session/prompt` response arrives on the session's update stream as
//! [`SessionMessage::PromptResponse`](crate::v1::SessionMessage::PromptResponse),
//! after the updates the agent sent for that turn. An error response arrives
//! there too, and `read_to_string` returns it. It does not close the
//! connection. Use `read_update` to see each
//! [`SessionMessage`](crate::v1::SessionMessage), including the stop reason.
//!
//! # 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 `v2::SessionBuilder::with_mcp_server` for new sessions or
//! `v2::ResumeSessionBuilder::with_mcp_server` for resumed sessions. With
//! `unstable_session_fork`, `v2::ForkSessionBuilder::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::rmcp_3::McpServerExt;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! # let my_mcp_server = McpServer::<Agent, _>::builder("tools").build();
//! # Client.v1().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::v1::schema::NewSessionRequest;
//! # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
//! Client.v1()
//! .on_receive_request(async |req: NewSessionRequest, responder, cx| {
//! cx.build_session_from(req)
//! .on_session_start(async |setup| {
//! // Handle `setup.session` and `setup.response`
//! 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 `v2::SessionBuilder::on_proxy_session_start` or
//! `v2::ResumeSessionBuilder::on_proxy_session_start` instead. The feature-gated
//! `v2::ForkSessionBuilder` exposes the same helper. Each forwards the complete
//! operation-specific response and then spawns the callback with an
//! `v2::SessionSetup`, 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: v2::schema::NewSessionRequest, responder, cx| {
//! cx.build_session_from(request)
//! .on_proxy_session_start(responder, async |setup| {
//! let SessionSetup { session, response, .. } = setup;
//! track_session(session.session_id(), 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::v1::ActiveSession
//! [`SessionSetup`]: crate::v1::SessionSetup