You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/rfds/rust-sdk-v2.mdx
+37-37Lines changed: 37 additions & 37 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@ Replace the current ACP Rust SDK with a new implementation based on SACP (Sympos
16
16
17
17
The current `agent-client-protocol` crate has a straightforward design with trait-based callbacks for common ACP methods and well-typed requests and responses. It's convenient for simple purposes but quickly becomes awkward when attempting more complex designs.
18
18
19
-
Two examples that we found pushed the limits of the design are the *conductor* (from the [proxy chains](./proxy-chains) RFD) and the [patchwork-rs](https://patchwork-lang.github.io/patchwork-rs/) project:
19
+
Two examples that we found pushed the limits of the design are the _conductor_ (from the [proxy chains](./proxy-chains) RFD) and the [patchwork-rs](https://patchwork-lang.github.io/patchwork-rs/) project:
20
20
21
21
The Conductor is an orchestrator that routes messages between proxies, agents, and MCP servers. It must adapt messages as they flow through the system and maintain the correct ordering.
22
22
@@ -80,7 +80,7 @@ In the current SDK, every incoming request or notification results is handled in
80
80
81
81
### Limitation: Confusing naming and 1:1 assumption
82
82
83
-
`AgentSideConnection` is ambiguous - does this represent the agent, or the connection *to* an agent? What's more, the SDK currently assumes that each connection has a single peer, but [proxies](./proxy-chains) may wish to send/receive messages from multiple peers (client or agent). This was a constant source of confusion in early versions of the conductor and frequently required the author to get out a pencil and paper and work things out very carefully.
83
+
`AgentSideConnection` is ambiguous - does this represent the agent, or the connection _to_ an agent? What's more, the SDK currently assumes that each connection has a single peer, but [proxies](./proxy-chains) may wish to send/receive messages from multiple peers (client or agent). This was a constant source of confusion in early versions of the conductor and frequently required the author to get out a pencil and paper and work things out very carefully.
84
84
85
85
**Goal:** Use directional naming like `ClientToAgent` that makes relationships explicit: "I am the client, the remote peer is an agent." Enable multiple peers.
86
86
@@ -94,7 +94,7 @@ When building tests and other applications, it's convenient to be able to create
94
94
95
95
This isn't a limitation of the current SDK per se, but a common pitfall in async Rust designs that we want to address.
96
96
97
-
We want the SDK to be independent from specific executors (not tied to tokio) while still supporting richer patterns like spawning background tasks. One specific common issue with Rust async APIs is *starvation*, which can occur with stream-like APIs where it is important to keep awaiting the stream so that items make progress. For example, in a setup like this one, the "connection" is not being "awaited" while each message is handled:
97
+
We want the SDK to be independent from specific executors (not tied to tokio) while still supporting richer patterns like spawning background tasks. One specific common issue with Rust async APIs is _starvation_, which can occur with stream-like APIs where it is important to keep awaiting the stream so that items make progress. For example, in a setup like this one, the "connection" is not being "awaited" while each message is handled:
98
98
99
99
```rust
100
100
// PROBLEMATIC: message handling starves while processing
@@ -124,23 +124,23 @@ We propose to replace the current Rust SDK with [`sacp`](https://github.com/symp
124
124
125
125
The following table summarizes the key API concepts and which goals they address:
-[elizacp](https://github.com/symposium-dev/symposium-acp/tree/main/src/elizacp) - agent implementing the classic ELIZA program
142
+
-[sacp-tee](https://crates.io/crates/sacp-tee) - proxy that logs messages before forwarding
143
+
-[yopo](https://crates.io/crates/yopo) ("You Only Prompt Once") - CLI tool for single prompts
144
144
145
145
The [Deep dive](#deep-dive) section below walks through each concept in detail. Code examples are also available in the [sacp-cookbook crate](https://docs.rs/sacp-cookbook/latest/sacp_cookbook/).
146
146
@@ -152,10 +152,10 @@ This section walks through the SDK concepts in detail, organized by what you're
152
152
153
153
#### Link types and directional naming
154
154
155
-
The SDK is organized around *link types* that describe who you are and who you're talking to. The two most common examples are:
155
+
The SDK is organized around _link types_ that describe who you are and who you're talking to. The two most common examples are:
156
156
157
-
*`ClientToAgent` - "I am a client, connecting to an agent"
158
-
*`AgentToClient` - "I am an agent, serving a client"
157
+
-`ClientToAgent` - "I am a client, connecting to an agent"
158
+
-`AgentToClient` - "I am an agent, serving a client"
159
159
160
160
To build a connection, start with the link type and invoke the `builder` method. Builders use the typical "fluent" style:
161
161
@@ -234,7 +234,7 @@ AgentToClient::builder()
234
234
.await
235
235
```
236
236
237
-
**`run_until()`** takes an async closure and runs your code concurrently with message handling. The closure receives a *connection context* (conventionally called `cx`) - this is how you interact with the connection, sending messages, spawning tasks, and adding dynamic handlers. When the closure returns, the connection closes:
237
+
**`run_until()`** takes an async closure and runs your code concurrently with message handling. The closure receives a _connection context_ (conventionally called `cx`) - this is how you interact with the connection, sending messages, spawning tasks, and adding dynamic handlers. When the closure returns, the connection closes:
238
238
239
239
```rust
240
240
ClientToAgent::builder()
@@ -250,7 +250,7 @@ ClientToAgent::builder()
250
250
.await
251
251
```
252
252
253
-
The `run_until` pattern directly addresses starvation. Instead of exposing an async stream that users might accidentally block, `run_until` runs your code *concurrently* with message handling.
253
+
The `run_until` pattern directly addresses starvation. Instead of exposing an async stream that users might accidentally block, `run_until` runs your code _concurrently_ with message handling.
254
254
255
255
The `cx` type (`JrConnectionCx`) follows the "handle" pattern: cloned values refer to the same connection. It's `'static` so it can be sent across threads or stored in structs. Handlers registered with `on_receive_*` also receive a `cx` parameter.
256
256
@@ -342,7 +342,7 @@ Use `cx.spawn` to run work concurrently with message handling:
342
342
/* ... */
343
343
Ok(())
344
344
})?;
345
-
345
+
346
346
// Handler returns immediately, spawned work continues
347
347
request_cx.respond(/* ... */)
348
348
}, sacp::on_receive_request!())
@@ -422,7 +422,7 @@ When you need to start a session from inside an `on_receive_*` handler but can't
422
422
letresponse=session.read_to_string().await?;
423
423
Ok(())
424
424
})?;
425
-
425
+
426
426
// Handler returns immediately, session runs in background
427
427
Ok(())
428
428
}, sacp::on_receive_request!())
@@ -456,7 +456,7 @@ For proxies that want to inject MCP servers but otherwise forward all messages,
456
456
.block_task()
457
457
.start_session_proxy(request_cx)
458
458
.await?;
459
-
459
+
460
460
// Session messages are automatically proxied between client and agent
461
461
Ok(())
462
462
}, sacp::on_receive_request!())
@@ -492,7 +492,7 @@ Request handlers receive an additional `request_cx` parameter for sending the re
@@ -579,25 +579,25 @@ Use `on_receive_message` with `MessageCx` to intercept any incoming message (req
579
579
580
580
#### Handler chains
581
581
582
-
A *message handler* takes ownership of a message and either handles it or returns a (possibly modified) copy to be tried by the next handler. Handlers are chained together - each gets a chance to claim the message before it passes to the next.
582
+
A _message handler_ takes ownership of a message and either handles it or returns a (possibly modified) copy to be tried by the next handler. Handlers are chained together - each gets a chance to claim the message before it passes to the next.
0 commit comments