Skip to content

Commit e2d686d

Browse files
authored
Merge pull request #8 from nikomatsakis/improve-docs
Further improve docs
2 parents d5ed664 + 9f08fcd commit e2d686d

File tree

12 files changed

+380
-53
lines changed

12 files changed

+380
-53
lines changed

src/elizacp/src/main.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
//! # elizacp
2+
//!
3+
//! A classic Eliza chatbot implemented as an ACP (Agent-Client Protocol) agent.
4+
//!
5+
//! ## Overview
6+
//!
7+
//! Elizacp provides a simple, predictable agent implementation that's useful for:
8+
//!
9+
//! - **Testing ACP clients** - Lightweight agent with deterministic pattern-based responses
10+
//! - **Protocol development** - Verify ACP implementations without heavy AI infrastructure
11+
//! - **Learning ACP** - Clean example of implementing the Agent-Client Protocol
12+
//!
13+
//! ## Features
14+
//!
15+
//! - **Classic Eliza patterns** - Pattern matching and reflection-based responses
16+
//! - **Full ACP support** - Session management, initialization, and prompt handling
17+
//! - **Per-session state** - Each session maintains its own Eliza instance
18+
//! - **Extensible patterns** - Easy to add new response patterns
19+
//!
20+
//! ## Usage
21+
//!
22+
//! ```bash
23+
//! # Build and run
24+
//! cargo run -p elizacp
25+
//!
26+
//! # With debug logging
27+
//! cargo run -p elizacp -- --debug
28+
//! ```
29+
//!
30+
//! The agent communicates over stdin/stdout using JSON-RPC, following the ACP specification.
31+
//!
32+
//! ## Implementation
33+
//!
34+
//! The agent maintains a `HashMap<SessionId, Eliza>` to track per-session state.
35+
//! Each session gets its own Eliza instance with independent conversation state.
36+
137
mod eliza;
238

339
use anyhow::Result;

