Skip to content

feat(loop-host): add schema-aware deferred tool search - #7273

Merged
serrrfirat merged 11 commits into
mainfrom
codex/7177-schema-aware-tool-search
Aug 7, 2026
Merged

serrrfirat merged 11 commits into
mainfrom
codex/7177-schema-aware-tool-search

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Replace deferred tool name/description substring matching with a bounded, host-owned weighted lexical index over capability IDs, provider aliases, parameter keys, and trusted nested schema descriptions.
  • Fit and cache retrieval only over the complete policy-qualified visible surface (capability ID, runtime, effects, approval, and maximum-count constraints), with deterministic metadata invalidation and tie-breaking.
  • Add privacy-safe build/query/selection telemetry, a 50-tool/72-query graded relevance corpus, per-class retrieval gates, and production caller-path regression coverage.

Retrieval Benchmark Findings

The committed benchmark now contains 50 representative deferred-tool schemas and 72 hand-judged queries across exact-name, canonical-ID, alias, parameter, nested-schema, provider, ambiguous, hard-negative, and no-match classes. Relevance is graded from 1 (acceptable) to 3 (primary match).

Metric Legacy name/description ranker Schema-aware candidate
Recall@1 0.521 0.786
Recall@5 0.685 0.948
Recall@10 0.695 0.966
MRR 0.684 0.949
nDCG@10 0.682 0.944
No-match accuracy 1.000 1.000

The largest gain comes from vocabulary that the legacy ranker cannot see: canonical capability IDs, provider aliases, parameter names, and bounded nested schema fields. Exact-name, canonical-ID, parameter, and provider classes each reached Recall@5 and nDCG@10 of 1.0. Hard-negative queries reached 0.938 Recall@5 and 0.959 nDCG@10; nested queries reached 1.0 Recall@5 and 0.877 nDCG@10.

Alias and ambiguous queries remain the weakest classes: aliases reached 0.819 Recall@5 / 0.865 nDCG@10, while ambiguous queries reached 0.889 / 0.845. The diagnostic report makes the five worst queries visible on every --nocapture run. Examples include look up mail, where a lexical ranker does not infer that mail means Gmail, and search documents, where many tools share generic search vocabulary. These are useful limits of the current deterministic lexical design, not hidden passing cases; semantic synonym retrieval is intentionally out of scope for this PR.

Recall uses the conventional full judged-set denominator. Consequently, Recall@1 is intentionally below 1.0 for queries with multiple relevant tools even when the first result is ideal. nDCG@10 is the primary graded-ordering gate, while Recall@5/10 protects retrieval breadth. The candidate must also improve nDCG@10 over the baseline by at least 0.15, every non-empty query class has its own gate, no-match accuracy must remain 1.0, and fixture validation prevents unknown tool labels, invalid grades, or corpus shrinkage below 50 tools / 60 queries.

The test prints index/query timings for diagnostics only. Machine-dependent latency thresholds and 500+ tool profiling remain follow-up work; no absolute timing gate was added to CI.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

Closes #7177

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings
  • cargo build (covered by the crate and integration test builds below)
  • Relevant tests pass: cargo test -p ironclaw_loop_host; cargo test -p ironclaw_integration_tests --test reborn_integration_tool_disclosure
  • cargo test --features integration if database-backed or integration behavior changed (the current focused Reborn integration package above is the owning integration target; no database behavior changed)
  • Manual testing: Railway preview QA passed on the preceding head; the new review-fix head is awaiting its CI preview. Prior evidence: feat(loop-host): add schema-aware deferred tool search #7273 (comment)
  • If a coding agent was used and supports it, review-pr or pr-shepherd --fix was run before requesting review (not run; CodeRabbit review completed and both actionable comments were addressed)

Test Strategy

User behavior: A model can discover a deferred tool using conceptual, provider, canonical capability, alias, or argument/schema vocabulary without loading full schemas into its prompt.

Risk areas:

  • Model behavior
  • Browser
  • Side effect
  • Persistence
  • Security or permissions
  • External provider
  • Cross-component behavior

Tests added or updated:

  • Unit or contract: Bounded schema traversal, unions/arrays, verified-description trust, exact identifiers, deterministic ties/fingerprints, authorization isolation, overlong input, and a 50-tool/72-query graded relevance gate with aggregate and per-class assertions.
  • Reborn integration: Production capability chain discovers GitHub file tools from the parameter-only term committer; a runtime-excluded full policy proves host-runtime tools and disclosure bridges are absent from the model request; existing narrowed-ID regressions also pass.
  • Recorded fixture: Committed mixed builtin/MCP/WASM/provider/extension-lifecycle corpus with 1-3 relevance judgments across exact, alias, canonical-ID, parameter, nested, provider, ambiguous, hard-negative, and no-match intents.
  • Browser E2E: The preceding-head Railway preview authenticated chat directly executed bridged tool_search with parameter-only vocabulary, verified the sole successful activity and rendered result, then passed refresh/read-back and console-health checks. New-head preview evidence is pending.
  • Backend or runtime: cargo test -p ironclaw_loop_host (626 unit tests plus crate integration/doc targets) and all 29 Reborn tool-disclosure integration scenarios pass.
  • Live canary: Not applicable: this is deterministic host retrieval with no production provider mutation; the PR-specific Railway preview is the canary.

What the tests prove: Across 50 tools and 72 graded queries, the candidate reaches recall@1/5/10 = 0.786/0.948/0.966, MRR = 0.949, nDCG@10 = 0.944, and no-match accuracy = 1.0 (baseline: 0.521/0.685/0.695, MRR 0.684, nDCG@10 0.682, no-match 1.0). Every non-empty query class clears its recall@5 and nDCG@10 gate; denied schemas are excluded before fitting; traversal and inputs are bounded; existing disclosure, describe, invoke, narrowing, and prompt-shape paths remain green.

