Skip to content

fix(loop-host): repair unavailable capability calls without aborting runs - #7551

Merged
serrrfirat merged 5 commits into
mainfrom
codex/fix-deferred-capability-unavailable-guard
Aug 13, 2026
Merged

serrrfirat merged 5 commits into
mainfrom
codex/fix-deferred-capability-unavailable-guard

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Remove the prompt-text unavailable-capability guard. User prose is not an authoritative capability surface and must not suppress otherwise valid provider calls.
  • Treat an actual provider call outside the advertised or resolvable capability surface as one-shot repairable model output.
  • Keep repair atomic: no call from the rejected batch is registered or executed; the provider receives model-visible feedback and may retry with an available capability or answer directly.

Change Type

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

Linked Issue

Related #7163

Validation

  • cargo fmt --all -- --check
  • Scoped clippy: cargo clippy -p ironclaw_loop_host --all-targets --all-features -- -D warnings
  • cargo build (covered by the crate test and clippy builds)
  • Relevant tests pass: cargo test -p ironclaw_loop_host
  • Database/runtime integration feature tests (not applicable: no database or runtime backend changed)
  • Manual investigation: correlated the Railway failure with the terminal OutsideCapabilitySurface path and reproduced it deterministically at the provider-to-capability registration seam
  • Two Track C approvals and green CI remain required before merge

Test Strategy

User behavior: naming a capability that is not available does not abort the run. Valid provider calls may continue. If the provider actually calls an unavailable capability, IronClaw rejects the batch before registration, gives the model repair feedback, and accepts a valid retry or direct answer.

Risk areas:

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

Tests added or updated:

  • Unit or contract: the gateway contract suite now verifies that a valid call survives an unrelated unavailable-capability mention and that a mixed valid/unknown provider batch is rejected atomically, repaired, and registers only the valid retry.
  • Reborn integration: Not applicable: the gateway contract test drives the complete provider response through capability-surface validation and registration; no product adapter, persistence layer, or runtime lane participates in this decision.
  • Recorded fixture: Not applicable: provider outputs are scripted deterministically in the gateway contract test.
  • Browser E2E: Not applicable: no WebUI behavior changed.
  • Backend or runtime: Not applicable: no database or runtime backend changed.
  • Live canary: deferred until the new Railway preview for this head is available; the previous preview proved the old prompt-suppression behavior but not the repaired unknown-call path.

What the tests prove:

  • User-message text cannot suppress a valid capability call.
  • An unknown provider call never reaches capability registration or execution.
  • All calls in the invalid batch are rejected together, and the provider gets a bounded repair opportunity.
  • A valid retry is registered normally.

Commands run:

cargo test -p ironclaw_loop_host --test llm_gateway gateway_allows_valid_call_when_user_also_names_unavailable_capability -- --exact
cargo test -p ironclaw_loop_host --test llm_gateway gateway_repairs_unknown_provider_tool_call_before_registration -- --exact
cargo test -p ironclaw_loop_host --test llm_gateway
cargo test -p ironclaw_loop_host
cargo clippy -p ironclaw_loop_host --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
git diff --check

Security Impact

Capability availability remains enforced by the decorated capability port against the provider's actual tool calls. Unknown calls remain fail-closed and are never registered or executed. The change removes heuristic policy decisions based on untrusted natural-language parsing and converts the resulting model-correctable failure into bounded repair feedback.

Reborn Trust-Boundary Checklist

  • No public policy, evidence, or trust-bearing types changed.
  • No prompt-envelope or escaping path changed; repair content uses the existing provider repair-message path and safe summaries.
  • No hashes changed.
  • No status, error, or wire variants changed; the existing InvalidOutputReason::OutsideCapabilitySurface classification is reused.
  • No serialization fields changed.
  • No new queues, maps, buffers, or counters were added.
  • Unknown capability calls remain blocked before registration and dispatch.
  • Runtime-lane and host trust boundaries are unchanged.

Database Impact

None.

Blast Radius

Limited to model-gateway handling of provider tool output. The existing one-shot repair path now also covers outside-surface calls. A rejected batch remains atomic, and a failed repair retains the existing terminal error behavior.

Rollback Plan

Revert commit 93ae624e80 (or the eventual PR merge commit). This restores the prompt-text suppression guard and removes outside-surface repair. No data migration or compatibility action is required.

Review Follow-Through

