Skip to content

feat(reborn): /v1/models, model validation, external-tool gate foundation - #5094

Merged
ilblackdragon merged 10 commits into
mainfrom
feat/reborn-openai-compat-models-and-external-tools
Jun 26, 2026
Merged

ilblackdragon merged 10 commits into
mainfrom
feat/reborn-openai-compat-models-and-external-tools

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Summary

OpenAI-compatible Reborn surface work in three parts. No production behavior change — the external-tool catalog is not yet fed tool specs (Responses-side registration + executor resume re-dispatch are follow-up stages), so the new decorator is a safe no-op today.

1. /v1/models (capability parity with pre-reborn)

  • models DTO + OpenAiCompatModelCatalog host port + GET /v1/models and /api/v1/models descriptors / handler / router state. Fails closed (401 without auth, 501 without a wired catalog) — same pattern as chat/responses.
  • Composition catalog backed by the operator LlmConfigService snapshot (deduped active + provider models), gated on root-llm-provider; CLI already mounts it. Extracted a shared build_llm_config_service helper so WebUI and openai-compat read one source.
  • Tests: descriptor lock (7→9), stub fail-closed cases, dedicated models_handlers_contract, list-envelope + snapshot-mapping unit tests.

2. Model-name validation

3. External-tool gate foundation (staged epic)

Client-supplied tools on the Responses API, where the agent loop pauses and hands control back to the API client.

  • turns: BlockedExternalTool status family (status / reason / gate-kind / precondition / pending-gate projection) + wire-stable round-trip tests.
  • agent_loop executor pause: CapabilityOutcome::ExternalToolPending + GateKind/LoopGateKind::ExternalTool + LoopBlockedKind::ExternalTool, mapped through GateStage to a parked BlockedExternalTool turn.
  • ExternalToolCatalog (turns): in-memory, run-scoped catalog of caller tool specs + input_ref↔call_id binding + submitted outputs; 8 tests.
  • ExternalToolCapabilityPort decorator (composition), wired into the per-run capability chain via a factory-owned shared catalog: offers caller tools to the model, rejects names shadowing host capabilities, parks via ExternalToolPending, and completes from the catalog on resume.

Remaining (follow-up stages, this branch)

  • 2d executor resume re-dispatch (pending_external_tool_resume slot, mirroring pending_auth_resume).
  • Phase 3 product ExternalToolInteractionService (submit output → catalog → resume_turn).
  • Phase 4 responses workflow: tools → register into catalog (via a new openai_compat port), parked call → function_call item, function_call_output + previous_response_id → resume; streaming + non-streaming; expose the catalog singleton to build_openai_compat_route_mount.
  • Phase 5 port tests/e2e_responses_api_external_tools.rs.

Validation

  • Pre-push hook ran the full suite: 5128 passed, 0 failed.
  • cargo fmt clean; composition lib+tests+clippy clean; turns / agent_loop / reborn_openai_compat suites pass; workspace compiles green.

…tion

OpenAI-compatible Reborn surface work, in three parts:

