Skip to content

Commit a9673d3

Browse files
authored
Merge pull request #100 from nikomatsakis/main
sacp 10.0.0-alpha.1: ordering guarantees, docs reorganization, and compilable examples
2 parents 809e9c9 + 795a080 commit a9673d3

File tree

19 files changed

+1470
-448
lines changed

19 files changed

+1470
-448
lines changed

commit-messages.txt

Lines changed: 0 additions & 129 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! The roles in ACP: clients, agents, proxies, and conductors.
2+
//!
3+
//! The Agent-Client Protocol defines how AI agents communicate with the
4+
//! applications that use them. Understanding the four roles helps you choose
5+
//! the right approach for what you're building.
6+
//!
7+
//! # Clients
8+
//!
9+
//! A **client** is an application that wants to use an AI agent. Examples include:
10+
//!
11+
//! - IDEs like VS Code or JetBrains
12+
//! - Command-line tools
13+
//! - Web applications
14+
//! - Automation scripts
15+
//!
16+
//! Clients send prompts to agents and receive responses. They can also handle
17+
//! requests from the agent (like permission requests or tool approvals).
18+
//!
19+
//! # Agents
20+
//!
21+
//! An **agent** is an AI-powered service that responds to prompts. Examples include:
22+
//!
23+
//! - Claude Code
24+
//! - Custom agents built with language models
25+
//!
26+
//! Agents receive prompts, process them (typically by calling an LLM), and stream
27+
//! back responses. They may also request permissions, invoke tools, or ask for
28+
//! user input during processing.
29+
//!
30+
//! # Proxies
31+
//!
32+
//! A **proxy** sits between a client and an agent, intercepting and potentially
33+
//! modifying messages in both directions. Proxies are useful for:
34+
//!
35+
//! - Adding tools via MCP (Model Context Protocol) servers
36+
//! - Injecting system prompts or context
37+
//! - Logging and debugging
38+
//! - Filtering or transforming messages
39+
//!
40+
//! Proxies can be chained - you can have multiple proxies between a client and
41+
//! an agent, each adding its own capabilities.
42+
//!
43+
//! # Conductors
44+
//!
45+
//! A **conductor** orchestrates a chain of proxies with a final agent. It:
46+
//!
47+
//! - Spawns and manages proxy processes
48+
//! - Routes messages through the chain
49+
//! - Handles initialization and shutdown
50+
//!
51+
//! The [`sacp-conductor`] crate provides a conductor implementation. Most users
52+
//! don't need to implement conductors themselves - they just configure which
53+
//! proxies to use.
54+
//!
55+
//! # Message Flow
56+
//!
57+
//! Messages flow through the system like this:
58+
//!
59+
//! ```text
60+
//! Client <-> Proxy 1 <-> Proxy 2 <-> ... <-> Agent
61+
//! ^ ^
62+
//! | |
63+
//! +------ Conductor manages -------+
64+
//! ```
65+
//!
66+
//! Each arrow represents a bidirectional connection. Requests flow toward the
67+
//! agent, responses flow back toward the client, and notifications can flow
68+
//! in either direction.
69+
//!
70+
//! # Next Steps
71+
//!
72+
//! Now that you understand the roles, see [Connections and Links](super::connections)
73+
//! to learn how to establish connections in code.
74+
//!
75+
//! [`sacp-conductor`]: https://crates.io/crates/sacp-conductor

