Skip to content

feat(acp-nats): add new_session handler with session.ready lifecycle#12

Merged
yordis merged 1 commit intomainfrom
feat/acp-nats-new-session
Feb 25, 2026
Merged

feat(acp-nats): add new_session handler with session.ready lifecycle#12
yordis merged 1 commit intomainfrom
feat/acp-nats-new-session

Conversation

@yordis
Copy link
Member

@yordis yordis commented Feb 25, 2026

Summary

  • Add new_session handler aligned with initialize.rs patterns (handler-specific error mapping via map_new_session_error, single-line generics, local subject variable, acp_prefix() accessor)
  • Publish ext.session.ready notification after successful session creation via publish_session_ready, with a 100ms delay (SESSION_READY_DELAY) to preserve the ordering guarantee documented on ExtSessionReady — the Agent trait returns the response before the transport layer sends it to the client, so without the delay the spawned task could publish session.ready before the client receives the session/new response
  • Add ExtSessionReady extension type, session_new and ext_session_ready NATS subject functions, and record_error() metrics
  • Change AcpPrefix internal storage from String to Arc<str> so cloning into spawned tasks is a ref-count bump instead of a heap allocation
  • Add comprehensive test suite (12 tests) mirroring the initialize handler tests

See upstream RFD: agentclientprotocol/agent-client-protocol#419

Test plan

  • cargo check -p acp-nats passes
  • cargo test -p acp-nats — all 30 tests pass (18 existing + 12 new)
  • Review handler error mapping covers all NatsError variants
  • Review spawned task captures only owned/Send values (no borrows from Bridge)
  • Verify SESSION_READY_DELAY provides sufficient margin for the transport to deliver the response before session.ready is published

@cursor
Copy link

cursor bot commented Feb 25, 2026

PR Summary

Medium Risk
Introduces a new async lifecycle publish (session.ready) after new_session, including a spawned task and fixed delay that could impact ordering/timing and shutdown behavior. Otherwise changes are localized and covered by new unit tests.

Overview
Implements the ACP new_session flow in acp-nats by forwarding NewSessionRequest over NATS (*.agent.session.new) with timeout/error mapping, tracing, and request metrics, replacing the previous not yet implemented stub.

After a successful session creation, the bridge now asynchronously publishes an ExtSessionReady message on *.agent.ext.session.ready.<session_id> (with a small fixed delay) to coordinate client/notification ordering. This also adds the ExtSessionReady extension type, new NATS subject helpers, re-exports NATS publish/retry/flush utilities, and a new acp.errors.total metric for publish failures.

Updates AcpPrefix to store the prefix as Arc<str> to make it cheap to clone into spawned tasks, and adds comprehensive tests for the new handler and metrics behavior.

Written by Cursor Bugbot for commit c79f468. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

Added a New Session flow: new agent::new_session module forwards NewSessionRequest over NATS, maps NATS errors to agent errors, records metrics, and causes the bridge to publish an external session-ready message; new NATS extension type and subject helpers were introduced, and a new errors metric was added.

Changes

