Skip to content

feat(tool-search): complete fair discovery and benchmark arms - #7410

Merged
serrrfirat merged 11 commits into
mainfrom
codex/7405-bounded-search-signatures
Aug 11, 2026
Merged

serrrfirat merged 11 commits into
mainfrom
codex/7405-bounded-search-signatures

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Aug 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Returns bounded complete input signatures from tool_search, removing the mandatory tool_describe round trip when the schema fits.
  • Adds fair semantic namespace summaries and deterministic representative-tool rounds instead of an alphabetical prefix.
  • Keeps the existing authorization-fitted BM25F ranker; this PR changes discovery presentation and interaction, not relevance scoring.
  • Keeps reviewed profile pins available in the explicit bridged mode, while production defaults to the safer unpinned namespaces mode.
  • Adds a resumable real-model benchmark for five disclosure arms at 100/500/1,000 tools, with task-level argument/order scoring and forbidden-attempt detection.
  • Records implementation judgments and rejected alternatives in docs/internal/tool-discovery-subjective-decisions.md.

What this achieves

Previously, a deferred tool generally required search → describe → invoke. A search hit now carries the canonical parameter schema plus schema_complete; the normal path is search → invoke. tool_describe remains available when a schema exceeds the 8 KiB per-result or 24 KiB aggregate response budget, or when explicit inspection is useful.

The passive preview reports authorized namespace counts, then spends its remaining fixed budget on representative tool names in fair namespace rounds. First-party ownership buckets such as builtin and ironclaw are regrouped deterministically by intent (coding, memory, scheduling, skills, observability, and so on). External tools retain their stable extension identity, such as github, gmail, or google-calendar.

Catalog construction starts from the effective policy-filtered surface. Search results, complete signatures, namespace counts, pins, direct calls, and wrapped calls cannot widen authority; every invocation still crosses validation, authorization, approval, hooks, safety, runtime dispatch, and evidence handling.

This is provider-neutral. It complements rather than replaces Anthropic-native defer_loading / tool_reference behavior and preserves the provider's stable advertised-array contract.

Multi-agent review findings and fixes

The eight-lens review completed with full diff coverage. It found that the first benchmark version could support a retrieval claim but not an end-to-end merge decision:

  • Completion checked tool names but not workflow order or arguments.
  • Unauthorized leakage was hard-coded to zero instead of inspecting attempted calls.
  • First-correct-tool latency incorrectly used the first arbitrary tool call.
  • Completed repetitions could be lost when a later repetition was interrupted.
  • Relevant tools were round-robined into anonymous packages, so the namespace experiment did not preserve semantic ownership.
  • Large schemas were fully serialized before the byte cap was checked.
  • Profile pins used raw string keys, and the decorator constructor silently defaulted to bridged before a follow-up override.
  • Gateway, profile-pin caller-path, namespace-overflow, and minimum-catalog cases lacked focused regressions.

The fixes now:

  • validate expected call order and task-owned argument fields;
  • inspect trace attempts, including wrapped tool_call targets, for forbidden calls;
  • measure latency to the first correct tool;
  • fsync each observation and resume by stable observation ID;
  • use 20 semantic MCP fixture identities and fill the smallest bucket deterministically;
  • stop schema serialization at the configured limit;
  • use validated CapabilitySurfaceProfileId keys and require the disclosure mode at construction;
  • reject invalid profile-pin configuration during production startup while preserving the parse cause;
  • cover the production caller and gateway paths with regression tests.

Static model-facing disclosure prose also moved into prompt assets, and runtime logging now reports the actual selected mode instead of calling every enabled arm “bridged.”

Corrected 100-tool benchmark verdict

The authoritative v2 comparison (artifact identity tool-discovery-v2-1a674e7-nearai-deepseek-v4-flash-seed7405-100tools-56obs) ran the shipping ironclaw serve binary at runtime commit 1a674e7724, NearAI deepseek-ai/DeepSeek-V4-Flash, temperature 0.0, catalog generator tool-search-scale-v2, 20 semantic hosted MCP integrations, deterministic seed 7405, seven task classes, and one cold plus three warm repetitions per task/arm: 56 observations total.

Arm Completion Median E2E Worst E2E Median tool calls Forbidden attempts
namespaces 26/28 (92.9%) 21.6s 186.9s 3.0 0
bridged 24/28 (85.7%) 14.6s 187.7s 2.0 0

The overall latency difference is directional, not a clean causal estimate: different task/arm groups independently hit the provider's roughly 187-second tail. The per-scenario results explain the tradeoff:

  • Pins materially accelerated the simple calendar and upload tasks.
  • Exact canonical-ID lookup was faster and more stable without pins in this run.
  • The two-step Gmail → calendar-create workflow completed 2/4 with namespaces and 0/4 with bridged under argument-and-order scoring.
  • One bridged workflow invoked the pinned calendar-list tool before attempting calendar creation, repeating the attraction failure previously seen in exploratory 500-tool evidence.
  • No-match and denied-capability controls completed 16/16 across both arms with zero forbidden attempts.

Verdict: keep namespaces as the production default. bridged is a useful latency optimization for deployments that validate their pin set against representative end-to-end workflows, but its lower completion rate fails the default-selection gate.

The earlier 268-observation exploratory run remains useful for discovering scale and provider-tail behavior, but its anonymous package allocation and name-only scoring are superseded by the v2 result above. NearAI usage fields were inconsistent/zero, so this PR does not invent token estimates from JSON bytes. Deterministic context bounds remain: passive preview ≤4,096 bytes, complete schema ≤8 KiB, the complete search-result JSON envelope ≤24 KiB, and the default adds no standing pinned definitions.

Configuration

REBORN_TOOL_DISCLOSURE Behavior
off Advertise all authorized schemas; rollback path
compact Legacy compact search/describe protocol
signatures Bounded complete search signatures
namespaces Namespace summaries + complete signatures; default
bridged Namespace summaries + signatures + reviewed profile pins; opt-in

Unknown values fail closed to off. Invalid pin JSON, profile IDs, or capability IDs reject production startup with the parse cause retained; an unset pin variable remains the empty default. Pins never widen authority.

Change Type

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