1. /v1/models endpoint (#capability parity with pre-reborn):
   - models DTO + OpenAiCompatModelCatalog host port + GET /v1/models
     and /api/v1/models descriptors/handler/router state (fails closed
     401/501 until wired); composition catalog backed by the operator
     LlmConfigService snapshot; shared build_llm_config_service helper.
   - Contract tests (descriptor lock 7->9, stub, models_handlers_contract)
     + unit tests for the list envelope and snapshot mapping.

2. model-name validation on chat + responses request parsing
   (non-empty, no surrounding whitespace, no control chars, <=256 bytes
   per the #2673 bounded-resources rule); unit + caller-level tests.

3. External-tool gate foundation (client-supplied tools on the Responses
   API, staged epic):
   - turns: BlockedExternalTool status family (status/reason/gate-kind/
     precondition/pending-gate projection) + wire-stable round-trip tests.
   - agent_loop executor pause: CapabilityOutcome::ExternalToolPending +
     GateKind/LoopGateKind::ExternalTool + LoopBlockedKind::ExternalTool,
     mapped through GateStage to a parked BlockedExternalTool turn.
   - ExternalToolCatalog (turns): in-memory run-scoped catalog of caller
     tool specs + input_ref<->call_id binding + submitted outputs; tests.
   - ExternalToolCapabilityPort decorator (composition) wired into the
     per-run capability chain via a factory-owned shared catalog: offers
     caller tools to the model, rejects names shadowing host capabilities,
     parks via ExternalToolPending, completes from the catalog on resume.

No production behavior change: the external-tool catalog is not yet fed
tool specs (Responses-side registration + executor resume re-dispatch are
follow-up stages), so the decorator is a safe no-op today.

Tests: turns, agent_loop, and reborn_openai_compat suites pass; composition
lib+tests+clippy clean; workspace compiles green.
Copilot AI review requested due to automatic review settings June 19, 2026 18:25

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@railway-app

railway-app Bot commented Jun 19, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jun 26, 2026 at 5:45 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 19, 2026 18:25 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 19, 2026
@coderabbitai

coderabbitai Bot commented Jun 19, 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: e93ce17c-7659-4807-8395-7d8df5517b65

📥 Commits

Reviewing files that changed from the base of the PR and between d8eb46d and 3c09204.

📒 Files selected for processing (1)
  • tests/reborn_qa_smoke_scenarios_e2e.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added support for external tool gates: runs can pause for client-provided tool output and resume automatically.
    • Added OpenAI-compatible model listing via authenticated GET /v1/models and /api/v1/models.
  • Bug Fixes
    • Improved external-tool pause/resume bookkeeping to prevent re-dispatch loops and clear stale resume data safely.
    • Added stricter model validation for OpenAI-compat chat and responses requests (clear 400 invalid_request for bad inputs).
  • Documentation
    • Documented OpenAI-compatible models listing behavior and request validation rules.

Walkthrough

Adds external-tool gating and resume plumbing across the loop executor, OpenAI-compatible /v1/models routing with shared model validation and catalog wiring, and explicit thread stack sizing for the serve command and one QA smoke test.

Changes

External Tool Gate

Layer / File(s) Summary
Core external-tool types
crates/ironclaw_turns/src/{status.rs, loop_exit.rs, request.rs, run_profile/host.rs, strategies/gate.rs, events.rs}, crates/ironclaw_turns/src/loop_exit/tests/mod.rs, crates/ironclaw_turns/tests/turn_coordinator_contract.rs
TurnStatus, BlockedReason, LoopBlockedKind, ResumeTurnPrecondition, CapabilityOutcome, LoopGateKind, GateKind, and TurnBlockedGateKind gain external-tool variants and matching validation or serde tests.
External tool catalog
crates/ironclaw_turns/src/external_tool_catalog.rs, crates/ironclaw_turns/src/lib.rs
The run-scoped external-tool catalog trait, in-memory implementation, spec validation, output consumption, and public re-exports are added.
Executor gate flow and resume
crates/ironclaw_agent_loop/src/{state.rs, executor/{capability_helpers.rs, gates.rs, capabilities.rs, canonical.rs, prompt.rs, mapping.rs, tests.rs}}, crates/ironclaw_reborn/src/planned_driver.rs
Parked external-tool resumes are stored, cleared, reconstructed, routed through gate handling, and covered by executor-level regression tests.
Local-dev wiring and decorator
crates/ironclaw_reborn_composition/src/{factory.rs, runtime/local_dev.rs, runtime/local_dev/{refreshing_capability_port.rs, external_tool_capability.rs, shell_tests.rs, tests.rs}}
A shared external-tool catalog is threaded through local-dev runtime services, and the local-dev capability port is wrapped with the external-tool decorator and updated tests.
Blocked status propagation
crates/ironclaw_event_projections/src/pending_gate_projection.rs, crates/ironclaw_reborn_composition/src/{projection/turn_events.rs, runtime.rs}, crates/ironclaw_loop_support/src/turn_event_publisher.rs, crates/ironclaw_turns/src/{lifecycle.rs, events.rs}, crates/ironclaw_reborn/src/subagent/completion_observer.rs, crates/ironclaw_turns/tests/turn_coordinator_contract.rs
The blocked external-tool status is propagated into projections, turn-event publishing, runtime waiting, completion labels, and recording tests.

OpenAI-compat GET /v1/models Endpoint

Layer / File(s) Summary
Model types and validation
crates/ironclaw_reborn_openai_compat/src/{model_validation.rs, models.rs, models_catalog.rs, chat_workflow.rs, responses_workflow.rs}, crates/ironclaw_reborn_openai_compat/tests/{chat_workflow_handlers_contract.rs, responses_workflow_handlers_contract.rs}
OpenAI-compatible model DTOs, catalog entry helpers, shared model-name validation, and parse-time validation for chat and responses requests are added.
Routes and handler wiring
crates/ironclaw_reborn_openai_compat/src/{descriptors.rs, router.rs, handlers.rs, lib.rs}, crates/ironclaw_reborn_openai_compat/CLAUDE.md, crates/ironclaw_reborn_openai_compat/tests/{descriptors_contract.rs, stub_handlers_contract.rs, models_handlers_contract.rs}
Models-list routes, router state, the authenticated handler, public re-exports, docs, and endpoint contract coverage are added.
LLM-config catalog wiring
crates/ironclaw_reborn_composition/src/{webui.rs, openai_compat_serve.rs, openai_compat_serve/tests.rs}
The WebUI LLM-config service helper, router-mount catalog wiring, snapshot-to-model mapping, and snapshot-mapping tests are added.

Worker Stack Fix

Layer / File(s) Summary
Runtime stack sizing
crates/ironclaw_reborn_cli/src/commands/serve.rs, tests/reborn_qa_smoke_scenarios_e2e.rs
The serve Tokio multi-thread runtime builder and one async QA smoke path set explicit thread stack sizes.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • nearai/ironclaw#4954: Extends the same denied-resume stamping and pending-resume bookkeeping flow now used for pending_external_tool_resume.
  • nearai/ironclaw#4899: Touches executor resume bookkeeping in the same codepath as the new external-tool resume candidate handling.

Suggested reviewers

  • henrypark133
  • serrrfirat

Poem

A gate said “wait,” then learned to mend,
A model list began to blend.
Tools came back with stacks held high,
And snake_case labels winked goodbye ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The summary is detailed, but most required template sections are missing, including Change Type, Linked Issue, Security Impact, and the trust-boundary checklist. Add the missing template sections and fill in Change Type, Linked Issue, Security Impact, DB Impact, Blast Radius, Rollback Plan, Review Follow-Through, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional-commits style and accurately summarizes the main additions: models, validation, and external-tool gate work.
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.

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for client-supplied ("external") tools in the agent loop, allowing runs to park as BlockedExternalTool when an external tool is called and resume once the client submits the output. It adds an ExternalToolCatalog to manage these transient tool definitions and outputs, integrates them into the capability port wiring, and implements a GET /v1/models endpoint to list configured models. Additionally, validation is added for the client-supplied model field to enforce a 256-byte limit and reject malformed names. A high-severity issue was identified in the external tool capability port where the conflict check for duplicate sanitized capability IDs is broken, which could allow different tool names that sanitize to the same ID to silently overwrite each other.

Important

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

Comment on lines +344 to +356
if surface
.descriptors
.iter()
.any(|descriptor| descriptor.capability_id == capability_id)
|| capability_ids_by_tool_name
.insert(spec.name().to_string(), capability_id.clone())
.is_some()
{
return Err(AgentLoopHostError::new(
AgentLoopHostErrorKind::InvalidInvocation,
"external tool conflicts with another capability id",
));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The conflict check for duplicate capability IDs is currently broken. It checks if capability_ids_by_tool_name.insert(spec.name().to_string(), ...) returns Some, but since spec.name() is unique (enforced by the catalog registration), this will always return None. As a result, different tool names that sanitize to the same capability_id (e.g., tool.a and tool_a both sanitizing to external_tool.tool_a) will silently overwrite each other in specs_by_capability_id without triggering a conflict error.

Instead, we should check if the sanitized capability_id already exists in specs_by_capability_id before inserting.

            if surface
                .descriptors
                .iter()
                .any(|descriptor| descriptor.capability_id == capability_id)
                || specs_by_capability_id.contains_key(&capability_id)
            {
                return Err(AgentLoopHostError::new(
                    AgentLoopHostErrorKind::InvalidInvocation,
                    "external tool conflicts with another capability id",
                ));
            }
            capability_ids_by_tool_name.insert(spec.name().to_string(), capability_id.clone());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit d16d19a. Changed to check specs_by_capability_id.contains_key(&capability_id) instead of the broken capability_ids_by_tool_name.insert().is_some() which always returned None since tool names are unique by catalog registration.

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

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_agent_loop/src/strategies/gate.rs (1)

62-68: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

External-tool gates are currently skippable, which breaks park-and-resume semantics

GateOutcome::SkipAndContinue should be invalid for GateKind::ExternalTool; otherwise the loop can proceed without client-submitted tool output.

Suggested fix
 pub(crate) fn validate_for_gate_kind(&self, kind: GateKind) -> Result<(), LoopFailureKind> {
     match (kind, self) {
         (GateKind::Approval, GateOutcome::SkipAndContinue { .. })
-        | (GateKind::AwaitDependentRun, GateOutcome::SkipAndContinue { .. }) => {
+        | (GateKind::AwaitDependentRun, GateOutcome::SkipAndContinue { .. })
+        | (GateKind::ExternalTool, GateOutcome::SkipAndContinue { .. }) => {
             Err(LoopFailureKind::DriverBug)
         }
         _ => Ok(()),
     }
 }
     for (variant, wire) in [
         (GateKind::Approval, "approval"),
         (GateKind::Auth, "auth"),
         (GateKind::Resource, "resource"),
         (GateKind::AwaitDependentRun, "await_dependent_run"),
+        (GateKind::ExternalTool, "external_tool"),
     ] {

Also applies to: 98-105

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

In `@crates/ironclaw_agent_loop/src/strategies/gate.rs` around lines 62 - 68, The
GateKind::ExternalTool variant should not allow GateOutcome::SkipAndContinue as
a valid outcome, since skipping external tool gates breaks park-and-resume
semantics and allows the loop to proceed without client-submitted tool output.
In the validation logic around lines 98-105 (likely a match statement handling
different gate kinds), add a check that explicitly rejects or prevents
SkipAndContinue outcomes for ExternalTool gates, ensuring that external tool
gates require explicit completion or handling before the loop can continue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_agent_loop/src/executor/capabilities.rs`:
- Around line 655-673: In the `ExternalToolPending` branch, verify that
`GateStage::process` properly clears the `state.pending_approval_resume` and
`state.pending_auth_resume` fields when handling `GateKind::ExternalTool`. If
`GateStage::process` does not normalize this resume state for external tool
gates, explicitly clear both `state.pending_approval_resume` and
`state.pending_auth_resume` before calling `GateStage.process()` in the
`ExternalToolPending` match arm to ensure stale approval/auth resume state is
not leaked.

In `@crates/ironclaw_reborn_composition/src/runtime.rs`:
- Around line 1821-1825: The code at lines 1821-1825 adds BlockedExternalTool to
the gate conditions that cause early return, but the docstring and nearby
comments for the containing function still describe only auth/approval/resource
gates. Update the function's docstring and any adjacent comments that document
gate-wait behavior to include BlockedExternalTool as one of the gate types that
causes short-circuiting and early return of the parked state, ensuring the
documentation accurately reflects the new behavior added to the TurnStatus
pattern match.

In `@crates/ironclaw_reborn_openai_compat/src/responses_workflow.rs`:
- Around line 1004-1006: The serde_json::from_slice call in the
OpenAiResponsesCreateRequest parsing is discarding the actual deserialization
error by using a wildcard pattern in map_err. Instead of ignoring the error with
|_|, capture it by binding it to a variable, log the error context using debug!
to preserve the cause for debugging, and then return the sanitized
invalid_request error. This preserves error context on the boundary parse path
while still returning a clean error response to the client.

In `@crates/ironclaw_reborn_openai_compat/tests/models_handlers_contract.rs`:
- Around line 66-97: The fail-closed parity checks in
models_endpoint_without_caller_returns_401_before_catalog and
models_endpoint_without_catalog_fails_closed_501 tests only assert behavior for
the /v1/models endpoint, but not the /api/v1/models alias endpoint. Add
equivalent assertions in both tests to verify the same 401 and 501 responses
respectively for the /api/v1/models path by making additional oneshot requests
to get_request("/api/v1/models") and asserting the same status codes and
response bodies to ensure the two endpoint paths maintain consistent fail-closed
behavior.

In
`@crates/ironclaw_reborn_openai_compat/tests/responses_workflow_handlers_contract.rs`:
- Around line 793-835: The test function
responses_rejects_invalid_model_before_product_workflow currently only validates
invalid model rejection for the /api/v1/responses endpoint. Add equivalent test
coverage for the /v1/responses endpoint to ensure both create routes enforce the
same validation contract. You can either iterate through both endpoint paths
within the test or add a duplicate test for the alternate endpoint path,
ensuring both routes properly reject oversized models, control characters, and
surrounding whitespace with a 400 BAD_REQUEST status and the expected error
structure before reaching the product workflow.

---

Outside diff comments:
In `@crates/ironclaw_agent_loop/src/strategies/gate.rs`:
- Around line 62-68: The GateKind::ExternalTool variant should not allow
GateOutcome::SkipAndContinue as a valid outcome, since skipping external tool
gates breaks park-and-resume semantics and allows the loop to proceed without
client-submitted tool output. In the validation logic around lines 98-105
(likely a match statement handling different gate kinds), add a check that
explicitly rejects or prevents SkipAndContinue outcomes for ExternalTool gates,
ensuring that external tool gates require explicit completion or handling before
the loop can continue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 05da5d1f-3521-4805-b459-1f9ca7c8488f

📥 Commits

Reviewing files that changed from the base of the PR and between c061baa and 37429fc.

📒 Files selected for processing (43)
  • crates/ironclaw_agent_loop/src/executor/capabilities.rs
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/mapping.rs
  • crates/ironclaw_agent_loop/src/strategies/gate.rs
  • crates/ironclaw_event_projections/src/pending_gate_projection.rs
  • crates/ironclaw_loop_support/src/turn_event_publisher.rs
  • crates/ironclaw_reborn/src/subagent/completion_observer.rs
  • crates/ironclaw_reborn_composition/src/openai_compat_serve.rs
  • crates/ironclaw_reborn_composition/src/openai_compat_serve/tests.rs
  • crates/ironclaw_reborn_composition/src/projection/turn_events.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/external_tool_capability.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/refreshing_capability_port.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/shell_tests.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/tests.rs
  • crates/ironclaw_reborn_composition/src/trigger_poller.rs
  • crates/ironclaw_reborn_composition/src/webui.rs
  • crates/ironclaw_reborn_openai_compat/CLAUDE.md
  • crates/ironclaw_reborn_openai_compat/src/chat_workflow.rs
  • crates/ironclaw_reborn_openai_compat/src/descriptors.rs
  • crates/ironclaw_reborn_openai_compat/src/handlers.rs
  • crates/ironclaw_reborn_openai_compat/src/lib.rs
  • crates/ironclaw_reborn_openai_compat/src/model_validation.rs
  • crates/ironclaw_reborn_openai_compat/src/models.rs
  • crates/ironclaw_reborn_openai_compat/src/models_catalog.rs
  • crates/ironclaw_reborn_openai_compat/src/responses_workflow.rs
  • crates/ironclaw_reborn_openai_compat/src/router.rs
  • crates/ironclaw_reborn_openai_compat/tests/chat_workflow_handlers_contract.rs
  • crates/ironclaw_reborn_openai_compat/tests/descriptors_contract.rs
  • crates/ironclaw_reborn_openai_compat/tests/models_handlers_contract.rs
  • crates/ironclaw_reborn_openai_compat/tests/responses_workflow_handlers_contract.rs
  • crates/ironclaw_reborn_openai_compat/tests/stub_handlers_contract.rs
  • crates/ironclaw_turns/src/events.rs
  • crates/ironclaw_turns/src/external_tool_catalog.rs
  • crates/ironclaw_turns/src/lib.rs
  • crates/ironclaw_turns/src/lifecycle.rs
  • crates/ironclaw_turns/src/loop_exit.rs
  • crates/ironclaw_turns/src/loop_exit/tests/mod.rs
  • crates/ironclaw_turns/src/request.rs
  • crates/ironclaw_turns/src/run_profile/host.rs
  • crates/ironclaw_turns/src/status.rs
  • crates/ironclaw_turns/tests/turn_coordinator_contract.rs

Comment thread crates/ironclaw_agent_loop/src/executor/capabilities.rs
Comment thread crates/ironclaw_reborn_composition/src/runtime.rs
Comment thread crates/ironclaw_reborn_openai_compat/src/responses_workflow.rs Outdated
Completes the engine half of the client-tool round-trip: a parked
BlockedExternalTool run now re-dispatches the client tool on resume and
completes from the run-scoped catalog output, without a model turn.

- agent_loop state: new pending_external_tool_resume slot + struct.
- GateStage populates the slot at block time for GateKind::ExternalTool.
- prompt/canonical: PromptStep::ResumeExternalTool re-dispatches the parked
  call (re-registering the provider tool call so the host decorator re-binds
  input_ref->call_id and restages arguments).
- capability_helpers: pending_external_tool_resume_candidate +
  clear_matching_pending_external_tool_resume (cleared at every outcome site,
  mirroring the auth/approval slots).
- capabilities: denied-guard so a cancelled external-tool gate surfaces a
  model-visible failure instead of re-parking forever.
- planned_driver: stamp_resume_disposition extended to the external-tool slot.

Tests: two executor integration tests (block stores the slot; resume
re-dispatches without a model turn and completes); 323 agent_loop tests pass;
workspace green; clippy clean.
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 19, 2026 19:09 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

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

Inline comments:
In `@crates/ironclaw_agent_loop/src/executor/capability_helpers.rs`:
- Around line 504-538: The validation in the
pending_external_tool_resume_candidate function only checks that
candidate.capability_id matches resume.capability_id, but is missing the
effective_capability_ids consistency check that exists in the similar
pending_auth_resume_candidate function. Update the if statement that validates
the candidate to also check that candidate.effective_capability_ids equals
resume.effective_capability_ids by adding an OR condition, matching the dual
validation pattern from pending_auth_resume_candidate to ensure consistency and
prevent drift.

In `@crates/ironclaw_reborn/src/planned_driver.rs`:
- Around line 270-299: The stamp_resume_disposition function now handles the
external_tool_matches case for stamping pending_external_tool_resume, but this
path lacks corresponding unit test coverage. Add a new unit test (following the
naming pattern of existing tests like
stamp_resume_disposition_stamps_auth_slot_when_last_gate_matches) that verifies
the external tool resume slot gets stamped correctly when
pending_external_tool_resume exists with a matching gate_ref. This test should
parallel the existing auth-only and approval-only test cases to ensure
consistent coverage of all three resume paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae913f87-da19-4b09-b5a8-7d9a93fbe2ec

📥 Commits

Reviewing files that changed from the base of the PR and between 37429fc and 442d304.

📒 Files selected for processing (9)
  • crates/ironclaw_agent_loop/src/executor.rs
  • crates/ironclaw_agent_loop/src/executor/canonical.rs
  • crates/ironclaw_agent_loop/src/executor/capabilities.rs
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/gates.rs
  • crates/ironclaw_agent_loop/src/executor/prompt.rs
  • crates/ironclaw_agent_loop/src/executor/tests.rs
  • crates/ironclaw_agent_loop/src/state.rs
  • crates/ironclaw_reborn/src/planned_driver.rs

Comment thread crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
Comment thread crates/ironclaw_reborn/src/planned_driver.rs
…se 4a)

Move the Arc<dyn ExternalToolCatalog> onto RebornLocalRuntimeServices so the
loop capability host (which offers caller tools and parks calls) and the
OpenAI-compatible Responses surface (which will register tool specs and submit
client outputs) share one run-scoped instance. The loop factory now reads the
shared catalog instead of constructing its own. No behavior change yet — the
catalog is still not fed specs until the Responses-side wiring lands.
Copilot AI review requested due to automatic review settings June 19, 2026 19:35
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 19, 2026 19:35 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…mpat-models-and-external-tools

Resolve semantic conflict: main added a new project_create test that
constructs LocalDevLoopCapabilityPortFactory, which this branch extended
with a required external_tool_catalog field. Add the field to the new
initializer (crates/ironclaw_reborn_composition/src/runtime/local_dev/tests.rs)
to fix the E0063 compile error in the merged tree.
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 22, 2026 05:02 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 (1)
crates/ironclaw_reborn_composition/src/trigger_poller.rs (1)

324-324: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add regression coverage for the new BlockedExternalTool terminal mapping.

Line 324 adds a new terminal-status branch, but the table test at Line 475 does not assert TurnStatus::BlockedExternalTool -> TriggerRunHistoryStatus::Error. Please add that case so this mapping cannot regress silently.

Suggested test delta
     fn terminal_turn_statuses_map_to_run_history_statuses() {
         let cases = [
             (TurnStatus::Completed, TriggerRunHistoryStatus::Ok),
             (TurnStatus::Cancelled, TriggerRunHistoryStatus::Error),
             (TurnStatus::Failed, TriggerRunHistoryStatus::Error),
             (TurnStatus::RecoveryRequired, TriggerRunHistoryStatus::Error),
+            (TurnStatus::BlockedExternalTool, TriggerRunHistoryStatus::Error),
         ];

As per coding guidelines, “Every bug fix must include a regression test (#[test] or #[tokio::test]).”

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

In `@crates/ironclaw_reborn_composition/src/trigger_poller.rs` at line 324, The
new TurnStatus::BlockedExternalTool terminal status mapping added at line 324
lacks a corresponding regression test case. Locate the table test around line
475 that tests the terminal-status-to-TriggerRunHistoryStatus mappings and add a
test case that asserts TurnStatus::BlockedExternalTool maps to
TriggerRunHistoryStatus::Error to prevent this mapping from silently regressing
in the future.

Source: Coding guidelines

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

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/trigger_poller.rs`:
- Line 324: The new TurnStatus::BlockedExternalTool terminal status mapping
added at line 324 lacks a corresponding regression test case. Locate the table
test around line 475 that tests the terminal-status-to-TriggerRunHistoryStatus
mappings and add a test case that asserts TurnStatus::BlockedExternalTool maps
to TriggerRunHistoryStatus::Error to prevent this mapping from silently
regressing in the future.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eb5125d7-b077-4669-8298-a9a705b4bc1f

📥 Commits

Reviewing files that changed from the base of the PR and between c540f59 and e02d9ec.

📒 Files selected for processing (7)
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/refreshing_capability_port.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/shell_tests.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/tests.rs
  • crates/ironclaw_reborn_composition/src/trigger_poller.rs

…tack overflow

The reborn QA smoke scenario `qa_error_process_repo_patch_and_cleanup_smokes`
aborted with `fatal runtime error: stack overflow`. A single capability-dispatch
poll of the agent loop (turn runner -> planned driver -> canonical executor ->
capability stage -> host dispatch -> first-party tool) consumes ~1.9 MB of stack
in debug builds, overflowing the default 2 MB worker thread. The chain was
already borderline on main; this branch's external-tool plumbing tipped it over.

Boxing the hot futures does not help here: in debug builds `Box::pin(fut)` still
constructs the whole future on the stack before moving it to the heap, so the
poll frame's peak is unchanged (verified by measurement). The codebase already
handles deep work by enlarging the thread stack (see the ironclaw_reborn_cli
traces tests and the src/cli stack_size sites), so do the same:

- serve runtime: set thread_stack_size(8 MB) on the production multi-thread
  tokio runtime so the turn-runner worker poll runs with adequate headroom.
- QA harness: run each turn-runner worker on a dedicated 8 MB std::thread with
  its own current-thread runtime, because `#[tokio::test]` runs spawned tasks on
  the libtest 2 MB thread and exposes no thread_stack_size knob.

Regression coverage: qa_error_process_repo_patch_and_cleanup_smokes and the
other harness-based QA/parity suites now pass at the default test stack.
Copilot AI review requested due to automatic review settings June 22, 2026 08:28
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 22, 2026 08:28 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

- Fix broken duplicate capability_id conflict check in
  external_tool_capability.rs: check specs_by_capability_id instead of
  capability_ids_by_tool_name.insert().is_some() which always returns
  None since tool names are unique by catalog registration
- Add GateKind::ExternalTool to SkipAndContinue rejection in
  validate_for_gate_kind and round-trip/default-handler tests
- Add effective_capability_ids consistency check to
  pending_external_tool_resume_candidate matching the dual-validation
  pattern from pending_auth_resume_candidate
- Preserve serde deserialization error context in
  parse_response_create_request with tracing::debug before returning
  sanitized error
- Update wait_for_terminal_or_gate docstring and comments to include
  external-tool as a client-resolvable gate
- Add /api/v1/models alias assertions to 401/501 fail-closed tests
- Add /v1/responses path to invalid-model rejection test
- Add stamp_resume_disposition unit test for external-tool resume slot
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 25, 2026 22:31 Destroyed
- Added activity_id field to PendingExternalToolResume (main added
  CapabilityActivityId to CapabilityCallCandidate /
  RegisterProviderToolCallRequest)
- Fixed short_circuit_denied_resume call to pass CapabilityActivityId
  instead of CapabilityId, removed extra None arg
- Added TurnBlockedGateKind::ExternalTool arms in turn_events.rs
- Fixed register_provider_tool_call impl in external_tool_capability.rs
  to accept RegisterProviderToolCallRequest
- Added activity_id to CapabilityCallCandidate construction
- Removed unused capability_activity_id_from_resume_token helper
- Accepted main's trigger_poller submodule extraction, scheduler
  refactor in harness.rs, auto-approve/permissions crate split
Copilot AI review requested due to automatic review settings June 25, 2026 23:19
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 25, 2026 23:19 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_agent_loop/src/executor/capability_helpers.rs (1)

519-556: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep external-tool resumes on the parked activity id.

This helper still remints activity identity: provider-backed resumes call RegisterProviderToolCallRequest::new(...), and the staged-input fallback uses CapabilityActivityId::new(). The auth path already preserves resume.activity_id_for_resume(). With the current code, an external-tool resume can complete/fail under a different activity than the one parked in GateStage, which breaks the activity-scoped denied/completion flow in CapabilityStage.

Suggested fix
 pub(super) async fn pending_external_tool_resume_candidate(
     host: &(dyn AgentLoopDriverHost + Send + Sync),
     resume: &PendingExternalToolResume,
     surface_version: CapabilitySurfaceVersion,
 ) -> Result<CapabilityCallCandidate, AgentLoopExecutorError> {
     if let Some(replay) = resume.provider_replay.as_ref() {
         let candidate = host
-            .register_provider_tool_call(RegisterProviderToolCallRequest::new(ProviderToolCall {
-                provider_id: replay.provider_id.clone(),
-                provider_model_id: replay.provider_model_id.clone(),
-                turn_id: Some(replay.provider_turn_id.clone()),
-                id: replay.provider_call_id.clone(),
-                name: replay.provider_tool_name.clone(),
-                arguments: replay.arguments.clone(),
-                response_reasoning: replay.response_reasoning.clone(),
-                reasoning: replay.reasoning.clone(),
-                signature: replay.signature.clone(),
-            }))
+            .register_provider_tool_call(RegisterProviderToolCallRequest::for_activity(
+                ProviderToolCall {
+                    provider_id: replay.provider_id.clone(),
+                    provider_model_id: replay.provider_model_id.clone(),
+                    turn_id: Some(replay.provider_turn_id.clone()),
+                    id: replay.provider_call_id.clone(),
+                    name: replay.provider_tool_name.clone(),
+                    arguments: replay.arguments.clone(),
+                    response_reasoning: replay.response_reasoning.clone(),
+                    reasoning: replay.reasoning.clone(),
+                    signature: replay.signature.clone(),
+                },
+                resume.activity_id_for_resume(),
+            ))
             .await
             .map_err(capability_host_error)?;
-        if candidate.capability_id != resume.capability_id
+        if candidate.activity_id != resume.activity_id_for_resume()
+            || candidate.capability_id != resume.capability_id
             || candidate.effective_capability_ids != resume.effective_capability_ids
         {
             return Err(AgentLoopExecutorError::PlannerContract {
                 detail: "external tool resume provider replay no longer matches blocked capability",
             });
         }
         return Ok(candidate);
     }
     Ok(CapabilityCallCandidate {
-        activity_id: ironclaw_turns::CapabilityActivityId::new(),
+        activity_id: resume.activity_id_for_resume(),
         surface_version,
         capability_id: resume.capability_id.clone(),
         input_ref: resume.input_ref.clone(),
         effective_capability_ids: resume.effective_capability_ids.clone(),
         provider_replay: resume.provider_replay.clone(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_agent_loop/src/executor/capability_helpers.rs` around lines
519 - 556, The external-tool resume flow is still minting a fresh activity
instead of reusing the parked one. Update pending_external_tool_resume_candidate
so both the provider_replay path and the staged-input fallback preserve
resume.activity_id_for_resume() rather than relying on
RegisterProviderToolCallRequest::new or CapabilityActivityId::new(). Make sure
the returned CapabilityCallCandidate carries the parked activity id consistently
with the auth path so CapabilityStage stays activity-scoped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_agent_loop/src/state.rs`:
- Around line 32-37: The checkpoint schema change in state.rs removes support
for existing reborn:default-loop-v1 payloads, which will block resume for
in-flight runs. Update the checkpoint handling around CHECKPOINT_SCHEMA_ID and
CHECKPOINT_SCHEMA_VERSION to remain backward-compatible by accepting v1 during
decode, or add an explicit migration path before enforcing the new v2 schema so
older checkpoints can still be resumed.

---

Outside diff comments:
In `@crates/ironclaw_agent_loop/src/executor/capability_helpers.rs`:
- Around line 519-556: The external-tool resume flow is still minting a fresh
activity instead of reusing the parked one. Update
pending_external_tool_resume_candidate so both the provider_replay path and the
staged-input fallback preserve resume.activity_id_for_resume() rather than
relying on RegisterProviderToolCallRequest::new or CapabilityActivityId::new().
Make sure the returned CapabilityCallCandidate carries the parked activity id
consistently with the auth path so CapabilityStage stays activity-scoped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e773e2cf-0372-4981-883f-6ccca8d87020

📥 Commits

Reviewing files that changed from the base of the PR and between d16d19a and 1cdc7c4.

📒 Files selected for processing (10)
  • crates/ironclaw_agent_loop/src/executor/capabilities.rs
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/gates.rs
  • crates/ironclaw_agent_loop/src/executor/mapping.rs
  • crates/ironclaw_agent_loop/src/executor/tests.rs
  • crates/ironclaw_agent_loop/src/state.rs
  • crates/ironclaw_loop_support/src/turn_event_publisher.rs
  • crates/ironclaw_reborn/src/planned_driver.rs
  • crates/ironclaw_reborn/src/subagent/completion_observer.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
💤 Files with no reviewable changes (4)
  • crates/ironclaw_loop_support/src/turn_event_publisher.rs
  • crates/ironclaw_reborn/src/subagent/completion_observer.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn/src/planned_driver.rs

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_agent_loop/src/executor/capability_helpers.rs (1)

519-556: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep external-tool resumes on the parked activity id.

This helper still remints activity identity: provider-backed resumes call RegisterProviderToolCallRequest::new(...), and the staged-input fallback uses CapabilityActivityId::new(). The auth path already preserves resume.activity_id_for_resume(). With the current code, an external-tool resume can complete/fail under a different activity than the one parked in GateStage, which breaks the activity-scoped denied/completion flow in CapabilityStage.

Suggested fix
 pub(super) async fn pending_external_tool_resume_candidate(
     host: &(dyn AgentLoopDriverHost + Send + Sync),
     resume: &PendingExternalToolResume,
     surface_version: CapabilitySurfaceVersion,
 ) -> Result<CapabilityCallCandidate, AgentLoopExecutorError> {
     if let Some(replay) = resume.provider_replay.as_ref() {
         let candidate = host
-            .register_provider_tool_call(RegisterProviderToolCallRequest::new(ProviderToolCall {
-                provider_id: replay.provider_id.clone(),
-                provider_model_id: replay.provider_model_id.clone(),
-                turn_id: Some(replay.provider_turn_id.clone()),
-                id: replay.provider_call_id.clone(),
-                name: replay.provider_tool_name.clone(),
-                arguments: replay.arguments.clone(),
-                response_reasoning: replay.response_reasoning.clone(),
-                reasoning: replay.reasoning.clone(),
-                signature: replay.signature.clone(),
-            }))
+            .register_provider_tool_call(RegisterProviderToolCallRequest::for_activity(
+                ProviderToolCall {
+                    provider_id: replay.provider_id.clone(),
+                    provider_model_id: replay.provider_model_id.clone(),
+                    turn_id: Some(replay.provider_turn_id.clone()),
+                    id: replay.provider_call_id.clone(),
+                    name: replay.provider_tool_name.clone(),
+                    arguments: replay.arguments.clone(),
+                    response_reasoning: replay.response_reasoning.clone(),
+                    reasoning: replay.reasoning.clone(),
+                    signature: replay.signature.clone(),
+                },
+                resume.activity_id_for_resume(),
+            ))
             .await
             .map_err(capability_host_error)?;
-        if candidate.capability_id != resume.capability_id
+        if candidate.activity_id != resume.activity_id_for_resume()
+            || candidate.capability_id != resume.capability_id
             || candidate.effective_capability_ids != resume.effective_capability_ids
         {
             return Err(AgentLoopExecutorError::PlannerContract {
                 detail: "external tool resume provider replay no longer matches blocked capability",
             });
         }
         return Ok(candidate);
     }
     Ok(CapabilityCallCandidate {
-        activity_id: ironclaw_turns::CapabilityActivityId::new(),
+        activity_id: resume.activity_id_for_resume(),
         surface_version,
         capability_id: resume.capability_id.clone(),
         input_ref: resume.input_ref.clone(),
         effective_capability_ids: resume.effective_capability_ids.clone(),
         provider_replay: resume.provider_replay.clone(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_agent_loop/src/executor/capability_helpers.rs` around lines
519 - 556, The external-tool resume flow is still minting a fresh activity
instead of reusing the parked one. Update pending_external_tool_resume_candidate
so both the provider_replay path and the staged-input fallback preserve
resume.activity_id_for_resume() rather than relying on
RegisterProviderToolCallRequest::new or CapabilityActivityId::new(). Make sure
the returned CapabilityCallCandidate carries the parked activity id consistently
with the auth path so CapabilityStage stays activity-scoped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_agent_loop/src/state.rs`:
- Around line 32-37: The checkpoint schema change in state.rs removes support
for existing reborn:default-loop-v1 payloads, which will block resume for
in-flight runs. Update the checkpoint handling around CHECKPOINT_SCHEMA_ID and
CHECKPOINT_SCHEMA_VERSION to remain backward-compatible by accepting v1 during
decode, or add an explicit migration path before enforcing the new v2 schema so
older checkpoints can still be resumed.

---

Outside diff comments:
In `@crates/ironclaw_agent_loop/src/executor/capability_helpers.rs`:
- Around line 519-556: The external-tool resume flow is still minting a fresh
activity instead of reusing the parked one. Update
pending_external_tool_resume_candidate so both the provider_replay path and the
staged-input fallback preserve resume.activity_id_for_resume() rather than
relying on RegisterProviderToolCallRequest::new or CapabilityActivityId::new().
Make sure the returned CapabilityCallCandidate carries the parked activity id
consistently with the auth path so CapabilityStage stays activity-scoped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e773e2cf-0372-4981-883f-6ccca8d87020

📥 Commits

Reviewing files that changed from the base of the PR and between d16d19a and 1cdc7c4.

📒 Files selected for processing (10)
  • crates/ironclaw_agent_loop/src/executor/capabilities.rs
  • crates/ironclaw_agent_loop/src/executor/capability_helpers.rs
  • crates/ironclaw_agent_loop/src/executor/gates.rs
  • crates/ironclaw_agent_loop/src/executor/mapping.rs
  • crates/ironclaw_agent_loop/src/executor/tests.rs
  • crates/ironclaw_agent_loop/src/state.rs
  • crates/ironclaw_loop_support/src/turn_event_publisher.rs
  • crates/ironclaw_reborn/src/planned_driver.rs
  • crates/ironclaw_reborn/src/subagent/completion_observer.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
💤 Files with no reviewable changes (4)
  • crates/ironclaw_loop_support/src/turn_event_publisher.rs
  • crates/ironclaw_reborn/src/subagent/completion_observer.rs
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn/src/planned_driver.rs
🛑 Comments failed to post (1)
crates/ironclaw_agent_loop/src/state.rs (1)

32-37: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Preserve v1 checkpoint readability during rollout.

Lines 34-35 explicitly drop v1 support. That makes any run blocked on a reborn:default-loop-v1 checkpoint unresumable after deploy, because resume validates the stored schema id/version before loading the payload. Keep the decoder backward-compatible or add an explicit migration path before switching the schema id.

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

In `@crates/ironclaw_agent_loop/src/state.rs` around lines 32 - 37, The checkpoint
schema change in state.rs removes support for existing reborn:default-loop-v1
payloads, which will block resume for in-flight runs. Update the checkpoint
handling around CHECKPOINT_SCHEMA_ID and CHECKPOINT_SCHEMA_VERSION to remain
backward-compatible by accepting v1 during decode, or add an explicit migration
path before enforcing the new v2 schema so older checkpoints can still be
resumed.

Use RegisterProviderToolCallRequest::for_activity() with
resume.activity_id_for_resume() (matching the auth-resume path) instead
of ::new() which allocates a fresh activity id. Also validate
candidate.activity_id in the replay-match check, and use
resume.activity_id_for_resume() in the staged-input fallback instead of
CapabilityActivityId::new().
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 26, 2026 00:16 Destroyed
Copilot AI review requested due to automatic review settings June 26, 2026 05:39
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5094 June 26, 2026 05:39 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ilblackdragon
ilblackdragon merged commit 7e191be into main Jun 26, 2026
119 checks passed
@ilblackdragon
ilblackdragon deleted the feat/reborn-openai-compat-models-and-external-tools branch June 26, 2026 06:05

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5094 — 3c092040 Deployed Jun 26, 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.

2 participants