Cohort / File(s) Summary
New session handler
rsworkspace/crates/acp-nats/src/agent/new_session.rs
New module implementing handle(...) -> Result<NewSessionResponse>: sends NATS request to agent.session.new with timeout, maps NatsError→agent errors, records timing and metrics, spawns/publishes session-ready notifications, and includes extensive tests.
Agent module update
rsworkspace/crates/acp-nats/src/agent/mod.rs
Added mod new_session; Agent::new_session(&self, args: NewSessionRequest) signature changed to delegate to new_session::handle(self, args).await; tests adjusted to use delegated flow.
NATS extensions & exports
rsworkspace/crates/acp-nats/src/nats/extensions.rs, rsworkspace/crates/acp-nats/src/nats/mod.rs
Added public ExtSessionReady { session_id } type (Serialize/Deserialize) and re-exported it; expanded re-exports from trogon_nats to include publish, PublishOptions, RetryPolicy, FlushPolicy, while keeping existing exports.
NATS subjects helpers
rsworkspace/crates/acp-nats/src/nats/subjects.rs
Added subject helper functions: session_new(prefix: &str) -> String and ext_session_ready(prefix: &str, session_id: &str) -> String to construct routing keys.
Telemetry metrics
rsworkspace/crates/acp-nats/src/telemetry/metrics.rs
Added errors_total: Counter<u64> to Metrics and record_error(&self, operation, reason) method to increment error counter; new session flow records metrics via existing/new helpers.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Agent as Bridge/Agent
    participant NATS as NATS Broker
    participant Metrics as Metrics

    Client->>Agent: new_session(NewSessionRequest)
    activate Agent
    Agent->>Agent: start timer, trace context
    Agent->>NATS: request -> agent.session.new (with timeout)
    activate NATS
    NATS-->>Agent: NewSessionResponse / NatsError
    deactivate NATS
    alt success
        Agent->>Metrics: record success & duration
        Agent->>Agent: spawn publish ext.session.ready(session_id)
        Agent->>NATS: publish ext.session.ready (ExtSessionReady)
    else failure
        Agent->>Metrics: record_error("new_session", reason)
        Agent->>Metrics: record failure & duration
    end
    Agent-->>Client: NewSessionResponse / Error
    deactivate Agent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped a NATS-lined trail today,
I sent a session call away,
A ready note, a tiny cheer,
Counters click — the path is clear,
Bridge hums softly — hop, hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a new_session handler and session.ready lifecycle management to the acp-nats crate.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing the new_session handler implementation and session.ready lifecycle management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/acp-nats-new-session

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 and usage tips.

@yordis yordis force-pushed the feat/acp-nats-new-session branch from ff7cdac to e6ce156 Compare February 25, 2026 02:47
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
rsworkspace/crates/acp-nats/src/telemetry/metrics.rs (1)

44-56: Consider adding a session_id attribute or structured log context to record_session_created.

Currently record_session_created() records no attributes. This is fine if you only need aggregate counts, but if you later need to correlate metrics with specific sessions, you'd need to revisit this. Given the caller already logs session_id via tracing, this is acceptable as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/telemetry/metrics.rs` around lines 44 - 56,
record_session_created currently increments sessions_created with no attributes;
update it to accept a session identifier and record it as an attribute so
metrics can be correlated with a session. Change the signature of
record_session_created (e.g., add a session_id parameter) and pass
KeyValue::new("session_id", session_id) alongside other attributes to
self.sessions_created.add; update all callers to provide the session_id and
preserve existing behavior when caller does not have an id.
rsworkspace/crates/acp-nats/src/agent/mod.rs (1)

88-93: Task list grows unbounded if sessions are created faster than tasks complete.