Track C still requires two approvals and green CI before merge. The separate NEAR AI streaming-header timeout observed in the original Railway logs is outside this fix.


Review track: C (security/runtime guard behavior)

@railway-app

railway-app Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

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

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7551 August 12, 2026 17:10 Destroyed
@github-actions github-actions Bot added size: S 10-49 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 373b3d32-6ff3-45dd-80ff-e20cca31b670

📥 Commits

Reviewing files that changed from the base of the PR and between 318a6e6 and 96eeab7.

📒 Files selected for processing (3)
  • crates/loop/ironclaw_loop_host/src/model_gateway.rs
  • crates/loop/ironclaw_loop_host/tests/llm_gateway.rs
  • tests/integration/tool_call.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of capabilities that are not initially advertised but can be resolved through available providers.
    • Preserved valid capability requests and discovery actions when another requested capability is unavailable.
    • Invalid tool results can now be returned for automatic repair and retry, including guidance to use an available capability or respond directly.
    • Prevented decimal prose from being misinterpreted as a capability request.
    • Unresolved or invalid requests continue to be safely blocked.
  • Tests

    • Expanded coverage for deferred capabilities, discovery workflows, retries, and mixed valid and unavailable requests.

Walkthrough

The model gateway no longer suppresses provider calls because a named capability is unavailable. It validates calls against advertised or resolvable capabilities and treats outside-surface tool results as repairable. Tests cover discovery ordering, valid calls, decimal prose, and repair retries.

Changes

Provider tool validation and repair

Layer / File(s) Summary
Gateway validation and repair
crates/loop/ironclaw_loop_host/src/model_gateway.rs
The gateway removes unavailable-capability guard handling. It marks outside-capability-surface errors as repairable and updates retry guidance.
Capability and repair regression coverage
crates/loop/ironclaw_loop_host/tests/llm_gateway.rs, tests/integration/tool_call.rs
Tests cover filtered discovery, prerequisite and discovery ordering, valid calls beside unavailable capabilities, decimal prose, batch rejection feedback, and recovery after an invalid tool result.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 96eea

The change repairs unavailable capability calls while preserving atomic rejection of invalid batches; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant ModelGateway
  participant CapabilityPort
  Model->>ModelGateway: submit provider tool call
  ModelGateway->>CapabilityPort: validate advertised or resolvable capability
  CapabilityPort-->>ModelGateway: return validation result
  ModelGateway-->>Model: return repairable tool-output error
  Model->>ModelGateway: submit recovered capability call
  ModelGateway-->>Model: register recovered call
Loading

Possibly related PRs

  • nearai/ironclaw#7233: Both modify capability-surface enforcement and recovery for provider tool calls.
  • nearai/ironclaw#7274: Both modify deferred capability loading and provider tool validation in model_gateway.rs.
  • nearai/ironclaw#7410: Both modify provider tool-call validation and deferred capability handling.

Suggested reviewers: benkurrek

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title uses Conventional Commits style and accurately describes the repair of unavailable capability calls.
Description check ✅ Passed The description covers the template sections, change scope, security impact, testing, rollback, and review requirements.

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.