Commands run:

  • cargo fmt --all -- --check
  • cargo check -p ironclaw_loop_host --tests
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings
  • bash scripts/pre-commit-safety.sh
  • cargo test -p ironclaw_loop_host
  • cargo test -p ironclaw_integration_tests --test reborn_integration_tool_disclosure
  • cargo test -p ironclaw_loop_host committed_corpus_quality_gate_and_benchmark_report -- --nocapture

Security Impact

Tool-search authorization is strengthened: definitions are admitted only from the complete policy-qualified visible surface before catalog/index construction, document-frequency fitting, fingerprinting, or ranking. Pre-surface access fails closed instead of rebuilding from incomplete definition metadata. Untrusted nested schema descriptions are excluded; schema traversal, query size, field count, node count, document bytes, and unique terms are bounded. Telemetry records only aggregate counts/classes/latencies/fingerprints/ranks and never raw query text, schemas, tool identity, or arguments. No network, secret, filesystem, sandbox, or execution-policy behavior changes.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: no new production public types; the index is crate-private and receives only definitions admitted by the resolved CapabilitySurfacePolicy visible surface.
  • Untrusted content enters prompts only through an envelope/escaping primitive. No new prompt content is introduced; rich schema vocabulary remains host-internal and the caller-path test proves it is absent from prompt-side tool descriptions.
  • Hashes declare purpose; trust/binding/authenticity uses SHA-256/BLAKE3 or separate authenticity check. The deterministic non-cryptographic fingerprint is cache invalidation only, never trust or authenticity evidence.
  • New/changed status, exit, policy, runtime, or error variants: downstream match sites audited. Command/output: no variants changed; rg and compiler coverage found no match sites to update.
  • Security/durability serde(default) fields fail closed or have migration tests. N/A: no persisted or security-bearing serde fields changed.
  • Queues/maps/buffers/counters have bounds and overflow-safe arithmetic. Query terms, schema depth/nodes/fields, field/document bytes, unique terms, and result limit are capped; counters use saturating arithmetic.
  • Driver/operator-visible errors have stable class semantics (Transient, Permanent, Misconfigured, PolicyDenied or equivalent). Overlong/empty queries remain recoverable invalid-input outcomes; no host error class changed.
  • Sandbox/native/host names accurately describe trust boundary. AuthorizedToolSearchIndex is host-owned, crate-private, and explicitly authorization-fitted.

Database Impact

None.

Blast Radius

Deferred discovery ranking and its per-turn catalog cache in ironclaw_loop_host. Existing direct-tool, describe-first, promotion, narrowed allow-set, and dispatch behavior is unchanged and covered by the full crate and focused integration suites. Main regression risks are ranking drift, overbroad schema ingestion, and stale metadata; the committed corpus, trust/bounds tests, and full-metadata fingerprint cover those paths.

Rollback Plan

Revert this commit to restore the prior name/description ranker. No schema, persistence, configuration, dependency, or wire-format migration is involved, so rollback is immediate and data-free.

Review Follow-Through

Prior-head Railway evidence is attached; new-head CI preview evidence is pending. Reviewer judgment is welcome on the initial field weights and corpus representativeness; both are deterministic and isolated in the host index.


Review track: B (feature)

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added faster, more relevant tool search across names, providers, descriptions, and parameter vocabulary.
    • Search supports exact matches, aliases, provider terms, and nested parameter fields.
    • Deferred tools can be discovered through relevant argument vocabulary without exposing it in prompts.
    • Results are limited to authorized tools with deterministic ranking.
  • Bug Fixes
    • Prevented overly long or complex queries from affecting performance.
    • Improved privacy by excluding unauthorized tool metadata from search results.
    • Ensured excluded runtimes and capabilities remain hidden from discovery.
  • Tests
    • Added coverage for relevance, authorization, schema matching, provider discovery, policy exclusions, and no-match behavior.

Walkthrough

Changes

The change replaces legacy catalog keyword scoring with a bounded, authorization-scoped search index. It indexes verified tool metadata and schema vocabulary, caches authorized results, records privacy-preserving metrics, and adds corpus and integration coverage.

Changes

Authorized tool search

Layer / File(s) Summary
Bounded search index and quality gates
crates/loop/ironclaw_loop_host/src/tool_search.rs, crates/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json
Adds bounded BM25-style search across authorized identifiers, providers, descriptions, and verified schema vocabulary. Adds deterministic ranking, stable fingerprints, adversarial-schema tests, and corpus quality metrics.
Authorized index integration and telemetry
crates/loop/ironclaw_loop_host/src/lib.rs, crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs, tests/integration/tool_disclosure.rs, tests/CLAUDE.md
Registers and caches the search index after authorization filtering. Enforces query-size limits, records search ranks, logs privacy-preserving metrics, and verifies policy filtering and parameter-only discovery.
Catalog migration, bridge contract, and policy harness
crates/loop/ironclaw_loop_host/src/tool_disclosure.rs, tests/integration/support/builder.rs, tests/integration/support/group_options.rs
Removes legacy catalog search state and ranking helpers. Updates the tool_search bridge description and regressions. Adds complete capability-surface policy overrides for bridged integration tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LoopCapabilityPort
  participant AuthorizedToolSearchIndex
  participant tool_describe
  LoopCapabilityPort->>AuthorizedToolSearchIndex: search filtered authorized definitions
  AuthorizedToolSearchIndex-->>LoopCapabilityPort: return ranked tool names and query class
  LoopCapabilityPort->>LoopCapabilityPort: record rank and privacy-preserving metrics
  LoopCapabilityPort->>tool_describe: describe the selected tool
Loading

Possibly related PRs