retain(|t| !t.is_finished()) only prunes on each new spawn_session_ready call. Under sustained high session-creation rates, the Vec could grow if publishes are slow. This is unlikely in practice (publish with flush is fast), but worth keeping in mind. If this ever becomes a concern, consider a bounded channel or a periodic sweep.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/mod.rs` around lines 88 - 93, The Vec
stored in session_ready_publish_tasks can grow if sessions are spawned faster
than publishes complete because retain(|t| !t.is_finished()) only runs on spawn;
replace the Vec+manual retain by a bounded, self-pruning structure such as
FuturesUnordered (store a FuturesUnordered<Task> in session_ready_publish_tasks
and push the spawned task into it) or else swap the Vec for a bounded mpsc
channel and have a dedicated cleanup background task that polls/completes
finished tasks; update spawn_session_ready to push into that structure instead
of pushing into the raw Vec and ensure the new structure is polled/cleaned
regularly so finished tasks are dropped automatically.
rsworkspace/crates/acp-nats/src/agent/new_session.rs (1)

25-30: Serialize error leaks internal details, inconsistent with the Deserialize branch.

Line 29 includes the raw serialization error in the user-facing message (format!("Failed to serialize...")), while the Deserialize branch on line 36 correctly returns a generic "Invalid response from agent". Consider whether exposing serialization internals is intentional.

Proposed fix for consistency
         NatsError::Serialize(inner) => {
             warn!(error = %inner, "failed to serialize new_session request");
             Error::new(
                 ErrorCode::InternalError.into(),
-                format!("Failed to serialize new_session request: {}", inner),
+                "Failed to serialize new_session request",
             )
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` around lines 25 - 30,
The Serialize branch currently logs the internal error but returns a user-facing
message containing the raw serialization error; change NatsError::Serialize
handling so it still logs the inner error with warn!(error = %inner, "failed to
serialize new_session request") but construct the
Error::new(ErrorCode::InternalError.into(), ...) with a non-sensitive generic
message (for example the same generic used by the Deserialize branch like
"Invalid response from agent" or "Failed to serialize new_session request")—do
not interpolate inner into the returned string; update the code around
NatsError::Serialize, warn!, and Error::new to reflect this.
rsworkspace/crates/acp-nats/src/nats/subjects.rs (1)

6-12: Consistent with existing pattern; consider domain types in a future pass.

The new helpers follow the same &str parameter convention as initialize. Per coding guidelines, domain-specific value objects (e.g., AcpPrefix, SessionId) are preferred over primitives. Since the existing code already uses &str here, this is fine for now but worth aligning in a dedicated refactor. As per coding guidelines: "Prefer domain-specific value objects over primitives (e.g. AcpPrefix not String)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs` around lines 6 - 12, The
helpers session_new and ext_session_ready currently accept primitive &str
parameters; change them to take domain types (e.g., AcpPrefix for prefix and
SessionId for session_id in ext_session_ready) to match coding guidelines,
update the body to use the domain value (e.g., prefix.as_str() or
prefix.value()) when formatting, and update all callers to pass the new types
(or provide From<&str>/Into implementations for easy conversion); if you can't
update callers now, add a short TODO to refactor these functions to
AcpPrefix/SessionId in a dedicated pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/acp-nats/src/agent/mod.rs`:
- Line 62: The CI failed due to formatting at the assignment "let metrics =
self.metrics.clone;" in rsworkspace/crates/acp-nats/src/agent/mod.rs; run cargo
fmt to apply the Rustfmt formatting (or manually reformat the assignment to
conform to rustfmt rules) so the declaration of the local variable metrics (from
self.metrics.clone()) matches the project's style and passes cargo fmt --check.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Line 38: The CI failed due to formatting differences in
rsworkspace/crates/acp-nats/src/agent/new_session.rs; run rustfmt via `cargo
fmt` (preferably at workspace root) to reformat new_session.rs (the module/file
containing the new session implementation and its closing brace) so the file
complies with `cargo fmt --check`, then commit the formatted changes.
- Around line 46-50: The span in the acp.session.new instrument macro is missing
a declaration for session_id, so the later Span::current().record(...) call in
new_session.rs is a no-op; fix it by adding session_id to the instrument fields
list (e.g. fields(cwd = ?args.cwd, mcp_servers = args.mcp_servers.len(),
session_id)) so that the call to Span::current().record("session_id", &value) in
the new_session function actually stores the value in the span.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/agent/mod.rs`:
- Around line 88-93: The Vec stored in session_ready_publish_tasks can grow if
sessions are spawned faster than publishes complete because retain(|t|
!t.is_finished()) only runs on spawn; replace the Vec+manual retain by a
bounded, self-pruning structure such as FuturesUnordered (store a
FuturesUnordered<Task> in session_ready_publish_tasks and push the spawned task
into it) or else swap the Vec for a bounded mpsc channel and have a dedicated
cleanup background task that polls/completes finished tasks; update
spawn_session_ready to push into that structure instead of pushing into the raw
Vec and ensure the new structure is polled/cleaned regularly so finished tasks
are dropped automatically.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Around line 25-30: The Serialize branch currently logs the internal error but
returns a user-facing message containing the raw serialization error; change
NatsError::Serialize handling so it still logs the inner error with warn!(error
= %inner, "failed to serialize new_session request") but construct the
Error::new(ErrorCode::InternalError.into(), ...) with a non-sensitive generic
message (for example the same generic used by the Deserialize branch like
"Invalid response from agent" or "Failed to serialize new_session request")—do
not interpolate inner into the returned string; update the code around
NatsError::Serialize, warn!, and Error::new to reflect this.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs`:
- Around line 6-12: The helpers session_new and ext_session_ready currently
accept primitive &str parameters; change them to take domain types (e.g.,
AcpPrefix for prefix and SessionId for session_id in ext_session_ready) to match
coding guidelines, update the body to use the domain value (e.g.,
prefix.as_str() or prefix.value()) when formatting, and update all callers to
pass the new types (or provide From<&str>/Into implementations for easy
conversion); if you can't update callers now, add a short TODO to refactor these
functions to AcpPrefix/SessionId in a dedicated pass.

In `@rsworkspace/crates/acp-nats/src/telemetry/metrics.rs`:
- Around line 44-56: record_session_created currently increments
sessions_created with no attributes; update it to accept a session identifier
and record it as an attribute so metrics can be correlated with a session.
Change the signature of record_session_created (e.g., add a session_id
parameter) and pass KeyValue::new("session_id", session_id) alongside other
attributes to self.sessions_created.add; update all callers to provide the
session_id and preserve existing behavior when caller does not have an id.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f05030 and ff7cdac.

📒 Files selected for processing (6)
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/new_session.rs
  • rsworkspace/crates/acp-nats/src/nats/extensions.rs
  • rsworkspace/crates/acp-nats/src/nats/mod.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
  • rsworkspace/crates/acp-nats/src/telemetry/metrics.rs

@yordis yordis force-pushed the feat/acp-nats-new-session branch from e6ce156 to c615276 Compare February 25, 2026 02:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
rsworkspace/crates/acp-nats/src/agent/mod.rs (1)

62-62: ⚠️ Potential issue | 🟡 Minor

Rustfmt is still required for this file.

CI reports formatting drift at Line 62 (cargo fmt --all -- --check).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/mod.rs` at line 62, The file fails
rustfmt; run cargo fmt --all (or rustfmt) to reformat mod.rs so the statement
including "let metrics = self.metrics.clone();" and surrounding code conform to
project formatting; if needed update/commit the formatted file and ensure
rustfmt settings (rustfmt.toml) are respected so CI passes.
rsworkspace/crates/acp-nats/src/agent/new_session.rs (2)

38-38: ⚠️ Potential issue | 🟡 Minor

Rustfmt is still required for this file.

CI reports formatting drift at Line 38 (cargo fmt --all -- --check).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` at line 38, Run rustfmt
to fix formatting drift in rsworkspace/crates/acp-nats/src/agent/new_session.rs:
reformat the file (e.g., cargo fmt --all or rustfmt on the file) so it passes
CI's cargo fmt --all -- --check and commit the resulting changes; ensure the
module new_session.rs is formatted according to rustfmt rules (no trailing
whitespace, correct brace/indentation) before pushing.

46-50: ⚠️ Potential issue | 🟡 Minor

Declare session_id in the span fields before recording it.

On Line 72, Span::current().record("session_id", ...) is ineffective because session_id is not declared in the #[instrument(fields(...))] list on Lines 46-50.

Proposed fix
 #[instrument(
     name = "acp.session.new",
     skip(bridge, args),
-    fields(cwd = ?args.cwd, mcp_servers = args.mcp_servers.len())
+    fields(
+        cwd = ?args.cwd,
+        mcp_servers = args.mcp_servers.len(),
+        session_id = tracing::field::Empty
+    )
 )]
@@
-        Span::current().record("session_id", response.session_id.to_string().as_str());
+        Span::current().record("session_id", tracing::field::display(&response.session_id));
#!/bin/bash
set -euo pipefail
rg -nP --type=rust -C2 '#\[instrument\(|fields\(|Span::current\(\)\.record\("session_id"' rsworkspace/crates/acp-nats/src/agent/new_session.rs

Also applies to: 72-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` around lines 46 - 50,
The span needs to declare session_id in the instrument attribute so
Span::current().record("session_id", ...) actually records; update the
#[instrument(..., fields(...))] on the function (the #[instrument(name =
"acp.session.new", skip(bridge, args), fields(...))] attribute in
new_session.rs) to include session_id (e.g. add session_id =
tracing::field::Empty) and then keep the existing
Span::current().record("session_id", &session_id) call as-is.
🧹 Nitpick comments (2)
rsworkspace/crates/acp-nats/src/agent/new_session.rs (1)