src/sacp-conductor/src/conductor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl Conductor {
221221
let (component_read, component_write) = tokio::io::split(component_stream);
222222
let (conductor_read, conductor_write) = tokio::io::split(conductor_stream);
223223

224-
let cx = serve_args.connection.json_rpc_cx();
224+
let cx = serve_args.connection.connection_cx();
225225

226226
// Create the component streams based on the provider type
227227
let cleanup = next_provider.create(

src/sacp-conductor/src/lib.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,86 @@
1+
//! # sacp-conductor
2+
//!
3+
//! Binary for orchestrating ACP proxy chains.
4+
//!
5+
//! ## What is the conductor?
6+
//!
7+
//! The conductor is a tool that manages proxy chains - it spawns proxy components and the base agent,
8+
//! then routes messages between them. From the editor's perspective, the conductor appears as a single ACP agent.
9+
//!
10+
//! ```text
11+
//! Editor ← stdio → Conductor → Proxy 1 → Proxy 2 → Agent
12+
//! ```
13+
//!
14+
//! ## Usage
15+
//!
16+
//! ### Agent Mode
17+
//!
18+
//! Orchestrate a chain of proxies in front of an agent:
19+
//!
20+
//! ```bash
21+
//! # Chain format: proxy1 proxy2 ... agent
22+
//! sacp-conductor agent "python proxy1.py" "python proxy2.py" "python base-agent.py"
23+
//! ```
24+
//!
25+
//! The conductor:
26+
//! 1. Spawns each component as a subprocess
27+
//! 2. Connects them in a chain
28+
//! 3. Presents as a single agent on stdin/stdout
29+
//! 4. Manages the lifecycle of all processes
30+
//!
31+
//! ### MCP Bridge Mode
32+
//!
33+
//! Connect stdio to a TCP-based MCP server:
34+
//!
35+
//! ```bash
36+
//! # Bridge stdio to MCP server on localhost:8080
37+
//! sacp-conductor mcp 8080
38+
//! ```
39+
//!
40+
//! This allows stdio-based tools to communicate with TCP MCP servers.
41+
//!
42+
//! ## How It Works
43+
//!
44+
//! **Component Communication:**
45+
//! - Editor talks to conductor via stdio
46+
//! - Conductor uses `_proxy/successor/*` protocol extensions to route messages
47+
//! - Each proxy can intercept, transform, or forward messages
48+
//! - Final agent receives standard ACP messages
49+
//!
50+
//! **Process Management:**
51+
//! - All components are spawned as child processes
52+
//! - When conductor exits, all children are terminated
53+
//! - Errors in any component bring down the entire chain
54+
//!
55+
//! ## Example Use Case
56+
//!
57+
//! Add Sparkle embodiment + custom tools to any agent:
58+
//!
59+
//! ```bash
60+
//! sacp-conductor agent \
61+
//! "sparkle-acp-proxy" \
62+
//! "my-custom-tools-proxy" \
63+
//! "claude-agent"
64+
//! ```
65+
//!
66+
//! This creates a stack where:
67+
//! 1. Sparkle proxy injects MCP servers and prepends embodiment
68+
//! 2. Custom tools proxy adds domain-specific functionality
69+
//! 3. Base agent handles the actual AI responses
70+
//!
71+
//! ## Related Crates
72+
//!
73+
//! - **[sacp-proxy](https://crates.io/crates/sacp-proxy)** - Framework for building proxy components
74+
//! - **[sacp](https://crates.io/crates/sacp)** - Core ACP SDK
75+
//! - **[sacp-tokio](https://crates.io/crates/sacp-tokio)** - Tokio utilities for process spawning
76+
177
use crate::{component::CommandComponentProvider, conductor::Conductor};
278

79+
/// Component abstraction for spawning and managing proxies/agents
380
pub mod component;
81+
/// Core conductor logic for orchestrating proxy chains
482
pub mod conductor;
83+
/// MCP bridge functionality for TCP-based MCP servers
584
mod mcp_bridge;
685

786
use clap::{Parser, Subcommand};

src/sacp-proxy/src/lib.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,111 @@
1+
#![deny(missing_docs)]
2+
3+
//! # sacp-proxy
4+
//!
5+
//! Framework for building ACP proxy components that extend agent functionality.
6+
//!
7+
//! ## What are proxies?
8+
//!
9+
//! Proxies are modular components that sit between an editor and an agent, intercepting and transforming messages.
10+
//! They enable **composable agent architectures** where functionality can be added without modifying the base agent.
11+
//!
12+
//! ```text
13+
//! Editor → Proxy 1 → Proxy 2 → Agent
14+
//! ```
15+
//!
16+
//! Use cases:
17+
//! - Add MCP tools/resources/prompts to any agent
18+
//! - Inject context or modify prompts before they reach the agent
19+
//! - Filter or transform agent responses
20+
//! - Add logging, metrics, or policy enforcement
21+
//!
22+
//! ## Quick Start
23+
//!
24+
//! The simplest proxy just forwards messages unchanged:
25+
//!
26+
//! ```rust,no_run
27+
//! use sacp::JrConnection;
28+
//! use sacp_proxy::{AcpProxyExt, McpServiceRegistry};
29+
//! use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
30+
//!
31+
//! # #[tokio::main]
32+
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
33+
//! JrConnection::new(
34+
//! tokio::io::stdout().compat_write(),
35+
//! tokio::io::stdin().compat()
36+
//! )
37+
//! .name("my-proxy")
38+
//! .provide_mcp(McpServiceRegistry::default()) // Provide MCP services
39+
//! .proxy() // Enable proxy mode
40+
//! .serve()
41+
//! .await?;
42+
//! # Ok(())
43+
//! # }
44+
//! ```
45+
//!
46+
//! To add MCP tools to the proxy, provide an MCP server:
47+
//!
48+
//! ```rust,no_run
49+
//! use sacp::JrConnection;
50+
//! use sacp_proxy::{AcpProxyExt, McpServiceRegistry};
51+
//! use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
52+
//!
53+
//! # #[tokio::main]
54+
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
55+
//! JrConnection::new(
56+
//! tokio::io::stdout().compat_write(),
57+
//! tokio::io::stdin().compat()
58+
//! )
59+
//! .name("my-proxy")
60+
//! // Add MCP servers to provide tools/resources/prompts
61+
//! .provide_mcp(
62+
//! McpServiceRegistry::default()
63+
//! // .with_rmcp_server("my-server", || MyMcpServer::new())?
64+
//! )
65+
//! .proxy()
66+
//! .serve()
67+
//! .await?;
68+
//! # Ok(())
69+
//! # }
70+
//! ```
71+
//!
72+
//! See the `with_mcp_server.rs` example for a complete implementation.
73+
//!
74+
//! ## Key Concepts
75+
//!
76+
//! **Predecessor vs Successor:**
77+
//! - **Predecessor** - The component closer to the editor (could be editor or another proxy)
78+
//! - **Successor** - The component farther from the editor (could be agent or another proxy)
79+
//!
80+
//! Messages flow: `Predecessor → This Proxy → Successor`
81+
//!
82+
//! **Extension Traits:**
83+
//! - [`AcpProxyExt`] - Adds proxy-specific methods to [`JrConnection`](sacp::JrConnection)
84+
//! - [`JrCxExt`] - Adds forwarding methods (`send_to_predecessor`, `send_to_successor`)
85+
//!
86+
//! ## Examples
87+
//!
88+
//! See the [examples directory](https://github.com/symposium-org/symposium-acp/tree/main/src/sacp-proxy/examples):
89+
//!
90+
//! - **[`minimal.rs`](https://github.com/symposium-org/symposium-acp/blob/main/src/sacp-proxy/examples/minimal.rs)** - Simplest possible proxy that forwards everything unchanged
91+
//! - **[`with_mcp_server.rs`](https://github.com/symposium-org/symposium-acp/blob/main/src/sacp-proxy/examples/with_mcp_server.rs)** - Proxy that adds MCP tools to any agent
92+
//!
93+
//! ## How Proxies Work
94+
//!
95+
//! When you call `.proxy()`, the framework:
96+
//! 1. Handles capability negotiation with predecessor and successor
97+
//! 2. Sets up message routing between components
98+
//! 3. Wraps your handlers to intercept specific message types
99+
//! 4. Forwards everything else automatically
100+
//!
101+
//! You only need to handle the messages you want to intercept or transform.
102+
//!
103+
//! ## Related Crates
104+
//!
105+
//! - **[sacp](https://crates.io/crates/sacp)** - Core ACP SDK
106+
//! - **[sacp-conductor](https://crates.io/crates/sacp-conductor)** - Binary for orchestrating proxy chains
107+
//! - **[sacp-tokio](https://crates.io/crates/sacp-tokio)** - Tokio utilities for spawning agents
108+
1109
/// Proxying MCP messages over ACP.
2110
mod mcp_over_acp;
3111
pub use mcp_over_acp::*;

src/sacp-proxy/src/mcp_over_acp.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ use sacp::{
44
};
55
use serde::{Deserialize, Serialize};
66

7+
/// JSON-RPC method name for MCP connect requests
78
pub const METHOD_MCP_CONNECT_REQUEST: &str = "_mcp/connect";
89

910
/// Creates a new MCP connection. This is equivalent to "running the command".
1011
#[derive(Debug, Clone, Serialize, Deserialize)]
1112
pub struct McpConnectRequest {
13+
/// The ACP URL to connect to (e.g., "acp:uuid")
1214
pub acp_url: String,
1315
}
1416

@@ -41,8 +43,10 @@ impl JrRequest for McpConnectRequest {
4143
type Response = McpConnectResponse;
4244
}
4345

46+
/// Response to an MCP connect request
4447
#[derive(Debug, Clone, Serialize, Deserialize)]
4548
pub struct McpConnectResponse {
49+
/// Unique identifier for the established MCP connection
4650
pub connection_id: String,
4751
}
4852

@@ -56,6 +60,7 @@ impl JrResponsePayload for McpConnectResponse {
5660
}
5761
}
5862

63+
/// JSON-RPC method name for MCP disconnect notifications
5964
pub const METHOD_MCP_DISCONNECT_NOTIFICATION: &str = "_mcp/disconnect";
6065

6166
/// Disconnects the MCP connection.
@@ -92,6 +97,7 @@ impl JrMessage for McpDisconnectNotification {
9297

9398
impl JrNotification for McpDisconnectNotification {}
9499

100+
/// JSON-RPC method name for MCP requests over ACP
95101
pub const METHOD_MCP_REQUEST: &str = "_mcp/request";
96102

97103
/// An MCP request sent via ACP. This could be an MCP-server-to-MCP-client request
@@ -155,6 +161,7 @@ impl<R: JrRequest> JrRequest for McpOverAcpRequest<R> {
155161
type Response = R::Response;
156162
}
157163

164+
/// JSON-RPC method name for MCP notifications over ACP
158165
pub const METHOD_MCP_NOTIFICATION: &str = "_mcp/notification";
159166

160167
/// An MCP notification sent via ACP, either from the MCP client (the ACP agent)

src/sacp-proxy/src/mcp_server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct McpServiceRegistryData {
4040
}
4141

4242
impl McpServiceRegistry {
43+
/// Creates a new empty MCP service registry
4344
pub fn new() -> Self {
4445
Self::default()
4546
}

src/sacp-proxy/src/to_from_successor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ impl<Req: JrNotification> JrNotification for SuccessorNotification<Req> {}
123123
// Proxy methods
124124
// ============================================================
125125

126+
/// Extension trait for JrConnection that adds proxy-specific functionality
126127
pub trait AcpProxyExt<OB: AsyncWrite, IB: AsyncRead, H: JrHandler> {
127128
/// Adds a handler for requests received from the successor component.
128129
///
@@ -252,6 +253,7 @@ where
252253
R: JrRequest,
253254
F: AsyncFnMut(R, JrRequestCx<R::Response>) -> Result<(), sacp::Error>,
254255
{
256+
/// Creates a new handler for requests from the successor
255257
pub fn new(handler: F) -> Self {
256258
Self {
257259
handler,
@@ -315,6 +317,7 @@ where
315317
N: JrNotification,
316318
F: AsyncFnMut(N, JrConnectionCx) -> Result<(), sacp::Error>,
317319
{
320+
/// Creates a new handler for notifications from the successor
318321
pub fn new(handler: F) -> Self {
319322
Self {
320323
handler,

src/sacp/src/acp.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
/// ACP messages sent from agent to client
12
pub mod agent_to_client;
3+
/// ACP messages sent from client to agent
24
pub mod client_to_agent;
5+
/// Enum type implementations for ACP message types
36
mod enum_impls;
47

58
pub use agent_to_client::*;

0 commit comments

Comments
 (0)