Skip to content

feat: agent runtime protocol#5000

Open
ehayes2000 wants to merge 1 commit into
mainfrom
agent-runtime
Open

feat: agent runtime protocol#5000
ehayes2000 wants to merge 1 commit into
mainfrom
agent-runtime

Conversation

@ehayes2000

Copy link
Copy Markdown
Contributor

No description provided.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2e9819d9-5376-44ae-a099-a99637f2bdb4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new agent_runtime_protocol workspace crate with v0 protocol schemas, role-oriented server/runtime connections, ACP routing, command and event handling, and lifecycle management. Implements a runtime-initiated jsonrpsee WebSocket transport with subscription tunneling. Adds wire transcript testing utilities, integration tests, protocol serialization tests, and examples covering in-memory communication, ACP interoperability, WebSocket connections, and an interactive mock container.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided, so the change intent can't be assessed from the author's summary. Add a brief description of the protocol, crate, and transport changes so reviewers have a clear summary.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format and clearly matches the new agent runtime protocol work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/agent_runtime_protocol/examples/mock_container.rs`:
- Around line 127-143: Update the permission handler registered by
on_receive_request so it does not automatically select AllowOnce or AllowAlways.
Default requests to RequestPermissionOutcome::Cancelled, or require explicit
user confirmation before creating SelectedPermissionOutcome; never select
AllowAlways without confirmation.
- Around line 58-75: Update the mock-container startup flow around
run_mock_container, carrier.accept, and acp_ready.notified so container task
failure is observed while waiting for either connection acceptance or readiness
notification. Select both waits against the mutable JoinHandle, propagate its
failure promptly, and preserve the existing successful connection/readiness
flow.

In `@crates/agent_runtime_protocol/SPEC.md`:
- Around line 228-230: Align the ACP delivery behavior with the specification by
updating the dispatch loops that ignore AcpRouter::route errors: add structured
TRACE logging for each failed delivery, including the message, agent, and
agent-instance identifiers. Alternatively, remove the failed-delivery TRACE
guarantee from the corresponding SPEC.md section if tracing is not intended.

In `@crates/agent_runtime_protocol/src/connection/mod.rs`:
- Around line 465-472: Update the COMMAND_METHOD branch in the request-handling
match so Command::parse_message failures produce an invalid-parameters JSON-RPC
error response using the available request_id, rather than continuing silently.
Preserve the existing behavior of ignoring commands that lack a request ID and
continue processing valid commands normally.
- Around line 160-174: Update the request flow surrounding the pending map
insertion and receiver.await so cancellation removes request_id from
self.pending via an asynchronous drop guard. Ensure the guard runs when the
caller drops or times out the future, while preserving the existing response
handling and outbound-send cleanup without removing entries prematurely on
successful responses.
- Around line 350-355: Update the close method to call
shutdown.send_replace(true) instead of shutdown.send(true), preserving the
shutdown state when no subscribers exist so later acp() calls cannot open a
channel. Add a regression test covering close before the first ACP channel is
created.

In `@crates/agent_runtime_protocol/src/schema/v0/mod.rs`:
- Around line 84-89: Update the agent-scoped event schema containing agent_id
and agent_instance_id so deserialization rejects payloads where exactly one
identifier is present, while accepting both or neither as required by SPEC.md.
Implement validation during deserialization or replace the independent options
with one optional pair, and add negative wire tests covering agentId-only and
agentInstanceId-only payloads.

In `@crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs`:
- Around line 180-209: The connection routing flow around IncomingConnection and
accept must discard superseded generations before returning a channel. Add the
route generation to each queued IncomingConnection, then have accept validate
that the queued connection still owns the matching route generation and skip
stale or cancelled entries until an active one remains; apply the same
generation-aware handling to the later IncomingConnection construction.
- Around line 143-156: The JSON-RPC transport currently authorizes routes solely
by connection_id; update the route registration, SEND_METHOD handler, and
subscribe flow to require and validate a server-issued per-client capability
token. Bind each route to its owning client, reject mismatched tokens, and
prevent replacement from leaving stale IncomingConnection entries queued;
preserve connection_not_found behavior for invalid or missing routes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d64eb8e-74d5-4f7e-98ed-6164b55dbebc

📥 Commits

Reviewing files that changed from the base of the PR and between 960f3b4 and b1aa510.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (18)
  • Cargo.toml
  • crates/agent_runtime_protocol/Cargo.toml
  • crates/agent_runtime_protocol/SPEC.md
  • crates/agent_runtime_protocol/examples/acp.rs
  • crates/agent_runtime_protocol/examples/commands_and_events.rs
  • crates/agent_runtime_protocol/examples/mock_container.rs
  • crates/agent_runtime_protocol/examples/websocket.rs
  • crates/agent_runtime_protocol/src/connection/mod.rs
  • crates/agent_runtime_protocol/src/connection/test.rs
  • crates/agent_runtime_protocol/src/lib.rs
  • crates/agent_runtime_protocol/src/schema/mod.rs
  • crates/agent_runtime_protocol/src/schema/v0/mod.rs
  • crates/agent_runtime_protocol/src/schema/v0/test.rs
  • crates/agent_runtime_protocol/src/testing/mod.rs
  • crates/agent_runtime_protocol/src/testing/test.rs
  • crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs
  • crates/agent_runtime_protocol/src/transport/jsonrpsee/test.rs
  • crates/agent_runtime_protocol/src/transport/mod.rs

Comment on lines +58 to +75
let mut container = tokio::spawn(run_mock_container(
format!("ws://{address}"),
Arc::clone(&acp_ready),
));
let incoming = carrier
.accept()
.await
.ok_or("mock container disconnected before subscribing")?;
println!(
"service accepted runtime connection {}",
incoming.connection_id()
);

let service = ServerConnection::connect(incoming.into_channel(), ());
acp_ready.notified().await;

let target = AgentTarget::new(AGENT_ID, AGENT_INSTANCE_ID);
let acp = service.acp(target)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Observe container failure while waiting for readiness.

If run_mock_container fails before subscribing or calling notify_one, main blocks forever on accept() or notified(). Select both readiness waits against &mut container, or use a oneshot readiness channel carrying Result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/examples/mock_container.rs` around lines 58 -
75, Update the mock-container startup flow around run_mock_container,
carrier.accept, and acp_ready.notified so container task failure is observed
while waiting for either connection acceptance or readiness notification. Select
both waits against the mutable JoinHandle, propagate its failure promptly, and
preserve the existing successful connection/readiness flow.