9-44: Prefer explicit NatsError arms instead of a wildcard fallback.

The wildcard arm catches PublishOperation, PublishOperationExhausted, and Other variants without explicit handling. This means future NatsError additions in the trogon-nats crate will be silently matched by the wildcard instead of producing a compile-time error, making it easy to miss required variant mappings during maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` around lines 9 - 44,
The match in map_new_session_error currently ends with a wildcard arm which
hides unhandled NatsError variants; replace the final "_ =>" fallback with
explicit match arms for the remaining NatsError variants (e.g.,
NatsError::PublishOperation, NatsError::PublishOperationExhausted,
NatsError::Other) and handle each by logging (warn! with relevant fields) and
returning an appropriate Error (similar pattern to existing arms, e.g.,
Error::new(ErrorCode::InternalError.into(), "Publish failed" / "Publish
exhausted" / include other error text)), so the compiler will force you to
handle any new NatsError variants in map_new_session_error.
rsworkspace/crates/acp-nats/src/agent/mod.rs (1)

58-64: The method already satisfies tokio::spawn requirements through struct-level bounds on N: RequestClient + PublishClient + FlushClient, which enforce Send + Sync + Clone + 'static. Since Config and Metrics both derive Clone and contain only Send types (Duration, metric counters), no additional bounds are needed. The suggestion to add explicit method-level bounds would duplicate what the impl block already specifies, providing only marginal clarity benefit without addressing any actual constraint or behavior change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/mod.rs` around lines 58 - 64, Don't add
redundant method-level Send/Sync/'static bounds to spawn_session_ready; the impl
already constrains N via the impl block (N: RequestClient + PublishClient +
FlushClient) and Config/Metrics are Clone and contain only Send types, so keep
the signature of spawn_session_ready as-is and rely on the existing
struct/impl-level bounds (spawn_session_ready, N, Config, Metrics, SessionId,
self.nats).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@rsworkspace/crates/acp-nats/src/agent/mod.rs`:
- Line 62: The file fails rustfmt; run cargo fmt --all (or rustfmt) to reformat
mod.rs so the statement including "let metrics = self.metrics.clone();" and
surrounding code conform to project formatting; if needed update/commit the
formatted file and ensure rustfmt settings (rustfmt.toml) are respected so CI
passes.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Line 38: Run rustfmt to fix formatting drift in
rsworkspace/crates/acp-nats/src/agent/new_session.rs: reformat the file (e.g.,
cargo fmt --all or rustfmt on the file) so it passes CI's cargo fmt --all --
--check and commit the resulting changes; ensure the module new_session.rs is
formatted according to rustfmt rules (no trailing whitespace, correct
brace/indentation) before pushing.
- Around line 46-50: The span needs to declare session_id in the instrument
attribute so Span::current().record("session_id", ...) actually records; update
the #[instrument(..., fields(...))] on the function (the #[instrument(name =
"acp.session.new", skip(bridge, args), fields(...))] attribute in
new_session.rs) to include session_id (e.g. add session_id =
tracing::field::Empty) and then keep the existing
Span::current().record("session_id", &session_id) call as-is.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/agent/mod.rs`:
- Around line 58-64: Don't add redundant method-level Send/Sync/'static bounds
to spawn_session_ready; the impl already constrains N via the impl block (N:
RequestClient + PublishClient + FlushClient) and Config/Metrics are Clone and
contain only Send types, so keep the signature of spawn_session_ready as-is and
rely on the existing struct/impl-level bounds (spawn_session_ready, N, Config,
Metrics, SessionId, self.nats).

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Around line 9-44: The match in map_new_session_error currently ends with a
wildcard arm which hides unhandled NatsError variants; replace the final "_ =>"
fallback with explicit match arms for the remaining NatsError variants (e.g.,
NatsError::PublishOperation, NatsError::PublishOperationExhausted,
NatsError::Other) and handle each by logging (warn! with relevant fields) and
returning an appropriate Error (similar pattern to existing arms, e.g.,
Error::new(ErrorCode::InternalError.into(), "Publish failed" / "Publish
exhausted" / include other error text)), so the compiler will force you to
handle any new NatsError variants in map_new_session_error.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff7cdac and e6ce156.

📒 Files selected for processing (6)
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/new_session.rs
  • rsworkspace/crates/acp-nats/src/nats/extensions.rs
  • rsworkspace/crates/acp-nats/src/nats/mod.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
  • rsworkspace/crates/acp-nats/src/telemetry/metrics.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs

@yordis yordis force-pushed the feat/acp-nats-new-session branch 3 times, most recently from 88e4d57 to e91db52 Compare February 25, 2026 03:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
rsworkspace/crates/acp-nats/src/agent/new_session.rs (2)

77-85: spawn_session_ready is fire-and-forget — no shutdown drain.

The spawned task's JoinHandle is dropped immediately (as the existing TODO at line 97 acknowledges). On graceful shutdown, in-flight session-ready publishes will be abandoned mid-flight. If the bridge needs ordered shutdown, these handles should be tracked (e.g. in a Vec<JoinHandle<()>> stored on Bridge) and awaited during teardown.

Do you want me to draft a session_ready_publish_tasks tracking field on Bridge with the corresponding drain logic?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` around lines 77 - 85,
spawn_session_ready is being fire-and-forget which drops its JoinHandle and
abandons in-flight publishes; change spawn_session_ready (or a thin wrapper) to
return a JoinHandle<()> and store that handle on Bridge (add a
session_ready_publish_tasks: Vec<tokio::task::JoinHandle<()>> or similar) when
you call it from new_session; ensure Bridge teardown/drain logic awaits (or
aborts gracefully) all handles in session_ready_publish_tasks to guarantee
ordered shutdown and completion of pending session-ready publishes. Use the
Bridge methods (e.g., where other shutdown work runs) to drain awaiting those
JoinHandle<()>s and remove completed handles as needed.

44-49: Consider handling NatsError::Timeout and NatsError::Request variants explicitly.

The wildcard _ arm catches several NatsError variants that are not semantically addressed: Timeout, Request, PublishOperation, PublishOperationExhausted, and Other. While this mirrors the pattern in initialize.rs, note that initialize.rs explicitly handles Timeout and Request variants because they carry semantic meaning (agent availability). If these errors are relevant to session creation semantics, adding explicit arms would improve clarity and maintainability when NatsError evolves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs` around lines 44 - 49,
The match in new_session currently uses a wildcard for NatsError variants; add
explicit match arms for NatsError::Timeout and NatsError::Request (and any other
semantically-relevant variants) instead of lumping them into the _ arm so their
meanings are preserved—map Timeout and Request to a clearer error (e.g., an
Unavailable/AgentUnavailable-like ErrorCode or a descriptive message) and keep
the remaining variants in a final _ =>
Error::new(ErrorCode::InternalError.into(), "New session request failed") arm;
update the warn!/error logging in each new arm to include %e and a specific
message so new_session, NatsError::Timeout, and NatsError::Request are all
explicitly handled and easily maintainable.
rsworkspace/crates/acp-nats/src/nats/subjects.rs (1)

10-12: session_id: &str should accept &SessionId per domain value object guidelines.

ext_session_ready accepts a raw &str for session_id, which causes the call site (new_session.rs line 108) to needlessly call session_id.to_string() to downgrade a SessionId to &str. The function should accept &SessionId directly and call .to_string() internally.

♻️ Suggested fix
-pub fn ext_session_ready(prefix: &str, session_id: &str) -> String {
-    format!("{}.agent.ext.session.ready.{}", prefix, session_id)
+pub fn ext_session_ready(prefix: &str, session_id: &SessionId) -> String {
+    format!("{}.agent.ext.session.ready.{}", prefix, session_id)
 }

And update the call site in new_session.rs:

-let subject = agent::ext_session_ready(&prefix, &session_id.to_string());
+let subject = agent::ext_session_ready(&prefix, &session_id);

As per coding guidelines: "Prefer domain-specific value objects over primitives (e.g. AcpPrefix not String)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs` around lines 10 - 12,
Change ext_session_ready to accept session_id: &SessionId instead of &str and
call session_id.to_string() inside the function (so the formatting uses the
SessionId domain type). Update the call site that currently passes
session_id.to_string() (in the new session creation code) to pass &session_id
directly. Ensure imports/references to SessionId are added where needed and keep
the function signature: pub fn ext_session_ready(prefix: &str, session_id:
&SessionId) -> String.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Around line 44-50: The catch-all arm in new_session currently fails cargo fmt
due to formatting around the Error::new(ErrorCode::InternalError.into(), "New
session request failed") call; run cargo fmt (or rustfmt) and reformat the block
so that the warn!(error = %e, "new_session NATS request failed"); and the
Error::new(...) invocation follow rustfmt's expected layout (preserving the
warn! call and the returned Error), ensuring new_session's match arm adheres to
rustfmt style and compiles cleanly.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/agent/new_session.rs`:
- Around line 77-85: spawn_session_ready is being fire-and-forget which drops
its JoinHandle and abandons in-flight publishes; change spawn_session_ready (or
a thin wrapper) to return a JoinHandle<()> and store that handle on Bridge (add
a session_ready_publish_tasks: Vec<tokio::task::JoinHandle<()>> or similar) when
you call it from new_session; ensure Bridge teardown/drain logic awaits (or
aborts gracefully) all handles in session_ready_publish_tasks to guarantee
ordered shutdown and completion of pending session-ready publishes. Use the
Bridge methods (e.g., where other shutdown work runs) to drain awaiting those
JoinHandle<()>s and remove completed handles as needed.
- Around line 44-49: The match in new_session currently uses a wildcard for
NatsError variants; add explicit match arms for NatsError::Timeout and
NatsError::Request (and any other semantically-relevant variants) instead of
lumping them into the _ arm so their meanings are preserved—map Timeout and
Request to a clearer error (e.g., an Unavailable/AgentUnavailable-like ErrorCode
or a descriptive message) and keep the remaining variants in a final _ =>
Error::new(ErrorCode::InternalError.into(), "New session request failed") arm;
update the warn!/error logging in each new arm to include %e and a specific
message so new_session, NatsError::Timeout, and NatsError::Request are all
explicitly handled and easily maintainable.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs`:
- Around line 10-12: Change ext_session_ready to accept session_id: &SessionId
instead of &str and call session_id.to_string() inside the function (so the
formatting uses the SessionId domain type). Update the call site that currently
passes session_id.to_string() (in the new session creation code) to pass
&session_id directly. Ensure imports/references to SessionId are added where
needed and keep the function signature: pub fn ext_session_ready(prefix: &str,
session_id: &SessionId) -> String.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6ce156 and 9c8d501.

📒 Files selected for processing (6)
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/new_session.rs
  • rsworkspace/crates/acp-nats/src/nats/extensions.rs
  • rsworkspace/crates/acp-nats/src/nats/mod.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
  • rsworkspace/crates/acp-nats/src/telemetry/metrics.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rsworkspace/crates/acp-nats/src/nats/mod.rs

@yordis yordis force-pushed the feat/acp-nats-new-session branch 3 times, most recently from 78457fe to 0358eab Compare February 25, 2026 04:41
- Add new_session handler aligned with initialize.rs patterns
  (handler-specific error mapping, single-line generics, local subject variable)
- Add spawn_session_ready to publish ext.session.ready after successful session creation
- Add SessionReady extension type for bridge-backend coordination
- Add session_new and ext_session_ready NATS subject functions
- Add record_session_created() and record_error() to Metrics
- Add Bridge task tracking for session_ready publish tasks
- Add comprehensive test suite mirroring initialize handler tests

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the feat/acp-nats-new-session branch from 0358eab to c79f468 Compare February 25, 2026 04:48
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

@yordis yordis merged commit e863586 into main Feb 25, 2026
4 checks passed
@yordis yordis deleted the feat/acp-nats-new-session branch February 25, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant