Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Driving an agent

Implement Client and hand it to spawn. Only session_update and request_permission are required; fs/*, terminal/* and elicitation/* decline until you implement them.

use cacp::{Client, Result, schema};
use std::sync::Arc;
use tokio::process::Command;

struct Ui;

impl Client for Ui {
    async fn session_update(&self, notification: schema::SessionNotification) {
        if let schema::SessionUpdate::AgentMessageChunk(chunk) = notification.update {
            print!("{:?}", chunk.content);
        }
    }

    async fn request_permission(
        &self,
        request: schema::RequestPermissionRequest,
    ) -> Result<schema::RequestPermissionResponse> {
        Ok(schema::RequestPermissionResponse::selected(
            request.options[0].option_id.clone(),
        ))
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let (agent, _child) = cacp::spawn(Command::new("my-agent").arg("--acp"), Arc::new(Ui))?;

    agent
        .initialize(schema::InitializeRequest::new(Default::default()))
        .await?;
    let session = agent
        .new_session(schema::NewSessionRequest::new("/path/to/repo"))
        .await?;
    let done = agent
        .prompt(schema::PromptRequest::new(
            session.session_id,
            vec!["explain this repo".into()],
        ))
        .await?;

    println!("{:?}", done.stop_reason);
    Ok(())
}

That is crates/cacp/examples/client.rs, included verbatim — cargo test compiles it, so this page cannot drift from working code.

spawn runs the agent as a subprocess and takes its stdin and stdout. stderr is left as you configured it, since a TUI usually wants it captured and a CLI usually does not. The agent is killed when the returned Child drops.

connect and connect_on are the same thing over a stream you already hold.

Cancelling

Two different cancellations, easy to confuse:

  • Ending a turn is session/cancel. The agent still answers the prompt, with StopReason::Cancelled — so keep awaiting the prompt future rather than dropping it.
  • Abandoning one request is what dropping its future does: the peer gets $/cancel_request and stops working on something nobody is waiting for.