Validation

  • cargo fmt --all -- --check
  • cargo clippy -p ironclaw_loop_host -p ironclaw_turn_runner -p ironclaw_composition --all-targets --all-features -- -D warnings
  • cargo test -p ironclaw_loop_host — package suite passed, including 89 gateway tests
  • cargo test -p ironclaw_integration_tests --test reborn_integration_tool_disclosure — 34 passed
  • cargo test -p ironclaw_composition --lib — 528 passed
  • cargo test -p ironclaw_architecture_tests — passed
  • python3 -m pytest scripts/tool_discovery_benchmark/test_run_benchmark.py -q — 8 passed
  • python3 scripts/ci/docs_publication_boundary.py
  • Corrected exact-runtime-head live comparison — 56 observations at 100 tools
  • Full ironclaw_turn_runner library suite — 242/243 passed; unrelated pre-existing trace_capture::tests::capture_skips_when_policy_missing_or_disabled fails because a queue directory exists for a non-enrolled scope. This PR does not modify trace_capture.rs.
  • Railway exact-current-head rerun after review fixes; earlier runtime QA evidence predates this review commit.
  • Approved 1,000-tool benchmark exception: the user explicitly requested a merge decision from the available corrected evidence; the unexecuted tier remains visible rather than being inferred.

Test Strategy

User behavior: complete search results can invoke directly; incomplete results recover through describe-first; invalid pin configuration now blocks startup with an actionable error.

Risk areas:

  • Model behavior
  • Security and permissions
  • Cross-component behavior
  • External provider

Tiers:

  • Unit/contract: schema byte limits, complete/compact shapes, semantic namespace fairness and overflow, mode parsing, typed pin validation, resumable benchmark observations, task arguments/order, leakage, and latency semantics.
  • Reborn integration: production default, matching-profile authorized pins, denied-pin absence, all selectable arms, and search → direct invoke through the production caller path.
  • Gateway integration: allowed search/describe/exact wrapped targets and suppression of mismatched wrapped targets.
  • Recorded fixture: Not applicable; no provider-specific transcript contract changed.
  • Browser E2E: earlier Railway QA passed the pre-review runtime; exact-current-head rerun remains explicit above.
  • Live canary: corrected production server + live provider comparison described above.

What the tests prove: response-envelope bounds match model-visible bytes, incomplete signatures cannot bypass recovery, pin errors fail at startup, benchmark resume/report data stays interpretable, and CI selection remains fail-closed for mixed paths.

Commands run: see the checked Validation list above; the review-fix gate additionally ran the complete loop-host suite, composition suite, disclosure integration binary, strict clippy, architecture tests, and 85 Python tests with 157 subtests.

Security Impact

Authorized canonical schemas may appear in bounded search results, and authorized namespace counts appear in the safe bridge description. Denied tools cannot affect counts, preview allocation, search results, signatures, pins, or callable targets. Forbidden attempts are now measured from model traces instead of reported as a constant.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: configuration is constructed only through the composition startup boundary; pins remain visibility preferences, not authority.
  • Untrusted content enters prompts only through existing bounded result/prompt assets; no new raw prompt interpolation was added.
  • Hashes declare purpose; no trust/authenticity primitive changed.
  • New/changed runtime errors audited: malformed pin configuration preserves its typed source into RebornRuntimeError.
  • No security/durability serde(default) field was added.
  • Search response buffers and counters remain bounded with checked/saturating arithmetic where applicable.
  • Operator-visible configuration errors are stable and actionable.
  • Runtime/namespace names describe behavior rather than trust authority.

Database Impact

None.

Blast Radius

Touches provider-neutral tool disclosure, planned-runtime configuration startup, the live benchmark/report schema, integration-test harness selection, and internal coverage documentation. Primary regressions would be an incomplete schema treated as callable, a valid deployment rejected by pin parsing, or stale benchmark rows contaminating a resumed report. No persistence schema, authorization grant, approval path, or provider-specific wire protocol changes.

Compatibility and rollback

No migration is required. namespaces remains the default; deployments with validated pins can select bridged. Set REBORN_TOOL_DISCLOSURE=off for immediate rollback. No persistent-data cleanup is needed.

Linked Issue

Closes #7405.

Review Follow-Through

Audited 7 conversation comments, 8 submitted review bodies, and all 13 inline threads with complete pagination. Valid findings were addressed in 4688d2e369; duplicate, already-addressed, and non-actionable items were documented in per-thread replies without resolving threads. The 1,000-tool tier remains an explicit user-approved exception, and exact-current-head Railway QA remains the only open validation item.

Review track

B — provider-neutral feature with model-behavior, authorization, context-budget, and benchmark-validity implications.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added compact, signature, and namespace tool-discovery modes.
    • Namespace discovery is now the default, with deterministic previews and relevance-focused results.
    • Search results identify complete schemas for immediate invocation; incomplete results can be inspected before use.
    • Added configurable profile pins for highlighting selected capabilities without bypassing authorization.
    • Added environment configuration for disclosure modes and profile mappings.
  • Bug Fixes

    • Improved capability filtering and deferred tool resolution for safer, more accurate tool calls.
  • Documentation

    • Added tool-discovery evaluation guidance and a real-model benchmarking workflow.

Walkthrough

Tool disclosure now supports namespace, compact, signature, and bridged modes. Search results can include bounded complete schemas. Profile-specific capability pins flow through runtime policy. Gateway tests cover authorized deferred discovery and invocation. A live benchmark measures discovery behavior across catalog sizes.

Changes

Tool disclosure

