# How the ACP Agent Harness Connects Relay Events to AI Subprocesses in Buzz

> Discover how the ACP agent harness connects relay events to AI subprocesses in Buzz. Learn about its JSON-RPC capabilities, NDJSON protocol, and real-time steering features.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: internals
- Published: 2026-08-29

---

**The ACP harness in the `buzz-acp` crate spawns AI agents as isolated subprocesses, negotiates JSON-RPC capabilities, and bridges Nostr relay events to agent stdin/stdout using an NDJSON-framed protocol with observer hooks for real-time steering and usage tracking.**

The ACP (AI Connector Protocol) agent harness is the critical infrastructure component in the [block/buzz](https://github.com/block/buzz) repository that transforms Nostr relay events into structured prompts for AI subprocesses. Implemented in Rust within the `crates/buzz-acp` directory, this harness manages the entire lifecycle of AI agent execution—from process spawning and protocol handshake to real-time message relay and graceful termination.

## Architecture Overview: Three-Stage Integration

The harness operates through a strict three-stage pipeline defined in [`crates/buzz-acp/src/acp.rs`](https://github.com/block/buzz/blob/main/crates/buzz-acp/src/acp.rs). First, `AcpClient::spawn` launches the agent binary as a child process with captured stdin/stdout. Second, the `initialize` method negotiates protocol version and capability extensions. Third, the pool component ([`src/pool.rs`](https://github.com/block/buzz/blob/main/src/pool.rs)) creates sessions and forwards relay events while the observer pattern handles asynchronous notifications.

This architecture cleanly separates **network I/O** (relay communication), **protocol handling** (JSON-RPC framing), and **process management** (subprocess lifecycle), allowing any AI binary that speaks the ACP schema to integrate without modification.

## Spawning the AI Subprocess with Process Isolation

The harness launches AI agents using `AcpClient::spawn`, defined in [`src/acp.rs#L54-L78`](https://github.com/block/buzz/blob/main/crates/buzz-acp/src/acp.rs#L54-L78). This method executes the agent binary (e.g., `"claude-agent-acp"`) as a child process in its own process group, enabling the harness to terminate the entire process tree during cleanup.

```rust
let client = AcpClient::spawn(
    command,                // e.g. "claude-agent-acp"
    &args,
    &extra_env,
    has_generated_codex_config,
).await?;

```

The harness wraps the child’s stdout in a `FramedRead` using `LinesCodec::new_with_max_length(MAX_LINE_SIZE)`, where `MAX_LINE_SIZE` is set to **10 MiB**. This hard limit prevents rogue agents from exhausting memory via infinite line output. The stdin/stdout streams are captured immediately, establishing the NDJSON communication channel.

## Protocol Initialization and Capability Discovery

After spawning, the harness negotiates the ACP protocol via the `initialize` method ([`src/acp.rs#L112-L122`](https://github.com/block/buzz/blob/main/crates/buzz-acp/src/acp.rs#L112-L122)). The harness sends a JSON-RPC *initialize* request specifying protocol version 2, and the agent responds with capability metadata.

```rust
let init_resp = client.initialize().await?;
// The response may contain:
//   "_meta.steering.supported": true/false
//   "clientCapabilities": { … }

```

The harness specifically inspects `_meta.steering.supported` to determine if the agent accepts mid-turn steering commands. This capability flag is stored internally and used later when injecting guidance via the `_session/steering` extension method (`ACP_STEER_METHOD`).

## Bridging Relay Events to Agent Sessions

The relay-to-agent message loop represents the core connection logic. The **pool** component in [`src/pool.rs`](https://github.com/block/buzz/blob/main/src/pool.rs) receives Nostr events from the relay and creates ACP sessions via `session_new`, which returns a unique `sessionId` identifying the logical conversation.

```rust
let sess = client.session_new(
    cwd,                     // absolute working directory
    mcp_servers,            // optional MCP servers
    Some(SystemPromptTransport::Field("You are a helpful assistant")),
    Some("Chat Session"),   // optional session title
).await?;

```

When a relay event contains prompt text, the pool invokes `session_prompt_with_idle_timeout` ([[`src/acp.rs`](https://github.com/block/buzz/blob/main/src/acp.rs)](https://github.com/block/buzz/blob/main/crates/buzz-acp/src/acp.rs)), writing a JSON-RPC `session/prompt` request to the child’s stdin:

```rust
let stop = client.session_prompt_with_idle_timeout(
    &sess,
    "Explain the difference between Nostr and HTTP.",
    Duration::from_secs(30),   // idle timeout
    Duration::from_secs(300),  // hard max turn duration
).await?;

```

The harness blocks on `read_until_response_with_idle_timeout`, which simultaneously drains stdout lines, respects both idle and hard deadlines, and intercepts asynchronous notifications like `session/update`.

## Notifications, Steering, and Usage Tracking

The harness installs an **observer** via `set_observer` ([`src/acp.rs#L90-L103`](https://github.com/block/buzz/blob/main/crates/buzz-acp/src/acp.rs#L90-L103)) to capture semantic events. Every read from the agent emits an `"acp_read"` event; every write emits `"acp_write"`. These events feed into `UsageTracker` implementations defined in [`src/usage.rs`](https://github.com/block/buzz/blob/main/src/usage.rs), supporting both `goose_usage` (legacy) and `standard_usage` adapters.

**Permission handling** occurs when the agent emits `session/request_permission` notifications. The harness stores these as `pending_permission_id` values and can automatically respond with `"allow_once"` or `"cancelled"` before the turn completes.

**Steering** leverages the `_meta.steering.supported` capability discovered during initialization. When enabled, the harness can inject mid-turn guidance messages to redirect the agent’s reasoning without terminating the session.

## Graceful Shutdown and Process Cleanup

Termination is handled by the `shutdown` method, which kills the entire process group and waits up to **5 seconds** for the child to exit. This prevents zombie processes and ensures that transient subprocesses (spawned by the AI agent) are properly reaped.

```rust
client.shutdown().await;

```

## Summary

- The **ACP harness** lives in `crates/buzz-acp` and manages AI agent lifecycles in the Buzz ecosystem.
- **Process isolation** uses process groups and a 10 MiB line limit via `LinesCodec` to prevent memory exhaustion.
- **Protocol negotiation** discovers capabilities like steering support through JSON-RPC initialization.
- **Relay integration** occurs via the pool component, which creates sessions and forwards prompts using `session_prompt_with_idle_timeout`.
- **Observer hooks** emit `acp_read` and `acp_write` events for usage tracking and UI updates.
- **Graceful shutdown** terminates the process tree with a 5-second timeout to prevent resource leaks.

## Frequently Asked Questions

### What is the ACP harness in Buzz?

The ACP (AI Connector Protocol) harness is the Rust-based infrastructure in the `buzz-acp` crate that connects Nostr relay events to AI agent subprocesses. It handles process spawning, JSON-RPC communication, session management, and real-time event streaming between the decentralized relay network and local AI binaries.

### How does the harness prevent memory overflow from malicious agents?

The harness enforces a **10 MiB line size limit** using `LinesCodec::new_with_max_length(MAX_LINE_SIZE)` when framing stdout from the agent subprocess. If an agent attempts to write a line exceeding this limit, the codec fails and the harness can terminate the connection, preventing memory exhaustion attacks via infinite output streams.

### What is the steering extension in the ACP protocol?

The steering extension is an optional capability advertised by agents during initialization via `_meta.steering.supported`. When enabled, the harness can send `_session/steering` JSON-RPC messages mid-turn to guide the agent’s reasoning process without canceling the current operation, enabling real-time interaction adjustments based on relay-side logic.

### How does the harness handle AI agent timeouts?

The harness implements dual-timeout protection in `session_prompt_with_idle_timeout`. An **idle timeout** (e.g., 30 seconds) triggers if the agent produces no output, while a **hard maximum duration** (e.g., 300 seconds) enforces an absolute ceiling on turn execution. Both timeouts are monitored during the `read_until_response_with_idle_timeout` loop, ensuring stuck or slow agents cannot block the relay message pipeline indefinitely.