@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/model_gateway.rs`:
- Around line 1427-1430: Complete the Track C review record for the
capability-admission change around unavailable_requested_capability_guard and
requested_capability_is_resolvable: document the rollback plan, confirm two
required approvals, and confirm green CI before merge.
🪄 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: 0fb070d4-cfb4-43ac-b224-880e690279cb

📥 Commits

Reviewing files that changed from the base of the PR and between 173f078 and 04bb234.

📒 Files selected for processing (2)
  • crates/loop/ironclaw_loop_host/src/model_gateway.rs
  • crates/loop/ironclaw_loop_host/tests/llm_gateway.rs

Comment thread crates/loop/ironclaw_loop_host/src/model_gateway.rs Outdated
@ironloopai

ironloopai Bot commented Aug 12, 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 5m 46s

IronLoop completed the review and posted it to GitHub.

🔗 Result

Open submitted review →

Run details

Run: 5fdfec82-d82b-405a-829e-dca11b6bfeb7
Base: main at 173f078
Head: codex/fix-deferred-capability-unavailable-guard at 04bb234
Created: 2026-08-12 17:15 UTC
Updated: 2026-08-12 17:21 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

One medium-severity regression found in mixed deferred/unknown capability requests.

Findings: 🟠 Medium 1

🟠 Medium · Retain suppression for later unresolved named capabilities

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

Validation

  • ✅ Diff integrity — No whitespace errors found in the reviewed change.
  • ⚪ Focused regression test — Not run. The run-local Rust build did not complete during the review.
Review details
  • Run: 5fdfec82-d82b-405a-829e-dca11b6bfeb7
  • Workflow: Review
  • Attempts: 1

Comment thread crates/loop/ironclaw_loop_host/src/model_gateway.rs Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7551 August 12, 2026 17:38 Destroyed
@github-actions github-actions Bot added size: M 50-199 changed lines and removed size: S 10-49 changed lines labels Aug 12, 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.

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/model_gateway.rs (1)

2005-2033: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add caller coverage for malformed capability probes.

The failed port-resolution path is covered. Add a stream_model_with_capabilities test for a valid CapabilityId whose encoded provider name exceeds ProviderToolName::MAX_BYTES. Assert that the probe stays unresolved and no substitute call is registered. This preserves the “Everything Goes Through Tools” and “Test through the caller” invariants.

🤖 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/model_gateway.rs` around lines 2005 -
2033, Add caller-level coverage in stream_model_with_capabilities for a valid
CapabilityId whose encoded provider name exceeds ProviderToolName::MAX_BYTES.
Assert the malformed availability probe remains unresolved and verify that no
substitute call is registered, preserving the existing “Everything Goes Through
Tools” behavior through the caller rather than testing
requested_capability_is_resolvable directly.

Source: Path instructions

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

Outside diff comments:
In `@crates/loop/ironclaw_loop_host/src/model_gateway.rs`:
- Around line 2005-2033: Add caller-level coverage in
stream_model_with_capabilities for a valid CapabilityId whose encoded provider
name exceeds ProviderToolName::MAX_BYTES. Assert the malformed availability
probe remains unresolved and verify that no substitute call is registered,
preserving the existing “Everything Goes Through Tools” behavior through the
caller rather than testing requested_capability_is_resolvable directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6f864fd8-6815-4a1c-86fa-c507aadce586

📥 Commits

Reviewing files that changed from the base of the PR and between 04bb234 and d1d7d5f.

📒 Files selected for processing (2)
  • crates/loop/ironclaw_loop_host/src/model_gateway.rs
  • crates/loop/ironclaw_loop_host/tests/llm_gateway.rs

@serrrfirat

serrrfirat commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — BLOCKED

Given / When / Then matrix

Acceptance Intended contract Actual contract exercised Evidence Status
Required Given a resolvable named capability followed by an unavailable named capability, when the model attempts to substitute another tool for the unavailable capability, then the host must suppress that substitute. Configured builtin.extension_search plus nonexistent builtin.disabled on /chat, using the intended provider/model. The model executed the explicitly requested valid time and extension_search operations, but did not attempt a substitute for builtin.disabled. The rendered response clearly stated that builtin.disabled did not exist and that no stand-in would be called. Because no substitute call was emitted, the narrow host-guard regression was not exercised. BLOCKED
Supplemental A configured deferred capability can be discovered and invoked without false unavailable suppression. Configured builtin.extension_search, preceded by builtin.time and tool_search. Host-rendered activity recorded successful time, tool_search, and extension_search; the final rendered result contained the GitHub extension match. PASS
Supplemental Exact PR head is deployed and the authenticated UI is healthy. Exact-head Railway status and /chat UI. Railway reported success; login, chat navigation, Settings, and rendered tool activity loaded successfully. PASS

Status derivation

  • Required passed: 0
  • Required failed: 0
  • Required blocked/not executed: 1 (the model did not emit a substitute call for the unavailable capability)
  • Overall: BLOCKED, because the exact substitute-suppression contract was not executed.

Regression result and diagnostics

This QA run is scoped only to PR #7551 and the capabilities configured in its own preview. The earlier builtin.document_edit sequence from #7163 is intentionally excluded from the acceptance matrix because this separate preview does not provide that capability.

The live behavior was user-friendly: the explicitly requested valid operations completed, and the assistant clearly reported that builtin.disabled was unavailable without calling a stand-in. This does not contradict the narrower safety goal of preventing substitution, because the model did not attempt a substitute. It therefore cannot establish a browser PASS or FAIL for the host-side substitute guard.