Layer / File(s) Summary
Disclosure modes and catalog selection
crates/loop/ironclaw_loop_host/src/tool_disclosure_mode.rs, crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
Adds disclosure modes, namespace summaries, capability-ID pins, deterministic previews, and mode-specific catalog descriptions.
Search schema delivery and fallback
crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs, crates/loop/ironclaw_loop_host/prompts/*, crates/loop/ironclaw_loop_host/src/system_prompt_assets.rs
Adds bounded complete schemas, compact fallback results, and conditional tool_describe guidance.
Runtime profile pins and integration wiring
crates/loop/ironclaw_turn_runner/src/runtime.rs, crates/app/ironclaw_composition/src/runtime.rs, tests/integration/support/*, tests/integration/tool_disclosure.rs, .env.example
Parses profile pins, applies them during capability-port construction, forwards disclosure modes, and tests production filtering.
Gateway authorization and deferred invocation
crates/loop/ironclaw_loop_host/src/model_gateway.rs, crates/loop/ironclaw_loop_host/tests/llm_gateway.rs
Permits authorized discovery, description, and exact deferred capability calls while suppressing unrelated unavailable-capability responses.
Protocol, benchmarks, and evaluation contract
scripts/tool_discovery_benchmark/*, docs/internal/tool-discovery-*.md, scripts/ci/*, tests/CLAUDE.md
Adds deterministic and live benchmark tooling, evaluation rules, rollout gates, and QA path classification.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • nearai/ironclaw#7273: Modifies schema-aware deferred tool_search behavior in the same disclosure components.
  • nearai/ironclaw#7409: Adds related tool-discovery evaluation and scalable catalog benchmarking.
  • nearai/ironclaw#7411: Modifies deferred tool retrieval in tool_disclosure_port.rs and tool_search.

Suggested reviewers: benkurrek, think-in-universe

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy #7405 through bounded complete schemas, namespace previews, typed profile pins, policy-safe disclosure, tests, and benchmark tooling.
Out of Scope Changes check ✅ Passed The code, documentation, tests, CI updates, and benchmark tooling all support the linked tool-discovery objectives without unrelated scope.
Title check ✅ Passed The title follows Conventional Commits style and accurately summarizes the tool discovery and benchmark changes.
Description check ✅ Passed The description follows the repository template and documents scope, validation, risks, security impact, rollback, linked issue, and known limitations.

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 9, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Aug 10, 2026 at 9:41 pm

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 9, 2026 21:52 Destroyed
@github-actions github-actions Bot added the size: M 50-199 changed lines label Aug 9, 2026
@github-actions github-actions Bot added risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 9, 2026
@ironloopai

ironloopai Bot commented Aug 9, 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

Automatic trigger · attempt 1 of 3 · completed in 11m 28s

IronLoop completed the review and posted it to GitHub.

🔗 Result

Open submitted review →

Run details

Run: 24e31143-583c-4b6f-b382-8e3b6a400362
Base: codex/7405-tool-discovery-benchmark at a7ec269
Head: codex/7405-bounded-search-signatures at ffbe9e0
Created: 2026-08-09 21:52 UTC
Updated: 2026-08-09 22:04 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

Found 1 medium-severity correctness issue.

Findings: 🟠 Medium 1

🟠 Medium · Reserve space for the tool-result envelope

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

Validation

  • ✅ Bounded-signature unit tests — All 3 focused bounded-signature tests passed.
  • ⚪ Model-visible response path — Not run. Static tracing was sufficient to establish the preview-cap interaction; no separate execution was needed.
Review details
  • Run: 24e31143-583c-4b6f-b382-8e3b6a400362
  • Workflow: Review
  • Attempts: 1


/// Maximum canonical JSON bytes devoted to complete input signatures in one
/// `tool_search` response. Compact result metadata is always returned.
const MAX_SEARCH_SIGNATURE_BYTES_TOTAL: usize = 24 * 1024;

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 · Reserve space for the tool-result envelope

The 24 KiB cap counts only `parameters`, while the serialized search result also contains the query, result array, names, descriptions, required fields, and JSON syntax. The production result path exposes only the first 24 KiB inline to the model, so a response whose included schemas reach this cap is necessarily truncated. Those entries still report `schema_complete: true`, even though the model receives an incomplete JSON preview and must page it through a result read. Budget the full serialized response (or reserve envelope space) before marking schemas complete.

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.

Addressed in 4688d2e369: tool_search now budgets the complete serialized JSON response before marking a schema complete, so complete signatures fit the model-visible inline result. Verification: 676 loop-host tests and strict Clippy passed.

@serrrfirat
serrrfirat force-pushed the codex/7405-bounded-search-signatures branch from ffbe9e0 to 8805277 Compare August 10, 2026 07:10
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 07:10 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Aug 10, 2026
@serrrfirat serrrfirat changed the title feat(tool-search): return bounded complete signatures feat(tool-search): return complete signatures and skip redundant describe Aug 10, 2026
Base automatically changed from codex/7405-tool-discovery-benchmark to main August 10, 2026 08:06
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 08:44 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Aug 10, 2026
@serrrfirat serrrfirat changed the title feat(tool-search): return complete signatures and skip redundant describe feat(tool-search): complete fair discovery and benchmark arms Aug 10, 2026

@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_disclosure_port.rs`:
- Around line 1114-1132: Update the search-result handling around search_ranks,
disclosed_names, and ranked_results so names are inserted into disclosed_names
only when the corresponding result has schema_complete: true; leave compact and
bounded incomplete results undisclosed so tool_describe can recover their
schemas. Add a caller-path regression covering an oversized-schema search
followed by an invalid target call, asserting should_describe_first returns the
schema rather than dispatching the target.

In `@crates/loop/ironclaw_turn_runner/src/runtime.rs`:
- Around line 147-176: The tool disclosure profile-pin configuration currently
suppresses parse and capability-ID errors; make it fallible and reject invalid
values during runtime construction. Update tool_disclosure_profile_pins_from_env
and its caller to propagate errors with contextual mapping via ?, while
retaining an unset REBORN_TOOL_DISCLOSURE_PROFILE_PINS_ENV as an empty default.
Preserve underlying serde and CapabilityId errors, and add a caller-path test
covering malformed JSON and invalid capability IDs.
🪄 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: e1663171-f9bc-4b96-9dff-aaae382dd5d5

📥 Commits

Reviewing files that changed from the base of the PR and between fa6e72d and 7a4cafe.

📒 Files selected for processing (15)
  • crates/app/ironclaw_composition/src/runtime.rs
  • crates/loop/ironclaw_loop_host/prompts/tool_disclosure_protocol.md
  • crates/loop/ironclaw_loop_host/src/system_prompt_assets.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_mode.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_scale_baseline.json
  • crates/loop/ironclaw_turn_runner/src/runtime.rs
  • docs/internal/tool-discovery-evaluation.md
  • docs/internal/tool-discovery-subjective-decisions.md
  • tests/integration/support/builder.rs
  • tests/integration/support/group.rs
  • tests/integration/support/group_options.rs
  • tests/integration/tool_disclosure.rs

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

Copy link
Copy Markdown
Collaborator Author

Railway QA — exact head 7a4cafea98

Preview: https://ironclaw-ironclaw-pr-7410.up.railway.app

The Railway deployment check reported success for the PR's exact head before browser testing began.

Case Result Observable evidence
Gateway authentication and app shell PASS Connected to the preview and opened a new conversation.
Passive namespace awareness PASS Without calling a tool, the model read builtin (22) and ironclaw (2) from the visible tool_search description: 24 deferred tools across 2 authorized namespaces.
Complete-signature direct invocation PASS tool_search returned builtin__trace_commons__status with schema_complete=true; the recorded activity contained exactly two successful tools, tool_search then status, with no tool_describe. The UI reported 14 seconds.
Read-only result PASS status returned runtime enrollment/auth-mode state; no write operation was performed.
No-match failure path PASS Searching lunar_unicorn_tax_filing_v99 returned {"results":[]}. Recorded activity contained exactly one tool_search; no substitute tool, describe, call, or write followed.
Browser console PASS No error-level console entries during the QA session.

This validates the deployed default bridged arm and the eliminated describe round trip on a real model run. It is not the full quantitative benchmark: Railway deployed one configured arm and a 24-tool catalog. The five-arm cold/warm 100/500/1,000 matrix remains required before closing #7405; token and latency fields will be populated from those provider runs rather than estimated locally.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 09:57 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: 4

🤖 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/tests/llm_gateway.rs`:
- Around line 737-803: Add regression tests covering the remaining
permits_policy_checked_call branches: a wrapped tool_call whose
provider-supplied arguments["name"] targets the requested canonical capability,
and an unrelated substitute capability that must be suppressed with
UNAVAILABLE_CAPABILITY_REPLY. Place these alongside the existing gateway
deferred-capability tests, asserting the authorized wrapper reaches capability
execution and the unrelated call is not emitted.

In `@scripts/tool_discovery_benchmark/README.md`:
- Around line 9-13: Update the README quickstart command block to state that a
live model credential is required and add an example export for NEARAI_API_KEY
before invoking run_benchmark.py; mention that LIVE_OPENAI_COMPATIBLE_API_KEY is
also accepted.

In `@scripts/tool_discovery_benchmark/run_benchmark.py`:
- Around line 148-173: The generated description namespace in the
tool-construction loop does not match the tool’s eventual bucket namespace.
Update the description generation around the tool append and bucket assignment
so its namespace is derived from the same bucket index used by
canonical_capability_id, or remove the namespace token from the description;
preserve consistent namespace-aware benchmark data.
- Around line 589-598: Update the summary construction around the git HEAD
lookup to use a checked subprocess API instead of os.popen, ensuring the command
failure raises or otherwise aborts rather than recording an empty head. Keep the
resolved commit string in summary["head"], and avoid blocking synchronous
process execution within the async benchmark flow by using the appropriate
asynchronous subprocess mechanism.
🪄 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: 1ca0015f-d995-4f10-9afb-f100a7bb0c6f

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4cafe and 753f091.

📒 Files selected for processing (6)
  • crates/loop/ironclaw_loop_host/src/model_gateway.rs
  • crates/loop/ironclaw_loop_host/tests/llm_gateway.rs
  • docs/internal/tool-discovery-subjective-decisions.md
  • scripts/tool_discovery_benchmark/README.md
  • scripts/tool_discovery_benchmark/run_benchmark.py
  • scripts/tool_discovery_benchmark/test_run_benchmark.py

Comment thread crates/loop/ironclaw_loop_host/tests/llm_gateway.rs
Comment thread scripts/tool_discovery_benchmark/README.md
Comment thread scripts/tool_discovery_benchmark/run_benchmark.py Outdated
Comment thread scripts/tool_discovery_benchmark/run_benchmark.py
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 12:51 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.

Caution

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

⚠️ Outside diff range comments (5)
tests/CLAUDE.md (2)

190-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the complete-search invocation contract.

The row lists the exposed tools but omits the changed result semantics. Complete tool_search results can call tool_call without tool_describe. Incomplete or ambiguous results retain the tool_describe fallback. Add both user-visible paths to the coverage sentence. Remove any matching §7 gap row if this row closes it.

Based on learnings, coverage rows must describe user-visible behavior, and a closed §7 gap must be removed when its coverage row is added.

🤖 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 `@tests/CLAUDE.md` at line 190, Update the tool_disclosure.rs coverage row in
tests/CLAUDE.md to document both complete-search results calling tool_call
directly and incomplete or ambiguous results falling back to tool_describe.
Remove the corresponding §7 gap row if it tracks this behavior, leaving only
unresolved gaps.

Source: Learnings


67-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Disambiguate the two Python coverage totals.

If Section 2 counts only active or registered Reborn coverage, label that scope at Line 67. Otherwise, reconcile it with Line 313. The file currently presents 102 files and 869 test functions alongside 103 files and 1,141 tests. Section 6 explicitly calls the latter exhaustive, but Section 2 calls its value a total.

Also applies to: 313-313

🤖 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 `@tests/CLAUDE.md` at line 67, Clarify the scope of the Python coverage figures
in Section 2 near the `102` files and `869` test functions entry: label them as
active or registered Reborn coverage if that is what they represent; otherwise
reconcile them with the exhaustive `103` files and `1,141` tests totals in
Section 6. Ensure the wording no longer presents both different figures as
unqualified totals.
scripts/ci/reborn_pr_test_plan.py (2)

764-777: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not let nextest widening bypass unknown-path validation. The early return can accept a mixed diff that contains an unclassified path and still produce a full Reborn plan.

  • scripts/ci/reborn_pr_test_plan.py#L764-L777: defer _full_plan(...) until all paths are classified, while retaining fail-closed errors for truly unclassified paths.
  • scripts/ci/test_reborn_pr_test_plan.py#L920-L937: add a mixed-diff regression with .config/nextest.toml and an unclassified scripts/** path.
🤖 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 `@scripts/ci/reborn_pr_test_plan.py` around lines 764 - 777, The nextest
configuration branch in scripts/ci/reborn_pr_test_plan.py:764-777 must not
return before validating every changed path; defer _full_plan(...) until
classification completes, preserving fail-closed errors for unclassified paths.
Add the mixed-diff regression in scripts/ci/test_reborn_pr_test_plan.py:920-937
covering .config/nextest.toml together with an unclassified scripts/** path.

343-354: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for scripts/e2e-skill-self-creation.sh.

PR_STATIC_CONTROL_PATHS contains this path, but RebornPrTestPlanTests.test_decided_repo_root_paths_are_owned_by_other_workflows does not assert that a change to it produces mode: none and crate_buckets: []. Add a regression in that table to match the new static-control entry.

🤖 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 `@scripts/ci/reborn_pr_test_plan.py` around lines 343 - 354, Add a regression
case to the table used by
RebornPrTestPlanTests.test_decided_repo_root_paths_are_owned_by_other_workflows
for scripts/e2e-skill-self-creation.sh, asserting that the planned result is
mode: none with crate_buckets: []. Match the existing cases for paths in
PR_STATIC_CONTROL_PATHS.
scripts/ci/test_reborn_pr_test_plan.py (1)

920-937: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover mixed diffs in this regression.

This test exercises .config/nextest.toml alone. It cannot detect the early return skipping a second unclassified path. Add a case with .config/nextest.toml and scripts/some-undecided-helper.sh, then assert the fail-closed 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 `@scripts/ci/test_reborn_pr_test_plan.py` around lines 920 - 937, Extend
test_nextest_config_widens_to_exhaustive_plan with a mixed changed-file set
containing .config/nextest.toml and an undecided helper path such as
scripts/some-undecided-helper.sh. Assert that plan raises the fail-closed
unclassified pull-request path error instead of returning the exhaustive plan,
covering processing of subsequent paths after the known configuration change.
🤖 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.

Outside diff comments:
In `@scripts/ci/reborn_pr_test_plan.py`:
- Around line 764-777: The nextest configuration branch in
scripts/ci/reborn_pr_test_plan.py:764-777 must not return before validating
every changed path; defer _full_plan(...) until classification completes,
preserving fail-closed errors for unclassified paths. Add the mixed-diff
regression in scripts/ci/test_reborn_pr_test_plan.py:920-937 covering
.config/nextest.toml together with an unclassified scripts/** path.
- Around line 343-354: Add a regression case to the table used by
RebornPrTestPlanTests.test_decided_repo_root_paths_are_owned_by_other_workflows
for scripts/e2e-skill-self-creation.sh, asserting that the planned result is
mode: none with crate_buckets: []. Match the existing cases for paths in
PR_STATIC_CONTROL_PATHS.

In `@scripts/ci/test_reborn_pr_test_plan.py`:
- Around line 920-937: Extend test_nextest_config_widens_to_exhaustive_plan with
a mixed changed-file set containing .config/nextest.toml and an undecided helper
path such as scripts/some-undecided-helper.sh. Assert that plan raises the
fail-closed unclassified pull-request path error instead of returning the
exhaustive plan, covering processing of subsequent paths after the known
configuration change.

In `@tests/CLAUDE.md`:
- Line 190: Update the tool_disclosure.rs coverage row in tests/CLAUDE.md to
document both complete-search results calling tool_call directly and incomplete
or ambiguous results falling back to tool_describe. Remove the corresponding §7
gap row if it tracks this behavior, leaving only unresolved gaps.
- Line 67: Clarify the scope of the Python coverage figures in Section 2 near
the `102` files and `869` test functions entry: label them as active or
registered Reborn coverage if that is what they represent; otherwise reconcile
them with the exhaustive `103` files and `1,141` tests totals in Section 6.
Ensure the wording no longer presents both different figures as unqualified
totals.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f933eddb-fd70-4d71-91fb-b6464b9bf22f

📥 Commits

Reviewing files that changed from the base of the PR and between ddab91a and 1fb510f.

📒 Files selected for processing (5)
  • crates/app/ironclaw_composition/src/runtime.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • scripts/ci/reborn_pr_test_plan.py
  • scripts/ci/test_reborn_pr_test_plan.py
  • tests/CLAUDE.md
💤 Files with no reviewable changes (1)
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — PASS

  • Tested head: 1fb510f1bd6ef33d448fc70994bd02e2a08bd7aa
  • Railway state: success
  • Preview: https://ironclaw-ironclaw-pr-7410.up.railway.app
  • Route: /chat
  • Frontend asset: assets/app-DYvnwsw7.js (unchanged, expected for this backend/CI-only head update)
Acceptance Given / When / Then Actual contract and evidence Status
Required Given the production default, when asked to list visible namespace summaries without tools, then summaries expose catalog breadth fairly. Exact-head chat returned builtin (22) and ironclaw (2) with no tool activity. PASS
Required Given a deferred runtime tool, when the model searches and invokes it, then tool_search returns the complete signature and tool_describe is optional. Search returned builtin__trace_commons__status with parameters, required, and schema_complete: true; activity was exactly tool_search → status; no tool_describe; status returned successfully. PASS
Required Given an impossible tool query, when searched once, then the model does not substitute an unrelated tool. lunar_unicorn_tax_filing_v99 returned zero results; activity contained exactly one tool_search; response said no match and made no substitute call. PASS

Status derivation

Required cases passed: 3. Failed: 0. Blocked or not executed: 0. Therefore the exact-head result is PASS.

Regression result and remaining scope

The preview confirms the selected namespaces default advertises namespace breadth, returns callable signatures from search without a mandatory describe round trip, and preserves safe no-match behavior. The prior live benchmark remains the source for comparative latency/recall results; the intentionally stopped 1,000-tool tier was not rerun as browser QA.

Cleanup: all three QA conversations were deleted and browser tabs were finalized.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 13:27 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/loop/ironclaw_loop_host/src/tool_disclosure.rs (1)

2028-2028: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale test rationale.

The test name now specifies namespace summaries and representative tools. The adjacent comment still requires every discoverable tool name in the index. Namespace mode intentionally uses fair representative sampling within a byte cap. Update the comment to describe the current contract.

The PR objective requires fair namespace previews rather than exhaustive catalog listing.

🤖 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/src/tool_disclosure.rs` at line 2028, Update
the comment adjacent to
tool_search_description_summarizes_namespace_and_representative_tool to describe
the current namespace-preview contract: summarize namespaces using fair
representative tool sampling within the byte cap, rather than requiring every
discoverable tool name in the index.
🤖 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.rs`:
- Around line 314-360: Replace the fixed namespace string returns in
discovery_namespace and builtin_discovery_namespace with a DiscoveryNamespace
enum covering first-party groups, plus a separate dynamic variant for
extension-derived namespaces. Update catalog-summary rendering to convert
DiscoveryNamespace values into model-facing labels at the boundary, while
preserving existing namespace classification and extension fallback behavior.

In `@scripts/tool_discovery_benchmark/test_run_benchmark.py`:
- Around line 43-48: Update
test_upload_task_is_self_contained_and_does_not_require_a_workspace_fixture to
assert that the upload prompt contains the concrete MIME value "text/csv", in
addition to the existing mime_type assertion.

---

Outside diff comments:
In `@crates/loop/ironclaw_loop_host/src/tool_disclosure.rs`:
- Line 2028: Update the comment adjacent to
tool_search_description_summarizes_namespace_and_representative_tool to describe
the current namespace-preview contract: summarize namespaces using fair
representative tool sampling within the byte cap, rather than requiring every
discoverable tool name in the index.
🪄 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: 8d9b8462-2090-452a-a19f-a955e35527c9

📥 Commits

Reviewing files that changed from the base of the PR and between 1fb510f and 3e51223.

📒 Files selected for processing (4)
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • docs/internal/tool-discovery-subjective-decisions.md
  • scripts/tool_discovery_benchmark/run_benchmark.py
  • scripts/tool_discovery_benchmark/test_run_benchmark.py

Comment thread crates/loop/ironclaw_loop_host/src/tool_disclosure.rs Outdated
Comment thread scripts/tool_discovery_benchmark/test_run_benchmark.py
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — PASS

Acceptance Given / When / Then Actual contract and evidence Status
Required Given the production namespace default, when asked to list visible summaries without tools, then first-party ownership buckets are replaced by useful semantic groups. Exact-head chat returned data (1), extensions (3), memory (1), messaging (2), observability (5), scheduling (3), skills (5), system (3), and web (1). Neither builtin nor ironclaw appeared, and no tool was called. PASS
Required Given a deferred runtime tool, when the model searches and invokes it, then the search result carries a complete signature and describe is optional. Result identified builtin__trace_commons__status with schema_complete=true; activity was exactly tool_search → status; no tool_describe; status returned successfully. PASS
Required Given an impossible query, when searched exactly once, then the model stops without substituting an unrelated tool. lunar_unicorn_tax_filing_v99 returned zero results; activity contained exactly one tool_search; response reported no match and made no substitute call. PASS

Status derivation

Required cases passed: 3. Failed: 0. Blocked or not executed: 0. Therefore the exact-head result is PASS.

Supplemental live-model benchmark

A focused 100-tool namespaces run exercised all seven task classes. Six completed immediately with zero unauthorized calls. The upload case found the correct Google Drive upload tool but exposed that the benchmark prompt named a nonexistent workspace file. The prompt now supplies deterministic inline content, its regression test passes, and the live rerun completed successfully. This was a benchmark-fixture correction rather than a product failure.

Cleanup: all three QA conversations were deleted and browser tabs were finalized.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 15:12 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)
tests/integration/support/group.rs (1)

965-970: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the disclosure-mode error text.

Line 965 accepts every enabled disclosure mode. The error still says that only Bridged mode is valid. A Compact, Signatures, or Namespaces caller receives incorrect remediation.

State that the policy override requires any enabled disclosure mode. Rename the bridged-only helper separately if its public name is now inaccurate.

🤖 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 `@tests/integration/support/group.rs` around lines 965 - 970, Update the error
text in the narrowed bridged policy validation to state that the override
requires any enabled tool disclosure mode, and provide remediation that applies
to Compact, Signatures, and Namespaces as well as Bridged. Separately review the
public name of with_narrowed_capability_surface_policy_for_bridged_test() and
rename it if the bridged-only wording is no longer accurate.
scripts/tool_discovery_benchmark/run_benchmark.py (1)

146-188: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject empty benchmark packages before start-up.

generate_catalog(50) leaves workflow-admin with zero tools; install_catalog rejects that run after startup with not installed.get(package_id, {}).get("tools"). Add one tool for every catalog before returning, with a precise generator failure 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 `@scripts/tool_discovery_benchmark/run_benchmark.py` around lines 146 - 188,
The generate_catalog function must ensure every namespace bucket contains at
least one tool before returning, including when the requested tool count is
smaller than the number of namespaces. Add a validation or generation step
before the return that raises a precise ValueError identifying the empty
namespace, or otherwise supplies the required tool according to the existing
catalog rules; preserve the existing install_catalog contract.
🤖 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/prompts/tool_search_empty_catalog.md`:
- Line 1: Replace the content of the empty-catalog prompt with a concise
response stating that no deferred or additional tools are available. Remove
instructions to search or inspect tools, preserving the no-match behavior used
when discoverable_namespaces() returns empty.

In `@scripts/tool_discovery_benchmark/run_benchmark.py`:
- Around line 645-648: Update the run metadata construction in run_task_group so
thermal_class reflects the cache state for the current invocation rather than
using repetition == 0. Track whether the first repetition executed in that
invocation has completed, label that first executed repetition "cold", and label
subsequent repetitions "warm", including resumed runs starting at repetition 1.
- Around line 714-724: Define a shared OBSERVATION_SCHEMA_VERSION constant with
value 2, use it in run_task_group instead of the literal schema version, and
update load_observations to reject rows whose schema_version is not the current
constant before deduplication. Ensure stale schema_version 1 rows cannot enter
observations or suppress re-execution.

---

Outside diff comments:
In `@scripts/tool_discovery_benchmark/run_benchmark.py`:
- Around line 146-188: The generate_catalog function must ensure every namespace
bucket contains at least one tool before returning, including when the requested
tool count is smaller than the number of namespaces. Add a validation or
generation step before the return that raises a precise ValueError identifying
the empty namespace, or otherwise supplies the required tool according to the
existing catalog rules; preserve the existing install_catalog contract.

In `@tests/integration/support/group.rs`:
- Around line 965-970: Update the error text in the narrowed bridged policy
validation to state that the override requires any enabled tool disclosure mode,
and provide remediation that applies to Compact, Signatures, and Namespaces as
well as Bridged. Separately review the public name of
with_narrowed_capability_surface_policy_for_bridged_test() and rename it if the
bridged-only wording is no longer accurate.
🪄 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: b564109a-1542-48e7-83fc-344054af53b5

📥 Commits

Reviewing files that changed from the base of the PR and between 3e51223 and 1a674e7.

📒 Files selected for processing (15)
  • .env.example
  • crates/app/ironclaw_composition/src/runtime.rs
  • crates/loop/ironclaw_loop_host/prompts/tool_search_empty_catalog.md
  • crates/loop/ironclaw_loop_host/prompts/tool_search_namespace_header.md
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_mode.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/loop/ironclaw_loop_host/tests/llm_gateway.rs
  • crates/loop/ironclaw_turn_runner/src/runtime.rs
  • docs/internal/tool-discovery-subjective-decisions.md
  • scripts/tool_discovery_benchmark/README.md
  • scripts/tool_discovery_benchmark/run_benchmark.py
  • scripts/tool_discovery_benchmark/test_run_benchmark.py
  • tests/integration/support/group.rs
  • tests/integration/tool_disclosure.rs

Comment thread crates/loop/ironclaw_loop_host/prompts/tool_search_empty_catalog.md Outdated
Comment thread scripts/tool_discovery_benchmark/run_benchmark.py Outdated
Comment thread scripts/tool_discovery_benchmark/run_benchmark.py
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 16:00 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)
docs/internal/tool-discovery-evaluation.md (1)

54-77: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use one canonical arm identifier.

Line 64 requires each observation to record the selected REBORN_TOOL_DISCLOSURE value. The table defines signatures and namespaces, but the schema example at Line 97 uses bounded_complete_signatures. The benchmark tests use short values such as namespaces and bridged in scripts/tool_discovery_benchmark/test_run_benchmark.py Lines 118-164. Different identifiers can split aggregates or break report consumers.

Change Line 97 to "arm": "signatures", or add a separate display-label field.

🤖 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 `@docs/internal/tool-discovery-evaluation.md` around lines 54 - 77, Update the
benchmark schema example’s arm identifier to use the canonical
REBORN_TOOL_DISCLOSURE value "signatures" instead of
"bounded_complete_signatures", keeping it consistent with the arm table and
benchmark tests.
🤖 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 `@docs/internal/tool-discovery-subjective-decisions.md`:
- Around line 375-384: The benchmark evidence must identify the exact evaluation
configuration and artifact. Update the evidence paragraph around the corrected
v2 runner to include the model route, temperature, catalog generator and seed,
thermal class, repetition metadata, and a link or unique identifier for the
result report, using the requirements in tool-discovery-evaluation.md.

---

Outside diff comments:
In `@docs/internal/tool-discovery-evaluation.md`:
- Around line 54-77: Update the benchmark schema example’s arm identifier to use
the canonical REBORN_TOOL_DISCLOSURE value "signatures" instead of
"bounded_complete_signatures", keeping it consistent with the arm table and
benchmark tests.
🪄 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: 9402176d-d6bf-4d69-9edb-4a1959393ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 1a674e7 and d94505b.

📒 Files selected for processing (2)
  • docs/internal/tool-discovery-evaluation.md
  • docs/internal/tool-discovery-subjective-decisions.md

Comment thread docs/internal/tool-discovery-subjective-decisions.md Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7410 August 10, 2026 21:33 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 (1)
scripts/tool_discovery_benchmark/run_benchmark.py (1)

538-542: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve unavailable provider usage as None.

Lines 538-542 convert a missing trace into zero token and cache values. _metric_delta can then persist 0 as provider usage even when no provider reported usage. Keep call counts separate, but return None for unavailable provider-token and cache metrics.

The benchmark contract requires unavailable cache measurements to remain null, not synthetic zeroes.

🤖 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 `@scripts/tool_discovery_benchmark/run_benchmark.py` around lines 538 - 542,
Update the missing-trace return path to keep call counts at zero while returning
None for all provider-token and cache metrics, including input_tokens,
output_tokens, cache_read_tokens, uncached_input_tokens, and cost_usd. Preserve
the existing empty list result and ensure unavailable cache measurements remain
null rather than synthetic zeroes.
🤖 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.rs`:
- Around line 318-365: Update DiscoveryNamespace equality to compare the values
returned by as_str() rather than using structural derived equality, and remove
the conflicting derived PartialEq/Eq implementations. Ensure the resulting
PartialEq and Eq behavior matches Ord/PartialOrd so first-party variants and
Extension values with identical labels compare equal.

In `@crates/loop/ironclaw_turn_runner/src/runtime.rs`:
- Around line 1157-1174: Update the child test invocation in
profile_pin_environment_rejects_invalid_configuration_at_runtime_startup to pass
--exact and the full module::tests::test_name filter, matching the sibling
tool_disclosure_mode_non_unicode_env_fails_closed pattern. Preserve the existing
invalid-environment arguments and success assertion.

In `@scripts/tool_discovery_benchmark/run_benchmark.py`:
- Around line 731-735: Update the result-building logic around result.success
and scored["completed"] so task_incomplete is assigned only when the live run
succeeds but task scoring fails. For unsuccessful runs, preserve a stable
failure category from the live result or caught runner error, and ensure that
category flows into failure_categories and aggregate results; add regression
coverage for both unsuccessful-run and successful-but-incorrect-task paths.

---

Outside diff comments:
In `@scripts/tool_discovery_benchmark/run_benchmark.py`:
- Around line 538-542: Update the missing-trace return path to keep call counts
at zero while returning None for all provider-token and cache metrics, including
input_tokens, output_tokens, cache_read_tokens, uncached_input_tokens, and
cost_usd. Preserve the existing empty list result and ensure unavailable cache
measurements remain null rather than synthetic zeroes.
🪄 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: 5c01009c-df26-44fc-a9b2-cf5fea2a0300

📥 Commits

Reviewing files that changed from the base of the PR and between d94505b and 4688d2e.

📒 Files selected for processing (16)
  • .env.example
  • crates/app/ironclaw_composition/src/runtime.rs
  • crates/loop/ironclaw_loop_host/prompts/tool_search_empty_catalog.md
  • crates/loop/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_mode.rs
  • crates/loop/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/loop/ironclaw_turn_runner/src/runtime.rs
  • docs/internal/tool-discovery-evaluation.md
  • docs/internal/tool-discovery-subjective-decisions.md
  • scripts/ci/reborn_pr_test_plan.py
  • scripts/ci/test_reborn_pr_test_plan.py
  • scripts/tool_discovery_benchmark/README.md
  • scripts/tool_discovery_benchmark/run_benchmark.py
  • scripts/tool_discovery_benchmark/test_run_benchmark.py
  • tests/CLAUDE.md
  • tests/integration/support/group.rs

Comment on lines +318 to +365
#[derive(Debug, Clone, PartialEq, Eq)]
enum DiscoveryNamespace {
Agents,
Coding,
Data,
Extensions,
Memory,
Messaging,
Observability,
Scheduling,
Settings,
Skills,
System,
Web,
Extension(String),
}

impl DiscoveryNamespace {
fn as_str(&self) -> &str {
match self {
Self::Agents => "agents",
Self::Coding => "coding",
Self::Data => "data",
Self::Extensions => "extensions",
Self::Memory => "memory",
Self::Messaging => "messaging",
Self::Observability => "observability",
Self::Scheduling => "scheduling",
Self::Settings => "settings",
Self::Skills => "skills",
Self::System => "system",
Self::Web => "web",
Self::Extension(extension_id) => extension_id,
}
}
}

impl PartialOrd for DiscoveryNamespace {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for DiscoveryNamespace {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make PartialEq agree with the label-based Ord.

Ord/PartialOrd compare self.as_str(), but PartialEq/Eq are derived structurally. DiscoveryNamespace::Memory and DiscoveryNamespace::Extension("memory".to_string()) then compare Ordering::Equal while == returns false. The same holds for an extension id equal to any first-party label (data, system, web, skills, …).

discoverable_namespaces uses this type as a BTreeMap key, and BTreeMap resolves keys through Ord only. Two distinct variants therefore collapse into one entry, and lookups become order-dependent. std::cmp::Ord requires consistency with PartialEq.

Derive the equality from the same label so both relations agree.

🔒️ Proposed fix
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone)]
 enum DiscoveryNamespace {
@@
+impl PartialEq for DiscoveryNamespace {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_str() == other.as_str()
+    }
+}
+
+impl Eq for DiscoveryNamespace {}
+
 impl PartialOrd for DiscoveryNamespace {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[derive(Debug, Clone, PartialEq, Eq)]
enum DiscoveryNamespace {
Agents,
Coding,
Data,
Extensions,
Memory,
Messaging,
Observability,
Scheduling,
Settings,
Skills,
System,
Web,
Extension(String),
}
impl DiscoveryNamespace {
fn as_str(&self) -> &str {
match self {
Self::Agents => "agents",
Self::Coding => "coding",
Self::Data => "data",
Self::Extensions => "extensions",
Self::Memory => "memory",
Self::Messaging => "messaging",
Self::Observability => "observability",
Self::Scheduling => "scheduling",
Self::Settings => "settings",
Self::Skills => "skills",
Self::System => "system",
Self::Web => "web",
Self::Extension(extension_id) => extension_id,
}
}
}
impl PartialOrd for DiscoveryNamespace {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DiscoveryNamespace {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
#[derive(Debug, Clone)]
enum DiscoveryNamespace {
Agents,
Coding,
Data,
Extensions,
Memory,
Messaging,
Observability,
Scheduling,
Settings,
Skills,
System,
Web,
Extension(String),
}
impl DiscoveryNamespace {
fn as_str(&self) -> &str {
match self {
Self::Agents => "agents",
Self::Coding => "coding",
Self::Data => "data",
Self::Extensions => "extensions",
Self::Memory => "memory",
Self::Messaging => "messaging",
Self::Observability => "observability",
Self::Scheduling => "scheduling",
Self::Settings => "settings",
Self::Skills => "skills",
Self::System => "system",
Self::Web => "web",
Self::Extension(extension_id) => extension_id,
}
}
}
impl PartialEq for DiscoveryNamespace {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for DiscoveryNamespace {}
impl PartialOrd for DiscoveryNamespace {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DiscoveryNamespace {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
🤖 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/src/tool_disclosure.rs` around lines 318 -
365, Update DiscoveryNamespace equality to compare the values returned by
as_str() rather than using structural derived equality, and remove the
conflicting derived PartialEq/Eq implementations. Ensure the resulting PartialEq
and Eq behavior matches Ord/PartialOrd so first-party variants and Extension
values with identical labels compare equal.

Comment on lines +1157 to +1174
for invalid in ["{", r#"{"interactive_tools":["invalid"]}"#] {
let output = std::process::Command::new(
std::env::current_exe().expect("current test executable"),
)
.args([
"profile_pin_environment_rejects_invalid_configuration_at_runtime_startup",
"--nocapture",
])
.env(CHILD_MARKER, "1")
.env(REBORN_TOOL_DISCLOSURE_PROFILE_PINS_ENV, invalid)
.output()
.expect("child test process executes");
assert!(
output.status.success(),
"runtime startup must reject invalid pin config: {}",
String::from_utf8_lossy(&output.stderr)
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Pin the child test filter with --exact and the full module path.

The child is invoked with a bare name filter. If the test is renamed or moved, the filter matches zero tests, the child harness exits 0, and the parent assertion passes without verifying anything. The sibling tool_disclosure_mode_non_unicode_env_fails_closed test avoids this by passing --exact with the full module::tests::name path.

💚 Proposed fix
             .args([
-                "profile_pin_environment_rejects_invalid_configuration_at_runtime_startup",
+                "--exact",
+                "runtime::tests::profile_pin_environment_rejects_invalid_configuration_at_runtime_startup",
+                "--test-threads=1",
                 "--nocapture",
             ])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for invalid in ["{", r#"{"interactive_tools":["invalid"]}"#] {
let output = std::process::Command::new(
std::env::current_exe().expect("current test executable"),
)
.args([
"profile_pin_environment_rejects_invalid_configuration_at_runtime_startup",
"--nocapture",
])
.env(CHILD_MARKER, "1")
.env(REBORN_TOOL_DISCLOSURE_PROFILE_PINS_ENV, invalid)
.output()
.expect("child test process executes");
assert!(
output.status.success(),
"runtime startup must reject invalid pin config: {}",
String::from_utf8_lossy(&output.stderr)
);
}
for invalid in ["{", r#"{"interactive_tools":["invalid"]}"#] {
let output = std::process::Command::new(
std::env::current_exe().expect("current test executable"),
)
.args([
"--exact",
"runtime::tests::profile_pin_environment_rejects_invalid_configuration_at_runtime_startup",
"--test-threads=1",
"--nocapture",
])
.env(CHILD_MARKER, "1")
.env(REBORN_TOOL_DISCLOSURE_PROFILE_PINS_ENV, invalid)
.output()
.expect("child test process executes");
assert!(
output.status.success(),
"runtime startup must reject invalid pin config: {}",
String::from_utf8_lossy(&output.stderr)
);
}
🤖 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_turn_runner/src/runtime.rs` around lines 1157 - 1174,
Update the child test invocation in
profile_pin_environment_rejects_invalid_configuration_at_runtime_startup to pass
--exact and the full module::tests::test_name filter, matching the sibling
tool_disclosure_mode_non_unicode_env_fails_closed pattern. Preserve the existing
invalid-environment arguments and success assertion.

Comment on lines +731 to +735
"ui_probe_success": result.success,
"installed_namespaces": len(packages),
"failure": None
if result.success and scored["completed"]
else "task_incomplete",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not classify every failed run as task_incomplete.

Lines 731-735 assign task_incomplete for both an unsuccessful live run and a completed live run with an incorrect task result. This removes provider and transport failure information from failure_categories and makes aggregate results unable to distinguish model-task failures from runner failures.

Reserve task_incomplete for a successful run that fails task scoring. Record a stable failure category from the live result or caught runner error for unsuccessful runs. Add regression cases for both paths.

🤖 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 `@scripts/tool_discovery_benchmark/run_benchmark.py` around lines 731 - 735,
Update the result-building logic around result.success and scored["completed"]
so task_incomplete is assigned only when the live run succeeds but task scoring
fails. For unsuccessful runs, preserve a stable failure category from the live
result or caught runner error, and ensure that category flows into
failure_categories and aggregate results; add regression coverage for both
unsuccessful-run and successful-but-incorrect-task paths.

@serrrfirat
serrrfirat added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 6f1ae70 Aug 11, 2026
67 checks passed
@serrrfirat
serrrfirat deleted the codex/7405-bounded-search-signatures branch August 11, 2026 08:39
@coderabbitai coderabbitai Bot mentioned this pull request Aug 11, 2026
7 of 21 tasks
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…#7410)

* test(tool-search): add large-catalog baseline

* feat(tool-search): return and use bounded signatures

* feat(tool-search): add fair discovery benchmark arms

* test(tool-discovery): add live benchmark harness

* feat(tool-discovery): default to namespace summaries

* ci(tool-discovery): classify benchmark harness

* feat(tool-discovery): use semantic namespaces

* fix(tool-search): harden discovery benchmark and mode wiring

* docs(tool-search): record corrected benchmark verdict

* fix(tool-search): address review feedback

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-7410 — 4688d2e3 Deployed Aug 10, 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 discovery with complete signatures and namespace-aware catalog previews

2 participants