Suggested reviewers: benkurrek

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation satisfies [#7177] with bounded schema-aware search, pre-index authorization filtering, deterministic caching, corpus gates, and regression coverage.
Out of Scope Changes check ✅ Passed The changes remain within [#7177], covering deferred retrieval, authorization isolation, caching, telemetry, fixtures, and related integration tests.
Title check ✅ Passed The title follows the preferred Conventional Commits format and accurately describes the schema-aware deferred tool search feature.
Description check ✅ Passed The description covers the required summary, change type, issue, validation, test strategy, security, trust boundary, database, blast radius, rollback, and review sections.

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 commented Aug 6, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Aug 7, 2026 at 11:11 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 09:02 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 6, 2026
@serrrfirat
serrrfirat marked this pull request as ready for review August 6, 2026 09:03
@ironloopai

ironloopai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Review · PR #7273

🔴 Failed

GitHub request failed

IronLoop could not complete a required GitHub request.

Automatic · PR opened · attempt 1 of 3 · failed after 2m 15s

Failure details
  • Repository: nearai/ironclaw
  • Base: main at d739b31
  • Head: codex/7177-schema-aware-tool-search at 6234fb8
  • Created: Aug 6, 2026, 9:08 AM UTC
  • Updated: Aug 6, 2026, 9:10 AM UTC
  • Run: a1a13729-66cb-4b92-873a-9548108a5048
  • Latest attempt: 1 · Completed · 0bdcce0b-a3b2-4043-804b-0b6fddb827fb
  • Failed during: GitHub writeback
  • Retryable: No
  • Failure: 356443d8-f357-4d75-b8ec-d6496d0249a6

@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

🤖 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/loop/ironclaw_loop_host/src/tool_search.rs`:
- Around line 527-535: Update the test around AuthorizedToolSearchIndex::new so
it constructs an unfiltered index containing first, second, and denied, while
the authorized index contains only first and second. Assert the unfiltered
search for “assignee” produces the expected denied-stuffed ordering or count
change, then assert the authorized result remains ["allowed__first",
"allowed__second"] and excludes denied, proving the denied document is actually
isolated.

In `@tests/integration/tool_disclosure.rs`:
- Around line 467-509: Add a plain-English coverage row for the test function
tool_search_discovers_authorized_tools_by_parameter_only_vocabulary in
tests/integration/CLAUDE.md, following the existing row format and same-commit
documentation rules. Do not add a §4 binary row because the test belongs to the
existing integration test binary.
🪄 Autofix

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: 9e09dac6-4d64-4f55-a689-8d5f80923792

📥 Commits

Reviewing files that changed from the base of the PR and between d739b31 and 6234fb8.

📒 Files selected for processing (6)
  • crates/loop/ironclaw_loop_host/src/lib.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/loop/ironclaw_loop_host/src/tool_search.rs
  • crates/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json
  • tests/integration/tool_disclosure.rs

Comment thread crates/loop/ironclaw_loop_host/src/tool_search.rs
Comment thread tests/integration/tool_disclosure.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 09:23 Destroyed
@github-actions github-actions Bot added the scope: docs Documentation label Aug 6, 2026
@serrrfirat

serrrfirat commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — PASS

  • Tested head: e9d1fa627cd6f9a02783c06dac445cae6f859576
  • Railway state: pass — head-specific deployment check succeeded
  • Preview: https://ironclaw-ironclaw-pr-7273.up.railway.app
  • Relevant route: /chat/875f3da4-f1f4-5374-88df-16e01727107a
  • Runtime mode: bridged progressive disclosure confirmed from rendered tool_search execution
  • Backend-only deploy asset: assets/app-B6Q0H-n5.js

Given / When / Then evidence

Case Given When Then / observed evidence Result
Exact-head deployment PR head e9d1fa627cd6f9a02783c06dac445cae6f859576 The head-specific Railway state was checked Railway reported pass for the intended preview PASS
Authenticated preview The exact-head preview was live The bearer was submitted only through the gateway login UI The IronClaw chat shell initialized normally PASS
Bridge activation Progressive disclosure was configured as bridged A fresh chat explicitly requested one tool_search call The rendered activity panel contained exactly one successful ok tool_search row PASS
Parameter-only retrieval display_handle appears in the deferred target schema, not its provider-facing name tool_search ran with {query: "display_handle", limit: 5} The rendered result returned builtin__trace_commons__profile_set, capability ID builtin.trace_commons.profile_set, and required field display_handle PASS
No target side effect The prompt prohibited invoking the returned target The turn completed The activity count was exactly one and the only activity was tool_search; the profile mutation target was not called PASS
Refresh/read-back The successful result and activity existed The exact conversation route was reloaded The same prompt, one-tool activity, result name, capability ID, and completed response remained visible PASS
Runtime health The bridge journey and reload completed Browser warning/error logs were inspected across the chat and scoped-log tabs No warning or error entries were present PASS

Exact regression result

The previously unavailable bridge is now active and directly exercised. A parameter-key-only query successfully retrieved the intended deferred capability through the production tool_search bridge. The UI's expanded result readback showed the exact query, canonical capability ID, provider-facing name, description, and required parameter. This confirms the issue's primary user-visible regression on the exact deployed head rather than inferring it from model prose.

Deterministic coverage remains green on the same head: the committed corpus reports candidate recall@1/5/10 and MRR of 1.0, with no-match precision 1.0; Reborn integration, authorization isolation, schema bounds/trust, deterministic ordering/fingerprints, prompt shape, describe/invoke, and all required CI lanes pass.

Cleanup, skips, and remaining risk

  • Browser tabs were finalized with an empty keep set; no temporary provider data or target-tool side effects were created.
  • Permission-role, persistence, streaming, uploads, responsive-layout, and frontend-specific cases are not applicable to this host retrieval change.
  • Live model choice is supplemental and can vary, but this run used an explicit single-tool instruction and verified the authoritative rendered tool activity/result. Deterministic host and Reborn tests cover ranking and security invariants independently.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 12:30 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: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json (1)

331-383: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

The corpus exercises the trust policy in one direction only.

google_calendar__create_event is the only tool with "verified": true, and its "Repeated event rule" description at line 364 is the only nested schema description in the corpus. Every other tool deserializes as Untrusted and carries no nested description.

The corpus can therefore confirm that verified prose is indexed. It cannot detect the inverse regression, where untrusted nested prose leaks into retrieval metadata. The unit test at crates/loop/ironclaw_loop_host/src/tool_search.rs:429-465 covers that direction, so this is a corpus-reinforcement gap, not an uncovered invariant.

Consider adding one untrusted tool with a distinctive nested schema description, then adding a no_match intent for a term that appears only in that description. The existing no_match_accuracy == 1.0 gate would then fail on a trust-policy regression.

🤖 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/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json`
around lines 331 - 383, Add an untrusted tool fixture with a distinctive nested
schema description, then add a corresponding no_match intent querying a term
used only in that description. Ensure the tool remains deserialized as Untrusted
and the term is absent from trusted metadata, allowing the existing
no_match_accuracy == 1.0 assertion to detect untrusted nested-prose leakage.
🤖 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/loop/ironclaw_loop_host/src/tool_search.rs`:
- Around line 855-868: Update the recall closure around recall_at_1/recall_at_5
so each query’s denominator is capped at the cutoff, using min(at,
intent.relevance.len()) instead of the full judged-set size; preserve the
existing hit-count filtering and aggregation, and retune the affected recall and
baseline-comparison thresholds to reflect the new normalization.

---

Outside diff comments:
In `@crates/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json`:
- Around line 331-383: Add an untrusted tool fixture with a distinctive nested
schema description, then add a corresponding no_match intent querying a term
used only in that description. Ensure the tool remains deserialized as Untrusted
and the term is absent from trusted metadata, allowing the existing
no_match_accuracy == 1.0 assertion to detect untrusted nested-prose leakage.
🪄 Autofix

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: 2add17f0-5c76-447c-8f76-5db8b5d812d9

📥 Commits

Reviewing files that changed from the base of the PR and between e9d1fa6 and 2d106a5.

📒 Files selected for processing (2)
  • crates/loop/ironclaw_loop_host/src/tool_search.rs
  • crates/loop/ironclaw_loop_host/tests/fixtures/tool_search_relevance.json

Comment thread crates/loop/ironclaw_loop_host/src/tool_search.rs
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — PASS

  • Tested head: 2d106a527228e1daff051fcfeb38984c2da5c0a4
  • Railway state: success — exact-head preview deployment confirmed
  • Preview: https://ironclaw-ironclaw-pr-7273.up.railway.app
  • Relevant route: /chat/7d748d49-f2d8-5649-bf83-188e17f4b1a5
  • Frontend asset: assets/app-B6Q0H-n5.js (unchanged as expected for a Rust test/fixture-only head)

Acceptance matrix

Acceptance Given / When / Then Intended contract Actual contract exercised Result
Required Given bridged tool disclosure, when a fresh authenticated chat requests exactly one tool_search for display_handle with limit 5, then the search succeeds and returns the schema-only match without invoking it. The production-composed deferred-tool bridge discovers a tool from parameter vocabulary while keeping the target deferred. The activity panel showed exactly one tool activity, ok tool_search. Its rendered result contained query display_handle, tool builtin__trace_commons__profile_set, capability ID builtin.trace_commons.profile_set, and required field display_handle. No target invocation appeared. PASS
Required Given the completed result, when the chat route is refreshed, then the prompt, sole activity, result, and assistant response remain readable. The user-visible disclosure result remains available through the normal thread read-back path. After reload and session initialization, the same prompt, Activity - 1 tool, matching tool, capability ID, required field, and response were rendered. PASS
Supplemental Given the completed journey, then the browser console has no warnings or errors. Preview UI health remains clean during the changed behavior. Browser warning/error log query returned no entries. PASS

Status derivation

  • Required passed: 2
  • Required failed: 0
  • Required blocked/not executed: 0
  • Supplemental passed: 1
  • Overall: PASS because every required case exercised the intended production contract and passed.

Regression result

The exact-head preview directly exercised bridged tool_search using parameter-only vocabulary. It returned the expected deferred target and required schema field while recording exactly one tool activity, proving the target itself was not invoked. Refresh/read-back passed.

Skipped cases and remaining risk

  • No destructive target invocation was attempted; the acceptance claim is discovery-only.
  • Permission-denial coverage was not repeated in the browser because the preview session exposes one role; deterministic authorization-isolation and production caller-path tests remain the owning evidence.
  • The expanded retrieval benchmark is deterministic Rust test evidence and is not itself user-visible in Railway; this exact-head run verifies that the unchanged shipping behavior still works after the benchmark-only commit.

Cleanup

  • Temporary chat cleanup: completed; the QA thread was deleted and the conversation list returned to empty.
  • Browser tab cleanup: completed after evidence construction.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 12:43 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — PASS

  • Tested head: c1c5d93c6263aba8d49b73e202e1116799f1b970
  • Railway state: success — exact-head preview deployment confirmed
  • Preview: https://ironclaw-ironclaw-pr-7273.up.railway.app
  • Relevant route: /chat/c7c3a2ee-b84f-507d-a6e7-fc0f4f48bdc7
  • Frontend asset: assets/app-B6Q0H-n5.js (unchanged as expected for a Rust test/fixture/documentation-only head)

Acceptance matrix

Acceptance Given / When / Then Intended contract Actual contract exercised Result
Required Given bridged tool disclosure, when a fresh authenticated chat requests exactly one tool_search for display_handle with limit 5, then the search succeeds and returns the schema-only match without invoking it. The production-composed deferred-tool bridge discovers a tool from parameter vocabulary while keeping the target deferred. The activity panel showed exactly one tool activity, ok tool_search. Its rendered result contained query display_handle, tool builtin__trace_commons__profile_set, capability ID builtin.trace_commons.profile_set, and required field display_handle. No target invocation appeared. PASS
Required Given the completed result, when the chat route is refreshed, then the prompt, sole activity, result, and assistant response remain readable. The user-visible disclosure result remains available through the normal thread read-back path. After reload and session initialization, the same prompt, Activity - 1 tool, matching tool, capability ID, required field, and response were rendered. PASS
Supplemental Given the completed journey, then the browser console has no warnings or errors. Preview UI health remains clean during the changed behavior. Browser warning/error log query returned no entries. PASS

Status derivation

  • Required passed: 2
  • Required failed: 0
  • Required blocked/not executed: 0
  • Supplemental passed: 1
  • Overall: PASS because every required case exercised the intended production contract and passed.

Regression result

The exact-head preview directly exercised bridged tool_search using parameter-only vocabulary. It returned the expected deferred target and required schema field while recording exactly one tool activity, proving the target itself was not invoked. Refresh/read-back passed.

Skipped cases and remaining risk

  • No destructive target invocation was attempted; the acceptance claim is discovery-only.
  • Permission-denial coverage was not repeated in the browser because the preview session exposes one role; deterministic authorization-isolation and production caller-path tests remain the owning evidence.
  • The expanded retrieval benchmark is deterministic Rust test evidence and is not itself user-visible in Railway; this exact-head run verifies that shipping behavior remains sound after the benchmark and recall-semantics documentation commits.

Cleanup

  • Temporary chat cleanup: completed; the QA thread was deleted and the conversation list returned to empty.
  • Browser tab cleanup: completed after evidence construction.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 12:58 Destroyed
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review (multi-agent)

Intent: Add schema-aware deferred tool search: replace name/description substring matching with a bounded, host-owned weighted lexical index over capability IDs, aliases, parameter keys, and trusted nested schema descriptions, with deterministic metadata invalidation, authorization-fitted retrieval, and a graded 50-tool/72-query relevance corpus.

Shape: normal primary mode; no modifiers. Selected because the PR targets main directly and is a single feature layer (not a stack). XL size (2,686 additions / 2,827 changed lines) drove complete packetization, not a mode change.

Coverage: complete. Diff source: local-git (exact base→head commit pair verified against PR snapshot; matches_supplied_files=true). File buckets: 4 production / 2 tests / 1 docs / 0 generated / 0 CI / 0 config. 4 diff packets, 0 oversized files, 0 failed reviewers. Limitations: none.

Stats: 14 findings (14 raw, 11 after overlap dedup) across 4 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach. Reviewers failed: 0. Body-only: 0.

Verified in-worktree before posting: git status clean at head, tool_search.rs 989 lines matching the diff; reviewers' probe_tmp scratch reports were transient worktree state (created/removed by the review harness), not part of the PR, and were excluded.

Bugs

  1. Low PROVIDER_WEIGHT never takes effect: provider terms enter at NAME_WEIGHT via capability_id field (crates/loop/ironclaw_loop_host/src/tool_search.rs:180-183, confidence 60) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:183
    IndexedDocument::new adds full capability_id (e.g. 'github.list_issues') with NAME_WEIGHT=8.0 first, then dot-split provider prefix ('github') with PROVIDER_WEIGHT=4.0. add_field's entry().and_modify(max) keeps 8.0, so the provider field's 4.0 weight is always subsumed and PROVIDER_WEIGHT (and its tuning intent) is dead. Provider-name queries rank as strongly as exact name tokens; deterministic gates pass, but the declared weight scheme is not what runs.
    Fix: Add provider field before capability_id, or index capability_id minus its provider segment at NAME_WEIGHT so the provider prefix can keep PROVIDER_WEIGHT.

Performance/Concurrency

  1. Medium Full-corpus deep schema fingerprint rehashed on every turn_state call under mutex (crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs:574-586, confidence 62) (also flagged by: security/Medium) — anchor: crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs:582
    turn_state() runs on every bridge capability call (tool_search, tool_describe, tool_call, promote_target, capability_info note, dispatch). Each call refetches definitions, filters, and recomputes definitions_fingerprint, which now deep-hashes every tool's full parameters JSON (hash_json recurses whole schema, unbounded size) plus sorts by capability_id (O(n log n)). Old fingerprint hashed only count + names. Rebuild is gated by the fingerprint, but the hash itself is recomputed unconditionally per call while holding the turn_state Mutex. For large MCP/extension tool surfaces (hundreds of tools x KB-sized schemas) this adds ms-scale per-message latency to the agent-loop hot path and lengthens mutex hold time.
    Fix: Cache per-call fingerprint: memoize deep hash per capability_id once (schemas immutable within a turn), or two-tier check - cheap names/count fingerprint first, deep hash only when cheap tier changes.

Tests

  1. Medium Equal-score deterministic tie-break by capability_id has no direct test (crates/loop/ironclaw_loop_host/src/tool_search.rs:147-157, confidence 70) (also flagged by: performance/Low) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:156
    Explicit constraint is deterministic ranking and tie-breaking. Sort uses score desc then capability_id asc (line 156). No test builds two documents with identical BM25 scores where capability_id order differs from name order, so the tie-break rule itself is unverified.
    Fix: Add test: two tools with identical name/description/parameter vocabulary whose capability_id ordering differs from name ordering.

  2. Medium MAX_QUERY_TERMS (32) silent truncation of long queries untested (crates/loop/ironclaw_loop_host/src/tool_search.rs:108-112, confidence 70) (also flagged by: bugs/Low) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:110
    search() truncates tokenized queries with .take(MAX_QUERY_TERMS) (line 110). Bounded-input constraint is explicit; a >32-term query (reachable under the 1024-byte port cap) silently drops tail terms, but no test exercises the truncation boundary or its effect on ranking.
    Fix: Add test covering a 33+-term query where only the first 32 terms determine results.

  3. Medium MAX_FIELD_BYTES / MAX_DOCUMENT_BYTES / MAX_UNIQUE_TERMS byte-limit paths untested (crates/loop/ironclaw_loop_host/src/tool_search.rs:217-246, confidence 65) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:217
    add_field truncates at 256-byte fields, 8192-byte documents, and 512 unique terms. The adversarial test only exercises depth and field-count caps; no test feeds a >256-byte description or parameter string or a corpus exceeding document/unique-term budgets, so the truncation branches (scan closure, admitted_bytes accounting) are unverified.
    Fix: Add test covering >256-byte description field and >MAX_DOCUMENT_BYTES set of fields with no panic and bounded term_weights.

  4. Low Empty-corpus branch (average_length=1.0, NoMatch) untested (crates/loop/ironclaw_loop_host/src/tool_search.rs:88-90, confidence 55) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:88
    new() special-cases documents.is_empty() to average_length 1.0 and search on an empty index returns NoMatch; every existing test builds 1-3 documents or the 50-tool corpus. Low risk because the port rejects an unavailable catalog, but the branch is dead-code-adjacent with no guard.
    Fix: Add test: AuthorizedToolSearchIndex::new(empty) returning NoMatch for any query.

  5. Low CamelCase token-boundary splitting in tokenize untested (crates/loop/ironclaw_loop_host/src/tool_search.rs:313-335, confidence 55) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:313
    tokenize inserts a space before an uppercase char following a lowercase/digit (camelCase splitting). No fixture or test uses camelCase identifiers (corpus uses snake_case names/params), so this branch - which affects indexing of mixed-case capability descriptions - has zero direct coverage. All-caps words and boundary state also unverified.
    Fix: Add test covering createEvent and HTTPUrl tokenizing to create/event and http/url.

  6. Low Fingerprint stability across shuffled multi-definition arrays untested (crates/loop/ironclaw_loop_host/src/tool_search.rs:339-370, confidence 55) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:339
    definitions_fingerprint sorts by capability_id before hashing, but the only test uses single-definition slices, which cannot detect an ordering bug in multi-definition input. Rebuild correctness depends on fingerprint stability for the same effective corpus in any order.
    Fix: Add test: two identical multi-definition arrays in different orders yielding the same u64.

  7. Low Same-turn rebuild preserving search_ranks across mid-turn fingerprint change untested (crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs:592-631, confidence 55) — anchor: crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs:610
    turn_state rebuild now filters to authorized definitions, widens the fingerprint to schema metadata, and preserves search_ranks across same-turn rebuilds. No unit or integration test exercises a mid-turn fingerprint change (e.g. schema-only mutation or extension activation) to verify ranks/disclosed_names survive and the index rebuilds.
    Fix: Add test covering a fingerprint change between two turn_state calls in one turn.

Local Patterns

  1. Low Stale test name references removed tool_search_rank function (crates/loop/ironclaw_loop_host/src/tool_disclosure.rs:1889-1895, confidence 65) — anchor: crates/loop/ironclaw_loop_host/src/tool_disclosure.rs:1890
    tool_search_rank removed from tool_disclosure.rs, replaced by AuthorizedToolSearchIndex::search in tool_search.rs. Test name tool_search_rank_scores_deterministically is now the only grep hit for the deleted symbol. Readers searching ranking logic land on a test named after a nonexistent function; name drift after rename hurts navigation.
    Fix: Rename test to describe current API, e.g. tool_search_index_scores_deterministically.

Maintainability

  1. Low Four near-identical bounded schema-traversal loops in collect_schema (crates/loop/ironclaw_loop_host/src/tool_search.rs:272-310, confidence 65) — anchor: crates/loop/ironclaw_loop_host/src/tool_search.rs:272
    The items (272), anyOf/oneOf/allOf (285), additionalProperties (295), and $defs/definitions (300) branches each repeat the same shape: grab children, .take(MAX_SCHEMA_NODES), guard self.schema_nodes >= MAX_SCHEMA_NODES, recurse with depth.saturating_add(1). The node-cap bookkeeping is copy-pasted five times (properties branch included). Any future change to traversal bounds or cap semantics must be applied in five places or the branches silently diverge.
    Fix: Extract one helper owning the shared node-cap guard and recursion; the four keyword branches collapse into three-line calls.

All findings are Low/Medium — no blocking defects. The strongest item is the per-call full-schema fingerprint re-hash (performance + security concur), which contradicts the PR's own 'bounded traversal and inputs' constraint on the hot path.

let definitions = self.inner.tool_definitions()?;
let fingerprint = definitions_fingerprint(&definitions);
// Fit and cache retrieval only over the effective authorized corpus.
// Denied schemas therefore cannot affect IDF, ordering, counts, cache

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Full-corpus deep schema fingerprint rehashed on every turn_state call under mutex.

turn_state() runs on every bridge capability call (tool_search, tool_describe, tool_call, promote_target, capability_info note, dispatch). Each call refetches definitions, filters, and recomputes definitions_fingerprint, which now deep-hashes every tool's full parameters JSON (hash_json recurses whole schema, unbounded size) plus sorts by capability_id (O(n log n)). Old fingerprint hashed only count + names. Rebuild is gated by the fingerprint, but the hash itself is recomputed unconditionally per call while holding the turn_state Mutex. For large MCP/extension tool surfaces (hundreds of tools x KB-sized schemas) this adds ms-scale per-message latency to the agent-loop hot path and lengthens mutex hold time.

Fix: Cache per-call fingerprint: memoize deep hash per capability_id once (schemas immutable within a turn), or two-tier check - cheap names/count fingerprint first, deep hash only when cheap tier changes.

Also flagged by: security/Medium

let capability_id = definition.capability_id.as_str();
builder.add_field(capability_id, NAME_WEIGHT);
builder.add_field(definition.name.as_str(), NAME_WEIGHT);
if let Some(provider) = capability_id.split('.').next() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — PROVIDER_WEIGHT never takes effect: provider terms enter at NAME_WEIGHT via capability_id field.

IndexedDocument::new adds full capability_id (e.g. 'github.list_issues') with NAME_WEIGHT=8.0 first, then dot-split provider prefix ('github') with PROVIDER_WEIGHT=4.0. add_field's entry().and_modify(max) keeps 8.0, so the provider field's 4.0 weight is always subsumed and PROVIDER_WEIGHT (and its tuning intent) is dead. Provider-name queries rank as strongly as exact name tokens; deterministic gates pass, but the declared weight scheme is not what runs.

Fix: Add provider field before capability_id, or index capability_id minus its provider segment at NAME_WEIGHT so the provider prefix can keep PROVIDER_WEIGHT.

right
.2
.total_cmp(&left.2)
.then_with(|| left.1.cmp(&right.1))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Equal-score deterministic tie-break by capability_id has no direct test.

Explicit constraint is deterministic ranking and tie-breaking. Sort uses score desc then capability_id asc (line 156). No test builds two documents with identical BM25 scores where capability_id order differs from name order, so the tie-break rule itself is unverified.

Fix: Add test: two tools with identical name/description/parameter vocabulary whose capability_id ordering differs from name ordering.

Also flagged by: performance/Low

let normalized_query = query.trim().to_lowercase();
let query_terms: Vec<String> = tokenize(&normalized_query)
.into_iter()
.take(MAX_QUERY_TERMS)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — MAX_QUERY_TERMS (32) silent truncation of long queries untested.

search() truncates tokenized queries with .take(MAX_QUERY_TERMS) (line 110). Bounded-input constraint is explicit; a >32-term query (reachable under the 1024-byte port cap) silently drops tail terms, but no test exercises the truncation boundary or its effect on ranking.

Fix: Add test covering a 33+-term query where only the first 32 terms determine results.

Also flagged by: bugs/Low

}

impl SearchDocumentBuilder {
fn add_field(&mut self, value: &str, weight: f64) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — MAX_FIELD_BYTES / MAX_DOCUMENT_BYTES / MAX_UNIQUE_TERMS byte-limit paths untested.

add_field truncates at 256-byte fields, 8192-byte documents, and 512 unique terms. The adversarial test only exercises depth and field-count caps; no test feeds a >256-byte description or parameter string or a corpus exceeding document/unique-term budgets, so the truncation branches (scan closure, admitted_bytes accounting) are unverified.

Fix: Add test covering >256-byte description field and >MAX_DOCUMENT_BYTES set of fields with no panic and bounded term_weights.

}
}

fn tokenize(value: &str) -> BTreeSet<String> {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — CamelCase token-boundary splitting in tokenize untested.

tokenize inserts a space before an uppercase char following a lowercase/digit (camelCase splitting). No fixture or test uses camelCase identifiers (corpus uses snake_case names/params), so this branch - which affects indexing of mixed-case capability descriptions - has zero direct coverage. All-caps words and boundary state also unverified.

Fix: Add test covering createEvent and HTTPUrl tokenizing to create/event and http/url.


/// Stable for a fixed ranker version and effective authorized metadata. Object
/// keys are sorted recursively so semantically identical schemas share a key.
pub(crate) fn definitions_fingerprint(definitions: &[ProviderToolDefinition]) -> u64 {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Fingerprint stability across shuffled multi-definition arrays untested.

definitions_fingerprint sorts by capability_id before hashing, but the only test uses single-definition slices, which cannot detect an ordering bug in multi-definition input. Rebuild correctness depends on fingerprint stability for the same effective corpus in any order.

Fix: Add test: two identical multi-definition arrays in different orders yielding the same u64.

.unwrap_or(true);
if rebuild {
let catalog = CapabilityCatalog::new(&definitions, &[]);
let index_started_at = std::time::Instant::now();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Same-turn rebuild preserving search_ranks across mid-turn fingerprint change untested.

turn_state rebuild now filters to authorized definitions, widens the fingerprint to schema metadata, and preserves search_ranks across same-turn rebuilds. No unit or integration test exercises a mid-turn fingerprint change (e.g. schema-only mutation or extension activation) to verify ranks/disclosed_names survive and the index rebuilds.

Fix: Add test covering a fingerprint change between two turn_state calls in one turn.

@@ -1971,7 +1888,7 @@ mod tests {

#[test]
fn tool_search_rank_scores_deterministically() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Stale test name references removed tool_search_rank function.

tool_search_rank removed from tool_disclosure.rs, replaced by AuthorizedToolSearchIndex::search in tool_search.rs. Test name tool_search_rank_scores_deterministically is now the only grep hit for the deleted symbol. Readers searching ranking logic land on a test named after a nonexistent function; name drift after rename hurts navigation.

Fix: Rename test to describe current API, e.g. tool_search_index_scores_deterministically.

self.collect_schema(schema, depth.saturating_add(1), trusted_descriptions);
}
}
if let Some(items) = object.get("items") {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Low — Four near-identical bounded schema-traversal loops in collect_schema.

The items (272), anyOf/oneOf/allOf (285), additionalProperties (295), and $defs/definitions (300) branches each repeat the same shape: grab children, .take(MAX_SCHEMA_NODES), guard self.schema_nodes >= MAX_SCHEMA_NODES, recurse with depth.saturating_add(1). The node-cap bookkeeping is copy-pasted five times (properties branch included). Any future change to traversal bounds or cap semantics must be applied in five places or the branches silently diverge.

Fix: Extract one helper owning the shared node-cap guard and recursion; the four keyword branches collapse into three-line calls.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 14:46 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed all comments from review pullrequestreview-4875394276 in a9dcd5a7f:

  • cache authorized search state by the metadata-complete visible-surface version, keeping refresh hashing outside the mutex while preserving same-turn disclosure/rank progress
  • preserve the intended provider weight and deterministic capability-ID tie-break
  • centralize bounded schema-child traversal and preserve first-query-term truncation semantics
  • add direct coverage for query/field/document/unique-term caps, empty indexes, camel/acronym tokenization, shuffled fingerprints, and same-turn schema refreshes
  • rename the stale ranker test

Validation: cargo fmt --all -- --check; cargo test -p ironclaw_loop_host (634 unit tests plus crate integration/doc targets); cargo clippy -p ironclaw_loop_host --all-targets --all-features -- -D warnings; full cargo clippy --all --benches --tests --examples --all-features -- -D warnings; bash scripts/pre-commit-safety.sh. An additional cargo test --workspace --lib attempt was environment-blocked by disk exhaustion after the relevant suite had passed.

@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: 1

🤖 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/loop/ironclaw_loop_host/src/tool_disclosure_port.rs`:
- Around line 1494-1551: Add #[cfg(any(test, feature = "test-support"))]
directly to the MutableDefinitionsPort test-helper implementation items
containing the lock().expect() calls, including its LoopCapabilityPort
implementation and any associated helper methods or type if needed. Ensure
scripts/check_no_panics.py recognizes these items as test-only without relying
on parent-module gating.
🪄 Autofix

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: b91e93bf-9631-48cc-949c-da16f85441a5

📥 Commits

Reviewing files that changed from the base of the PR and between b62a37b and a9dcd5a.

📒 Files selected for processing (3)
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/loop/ironclaw_loop_host/src/tool_search.rs

Comment thread crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed the latest CodeRabbit comment in e9b6fe5.

  • Added item-level #[cfg(any(test, feature = "test-support"))] gates to MutableDefinitionsPort and its LoopCapabilityPort implementation so source-based production safety scans do not inspect the test fixture.
  • Verified with cargo fmt --all -- --check, bash scripts/pre-commit-safety.sh, cargo check -p ironclaw_loop_host --tests, scripts/check_no_panics.py --base a9dcd5a7f --head HEAD, and the panic scanner self-test (34/34).

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 6, 2026 16:20 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 7, 2026 08:11 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 7, 2026 08:46 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: 1

🤖 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/loop/ironclaw_loop_host/src/tool_disclosure_port.rs`:
- Around line 602-607: The tool disclosure filtering around tool_definitions
must apply the complete CapabilitySurfacePolicy, not only permits_capability_id.
Before computing definitions_fingerprint and constructing the disclosure catalog
or search index, filter definitions using allowed_runtimes, allowed_effects,
include_requires_approval, and max_capabilities consistently with
effective_entries and permits, reusing the existing policy semantics.
🪄 Autofix

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: 26dc2034-33bd-417f-a0e1-1dbecbdc389a

📥 Commits

Reviewing files that changed from the base of the PR and between e9b6fe5 and a896252.

📒 Files selected for processing (5)
  • crates/loop/ironclaw_loop_host/src/lib.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • tests/CLAUDE.md
  • tests/integration/tool_disclosure.rs

Comment thread crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
@think-in-universe

Copy link
Copy Markdown
Collaborator

@ironloopai review

@ironloopai

ironloopai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

🧭 IronLoop Run · Review

This comment updates in place as the Run moves through its stages.

🟩 Final result · Completed

🟨 Queued → 🟦 Working → 🟦 Posting results → 🟩 Completed

Manual command by think-in-universe · attempt 1 of 3 · completed in 6m 41s

IronLoop completed the review and posted it to GitHub.

🔗 Result

Open submitted review →

Run details

Run: 6b7aac39-0665-43a1-b4af-3d8440f133cd
Base: main at 389aa99
Head: codex/7177-schema-aware-tool-search at a896252
Created: 2026-08-07 10:23 UTC
Updated: 2026-08-07 10:29 UTC

@ironloopai ironloopai 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.

🔍 IronLoop review

Reviewed the complete merge-base-to-head comparison. Found one authorization-surface defect in the new schema-aware index.

Findings: 🟠 Medium 1

🟠 Medium · Fit the index to the complete capability surface policy

Inline on crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs:605. See the inline comment for details.

Validation

  • ✅ Complete diff inspection — Inspected all seven changed files and traced the affected disclosure, policy-filter, runner-composition, cache, schema-trust, and integration-test paths.
  • ✅ Patch integrity — `git diff --check refs/ironloop/merge-base refs/ironloop/head` completed successfully.
  • ⚪ Targeted loop-host tests — Not run. The targeted Cargo test began downloading and compiling the locked workspace dependencies but did not reach test execution within the review window; it was interrupted during compilation.
Review details
  • Run: 6b7aac39-0665-43a1-b4af-3d8440f133cd
  • Workflow: Review
  • Attempts: 1

let fingerprint = definitions_fingerprint(&definitions);
let authorized_definitions: Vec<_> = definitions
.into_iter()
.filter(|definition| self.policy.permits_capability_id(&definition.capability_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.

🔍 IronLoop review · Inline finding

🟠 Medium · Fit the index to the complete capability surface policy

`authorized_definitions` applies only `capability_ids`, although `CapabilitySurfacePolicy` can also exclude tools by runtime, declared effects, approval requirement, and `max_capabilities`. The inner policy decorator likewise narrows `tool_definitions()` only by ID; its complete constraints apply later to visible descriptors. Consequently, a profile that permits an ID but excludes its runtime/effects/approval status—or truncates it via the count limit—still adds that tool’s names, parameters, and trusted nested schema descriptions to this index. A model can discover and receive metadata for a tool outside its effective visible surface, and excluded documents also alter IDF/ranking. Build the corpus from IDs surviving the complete effective surface policy, preserving its registry-order limit, before fingerprinting or indexing it.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7273 August 7, 2026 11:05 Destroyed
@serrrfirat
serrrfirat added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 12e5a30 Aug 7, 2026
46 checks passed
@serrrfirat
serrrfirat deleted the codex/7177-schema-aware-tool-search branch August 7, 2026 12:23
@serrrfirat serrrfirat mentioned this pull request Aug 7, 2026
24 of 29 tasks
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
* feat(loop-host): add schema-aware deferred tool search

* test(loop-host): strengthen tool search isolation coverage

* test(loop-host): expand tool search relevance benchmark

* docs(loop-host): clarify retrieval recall semantics

* fix(loop-host): address serrrfirat review — harden tool search bounds (nearai#7273)

* fix(loop-host): address coderabbit review — gate test fixture (nearai#7273)

* fix(loop-host): address coderabbit review — honor full surface policy (nearai#7273)

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-7273 — b0a9ce48 Deployed Aug 7, 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: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve deferred tool retrieval with schema-aware ranked search

3 participants