Comment on lines +127 to +143
.on_receive_request(
async move |request: RequestPermissionRequest, responder, _connection| {
println!("\n[ACP permission request] {request:#?}");
let selected = request.options.iter().find(|option| {
matches!(
option.kind,
PermissionOptionKind::AllowOnce | PermissionOptionKind::AllowAlways
)
});
let outcome = selected.map_or(RequestPermissionOutcome::Cancelled, |option| {
println!("[ACP permission selected] {}", option.name);
RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(
option.option_id.clone(),
))
});
responder.respond(RequestPermissionResponse::new(outcome))
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not automatically approve ACP permission requests.

This selects AllowOnce or AllowAlways without user confirmation, allowing the adapter to perform privileged actions on the host. Default to Cancelled or require explicit confirmation; never automatically choose AllowAlways.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/examples/mock_container.rs` around lines 127 -
143, Update the permission handler registered by on_receive_request so it does
not automatically select AllowOnce or AllowAlways. Default requests to
RequestPermissionOutcome::Cancelled, or require explicit user confirmation
before creating SelectedPermissionOutcome; never select AllowAlways without
confirmation.

Comment on lines +228 to +230
Unknown targets are discarded locally because the outer message is a
notification and has no response. Each failed delivery is traced at `TRACE`
level with its message, agent, and agent-instance identifiers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the failed ACP-delivery guarantee with the implementation.

The dispatch loops discard AcpRouter::route errors without tracing, so the promised TRACE entry is never emitted. Add structured tracing at the ignored error sites or remove this guarantee from the specification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/SPEC.md` around lines 228 - 230, Align the ACP
delivery behavior with the specification by updating the dispatch loops that
ignore AcpRouter::route errors: add structured TRACE logging for each failed
delivery, including the message, agent, and agent-instance identifiers.
Alternatively, remove the failed-delivery TRACE guarantee from the corresponding
SPEC.md section if tracing is not intended.

Comment on lines +160 to +174
let (sender, receiver) = oneshot::channel();
self.pending
.lock()
.map_err(|_| ConnectionError::Closed)?
.insert(request_id, sender);

if self.outbound.unbounded_send(Ok(message)).is_err() {
self.pending
.lock()
.map_err(|_| ConnectionError::Closed)?
.remove(&request_id);
return Err(ConnectionError::Closed);
}

receiver.await.unwrap_or(Err(ConnectionError::Closed))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Remove cancelled commands from pending.

If the caller drops or times out this future after insertion, the receiver disappears but the map entry remains until a response or connection shutdown. A non-responsive peer therefore causes unbounded growth. Use a drop guard that removes request_id on cancellation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/connection/mod.rs` around lines 160 - 174,
Update the request flow surrounding the pending map insertion and receiver.await
so cancellation removes request_id from self.pending via an asynchronous drop
guard. Ensure the guard runs when the caller drops or times out the future,
while preserving the existing response handling and outbound-send cleanup
without removing entries prematurely on successful responses.

Comment on lines +350 to +355
fn close(&self) {
let _ = self.shutdown.send(true);
if let Ok(mut channels) = self.inbound.lock() {
channels.clear();
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== mod.rs outline ==\n'
ast-grep outline crates/agent_runtime_protocol/src/connection/mod.rs --view expanded | sed -n '1,220p'

printf '\n== relevant lines around close and acp ==\n'
sed -n '1,260p' crates/agent_runtime_protocol/src/connection/mod.rs | cat -n | sed -n '1,260p'

printf '\n== search for shutdown and acp usages ==\n'
rg -n "shutdown|fn acp|send_replace|watch::" crates/agent_runtime_protocol/src/connection/mod.rs crates/agent_runtime_protocol/src -g '!target'

Repository: macro-inc/macro

Length of output: 15136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== AcpRouter methods ==\n'
sed -n '260,360p' crates/agent_runtime_protocol/src/connection/mod.rs | cat -n

printf '\n== connection tests ==\n'
sed -n '1,520p' crates/agent_runtime_protocol/src/connection/test.rs | cat -n | sed -n '1,520p'

printf '\n== search for close-before-open or shutdown-related tests ==\n'
rg -n "close|shutdown|acp\(" crates/agent_runtime_protocol/src/connection/test.rs crates/agent_runtime_protocol/src -g '!target'

Repository: macro-inc/macro

Length of output: 29607


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== run_server and run_runtime cleanup paths ==\n'
sed -n '360,540p' crates/agent_runtime_protocol/src/connection/mod.rs | cat -n

Repository: macro-inc/macro

Length of output: 5975


🌐 Web query:

tokio watch Sender send no receivers current value retained send_replace semantics

💡 Result:

In Tokio's watch channel, the behavior when there are no receivers depends on which send method is used [1][2]. The standard send method, Sender::send, fails if the channel has no active receivers [1][3]. When it fails, the provided value is not stored in the channel and is instead returned to the caller within a SendError [1][4]. This prevents the value from being available to any future receivers that might be created later [1]. In contrast, methods like Sender::send_replace (as well as send_modify and send_if_modified) permit sending a value even when no receivers exist [1][3]. When you call send_replace, the new value is successfully stored as the current value of the channel, replacing the previous one, and is immediately available to any receiver created via Sender::subscribe [1][2]. The method returns the previous value held by the channel [1][2]. Because of this distinction, send_replace is often considered a safer or more robust default when the intention is to ensure the channel always holds the most recent state, regardless of whether a consumer is currently connected [4]. While the channel is technically "closed" when all receivers are dropped, calling subscribe on the Sender will "reopen" it, allowing new receivers to access the current value stored by previous send_replace operations [1][5].

Citations:


Persist the shutdown state even when no ACP channel is open. send(true) drops the closed state when there are no subscribers, so a later acp() can still open a channel after the connection has already been shut down. Use send_replace(true) here and add a regression test for close-before-first-ACP.

🧰 Tools
🪛 ast-grep (0.44.1)

[hint] 250-355: mod.rs should only declare submodules and re-exports; move logic into its own module file.
Context: impl AcpRouter {
fn new(outbound: LogicalSender) -> Self {
let (shutdown, _) = watch::channel(false);
Self {
outbound,
inbound: Arc::new(Mutex::new(HashMap::new())),
next_message_id: Arc::new(AtomicU64::new(1)),
shutdown,
}
}

fn open(&self, target: AgentTarget) -> Result<Channel, ConnectionError> {
    if *self.shutdown.borrow() {
        return Err(ConnectionError::Closed);
    }
    let (peer, bridge) = Channel::duplex();
    let Channel { mut rx, tx } = bridge;
    let mut inbound = self.inbound.lock().map_err(|_| ConnectionError::Closed)?;
    if inbound.contains_key(&target) {
        return Err(ConnectionError::InvalidMessage(format!(
            "ACP channel already open for {}/{}",
            target.agent_id, target.agent_instance_id
        )));
    }
    inbound.insert(target.clone(), tx);
    drop(inbound);

    let outbound = self.outbound.clone();
    let inbound = Arc::clone(&self.inbound);
    let next_message_id = Arc::clone(&self.next_message_id);
    let mut shutdown = self.shutdown.subscribe();
    tokio::spawn(async move {
        loop {
            tokio::select! {
                message = rx.next() => {
                    let Some(Ok(message)) = message else {
                        break;
                    };
                    let message_id = next_message_id.fetch_add(1, Ordering::Relaxed);
                    let delivery = AcpMessage::new(
                        format!("acp-{message_id}"),
                        target.agent_id.clone(),
                        target.agent_instance_id.clone(),
                        message,
                    );
                    let Ok(params) = serde_json::to_value(delivery) else {
                        break;
                    };
                    let Ok(message) =
                        RawJsonRpcMessage::notification(ACP_METHOD.to_owned(), params)
                    else {
                        break;
                    };
                    if outbound.unbounded_send(Ok(message)).is_err() {
                        break;
                    }
                }
                _ = shutdown.changed() => break,
            }
        }

        if let Ok(mut channels) = inbound.lock() {
            channels.remove(&target);
        }
    });

    Ok(peer)
}

#[tracing::instrument(
    name = "route_acp_message",
    level = "trace",
    skip(self, delivery),
    fields(
        message_id = %delivery.message_id,
        agent_id = %delivery.agent_id,
        agent_instance_id = %delivery.agent_instance_id,
    ),
    err(level = "trace")
)]
fn route(&self, delivery: AcpMessage) -> Result<(), ConnectionError> {
    let target = AgentTarget::new(delivery.agent_id, delivery.agent_instance_id);
    let sender = self
        .inbound
        .lock()
        .map_err(|_| ConnectionError::Closed)?
        .get(&target)
        .cloned()
        .ok_or_else(|| {
            ConnectionError::InvalidMessage(format!(
                "no ACP channel for {}/{}",
                target.agent_id, target.agent_instance_id
            ))
        })?;
    sender
        .unbounded_send(Ok(delivery.message))
        .map_err(|_| ConnectionError::Closed)
}

fn close(&self) {
    let _ = self.shutdown.send(true);
    if let Ok(mut channels) = self.inbound.lock() {
        channels.clear();
    }
}

}
Note: Rule CS-25 in docs/STYLE_GUIDE.md, mined from PR #4175.
Define functions, types, and impls in a named module file and re-export
from mod.rs if needed. lib.rs is deliberately exempt: a small crate
living entirely in lib.rs is fine.
Aspirational: ~246 existing mod.rs files violate this; apply it to new
modules and to mod.rs files you are already refactoring.

(rust-mod-rs-declarations-only)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/connection/mod.rs` around lines 350 - 355,
Update the close method to call shutdown.send_replace(true) instead of
shutdown.send(true), preserving the shutdown state when no subscribers exist so
later acp() calls cannot open a channel. Add a regression test covering close
before the first ACP channel is created.

Comment on lines +465 to +472
match method {
COMMAND_METHOD => {
let Some(request_id) = request_id else {
continue;
};
let Ok(command) = Command::parse_message(method, &params) else {
continue;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Respond to malformed command requests instead of dropping them.

A command with a valid request ID but invalid parameters is silently ignored, leaving its caller waiting indefinitely. Return an invalid-parameters JSON-RPC response using the same request ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/connection/mod.rs` around lines 465 - 472,
Update the COMMAND_METHOD branch in the request-handling match so
Command::parse_message failures produce an invalid-parameters JSON-RPC error
response using the available request_id, rather than continuing silently.
Preserve the existing behavior of ignoring commands that lack a request ID and
continue processing valid commands normally.

Comment on lines +84 to +89
/// Stable logical agent identifier for an agent-scoped event.
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
/// Current agent-process identifier for an agent-scoped event.
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_instance_id: Option<String>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject partially scoped system events.

These independent optional fields accept an event containing only agentId or only agentInstanceId, despite SPEC.md Lines 160-161 requiring both or neither. Such events parse successfully and reach the system-event handler. Add deserialization validation or model the target as one optional pair, with negative wire tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/schema/v0/mod.rs` around lines 84 - 89,
Update the agent-scoped event schema containing agent_id and agent_instance_id
so deserialization rejects payloads where exactly one identifier is present,
while accepting both or neither as required by SPEC.md. Implement validation
during deserialization or replace the independent options with one optional
pair, and add negative wire tests covering agentId-only and agentInstanceId-only
payloads.

Comment on lines +143 to +156
module.register_async_method(SEND_METHOD, move |params, _, _| {
let state = state.clone();
async move {
let delivery = params.parse::<RuntimeMessage>()?;
let inbound = state
.routes
.lock()
.map_err(|_| internal_error("connection registry is unavailable"))?
.get(&delivery.connection_id)
.map(|route| route.inbound.clone())
.ok_or_else(|| connection_not_found(&delivery.connection_id))?;
inbound
.unbounded_send(Ok(delivery.message))
.map_err(|_| connection_not_found(&delivery.connection_id))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target module and inspect relevant symbols.
ast-grep outline crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs --view expanded || true

echo
echo "=== Relevant lines in mod.rs ==="
sed -n '1,260p' crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs | cat -n | sed -n '1,260p'

echo
echo "=== Search for connection_id / route ownership / accept queue ==="
rg -n "connection_id|IncomingConnection|register_async_method\\(|SEND_METHOD|accept\\(" crates/agent_runtime_protocol/src/transport/jsonrpsee -S

echo
echo "=== Search broader transport/protocol references ==="
rg -n "IncomingConnection|OutgoingConnection|connection_id|routes\\.lock|unbounded_send|connection_not_found" crates/agent_runtime_protocol/src -S

Repository: macro-inc/macro

Length of output: 24322


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== connect_runtime and helpers ==="
sed -n '260,380p' crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs | cat -n

echo
echo "=== jsonrpsee transport tests ==="
sed -n '1,360p' crates/agent_runtime_protocol/src/transport/jsonrpsee/test.rs | cat -n

echo
echo "=== call sites / construction of ServerTransport ==="
rg -n "ServerTransport::new\\(|rpc_module\\(|connect_runtime\\(" crates/agent_runtime_protocol/src -S

Repository: macro-inc/macro

Length of output: 17857


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== connect_runtime call sites across repo ==="
rg -n "connect_runtime\\(" -S .

echo
echo "=== higher-level transport wiring around jsonrpsee ==="
rg -n "jsonrpsee|ServerTransport|SUBSCRIBE_METHOD|SEND_METHOD" crates/agent_runtime_protocol/src -S

Repository: macro-inc/macro

Length of output: 4753


Bind routes to the owning client, not just connectionId.
RpcModule<()> has no caller identity, so any websocket client can send to or replace another client's route by guessing its connectionId. A replacement before accept() can also leave a stale IncomingConnection in the queue. Use an authenticated per-connection token or other server-issued capability for both send and subscribe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs` around lines
143 - 156, The JSON-RPC transport currently authorizes routes solely by
connection_id; update the route registration, SEND_METHOD handler, and subscribe
flow to require and validate a server-issued per-client capability token. Bind
each route to its owning client, reject mismatched tokens, and prevent
replacement from leaving stale IncomingConnection entries queued; preserve
connection_not_found behavior for invalid or missing routes.

Comment on lines +180 to +209
let generation = state.generation.fetch_add(1, Ordering::Relaxed);
let (connection, carrier) = Channel::duplex();
let Channel {
mut rx,
tx: inbound,
} = carrier;
let (cancel, mut cancelled) = oneshot::channel();
let previous = state.routes.lock().ok().and_then(|mut routes| {
routes.insert(
subscribe.connection_id.clone(),
Route {
generation,
inbound,
cancel,
},
)
});
if let Some(previous) = previous {
let _ = previous.cancel.send(());
}

if state
.incoming
.send(IncomingConnection {
connection_id: subscribe.connection_id.clone(),
channel: connection,
})
.is_err()
{
remove_route(&state, &subscribe.connection_id, generation);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Discard superseded connections before returning from accept().

If a replacement subscribes before the service drains incoming, the cancelled connection remains queued ahead of the active generation. accept() then returns a closed, stale channel first. Carry the generation in IncomingConnection and skip entries that no longer own the route.

Also applies to: 240-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agent_runtime_protocol/src/transport/jsonrpsee/mod.rs` around lines
180 - 209, The connection routing flow around IncomingConnection and accept must
discard superseded generations before returning a channel. Add the route
generation to each queued IncomingConnection, then have accept validate that the
queued connection still owns the matching route generation and skip stale or
cancelled entries until an active one remains; apply the same generation-aware
handling to the later IncomingConnection construction.

@whutchinson98 whutchinson98 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanaye do you think the agent_runtime_protocol could be hexified?

I feel like it would be useful to do this to make it easy to test/mock for testing.

cc @ehayes2000 please update this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants