Skip to content

Commit 7cc72ce

Browse files
committed
docs(acp-nats-agent): add README
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 8df36ff commit 7cc72ce

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# acp-nats-agent
2+
3+
Server-side framework for building [ACP](https://agentclientprotocol.com/) agents over NATS.
4+
5+
Mirrors the ACP SDK's `AgentSideConnection` pattern — implement the `Agent` trait, pass a NATS client, and the framework handles subscription, dispatch, serialization, and replies.
6+
7+
## Usage
8+
9+
```rust
10+
use acp_nats::AcpPrefix;
11+
use acp_nats_agent::AgentSideNatsConnection;
12+
use agent_client_protocol::*;
13+
14+
struct MyAgent;
15+
16+
#[async_trait::async_trait(?Send)]
17+
impl Agent for MyAgent {
18+
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse> {
19+
Ok(InitializeResponse::new(ProtocolVersion::V0))
20+
}
21+
22+
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse> {
23+
Ok(NewSessionResponse::new("session-123"))
24+
}
25+
26+
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse> {
27+
// Your LLM logic here
28+
Ok(PromptResponse::new(StopReason::EndTurn))
29+
}
30+
}
31+
32+
#[tokio::main]
33+
async fn main() {
34+
let nats = async_nats::connect("localhost:4222").await.unwrap();
35+
36+
let (connection, io_task) = AgentSideNatsConnection::new(
37+
MyAgent,
38+
nats,
39+
AcpPrefix::new("acp").unwrap(),
40+
|fut| { tokio::task::spawn_local(fut); },
41+
);
42+
43+
// connection.client_for_session(session_id) returns a Client
44+
// for sending notifications, permissions, file ops back to the IDE
45+
46+
let local = tokio::task::LocalSet::new();
47+
local.run_until(io_task).await.unwrap();
48+
}
49+
```
50+
51+
## Architecture
52+
53+
```
54+
IDE <-> Bridge (acp-nats-stdio) <-> NATS <-> Agent (acp-nats-agent)
55+
```
56+
57+
The bridge sits between the IDE and NATS. This framework sits on the other side — it subscribes to agent subjects, dispatches to your `Agent` trait implementation, and replies via NATS.
58+
59+
### What the framework handles
60+
61+
- NATS subscription to `{prefix}.agent.>` and `{prefix}.*.agent.>`
62+
- Subject parsing and dispatch to all 15 ACP 0.10.2 agent methods
63+
- JSON serialization/deserialization of requests and responses
64+
- Error replies on deserialization failure (`InvalidParams`)
65+
- Ext method vs ext notification detection (by reply subject presence)
66+
- Trace context propagation on replies
67+
- Flush after every reply
68+
69+
### What you implement
70+
71+
- The `Agent` trait — your business logic
72+
- Session state management (if needed)
73+
- Concurrency strategy (via the `spawn` callback)
74+
75+
## Client callbacks
76+
77+
During prompt handling, agents often need to call back to the IDE — send notifications, request permissions, read files, run terminal commands. Use `connection.client_for_session(session_id)` to get a `Client` impl scoped to that session:
78+
79+
```rust
80+
let client = connection.client_for_session(session_id);
81+
82+
// Send progress updates
83+
client.session_notification(notification).await?;
84+
85+
// Request user permission for a tool call
86+
let response = client.request_permission(request).await?;
87+
88+
// Read a file from the IDE
89+
let file = client.read_text_file(request).await?;
90+
```
91+
92+
## Concurrency
93+
94+
The `spawn` callback controls how incoming messages are dispatched. Each message is spawned as a separate task via your callback — the message loop never blocks.
95+
96+
```rust
97+
// Single-threaded cooperative (recommended)
98+
|fut| { tokio::task::spawn_local(fut); }
99+
100+
// With backpressure
101+
let semaphore = Rc::new(Semaphore::new(64));
102+
|fut| {
103+
let permit = semaphore.clone();
104+
tokio::task::spawn_local(async move {
105+
let _permit = permit.acquire().await;
106+
fut.await;
107+
});
108+
}
109+
```

0 commit comments

Comments
 (0)