src/sacp/src/concepts/callbacks.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//! Handling incoming messages with `on_receive_*` callbacks.
2+
//!
3+
//! So far we've seen how to *send* messages. But ACP is bidirectional - the
4+
//! remote peer can also send messages to you. Use callbacks to handle them.
5+
//!
6+
//! # Handling Requests
7+
//!
8+
//! Use `on_receive_request` to handle incoming requests that expect a response:
9+
//!
10+
//! ```
11+
//! # use sacp::{ClientToAgent, AgentToClient, Component};
12+
//! # use sacp_test::{ValidateRequest, ValidateResponse};
13+
//! # async fn example(transport: impl Component<AgentToClient>) -> Result<(), sacp::Error> {
14+
//! ClientToAgent::builder()
15+
//! .on_receive_request(async |req: ValidateRequest, request_cx, cx| {
16+
//! // Process the request
17+
//! let is_valid = req.data.len() > 0;
18+
//!
19+
//! // Send the response
20+
//! request_cx.respond(ValidateResponse { is_valid, error: None })
21+
//! }, sacp::on_receive_request!())
22+
//! .run_until(transport, async |cx| { Ok(()) })
23+
//! .await?;
24+
//! # Ok(())
25+
//! # }
26+
//! ```
27+
//!
28+
//! Your callback receives three arguments:
29+
//! - The request payload (e.g., `PermissionRequest`)
30+
//! - A [`JrRequestCx`] for sending the response
31+
//! - A [`JrConnectionCx`] for sending other messages
32+
//!
33+
//! # Handling Notifications
34+
//!
35+
//! Use `on_receive_notification` for fire-and-forget messages that don't need
36+
//! a response:
37+
//!
38+
//! ```
39+
//! # use sacp::{ClientToAgent, AgentToClient, Component};
40+
//! # use sacp_test::StatusUpdate;
41+
//! # async fn example(transport: impl Component<AgentToClient>) -> Result<(), sacp::Error> {
42+
//! ClientToAgent::builder()
43+
//! .on_receive_notification(async |notif: StatusUpdate, cx| {
44+
//! println!("Status: {}", notif.message);
45+
//! Ok(())
46+
//! }, sacp::on_receive_notification!())
47+
//! # .run_until(transport, async |_| Ok(())).await?;
48+
//! # Ok(())
49+
//! # }
50+
//! ```
51+
//!
52+
//! # The Request Context
53+
//!
54+
//! The [`JrRequestCx`] lets you send a response to the request:
55+
//!
56+
//! ```
57+
//! # use sacp::{ClientToAgent, AgentToClient, Component};
58+
//! # use sacp_test::{MyRequest, MyResponse};
59+
//! # async fn example(transport: impl Component<AgentToClient>) -> Result<(), sacp::Error> {
60+
//! # ClientToAgent::builder()
61+
//! # .on_receive_request(async |req: MyRequest, request_cx, cx| {
62+
//! // Send a successful response
63+
//! request_cx.respond(MyResponse { status: "ok".into() })?;
64+
//! # Ok(())
65+
//! # }, sacp::on_receive_request!())
66+
//! # .run_until(transport, async |_| Ok(())).await?;
67+
//! # Ok(())
68+
//! # }
69+
//! ```
70+
//!
71+
//! Or send an error:
72+
//!
73+
//! ```
74+
//! # use sacp::{ClientToAgent, AgentToClient, Component};
75+
//! # use sacp_test::{MyRequest, MyResponse};
76+
//! # async fn example(transport: impl Component<AgentToClient>) -> Result<(), sacp::Error> {
77+
//! # ClientToAgent::builder()
78+
//! # .on_receive_request(async |req: MyRequest, request_cx, cx| {
79+
//! request_cx.respond_with_error(sacp::Error::invalid_params())?;
80+
//! # Ok(())
81+
//! # }, sacp::on_receive_request!())
82+
//! # .run_until(transport, async |_| Ok(())).await?;
83+
//! # Ok(())
84+
//! # }
85+
//! ```
86+
//!
87+
//! You must send exactly one response per request. If your callback returns
88+
//! without responding, an error response is sent automatically.
89+
//!
90+
//! # Multiple Handlers
91+
//!
92+
//! You can register multiple handlers. They're tried in order until one
93+
//! handles the message:
94+
//!
95+
//! ```
96+
//! # use sacp::{ClientToAgent, AgentToClient, Component};
97+
//! # use sacp_test::{ValidateRequest, ValidateResponse, ExecuteRequest, ExecuteResponse};
98+
//! # async fn example(transport: impl Component<AgentToClient>) -> Result<(), sacp::Error> {
99+
//! ClientToAgent::builder()
100+
//! .on_receive_request(async |req: ValidateRequest, request_cx, cx| {
101+
//! // Handle validation requests
102+
//! request_cx.respond(ValidateResponse { is_valid: true, error: None })
103+
//! }, sacp::on_receive_request!())
104+
//! .on_receive_request(async |req: ExecuteRequest, request_cx, cx| {
105+
//! // Handle execution requests
106+
//! request_cx.respond(ExecuteResponse { result: "done".into() })
107+
//! }, sacp::on_receive_request!())
108+
//! # .run_until(transport, async |_| Ok(())).await?;
109+
//! # Ok(())
110+
//! # }
111+
//! ```
112+
//!
113+
//! # Ordering Guarantees
114+
//!
115+
//! Callbacks run inside the dispatch loop and block further message processing
116+
//! until they complete. This gives you ordering guarantees but also means you
117+
//! need to be careful about deadlocks.
118+
//!
119+
//! See [Ordering](super::ordering) for the full details.
120+
//!
121+
//! # Next Steps
122+
//!
123+
//! - [Explicit Peers](super::peers) - Use `_from` variants to specify the source peer
124+
//! - [Ordering](super::ordering) - Understand dispatch loop semantics
125+
//!
126+
//! [`JrRequestCx`]: crate::JrRequestCx
127+
//! [`JrConnectionCx`]: crate::JrConnectionCx

0 commit comments

Comments
 (0)