No persistence or refresh case was relevant. No external extension setup was performed; extension_search was read-only.

Cleanup

Complete. Removed all three railway-test-7551* conversations and verified that no matching test conversations remained. No files, extensions, credentials, or settings were created or changed.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7551 August 12, 2026 21:26 Destroyed
@github-actions github-actions Bot added size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Aug 12, 2026
@serrrfirat serrrfirat changed the title fix(loop-host): resolve deferred capabilities before unavailable guard fix(loop-host): repair unavailable capability calls without aborting runs Aug 12, 2026
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Corrected the runtime contract in 93ae624 based on the clarified expected behavior. The prompt-text suppression guard has been removed: mentioning an unavailable capability no longer suppresses valid calls. An actual provider call outside the capability surface remains fail-closed before registration, but now enters the existing one-shot repair loop so the model can retry with an available capability or answer directly. This supersedes the earlier mixed-request suppression expectation; the replacement caller-path regressions pass along with the full ironclaw_loop_host suite and scoped all-feature clippy.

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — BLOCKED

Prompts

Required natural-language journey:

What time is it in UTC right now? Also tell me whether you could email that time from this workspace, but do not send anything. If email isn’t available here, still give me the time and explain what’s unavailable.

Supplemental natural-language repeat:

Please double-check the current UTC time and whether any email integration is available now. Do not send anything; if email is still unavailable, give me the time anyway and explain that limitation.

Given / When / Then matrix

Acceptance Given When Then Actual contract exercised Result
Required The preview has a valid time capability but no configured email delivery target The user asks for UTC time and asks whether email is available, explicitly prohibiting any send The run must still return the time and explain the unavailable email function instead of aborting Rendered activity showed successful time and builtin__outbound_delivery_targets_list calls. The assistant returned 2026-08-12T21:48:54Z, reported no delivery targets/email integration, and confirmed nothing was sent PASS
Required The provider emits an actual call outside the advertised/resolvable capability surface The host rejects the batch and supplies repair feedback No rejected call is registered or executed, and the model retries or answers directly Not executed. The live model selected only valid available/discovery tools, so the new OutsideCapabilitySurface repair branch did not run BLOCKED
Supplemental The same thread remains usable after the first unavailable-email response The user asks for a safe double-check The agent again returns time and an actionable limitation without sending anything Rendered activity showed successful time, builtin__outbound_delivery_targets_list, and extension_search; the assistant returned 2026-08-12T21:50:40Z, reported zero email integrations, and confirmed nothing was sent PASS

Status derivation

  • Required passed: 1
  • Required failed: 0
  • Required blocked/not executed: 1
  • Overall: BLOCKED. The natural user journey passed, but the exact invalid provider-call repair branch could not be forced through a natural-language browser prompt without contaminating the test with internal capability names.

Regression result

The preview did not reproduce the original run-aborting behavior. A request combining a valid operation with an unavailable email function completed normally, returned the valid result, and gave a clear user-facing explanation. However, all observed provider calls were valid, so this live run cannot prove that an actual unknown provider call receives the new one-shot repair feedback. The deterministic gateway regression tests remain the evidence for that internal seam.

Skipped and remaining risk

  • No implementation-directed diagnostic prompt was sent because the user requested product QA, not a tool-name/capability-ID probe; such a probe could not replace Required natural-language evidence.
  • The scoped live logs view exposed no historical entries for this thread, so provider/model metadata and the internal repair classification were not independently visible there.
  • No email, external message, extension installation, authentication, or other side effect was attempted.

Cleanup

  • The test conversation created by this run was deleted and the empty conversation list was verified.
  • Browser tabs were finalized after evidence capture.

@serrrfirat
serrrfirat added this pull request to the merge queue Aug 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 13, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7551 August 13, 2026 11:33 Destroyed
@coderabbitai

coderabbitai Bot commented Aug 13, 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 added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 5308b83 Aug 13, 2026
45 checks passed
@serrrfirat
serrrfirat deleted the codex/fix-deferred-capability-unavailable-guard branch August 13, 2026 12:12
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…runs (nearai#7551)

* fix(loop-host): resolve deferred capabilities before guard

* fix(loop-host): retain mixed-request suppression (nearai#7551)

* fix(loop-host): repair unavailable capability calls

* test(loop-host): assert gateway repair feedback

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-7551 — 96eeab79 Deployed Aug 13, 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 size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants