Skip to content

Add live latency trace instrumentation - #5472

Merged
serrrfirat merged 6 commits into
mainfrom
codex/live-latency-spans
Jul 1, 2026
Merged

serrrfirat merged 6 commits into
mainfrom
codex/live-latency-spans

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

Adds live-only latency tracing under the ironclaw_latency target to debug instances where requests stall together and then resolve together.

This instruments the Reborn submit path, turn coordinator, scheduler claim/execution, Reborn executor, model gateway, host runtime capability invocation, process execution, and scoped filesystem operations. Filesystem events intentionally emit only a redacted path_class rather than full virtual paths.

The follow-up cleanup centralizes the trace target/elapsed-time helpers in ironclaw_observability, guards trace-only formatting when ironclaw_latency=trace is disabled, and splits scheduler trace plumbing out of turn_scheduler.rs so the branch does not push that file past 1k lines.

Operator usage

Enable short diagnostic windows with:

RUST_LOG=info,ironclaw_latency=trace

See docs/internal/live-latency-instrumentation.md for the interpretation map.

Validation

  • cargo fmt
  • cargo check -p ironclaw_observability -p ironclaw_filesystem -p ironclaw_turns -p ironclaw_host_runtime -p ironclaw_reborn -p ironclaw_reborn_composition
  • cargo test -p ironclaw_observability -p ironclaw_filesystem -p ironclaw_turns -p ironclaw_host_runtime --lib
  • git diff --check origin/main...HEAD

Note: cargo check still reports the pre-existing LOCAL_DEV_DB_FILENAME dead-code warning in ironclaw_reborn_composition/src/factory.rs.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: be603909-e402-4738-b185-f4233cb87cb3

📥 Commits

Reviewing files that changed from the base of the PR and between 9c67b48 and b84825c.

📒 Files selected for processing (2)
  • crates/ironclaw_filesystem/src/scoped.rs
  • crates/ironclaw_filesystem/src/scoped/tests.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added live latency tracing across capability invocation, process execution, turn scheduling/execution, filesystem operations, and model-provider handling, emitting consistent ok/error timing signals for key workflow steps.
    • Filesystem tracing includes path classification to help localize latency sources by segment.
  • Documentation
    • Added internal guidance for enabling and interpreting live latency traces, including correlation tips and filesystem path-class troubleshooting.
  • Tests
    • Added a unit test covering filesystem scoped path-class bucketing behavior.

Walkthrough

Adds a new ironclaw_observability crate and wires live-latency tracing through filesystem, host runtime, scheduler, reborn, runtime composition, turns, and internal documentation.

Changes

Live latency tracing rollout

Layer / File(s) Summary
Observability crate and workspace wiring
Cargo.toml, crates/ironclaw_observability/Cargo.toml, crates/ironclaw_observability/src/lib.rs, crates/*/Cargo.toml
Adds the ironclaw_observability crate with elapsed-time and live-latency helpers, and includes it in the workspace and crate dependency lists.
Filesystem operation latency tracing
crates/ironclaw_filesystem/src/scoped.rs, crates/ironclaw_filesystem/src/scoped/tests.rs
Adds filesystem latency helpers and traces scoped filesystem operations with start timestamps, operation names, path classes, error kinds, and byte counts where available.
Capability invocation and process execution tracing
crates/ironclaw_host_runtime/src/production.rs, crates/ironclaw_host_runtime/src/services/process_executor.rs
Adds tracing around capability dispatch, credential preflight, sandbox execution, dispatch execution, and cancellation paths.
Turn scheduler latency tracing and outcome refactor
crates/ironclaw_host_runtime/src/turn_scheduler.rs, crates/ironclaw_host_runtime/src/turn_scheduler/executor_task.rs, crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs
Extracts executor outcome mapping and adds scheduler latency tracing for wake notifications, claim draining, and executor task results.
Model gateway and turn executor latency tracing
crates/ironclaw_reborn/src/model_gateway.rs, crates/ironclaw_reborn/src/turn_run_executor.rs
Traces provider completion, tool-response conversion, driver setup, driver execution, and loop-exit handling in reborn execution paths.
Runtime submission latency tracing
crates/ironclaw_reborn_composition/src/runtime.rs, crates/ironclaw_reborn_composition/src/runtime/latency.rs, crates/ironclaw_reborn_composition/Cargo.toml
Adds runtime latency helpers and traces user-message submission, inbound acceptance, skill recording, coordinator submission, and scheduler notification.
Turn coordinator store-call latency tracing
crates/ironclaw_turns/src/coordinator.rs, crates/ironclaw_turns/Cargo.toml
Traces store calls and wake notifications for submit, resume, cancel, and child-run flows.
Latency instrumentation docs
docs/internal/live-latency-instrumentation.md
Documents the live-latency trace target, emitted fields, filesystem path classes, and a diagnostic checklist.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • nearai/ironclaw#4955: Both PRs touch crates/ironclaw_host_runtime/src/turn_scheduler.rs and its run/executor instrumentation path.
  • nearai/ironclaw#5455: Both PRs modify crates/ironclaw_filesystem/src/scoped.rs; the added latency tracing wraps the same scoped filesystem operations.

Poem

Timers start before the work,
then ok or error leaves a mark.
Filesystems hum, schedulers spin,
and traces tell where time leaked in. ⏱️

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning It covers summary and validation, but omits many required template sections for this repo. Add the missing sections: Change Type, Linked Issue, Security Impact, Trust-Boundary Checklist, Database Impact, Blast Radius, Rollback Plan, Review Follow-Through, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the added live-latency tracing instrumentation.
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.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5472 June 30, 2026 22:34 Destroyed
@github-actions github-actions Bot added scope: docs Documentation scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Jun 30, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new low-level observability crate, ironclaw_observability, and integrates live latency tracing across various core subsystems, including the filesystem, host runtime, process executor, turn scheduler, model gateway, and reborn runtime. The review feedback highlights a critical performance concern: unconditionally calling Instant::now() on hot paths introduces unnecessary timing overhead when latency tracing is disabled. To optimize this, the reviewer suggests lazily initializing timestamps as Option<Instant> using live_latency_enabled().then(Instant::now) and updating the tracing helpers to accept optional timestamps.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +67 to +78
fn trace_fs_latency<T, E>(
operation: &'static str,
path: &ScopedPath,
started_at: Instant,
result: &Result<T, E>,
bytes: Option<usize>,
) where
E: fmt::Display,
{
if !live_latency_enabled() {
return;
}

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.

medium

Calling Instant::now() unconditionally on every filesystem operation introduces unnecessary overhead (system calls or VDSO reads) on hot paths when live latency tracing is disabled.

By changing started_at to Option<Instant> and initializing it with live_latency_enabled().then(Instant::now), we can completely avoid the timing overhead in the default production state.

Suggested change
fn trace_fs_latency<T, E>(
operation: &'static str,
path: &ScopedPath,
started_at: Instant,
result: &Result<T, E>,
bytes: Option<usize>,
) where
E: fmt::Display,
{
if !live_latency_enabled() {
return;
}
fn trace_fs_latency<T, E>(
operation: &'static str,
path: &ScopedPath,
started_at: Option<Instant>,
result: &Result<T, E>,
bytes: Option<usize>,
) where
E: fmt::Display,
{
let Some(started_at) = started_at else {
return;
};

Comment on lines +159 to +164
let started_at = Instant::now();
let bytes = entry.body.len();
let virtual_path =
self.resolve_with_permission(scope, path, FilesystemOperation::WriteFile)?;
self.root.put(&virtual_path, entry, cas).await
let result = self.root.put(&virtual_path, entry, cas).await;
trace_fs_latency("put", path, started_at, &result, Some(bytes));

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.

medium

Avoid calling Instant::now() unconditionally when live latency tracing is disabled.

Suggested change
let started_at = Instant::now();
let bytes = entry.body.len();
let virtual_path =
self.resolve_with_permission(scope, path, FilesystemOperation::WriteFile)?;
self.root.put(&virtual_path, entry, cas).await
let result = self.root.put(&virtual_path, entry, cas).await;
trace_fs_latency("put", path, started_at, &result, Some(bytes));
let started_at = live_latency_enabled().then(Instant::now);
let bytes = entry.body.len();
let virtual_path =
self.resolve_with_permission(scope, path, FilesystemOperation::WriteFile)?;
let result = self.root.put(&virtual_path, entry, cas).await;
trace_fs_latency("put", path, started_at, &result, Some(bytes));

Comment on lines +119 to +127
fn trace_runtime_latency_ok(
operation: &'static str,
thread_id: &ThreadId,
run_id: Option<TurnRunId>,
started_at: Instant,
) {
if !live_latency_enabled() {
return;
}

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.

medium

By changing started_at to Option<Instant>, we can avoid calling Instant::now() when live latency tracing is disabled.

fn trace_runtime_latency_ok(
    operation: &'static str,
    thread_id: &ThreadId,
    run_id: Option<TurnRunId>,
    started_at: Option<Instant>,
) {
    let Some(started_at) = started_at else {
        return;
    };

Comment on lines +141 to +152
fn trace_runtime_latency_error<E>(
operation: &'static str,
thread_id: &ThreadId,
run_id: Option<TurnRunId>,
started_at: Instant,
error: &E,
) where
E: fmt::Display + ?Sized,
{
if !live_latency_enabled() {
return;
}

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.

medium

By changing started_at to Option<Instant>, we can avoid calling Instant::now() when live latency tracing is disabled.

fn trace_runtime_latency_error<E>(
    operation: &'static str,
    thread_id: &ThreadId,
    run_id: Option<TurnRunId>,
    started_at: Option<Instant>,
    error: &E,
) where
    E: fmt::Display + ?Sized,
{
    let Some(started_at) = started_at else {
        return;
    };

Comment on lines +1742 to +1743
let total_started_at = Instant::now();
let submit_started_at = Instant::now();

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.

medium

In send_user_message, calling Instant::now() twice consecutively is redundant and adds unnecessary overhead. Furthermore, calling it unconditionally introduces timing overhead even when latency tracing is disabled.

We can optimize this by checking live_latency_enabled() first, reusing the timestamp, and passing Option<Instant> to the trace helpers.

        let total_started_at = live_latency_enabled().then(Instant::now);
        let submit_started_at = total_started_at;

Comment on lines +14 to +22
fn trace_coordinator_latency_ok(
operation: &'static str,
scope: &TurnScope,
run_id: Option<TurnRunId>,
started_at: Instant,
) {
if !live_latency_enabled() {
return;
}

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.

medium

By changing started_at to Option<Instant>, we can avoid calling Instant::now() when live latency tracing is disabled.

Suggested change
fn trace_coordinator_latency_ok(
operation: &'static str,
scope: &TurnScope,
run_id: Option<TurnRunId>,
started_at: Instant,
) {
if !live_latency_enabled() {
return;
}
fn trace_coordinator_latency_ok(
operation: &'static str,
scope: &TurnScope,
run_id: Option<TurnRunId>,
started_at: Option<Instant>,
) {
let Some(started_at) = started_at else {
return;
};

@railway-app

railway-app Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5472 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jun 30, 2026 at 11:43 pm

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5472 June 30, 2026 22:55 Destroyed
@serrrfirat
serrrfirat marked this pull request as ready for review June 30, 2026 22:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_host_runtime/src/services/process_executor.rs (1)

172-210: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Trace the dispatch_json failure branch before ? returns.

Line 195 still exits on dispatcher failure before runtime_dispatch_execute emits an error event, so the stage that most often stalls/fails stays invisible in the new latency target. Capture the mapped ProcessExecutionError, trace it, then return it.

As per path instructions, "Fail loud: flag silent-failure patterns ... Errors propagate with ? into thiserror types with context."

Possible fix
-        let result = self
-            .dispatcher
-            .dispatch_json(CapabilityDispatchRequest {
-                capability_id: request.capability_id,
-                scope: request.scope,
-                estimate: request.estimate,
-                mounts: Some(request.mounts),
-                resource_reservation: request.resource_reservation,
-                input: request.input,
-            })
-            .await
-            .map_err(|error| ProcessExecutionError::new(error.event_kind()))?;
+        let result = match self
+            .dispatcher
+            .dispatch_json(CapabilityDispatchRequest {
+                capability_id: request.capability_id,
+                scope: request.scope,
+                estimate: request.estimate,
+                mounts: Some(request.mounts),
+                resource_reservation: request.resource_reservation,
+                input: request.input,
+            })
+            .await
+        {
+            Ok(result) => result,
+            Err(error) => {
+                let error = ProcessExecutionError::new(error.event_kind());
+                trace_process_latency_error(
+                    "runtime_dispatch_execute",
+                    fields.as_ref(),
+                    started_at,
+                    &error,
+                );
+                return Err(error);
+            }
+        };
🤖 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/ironclaw_host_runtime/src/services/process_executor.rs` around lines
172 - 210, The dispatch_json failure path in
process_executor::runtime_dispatch_execute currently maps the error with ?
before any latency/error trace is emitted, so the failure stays silent. Capture
the mapped ProcessExecutionError from dispatcher.dispatch_json(), call
trace_process_latency_error with "runtime_dispatch_execute", fields.as_ref(),
and started_at, then return the error instead of using ?. Keep the existing
cancellation and success tracing paths unchanged.

Source: Path instructions

🤖 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/ironclaw_filesystem/Cargo.toml`:
- Line 23: The ironclaw_observability dependency is declared inconsistently
across crates, with ironclaw_filesystem adding a version alongside path while
other dependents do not. Update the dependency entries in ironclaw_filesystem
and align them with the chosen workspace-wide convention used by
ironclaw_host_runtime and ironclaw_reborn_composition, keeping the declaration
consistent for ironclaw_observability so future version bumps or crates.io
publishing do not diverge.

In `@crates/ironclaw_filesystem/src/scoped.rs`:
- Around line 67-103: The `trace_fs_latency` error branch is leaking redacted
path/backend details by tracing `error = %error`, since `FilesystemError` can
format full `VirtualPath` and backend `reason` text. Update `trace_fs_latency`
to emit only a coarse error kind/variant label instead of the full error,
ideally by matching on `FilesystemError` (or adding a small mapper) and tracing
that sanitized value in the `live_latency_trace!` call. Keep the `path_class`
redaction intact and ensure failed filesystem ops never log raw paths or backend
internals.
- Around line 54-65: The scoped_path_class helper currently derives its label
from the first path segment, which can leak raw host aliases or tenant/user
identifiers into ironclaw_latency. Update scoped_path_class to return a fixed
bucket/category instead of echoing the segment from ScopedPath, and keep the
calling path in MountView::scoped_path or related logging code unchanged except
for using the redacted class label.

In `@crates/ironclaw_host_runtime/src/production.rs`:
- Around line 77-103: `trace_capability_latency_error` is logging the raw
`%error` into `ironclaw_latency`, which can leak backend or user content. Update
this helper to emit a fixed sanitized failure kind/category field instead of
formatting the error directly, and keep the existing metadata from `operation`,
`capability_id`, and `scope` so callers of `trace_capability_latency_error`
still get latency context without exposing the underlying error string.

In `@crates/ironclaw_host_runtime/src/services/process_executor.rs`:
- Around line 10-34: Process latency events are currently dropping parts of the
request scope, so update ProcessLatencyFields::from_request to carry the full
ResourceScope information from request.scope instead of only
thread_id/invocation_id. Add the missing tenant/user and any other scoped
identifiers used elsewhere in the runtime, and keep the field extraction aligned
with how production.rs reads ResourceScope so process latency records preserve
the same authority/state/event scope invariants.

In `@crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs`:
- Around line 11-28: The latency helpers are collapsing canonical scope into
only thread_id, which drops tenant/agent/project context from scheduler traces.
Update run_fields_from_wake and scope_thread_id to carry the full scoped
identity from TurnRunWake/TurnScope instead of formatting just thread_id, and
make sure the callers in the scheduler latency path keep using that canonical
scope for notify_queued_run, claim_next_run*, and execute_claimed_run events.

In `@crates/ironclaw_observability/src/lib.rs`:
- Around line 25-30: The live_latency_trace! macro is expanding to
tracing::trace! at the call site, which creates an unwanted downstream
dependency on tracing being in scope. Update the macro in lib.rs to invoke the
tracing macro through $crate::tracing::trace! and make sure the crate re-exports
tracing so callers can use live_latency_trace! without importing tracing
directly.

In `@crates/ironclaw_reborn_composition/src/runtime.rs`:
- Around line 119-166: The new tracing helpers in runtime.rs duplicate
boilerplate and push an already oversized file further past the “shrink when
touched” guideline. Move trace_runtime_latency_ok and
trace_runtime_latency_error, along with any runtime-composition-specific tracing
glue, into a dedicated module/file and have runtime.rs call into that shared
location, following the existing patterns used by model_gateway.rs,
turn_run_executor.rs, and coordinator.rs.

In `@crates/ironclaw_reborn/Cargo.toml`:
- Line 59: Align the `ironclaw_observability` dependency declaration in
`Cargo.toml` with the other workspace crates by removing the unnecessary pinned
version from the path dependency. Update the `ironclaw_observability` entry so
it matches the style used by `ironclaw_host_runtime` and
`ironclaw_reborn_composition`, keeping the path-based dependency consistent
across the workspace.

In `@crates/ironclaw_reborn/src/model_gateway.rs`:
- Around line 66-113: The new trace wrapper helpers in trace_model_latency_ok
and trace_model_latency_error are duplicated across multiple crates and are
making this already-large file grow further; centralize the shared ok/error
latency-tracing shape into ironclaw_observability instead of keeping per-crate
copies. Add a generic helper that accepts the component name plus the shared
identifiers and started_at/error data, then update model_gateway.rs and the
matching helpers in turn_run_executor.rs, runtime.rs, and coordinator.rs to call
it with only their component-specific values.

---

Outside diff comments:
In `@crates/ironclaw_host_runtime/src/services/process_executor.rs`:
- Around line 172-210: The dispatch_json failure path in
process_executor::runtime_dispatch_execute currently maps the error with ?
before any latency/error trace is emitted, so the failure stays silent. Capture
the mapped ProcessExecutionError from dispatcher.dispatch_json(), call
trace_process_latency_error with "runtime_dispatch_execute", fields.as_ref(),
and started_at, then return the error instead of using ?. Keep the existing
cancellation and success tracing paths unchanged.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 839871e4-8b9f-41ae-a3a6-ef57e03edad0

📥 Commits

Reviewing files that changed from the base of the PR and between 8eb334a and 580c870.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (19)
  • Cargo.toml
  • crates/ironclaw_filesystem/Cargo.toml
  • crates/ironclaw_filesystem/src/scoped.rs
  • crates/ironclaw_host_runtime/Cargo.toml
  • crates/ironclaw_host_runtime/src/production.rs
  • crates/ironclaw_host_runtime/src/services/process_executor.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/executor_task.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs
  • crates/ironclaw_observability/Cargo.toml
  • crates/ironclaw_observability/src/lib.rs
  • crates/ironclaw_reborn/Cargo.toml
  • crates/ironclaw_reborn/src/model_gateway.rs
  • crates/ironclaw_reborn/src/turn_run_executor.rs
  • crates/ironclaw_reborn_composition/Cargo.toml
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_turns/Cargo.toml
  • crates/ironclaw_turns/src/coordinator.rs
  • docs/internal/live-latency-instrumentation.md

Comment thread crates/ironclaw_filesystem/Cargo.toml Outdated
Comment thread crates/ironclaw_filesystem/src/scoped.rs Outdated
Comment thread crates/ironclaw_filesystem/src/scoped.rs Outdated
Comment thread crates/ironclaw_host_runtime/src/production.rs Outdated
Comment thread crates/ironclaw_host_runtime/src/services/process_executor.rs
Comment thread crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs
Comment thread crates/ironclaw_observability/src/lib.rs
Comment thread crates/ironclaw_reborn_composition/src/runtime.rs Outdated
Comment thread crates/ironclaw_reborn/Cargo.toml Outdated
Comment thread crates/ironclaw_reborn/src/model_gateway.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5472 June 30, 2026 23:16 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/ironclaw_host_runtime/src/production.rs (1)

440-471: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Early invoke_capability rejections never emit the overall latency trace.

Policy-rejected (line 446) and trust-rejected (line 463) exits only emit the stage trace (invoke_capability_policy_rejected / invoke_capability_trust_rejected), not an invoke_capability trace against total_started_at. Compare with the auth_required exit at lines 503-508, which fires both the stage trace and an invoke_capability ok trace. Since this PR's stated goal is correlating stalls that "resolve together," dropping the top-level metric on two of the early-exit paths leaves gaps in the invoke_capability timeline for exactly the fast-fail cases that matter for diagnosing stuck-vs-rejected requests.

🔧 Proposed fix
             trace_capability_latency_ok(
                 "invoke_capability_policy_rejected",
                 &capability_id,
                 &scope,
                 total_started_at,
             );
+            trace_capability_latency_ok(
+                "invoke_capability",
+                &capability_id,
+                &scope,
+                total_started_at,
+            );
             return Ok(runtime_policy_failure(capability_id, error));

(same pattern for the trust-rejected branch)

🤖 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/ironclaw_host_runtime/src/production.rs` around lines 440 - 471, Early
policy- and trust-rejected exits in invoke_capability only record the
stage-specific trace and skip the top-level invoke_capability latency trace.
Update the early-return branches in invoke_capability around
enforce_runtime_policy and evaluate_invocation_trust to also emit the overall
trace using total_started_at, matching the auth_required path and preserving a
complete latency timeline for fast-fail cases.
crates/ironclaw_reborn_composition/src/runtime/latency.rs (1)

1-41: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

reborn_runtime latency events drop tenant/agent/project/mission scope.

Unlike the peer helpers in production.rs (host_runtime), process_executor.rs (process_executor), and mostly coordinator.rs (turn_coordinator), these functions only carry thread_id and run_id — no tenant_id, agent_id, project_id, or mission_id. Callers in runtime.rs (e.g. submit_user_turn) already compute a full scope via turn_scope_for, so the data is available but not threaded through. This breaks cross-component correlation for exactly the multi-tenant stall-diagnosis use case this PR is for, and runs counter to the explicit invariant.

As per coding guidelines, "Preserve tenant/user/agent/project/mission/thread scope on authority, state, memory, process, network, outbound, resource, and event records."

🔧 Proposed fix
+use ironclaw_turns::TurnScope;
+
 pub(super) fn trace_runtime_latency_ok(
     operation: &'static str,
-    thread_id: &ThreadId,
+    scope: &TurnScope,
     run_id: Option<TurnRunId>,
     started_at: Option<Instant>,
 ) {
     let run_id = run_id.map(|id| id.to_string()).unwrap_or_default();
     ironclaw_observability::live_latency_trace_ok!(
         "reborn_runtime",
         operation,
         started_at,
-        thread_id = %thread_id,
+        tenant_id = %scope.tenant_id,
+        agent_id = scope.agent_id.as_ref().map(|id| id.as_str()).unwrap_or(""),
+        project_id = scope.project_id.as_ref().map(|id| id.as_str()).unwrap_or(""),
+        thread_id = %scope.thread_id,
         run_id = run_id.as_str(),
         "reborn runtime operation completed",
     );
 }

(mirror for the error helper; call sites that don't yet have scope resolved would need it threaded through earlier, e.g. before submit_user_turn calls accept_inbound_message)

🤖 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/ironclaw_reborn_composition/src/runtime/latency.rs` around lines 1 -
41, The reborn_runtime latency helpers are only tagging thread_id and run_id, so
they lose tenant/agent/project/mission scope needed for correlation. Update
trace_runtime_latency_ok and trace_runtime_latency_error to accept the full
scope data and pass those fields into the
live_latency_trace_ok!/live_latency_trace_error! macros, matching the patterns
used in production.rs, process_executor.rs, and coordinator.rs. Then thread the
already-computed scope from runtime.rs (for example from submit_user_turn via
turn_scope_for) through the call sites so both helpers emit the complete scope.

Source: Coding guidelines

🤖 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/ironclaw_filesystem/src/scoped.rs`:
- Around line 54-56: The current scoped_path_class implementation in scoped.rs
ignores the ScopedPath input and returns a constant, which makes all trace
events lose useful categorization. Update scoped_path_class to inspect the fixed
top-level mount segment from ScopedPath and map it into a small set of redacted
buckets (for example workspace, memory, artifacts, or other) so put/get/query
traces still emit a meaningful path_class without exposing raw identifiers. Use
the existing scoped_path_class and ScopedPath symbols to keep the change
localized, and preserve the redaction goal by never returning the raw segment.

In `@crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs`:
- Around line 60-88: Gate the ScopeFields materialization in operation_ok and
operation_error so it only happens when started_at is present; right now
ScopeFields::from_scope(scope) is evaluated before trace_ok/trace_error can
short-circuit, causing unnecessary cloning on the hot path. Update these helpers
in latency.rs to return early when started_at.is_none() or otherwise defer
constructing ScopeFields until after the macro gate, keeping the trace-only work
out of the scheduler path.
- Around line 74-87: The scheduler trace helper currently hardcodes the error
kind, so panics and normal executor failures are reported the same way. Update
operation_error in turn_scheduler/latency.rs to accept and forward the
caller-provided error_kind instead of always emitting executor_error, or split
out a dedicated panic helper. Then ensure executor_task::result_to_outcome
passes executor_error on the normal failure path and scheduler_executor_panic on
the unwind path so traces preserve the distinct failure modes.

---

Outside diff comments:
In `@crates/ironclaw_host_runtime/src/production.rs`:
- Around line 440-471: Early policy- and trust-rejected exits in
invoke_capability only record the stage-specific trace and skip the top-level
invoke_capability latency trace. Update the early-return branches in
invoke_capability around enforce_runtime_policy and evaluate_invocation_trust to
also emit the overall trace using total_started_at, matching the auth_required
path and preserving a complete latency timeline for fast-fail cases.

In `@crates/ironclaw_reborn_composition/src/runtime/latency.rs`:
- Around line 1-41: The reborn_runtime latency helpers are only tagging
thread_id and run_id, so they lose tenant/agent/project/mission scope needed for
correlation. Update trace_runtime_latency_ok and trace_runtime_latency_error to
accept the full scope data and pass those fields into the
live_latency_trace_ok!/live_latency_trace_error! macros, matching the patterns
used in production.rs, process_executor.rs, and coordinator.rs. Then thread the
already-computed scope from runtime.rs (for example from submit_user_turn via
turn_scope_for) through the call sites so both helpers emit the complete scope.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 71a06843-5db7-48a7-bb91-1e5cf307f25e

📥 Commits

Reviewing files that changed from the base of the PR and between 580c870 and 882907f.

📒 Files selected for processing (15)
  • crates/ironclaw_filesystem/Cargo.toml
  • crates/ironclaw_filesystem/src/scoped.rs
  • crates/ironclaw_host_runtime/src/production.rs
  • crates/ironclaw_host_runtime/src/services/process_executor.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/executor_task.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs
  • crates/ironclaw_observability/src/lib.rs
  • crates/ironclaw_reborn/Cargo.toml
  • crates/ironclaw_reborn/src/model_gateway.rs
  • crates/ironclaw_reborn/src/turn_run_executor.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/latency.rs
  • crates/ironclaw_turns/Cargo.toml
  • crates/ironclaw_turns/src/coordinator.rs

Comment thread crates/ironclaw_filesystem/src/scoped.rs Outdated
Comment thread crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs
Comment thread crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5472 June 30, 2026 23:29 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs (1)

122-143: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Gate claim_next_run_result's Ok(Some(...)) arm on started_at too.

operation_ok/operation_error were just fixed to early-return on started_at.is_none() before building ScopeFields. This function's Ok(Some(claimed)) arm still calls ScopeFields::from_scope(&claimed.state.scope) unconditionally, reintroducing the same trace-only allocation on the scheduler hot path the PR objective calls out: "avoids trace-only formatting work when ironclaw_latency=trace is disabled." The Ok(None)/Err arms correctly rely on a pre-gated scope_filter, but the Ok(Some) arm sidesteps that gating.

🔧 Proposed fix
 pub(super) fn claim_next_run_result<E>(
     scope_filter: Option<&ScopeFields>,
     started_at: Option<Instant>,
     claim: &Result<Option<ClaimedTurnRun>, E>,
 ) {
+    if started_at.is_none() {
+        return;
+    }
     match claim {
         Ok(Some(claimed)) => trace_ok(
🤖 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/ironclaw_host_runtime/src/turn_scheduler/latency.rs` around lines 122
- 143, The Ok(Some(claimed)) branch in claim_next_run_result still builds
ScopeFields unconditionally, so add the same started_at.is_none() early return
used in operation_ok and operation_error before calling trace_ok. Reuse the
existing scope_filter when tracing is disabled, and only construct
ScopeFields::from_scope(&claimed.state.scope) when started_at is Some so the
scheduler hot path avoids trace-only formatting work.
🤖 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/ironclaw_filesystem/src/scoped.rs`:
- Around line 54-61: Add a unit test for scoped_path_class to lock in the
redaction bucket mapping contract. Cover the known buckets returned by
scoped_path_class for workspace, memory, artifacts, and turns, and also verify
an arbitrary identifier-bearing path like /users/alice falls back to other.
Place the test near scoped_path_class so future changes to the path bucketing
logic are caught immediately.
- Around line 54-61: The scoped path bucket logic in scoped_path_class currently
returns bare &'static str literals for a fixed set of categories; replace this
with a typed PathClass enum and have scoped_path_class return that enum instead
of matching on string literals. Add serde support with #[serde(rename_all =
"snake_case")] and update any callers/tests that consume the class value so the
fixed set is represented as an enum consistently.

---

Outside diff comments:
In `@crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs`:
- Around line 122-143: The Ok(Some(claimed)) branch in claim_next_run_result
still builds ScopeFields unconditionally, so add the same started_at.is_none()
early return used in operation_ok and operation_error before calling trace_ok.
Reuse the existing scope_filter when tracing is disabled, and only construct
ScopeFields::from_scope(&claimed.state.scope) when started_at is Some so the
scheduler hot path avoids trace-only formatting work.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: bbb3ce03-26b6-4cfc-8b7b-a719c21b3e4b

📥 Commits

Reviewing files that changed from the base of the PR and between 882907f and 9c67b48.

📒 Files selected for processing (3)
  • crates/ironclaw_filesystem/src/scoped.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/executor_task.rs
  • crates/ironclaw_host_runtime/src/turn_scheduler/latency.rs

Comment on lines +54 to +61
fn scoped_path_class(path: &ScopedPath) -> &'static str {
match path.as_str().split('/').nth(1) {
Some("workspace") => "workspace",
Some("memory") => "memory",
Some("artifacts") => "artifacts",
Some("turns") => "turns",
_ => "other",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a unit test locking in the redaction bucket contract.

This function has flip-flopped across three review rounds (raw segment → no-op constant → fixed buckets) on a security-relevant redaction boundary. A small test asserting workspace/memory/artifacts/turns map correctly and an arbitrary/identifier-bearing segment (e.g. /users/alice) maps to other would prevent regressions without needing another review cycle.

🤖 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/ironclaw_filesystem/src/scoped.rs` around lines 54 - 61, Add a unit
test for scoped_path_class to lock in the redaction bucket mapping contract.
Cover the known buckets returned by scoped_path_class for workspace, memory,
artifacts, and turns, and also verify an arbitrary identifier-bearing path like
/users/alice falls back to other. Place the test near scoped_path_class so
future changes to the path bucketing logic are caught immediately.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider a typed PathClass enum instead of bare &'static str literals.

Functionally correct now (fixed bucket set, no raw segment leak — matches the prior review's suggested fix). But per coding guidelines, fixed small value sets should be enums rather than string-literal matching.

♻️ Optional typed refactor
-fn scoped_path_class(path: &ScopedPath) -> &'static str {
-    match path.as_str().split('/').nth(1) {
-        Some("workspace") => "workspace",
-        Some("memory") => "memory",
-        Some("artifacts") => "artifacts",
-        Some("turns") => "turns",
-        _ => "other",
-    }
+enum PathClass {
+    Workspace,
+    Memory,
+    Artifacts,
+    Turns,
+    Other,
+}
+
+impl PathClass {
+    fn as_str(&self) -> &'static str {
+        match self {
+            Self::Workspace => "workspace",
+            Self::Memory => "memory",
+            Self::Artifacts => "artifacts",
+            Self::Turns => "turns",
+            Self::Other => "other",
+        }
+    }
+}
+
+fn scoped_path_class(path: &ScopedPath) -> PathClass {
+    match path.as_str().split('/').nth(1) {
+        Some("workspace") => PathClass::Workspace,
+        Some("memory") => PathClass::Memory,
+        Some("artifacts") => PathClass::Artifacts,
+        Some("turns") => PathClass::Turns,
+        _ => PathClass::Other,
+    }
}

As per coding guidelines, "Fixed small sets of values must be represented as enums with #[serde(rename_all = "snake_case")]... never compare against string literals like status == "in_progress"."

🤖 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/ironclaw_filesystem/src/scoped.rs` around lines 54 - 61, The scoped
path bucket logic in scoped_path_class currently returns bare &'static str
literals for a fixed set of categories; replace this with a typed PathClass enum
and have scoped_path_class return that enum instead of matching on string
literals. Add serde support with #[serde(rename_all = "snake_case")] and update
any callers/tests that consume the class value so the fixed set is represented
as an enum consistently.

Source: Coding guidelines

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5472 June 30, 2026 23:36 Destroyed
@serrrfirat
serrrfirat merged commit 940ca7a into main Jul 1, 2026
108 checks passed
@serrrfirat
serrrfirat deleted the codex/live-latency-spans branch July 1, 2026 07:01

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5472 — b84825c9 Deployed Jun 30, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant