Skip to content

refactor(loop): shed the model gateway and tool disclosure into loop_host (WS3/WS4) - #7064

Merged
BenKurrek merged 15 commits into
mainfrom
ws3/runner-sheds
Aug 3, 2026
Merged

BenKurrek merged 15 commits into
mainfrom
ws3/runner-sheds

Conversation

@BenKurrek

@BenKurrek BenKurrek commented Aug 3, 2026 •

Copy link
Copy Markdown
Collaborator

Wave 3, WS3 runner sheds + the WS4 re-layer. Move-only plus two layer = lines — no behaviour change, and the un-masking below shows zero tests lost or edited.

What moved, and the measurement behind each call

Model gateway + port adapters → ironclaw_loop_host (DONE)

PROPOSAL §6.7.2's "Gains: runner's model-gateway adapter (a host-port adapter by charter) and runner's port adapters, so the port-implementer census shrinks", executed.

from ironclaw_runner/src to ironclaw_loop_host/src
model_gateway.rs (+ model_gateway/prompt_cache_activity.rs) same names
model_gateway_error_mapping.rs, model_routes.rs same names
loop_driver_host/model_gateway.rs thread_resolving_model_gateway.rs
loop_driver_host/port_adapters.rs (+ its tests) driver_host_port_adapters.rs (+ tests)
tests/llm_gateway.rs, tests/model_routes.rs same names

Two dispositions the row did not predict, both forced by the call graph rather than the filename prefix:

  • model_routes.rs had to travel and is not optional. It reads as separable route policy vocabulary with its own runner and composition consumers — but model_gateway.rs names eight of its types, so leaving it behind makes loop_host → runner a cycle against the pre-existing runner → loop_host edge.
  • model_failure_mapping.rs must NOT travel, though its name puts it in the cluster. Its only callers are planned_driver.rs and text_loop_driver.rs, which stay, and its test needs runner-private retry_disposition. Moving it would create a cross-crate call in the wrong direction for no benefit.

Also moved: turn_error_to_host_error (the TurnError → AgentLoopHostError mapper the checkpoint port calls — every arm is ironclaw_turns/loop_contracts vocabulary, no runner concept in it).

Tool disclosure → ironclaw_loop_host (DONE; the /product half is refuted)

tool_disclosure.rs, tool_disclosure_port.rs, context_shadow.rs, and ToolDisclosureMode/REBORN_TOOL_DISCLOSURE_ENV (lifted out of runtime.rs into tool_disclosure_mode.rs). tool_disclosure_bridge.rs — a 13-line one-function delegate that existed only because the catalog was private — is deleted, its test folded into tool_disclosure.rs verbatim.

Zero new dependencies. Every crate the cluster needs was already a loop_host dependency; its ironclaw_loop_host:: imports simply became crate::. It belongs there by charter: it is a LoopCapabilityPort decorator, and families/loop.md gives loop_host "the base capability-port adapter and its capability-surface-filtering decorators".

§6.7.3's "→ loop_host / product prompt policy" cannot mean product, measured. ~160 of the cluster's 2,314 production lines are prompt content (the catalog-index prose, the three bridge tool descriptions, CORE_TOOL_NAMES, the describe-first note). Moving them to ironclaw_product needs loop_host → product; loops → products is upward and illegal. It would take an injection seam (composition supplying product-owned policy data) — a design change, not a move — and it is unnecessary: unlike a contracts crate (§6.1.4), nothing forbids prompt content in the loop tier, and crates/ironclaw_loop_host/prompts/ already holds five include_str! assets. Read the clause as "→ loop_host". PROPOSAL and CHECKLIST carry dated amendments quoting the text they replace.

What stayed, and why

runtime.rs's decorator-chain composition, planned_driver*, text_loop_driver, driver_registry, loop_exit_applier, turn_scheduler, turn_run_executor, subagent/**, failure_categories, model_failure_mapping. families/loop.md assigns the ordering of the capability-port decorator chain to the turn runner; this PR moves the pieces, not the ordering.

Manifest result

ironclaw_runner sheds ironclaw_llm, ironclaw_common, base64, jsonschema — the provider (reqwest/rig-core/Bedrock) cone is out of the turn runner entirely. ironclaw_loop_host gains ironclaw_llm (default-features = false), ironclaw_common, base64, plus http/reqwest dev-deps for the moved test targets.

ironclaw_runner src: 33,209 → 22,039. ironclaw_loop_host src: 38,496 → 49,727.

Exception delta: 13 → 10, with per-edge evidence

edge deleted evidence
ironclaw_runner → ironclaw_agent_loop runner re-declared layer = "loops"; loops → loops is matrix-legal
ironclaw_runner → ironclaw_loop_host same
ironclaw_hooks → ironclaw_wasm_limiter hooks re-declared layer = "loops"; loops → runtimes is matrix-legal

WS0_LAYER_MATRIX_EXCEPTION_BASELINE 13 → 10 in the same change.

The re-layer row read as if it were gated on the sheds; measured, it was not. Both moves are strictly permissive (kernel's allowed set ⊂ loops's; substrates's ⊂ loops's), so they can only break consumers — and the complete consumer sets are ironclaw_reborn_composition (app) plus each other. The PROPOSAL preconditions were already met on main: #6696's supervisor inversion (§6.7.3) and WS1.2's loop_contracts dependency (§6.7.4). It is two layer = lines. This is the first exception movement since WS0; the register only responds to a crate changing layer, which is why Wave 2 moved it zero times.

Enforcement — crates/ironclaw_architecture/tests/reborn_runner_sheds.rs (8 tests)

Four halves, each failing for its own reason:

  1. Moved items at the new home, absent from the old. 29 pinned symbols, each defined exactly once in loop_host and never in runner.
  2. Manifest edges through cargo metadata, not literal paths, so the WS7 family move cannot blind it. Both directions asserted, with a non-vacuity control proving the reader found the right package.
  3. Residue: exact-match, shrink-only, per-entry reason. Two runner files, each with the reason it is legitimate. New rows fail; stale rows fail.
  4. The two loops declarations are pinned — reverting one would need three deleted exceptions back, and that register is shrink-only, so the revert must fail at the declaration rather than as an undeclared-edge message three crates away.

Discipline: production-only via ratchet_support::production_rust_files (subtracts #[cfg(test)] mod chains, not just test-shaped filenames); comments/strings stripped before #[cfg(test)]; every I/O error fatal; non-vacuity asserted with a positive control. Self-tested over 9 definition shapes including the ModelRouteError/ModelRouteErrorKind prefix trap, and the ordering guard proves the naive order loses production code rather than merely documenting it.

Negative probes (each restored afterwards, tree byte-clean):

probe fails at
re-define LlmProviderModelGateway in the runner :361 (left-behind) + :495 (residue)
re-add ironclaw_llm to the runner manifest :440 (manifest)
new unlisted runner file names a moved symbol :495 (residue)
revert ironclaw_hooks to layer = "substrates" :544 (layer)

Gates updated, never relaxed

  • reborn_dependency_boundaries.rs: reborn_runner_llm_wiring_is_isolated repointed and strengthened (it now also asserts the file is gone from the runner); the default-features = false allowlist follows the edge to loop_host; three provider_tool_names allowlist paths repointed; two dead pub use entries removed from the runner surface lock.
  • reborn_extension_specificity.rs: 5 rows repointed — count unchanged, so the shrink-only baseline is untouched.
  • reborn_struct_test_support_ratchet.rs: one FROZEN_PATH_COUNTS row repointed.
  • scripts/no_panics_reborn_baseline.txt: 2 entries repointed (scanner re-run: OK, 1189 files / 51 invariants).
  • scripts/reborn-e2e-rust.sh: exact selector feat: Sandbox jobs #4 -p ironclaw_runner → -p ironclaw_loop_host — all 10 selectors verified to match exactly one test under bash … < /dev/null.
  • tests/integration/coverage-floor.toml: ironclaw_runner's absolute numerator is now structurally unreachable (~10.5k lines and two test targets left), so it drops to a percentage floor for one release with floor_percent retained unchanged, not weakened — the exact resolution WS1.2 used for the ironclaw_turns split. A [[crate]] entry for ironclaw_loop_host is added in the same change so the moved lines keep a ratchet — the gap WS1.2 left open for ironclaw_loop_contracts and did not close. Both numerators are owed from this PR's own merged artifact, per the file's same-PR workflow.
  • code_style.yml + ws12_workflow_contracts.py: has_reborn_cli widened to ironclaw_loop_host. Without it, the move would have narrowed CI scope as a side effect — a PR touching only the new home would no longer light the lane that runs cargo test -p ironclaw_architecture reborn.

Registration traps verified empirically, not assumed: classify-test-scope.sh reports has_reborn_tests=true for files in all three crates and for the new arch test; reborn-crate-test-buckets.sh still maps ironclaw_loop_host → agent-runtime, ironclaw_runner → reborn-core, ironclaw_architecture → architecture-misc; discover-reborn-package-crates.sh still carries ironclaw_loop_host through the binary's cargo-tree closure. The new arch test is named reborn_* (file and every test fn) so code_style.yml's name-filtered lane picks it up.

Un-masking

crate before after accounting
ironclaw_runner 458 259 −199 moved out, 3 changed module path only (port_adapter_tests:: → thread_scope_tests::, same names)
ironclaw_loop_host 572 771 +199, every one matching a name removed from the runner
ironclaw_reborn_composition 930 930 identical roster

1030 → 1030. Zero unaccounted, zero lost, zero new tests smuggled in. The only content edit any moved test carries is one self-referential path literal in tool_disclosure_mode_non_unicode_env_fails_closed, which re-runs itself by module path; it is called out in a comment at the line.

Deferred, with measurements — the two clauses this PR did not execute

runtime.rs build_* → composition. Payload is ~465 production lines. Executing it as written costs eleven pub(crate) → pub widenings in ironclaw_runner, four of which this PR already dissolved by moving tool disclosure out, leaving seven: subagent::capability_surface (module + resolver + ::new), loop_driver_host::{capability_resolve_error_to_agent_host_error, apply_capability_surface_profile, with_resolved_profiled_capability_port_factory}, SchedulerWakeWiring::start. That grows the public API of the crate the row exists to narrow. It also relocates RuntimeProfiledCapabilityPortFactory and SubagentSpawnCapabilityDecorator — the ordering of the capability-port decorator chain — into an app-layer crate, contradicting families/loop.md. The design that resolves both is one runner-owned pub fn build_profiled_capability_port_factory(...), collapsing seven widenings to one — a semantic change PLAN principle 2 keeps out of a move PR. Worth noting for whoever takes it: build_product_live_planned_runtime has zero production callers, so part of this row is a test-seam question.

production_readiness deletion. "No production caller" re-verified: only tests/production_readiness.rs and five production_readiness_* tests in tests/driver_registry.rs (composition's same-named test-support module is unrelated — it wraps ironclaw_host_runtime::ProductionWiringReport). But it cascades: DriverReadinessMode, HostGraphReadiness, DriverReadinessDiagnosticCode, ConfiguredRunProfile, PersistedRunDriverIdentity have no other consumer, so the deletion propagates into a 589-line file that otherwise stays. That is an un-masking slice under PLAN principle 4. WS8's row now carries the cascade inventory.

Row corrections landed in the docs

  • CHECKLIST WS4 re-layer row ticked, with the finding that it was never gated on the sheds.
  • CHECKLIST WS4 runner-sheds row → [~] with per-clause status and both refutations.
  • CHECKLIST WS4 loop_host re-charter row → [~]; the "absorb runner's model-gateway/port adapters" half is done.
  • CHECKLIST WS8 llm::reasoning placement question answered and closed: clean_response / contains_codex_text_tool_call_syntax / recover_codex_text_tool_calls_from_tool_names stay in ironclaw_llm. Their sole caller moved to loop_host, so the coupling that made the placement ambiguous (a kernel crate reaching a substrates provider crate) is now an ordinary loops → substrates edge.
  • PROPOSAL §2.4 runner inventory re-measured (the 24-with_* figure was 28; the model-gateway and tool-disclosure lines are retired).
  • PROPOSAL §8.3's live-register note corrected: it claimed 13 and named first_party_extensions, a crate refactor(extensions): colocate packages under crates/extensions/ (WS2) #7037 renamed to extension_support. Now 10, with the current names.
  • PLAN Wave 3 gains a four-point landing note, including that the milestone's "12 →" starting figure was stale (Wave 1 closed at 13).

Verification

fmt clean · per-crate clippy -D warnings --all-targets --all-features clean on all six touched crates · per-crate unfiltered tests: loop_host 771, runner 259, hooks 344, composition 928, product 1032, architecture 196 — 0 failures · cargo test -p ironclaw_architecture 32/32 binaries green · workspace cargo check --all-targets --all-features clean · per-crate cargo build -p <crate> alone for the three re-layered/changed crates · cargo metadata --locked clean · composition budget + include-str + panic-baseline + ws12-contract + classify-test-scope + crate-bucket self-tests all green.

🤖 Generated with Claude Code


CI status (kept current — detail in the comments)

At head dc190de227: Code Style ✅ · Platform & Compat ✅ · Reborn E2E ✅ · IronClaw Stress ✅ · Regression Test Check ✅ · PR: History Check ✅ · Tests (Reborn) ❌.

The single red lane is #7066's llvm-cov environment defect, reproduced on this tree and not caused by this PR: run-hermetic-test-process.sh strips REBORN_COV_COLLECT, which reborn-coverage-lane-run.sh then defaults back to true, so a lane with coverage off calls a cargo llvm-cov that was correctly never installed. This PR needs a main merge once #7066 lands; its one-token fix is deliberately not duplicated here.

Local evidence that the lanes themselves are fine: both failing lanes' complete suite lists, computed with the workflow's own modulo-4 partitioning — 27 suites, 461 passed, 4 failed, and all four failures carry the explicit "StorageMode::Postgres requires a reachable Docker daemon" gate. reborn_integration_tool_disclosure (in failing lane 1, the suite that most directly exercises the moved cluster) is 26/26.

Three CI-plumbing commits ride along, all fixing pre-existing defects this PR was the first to hit — two in the Reborn PR test planner (.claude/ and repo-root scripts/ were unclassified, so its fail-closed arm skipped the entire Reborn suite on a docs-only path) and one worked around in check_no_panics.py (flat-only #[path] recognition). Both planner gaps are fixed with regression tests verified red by reverting; the panic-scanner gap is reported on CHECKLIST WS10 rather than fixed, because widening that regex touches a security-adjacent gate's classification and has panic-baseline implications.

One evidence gap, stated plainly: Rust Reborn in Reborn E2E was skipped by scope, so the repointed llm_gateway selector has not run in CI. All 10 selectors were verified locally under bash … < /dev/null, each matching exactly one test.


Merge-queue rejection and the floor recapture (b75bdacc10)

This PR jammed the merge queue and took #7040 down with it. Run 30855460733, job Reborn integration-tier coverage report:

RATCHET FAIL: ironclaw_runner
  observed: 82.53% (9470 / 11474 lines)
  floor:    85.55% (tolerance 0.5pp -> effective floor 85.05%)

Why the PR page said 30/30 green: scripts/ci/reborn_pr_test_plan.py:21's FULL_EVENTS omits pull_request, so PR runs get coverage_mode: "none" and the ratchet never executes on a PR. merge_group runs it for the first time. Tracked as #7036; not fixed here (CI policy frozen).

The held floor was an unverified prediction, and it was wrong

This entry held floor_percent = 85.55 across the shed on the reasoning — now deleted from the file — that "the moved half is adapter code with roughly the crate's own coverage profile, so the ratio is the invariant that survives a split". Measured, the moved files score 6,108/6,577 = 92.87% at their new home — 10.33pp above the 82.53% of what stayed. The shed removed the crate's better-covered half and un-masked a weaker remainder that had been riding on it.

Move, not regression — proven, not asserted

evidence result
Counterfactual: add the moved files back to the runner 15,578/18,051 = 86.30% — clears the old 85.55% floor by +0.75pp. The composition shift alone explains the entire drop.
Test roster across both crates, set-diff of test-fn names at origin/main vs branch 1,070 → 1,072: +2 added (request_shaped_errors_map_to_invalid_invocation, unauthorized_maps_to_unauthorized), zero names lost
Moved files' coverage at the new home 92.87%, i.e. their tests came with them — a move that dropped tests would show these files sagging

Had the moved code stayed, the runner would have passed. That is the definition of a numerator/denominator shift.

Both entries recaptured from this PR's own merged artifact

Read from run 30855460733's reborn-integration-coverage-merged through the gate's own aggregate() (scripts/ci/lib/reborn_coverage_lcov.py) — never estimated:

crate floor_percent floor_covered_lines captured_total_lines
ironclaw_runner 85.55 → 82.53 — → 9470 — → 11474
ironclaw_loop_host 85.55 → 90.89 — → 24598 — → 27063

The destination is raised, not left inheriting the source's 85.55 — that would have handed the absorbed code 5.34pp of arrival slack (~1.4k covered lines it could have lost silently). This is the WS2.4 extension_host/extension_manager pattern: both halves captured in the same PR, so no release runs on a half-captured entry.

Verification — the exact failing artifact, replayed

The ratchet does not run on pull_request, so this PR's own checks cannot prove the fix — confirmed directly on this PR: the Reborn integration-tier coverage report check is skipping (run 30857632335, job 91836054560), even though this PR now edits coverage-floor.toml. Editing a coverage floor does not escalate a PR to the full plan: coverage-floor.toml is in the planner's PR_STATIC_CONTROL_PATHS (reborn_pr_test_plan.py:43), whose branch at :353 de-escalates ("static CI or workspace-policy checks own"). Measured, coverage-floor.toml, coverage-exemptions.toml and changed-coverage-exemptions.toml each yield coverage_mode: none on --event pull_request. Real signal instead:

  1. Local replay of the exact CI input. Downloaded run 30855460733's reborn-integration-coverage-merged artifact (unexpired, single file, md5 efc5ccbf1502f8277dd50008120c7379) and ran scripts/ci/reborn-coverage-ratchet.sh over it:

    • before the change: reproduces the CI verdict byte-for-byte (RATCHET FAIL: ironclaw_runner, 82.53%, 9470/11474) — confirming the harness is faithful;
    • after the change: exit 0, zero RATCHET FAIL, all 19 crates + global PASS.
  2. Cross-check against the stacked variant. refactor(contracts): close the WS2 strays and WS2.1 follow-ups #7040-on-refactor(loop): shed the model gateway and tool disclosure into loop_host (WS3/WS4) #7064 (run 30855963015) measured 82.55% (9472/11474) — a 2-line wobble. Against the new floors: 82.55% ≥ 82.03% effective, 9472 ≥ 9450 effective. Passes with the tolerance ~10× the observed wobble.

  3. Full workflow_dispatch run — CONFIRMED GREEN. Run 30857389891 at head b75bdacc10 (workflow_dispatch ∈ FULL_EVENTS, so the full plan including the coverage ratchet ran). Per-job tally: 28 success, 1 skipped, 0 failures — and the job that rejected this PR in the queue, Reborn integration-tier coverage report (job 91834469469), is success with zero RATCHET FAIL:

    Ratchet mode: ENFORCING
    RATCHET PASS: ironclaw_runner
      observed: 82.53% (9470 / 11474 lines)
      floor:    82.53% (tolerance 0.5pp -> effective floor 82.03%)
    RATCHET PASS: ironclaw_loop_host
      observed: 90.89% (24598 / 27063 lines)
      floor:    90.89% (tolerance 0.5pp -> effective floor 90.39%)
    

    Note this run measured 9470/11474 and 24598/27063 — byte-identical to the queue artifact the floors were captured from, on a fresh run against a different base. The capture is stable, not a fluke. (The dispatch roll-up also came back success here; reborn-tests.yml: workflow_dispatch runs structurally fail the Tests (Reborn) roll-up (critical-mutation skipped but disallowed) #6978's structural-red caveat did not bite, but the per-job tally above is the signal either way.)

  4. cargo fmt clean; cargo test -p ironclaw_architecture --test reborn_restructure_baselines green (it parses this file); no stranded entry in changed-coverage-exemptions.toml (every path resolves on HEAD — a stranded one makes the fail-closed validator abort with no verdict).

#7040's exposure: none of its own

#7040 touches ironclaw_extension_host, ironclaw_extension_manager, ironclaw_product, ironclaw_product_contracts, ironclaw_reborn_composition, ironclaw_webui. Its queue entry on the un-stacked base (run 30856287488) was green; stacked behind this PR it inherited the identical RATCHET FAIL: ironclaw_runner. It was pure collateral — no change needed there.

Rule recorded

docs/reborn/target-architecture/CHECKLIST.md (WS10 ratchet row): a shed/move re-captures the SOURCE crate's floor, not just the destination's — a held source floor is not conservative, it is an unverified prediction about a ratio, and the merge queue is where it gets checked. The note carries the counterfactual recipe so the next shed proves move-vs-regression before touching a number.

…host (WS3/WS4)

Moves two clusters out of `ironclaw_runner` into `ironclaw_loop_host` and
re-layers the two loop-tier crates, clearing three `LAYER_MATRIX_EXCEPTIONS`.

Model gateway + port adapters -> loop_host (PROPOSAL §6.7.2 "gains: runner's
model-gateway adapter"): `model_gateway.rs` (+ `prompt_cache_activity`),
`model_gateway_error_mapping.rs`, `model_routes.rs`, the driver-host model
gateway and port adapters, and their two integration targets. `model_routes`
had to travel (the gateway names eight of its types, so leaving it behind
would make `loop_host -> runner` a cycle); `model_failure_mapping.rs` had to
stay (its only callers are the two drivers that stay).

Tool disclosure -> loop_host with zero new dependencies: it is a
`LoopCapabilityPort` decorator, which `families/loop.md` already assigns to
loop_host. The row's `/product` alternative is refuted, not skipped —
`loops -> products` is an illegal upward edge, so its ~160 lines of prompt
content cannot relocate there, and need not: `crates/ironclaw_loop_host/prompts/`
already holds five prompt assets.

Net: `ironclaw_runner` sheds `ironclaw_llm`, `ironclaw_common`, `base64` and
`jsonschema` outright — the provider cone is out of the turn runner — and drops
33.2k -> 22.0k source lines. `LAYER_MATRIX_EXCEPTIONS` 13 -> 10 (`runner ->
agent_loop`, `runner -> loop_host`, `hooks -> wasm_limiter`), with the baseline
lowered in the same change.

Enforcement: `reborn_runner_sheds.rs` pins the moved items at their new home
and absent from the old, proves the manifest edges through `cargo metadata`,
holds a reasoned shrink-only residue list, and pins the two `loops` layer
declarations so a revert cannot silently need the deleted exceptions back.

Un-masking: runner 458 -> 259, loop_host 572 -> 771 — 199 moved by identical
name, 3 changed module path only, 0 lost, 0 edited for content.

Deferred with measurements (see CHECKLIST WS4): `runtime.rs` `build_*` ->
composition costs seven `pub(crate)` -> `pub` widenings in the crate the row
narrows and moves decorator-chain ownership into the app layer; the
`production_readiness` deletion is callerless as claimed but cascades into
five `driver_registry.rs` types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7064 August 3, 2026 14:25 Destroyed
@railway-app

railway-app Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

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

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

@github-actions github-actions Bot added scope: ci CI/CD workflows scope: docs Documentation scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added model routing, host adapters, progressive tool disclosure, prompt-cache tracking, and token estimation.
    • Added configurable disclosure modes and structured loop-error handling.
  • Refactor
    • Consolidated model, routing, and tool-handling responsibilities within the loop host.
  • Bug Fixes
    • Improved architecture validation and change detection for relocated components.
  • Documentation
    • Updated architecture plans, ownership guidance, and parity references.
  • Tests
    • Expanded coverage for routing, disclosure, boundaries, coverage, and CI planning.

Walkthrough

The PR moves model gateway, model-route, host-port adapter, and progressive tool-disclosure ownership from ironclaw_runner to ironclaw_loop_host. It rewires consumers, updates dependencies and documentation, and adds architecture, CI, coverage, and residue checks.

Changes

WS3 runner shed

Layer / File(s) Summary
Loop-host contracts and implementations
crates/ironclaw_loop_host/...
Adds model-route infrastructure, prompt-cache telemetry, public host-port adapters, token estimation, tool-disclosure mode, and public re-exports.
Runner and consumer rewiring
crates/ironclaw_runner/..., crates/ironclaw_reborn_composition/..., crates/ironclaw_product/tests/..., tests/integration/...
Removes relocated runner modules and dependencies. Consumers import the relocated APIs from ironclaw_loop_host.
Architecture guardrails
crates/ironclaw_architecture/tests/..., crates/ironclaw_hooks/Cargo.toml
Adds unique-definition, dependency, residue, and layer metadata checks. Updates architecture exceptions and allowlists.
CI and guidance updates
.github/workflows/..., scripts/..., docs/reborn/..., crates/ironclaw_*/CLAUDE.md, crates/ironclaw_*/AGENTS.md
Updates Reborn path detection, test planning, coverage and panic baselines, architecture documentation, and test references.

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

Possibly related issues

  • nearai/ironclaw#4470 — Both changes add CI-enforced architecture boundary tests for Reborn crate ownership.

Suggested reviewers: think-in-universe

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the preferred Conventional Commits format and accurately describes the runner shedding and loop-host re-layering.
Description check ✅ Passed The description is highly detailed and covers scope, rationale, risks, validation, CI status, deferred work, and follow-up, despite omitting some template headings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

…d home

Fixes the live references the WS3 move invalidated: the trace command's
model-call row, the engine-v2 parity map's test paths, the integration
test's path + visibility note, and one scenario doc comment. Also corrects
`model_gateway.rs`'s module doc, which claimed the adapter lives "in the
standalone Reborn composition crate" — never true of any tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ironloopai

ironloopai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Review · PR #7064

🔴 Failed

Execution result is invalid

The structured result could not be verified.

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

Failure details
  • Repository: nearai/ironclaw
  • Base: main at e9e738c
  • Head: ws3/runner-sheds at 47869fa
  • Created: Aug 3, 2026, 2:30 PM UTC
  • Updated: Aug 3, 2026, 2:32 PM UTC
  • Run: 85f08393-b407-4c46-b559-18cc5b06be81
  • Latest attempt: 1 · Completed · 104725d6-f88d-42b8-a76c-88e623f9a875
  • Failed during: Verification
  • Retryable: No
  • Failure: 5da644ec-bb7a-4267-a6cf-49e691f36063

# Conflicts:
#	docs/reborn/target-architecture/CHECKLIST.md
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7064 August 3, 2026 14:33 Destroyed
@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Two follow-up commits on top of the shed:

  • 01897829ac — repoints the live guidance the move invalidated: .claude/commands/trace.md's model-call row, docs/reborn/engine-v2-to-reborn-parity.md's test paths, tests/integration/tool_disclosure.rs's path + visibility note, and one scenario doc comment. It also corrects model_gateway.rs's module doc, which claimed the adapter lives "in the standalone Reborn composition crate" — that was never true of any tree, so the move exposed pre-existing drift rather than creating it. (Dated docs/plans/ entries are left alone: they are point-in-time records, not live guidance.)
  • 7d2e7ac140 — merge of origin/main (docs(target-architecture): resolve the open Wave 2 architecture decisions #7033, perf: recover hosted Postgres API capacity regressed by the row-native process journal (#6696) #7050). The PR was CONFLICTING before this, which is why none of the seven Reborn workflows had attached — worth knowing for anyone else opening a Wave 3 slice against a moving main: an empty check list is not "CI is slow", it is a dirty merge ref.

The only true overlap with main's two new commits is Cargo.lock plus the two target-architecture docs; the code is disjoint. #7033 edited the same loop_host re-charter row this PR amends, so the resolution takes main's whole row — carrying its ✅ RESOLVED 2026-08-02 ModelCostTable ruling intact (7 occurrences of that marker survive) — and re-applies only the WS3 status clause on top. Verified by diffing against origin/main: exactly the six rows this PR amends differ, nothing else.

Re-verified post-merge: cargo fmt --check clean, workspace cargo check --all-targets --all-features clean, cargo test -p ironclaw_architecture 32/32 binaries green.

BenKurrek and others added 2 commits August 3, 2026 10:37
`scripts/ci/reborn_pr_test_plan.py` had no rule for `.claude/`, so its
fail-closed arm raised `unclassified pull-request path` on any PR that edited
a skill, a command, or a rule — failing the `Detect Reborn test scope` job and
skipping every downstream Reborn lane, on a documentation-only change. This PR
hit it by repointing `.claude/commands/trace.md`'s model-call row at the moved
gateway.

Agent guidance is prose with no Rust or E2E surface any Reborn lane can
exercise — the same class as `docs/`, which is already ignored. Classifying it
is the fix; loosening the fail-closed arm is not, and the arm is untouched.

Two regression tests, both red without the classification (verified by
reverting it): guidance paths are accepted and select no lane, and a guidance
edit riding along with a crate change still selects that crate's lane, so the
ignore stays per-path rather than per-PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`check_no_panics.py`'s `has_cfg_test_module_declaration` only recognises a
FLAT `#[path = "x.rs"]`; a module declared in a non-`mod.rs` file must spell
the directory (`#[path = "loop_driver_host/x.rs"]`), which the regex misses.
The carved file therefore read as production to the delta scan, and its six
fixture `.unwrap()`s failed `Fast deterministic checks`.

Inlining the module is both the fix and the local convention —
`loop_driver_host.rs` already carries four inline `#[cfg(test)]` modules — and
it keeps the three test paths identical (`loop_driver_host::thread_scope_tests::*`).

The scanner gap is pre-existing and latent for the two sibling files declared
the same way (`tests.rs`, `compaction_tests.rs`); neither has ever tripped it
because their panics sit under item-level `#[cfg(test)]` attributes the scanner
does track. Reported rather than fixed here: widening that regex changes a
security-adjacent gate's classification and has baseline implications.

Verified: `--base origin/main --head HEAD`, `--reborn-baseline`, and
`--self-test` all clean; runner test roster unchanged at 259.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

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

🤖 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_architecture/tests/reborn_runner_sheds.rs`:
- Around line 54-57: The MOVED_ITEMS documentation and entries disagree about
whether module-private disclosure helpers are pinned. Update the inventory
around MOVED_ITEMS and the entries for CapabilityCatalog, PromotedSet,
DisclosureCaps, ActiveSet, and ToolTier so the documented contract is
consistent: either describe the tool-disclosure cluster as an intentionally
pinned package or retain only package-level types in the inventory.
- Around line 471-486: Hoist the loop-invariant crate_directory(&root, RUNNER)
call out of the per-file loop and compute the crate directory once before
iterating over runner entries. Reuse that value in the relative(path, ...) call
while preserving the existing matching and insertion behavior.
- Around line 582-583: Update the negative assertion in the relevant self-test
to query the fixture’s actual impl-header name, LlmProviderModelGateway, rather
than the absent SomethingNeverDefined name. Keep the assertion verifying that
defines does not treat the impl header as a definition, while preserving the
existing positive definition coverage.
- Around line 405-415: Update the documentation above dependency_names to
accurately describe cargo metadata --no-deps output: dependency["name"] is the
package identity, while rename is the optional manifest alias; alternatively,
change the helper to assert the intended package-name field. Keep the
implementation and renamed-dependency behavior consistent with the corrected
documentation.

In `@crates/ironclaw_loop_host/src/driver_host_port_adapters.rs`:
- Around line 480-525: Add tests in turn_error_to_host_error_tests for the
missing mappings: verify TurnError::Unauthorized produces
AgentLoopHostErrorKind::Unauthorized, and verify both TurnError::InvalidRequest
and TurnError::InvalidRunOriginAdapter produce
AgentLoopHostErrorKind::InvalidInvocation.

In `@crates/ironclaw_loop_host/src/model_gateway.rs`:
- Around line 17-23: Add a linked decomposition tracking issue for
model_gateway.rs, documenting planned boundaries for provider conversion, route
handling, error mapping, and tests. Keep this decomposition separate from the
current behavior-preserving change and reference the issue using the
repository’s established tracking convention.

In `@crates/ironclaw_loop_host/src/model_routes.rs`:
- Around line 50-117: Remove the identity-mirror struct ActiveModelRouteSettings
and its constructors/accessors, including from_active_settings on ModelRoute.
Add ModelRoute::from_llm_config accepting ironclaw_llm::LlmConfig and construct
the route directly from active_provider_id() and active_model_name(), preserving
existing validation through ModelRoute::new.
- Around line 415-443: Update validate_route_component and ModelRouteError so
validation failures preserve the underlying reason from
validate_model_route_component_value, including whether the value exceeds the
limit or contains a disallowed character. Add or populate a reason field when
constructing ModelRouteError instead of mapping the error to a reasonless
InvalidRoute; ensure Display or equivalent error reporting exposes that
preserved detail for validate_provider_id, validate_model_id, and
validate_version_token.

In `@crates/ironclaw_loop_host/src/thread_resolving_model_gateway.rs`:
- Around line 18-40: Keep the fields of ThreadResolvingLoopModelGateway private
and replace external struct-literal construction with a public owning-crate
factory, preferably ThreadResolvingLoopModelGateway::new accepting a typed
configuration object. Update ironclaw_runner’s loop_driver_host.rs construction
site to call that factory while preserving all existing initialization values
and invariants.

In `@docs/reborn/target-architecture/PROPOSAL.md`:
- Line 84: Reconcile the two pre-move ironclaw_runner line counts in this bullet
by selecting the authoritative 33.2k baseline used by the loop_host arithmetic,
or explicitly label 33.1k as the older measurement. Update the opening count and
related “was” wording so §2.4 and the checklist derive from one consistent
baseline.

In `@tests/integration/coverage-floor.toml`:
- Around line 71-95: Restore artifact-derived absolute coverage ratchets in both
the ironclaw_runner entry and the ironclaw_loop_host entry: add
floor_covered_lines and captured_total_lines values from the merged coverage
artifact, without estimating them. Preserve the existing percentage floors and
rationale, or document an explicit bounded exception if artifact values cannot
be added.
🪄 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: 90d555a9-a0a0-4878-928f-ee31d5754a70

📥 Commits

Reviewing files that changed from the base of the PR and between e9e738c and 47869fa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (55)
  • .github/workflows/code_style.yml
  • crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • crates/ironclaw_architecture/tests/reborn_runner_sheds.rs
  • crates/ironclaw_architecture/tests/reborn_struct_test_support_ratchet.rs
  • crates/ironclaw_hooks/Cargo.toml
  • crates/ironclaw_loop_host/AGENTS.md
  • crates/ironclaw_loop_host/CLAUDE.md
  • crates/ironclaw_loop_host/Cargo.toml
  • crates/ironclaw_loop_host/src/context_shadow.rs
  • crates/ironclaw_loop_host/src/driver_host_port_adapters.rs
  • crates/ironclaw_loop_host/src/driver_host_port_adapters/tests.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_loop_host/src/model_gateway.rs
  • crates/ironclaw_loop_host/src/model_gateway/prompt_cache_activity.rs
  • crates/ironclaw_loop_host/src/model_gateway_error_mapping.rs
  • crates/ironclaw_loop_host/src/model_routes.rs
  • crates/ironclaw_loop_host/src/thread_resolving_model_gateway.rs
  • crates/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/ironclaw_loop_host/src/tool_disclosure_mode.rs
  • crates/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/ironclaw_loop_host/tests/llm_gateway.rs
  • crates/ironclaw_loop_host/tests/model_routes.rs
  • crates/ironclaw_product/tests/inbound_turn_contract.rs
  • crates/ironclaw_product/tests/support/planned_agent_loop.rs
  • crates/ironclaw_reborn_composition/src/factory/production_backend_assembly.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
  • crates/ironclaw_reborn_composition/src/model_gateway_assembly.rs
  • crates/ironclaw_reborn_composition/src/root/product_live_adapters.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/default_system_prompt.rs
  • crates/ironclaw_reborn_composition/src/runtime_input.rs
  • crates/ironclaw_reborn_composition/tests/product_live_adapters.rs
  • crates/ironclaw_reborn_composition/tests/trigger_poller_e2e.rs
  • crates/ironclaw_runner/AGENTS.md
  • crates/ironclaw_runner/CLAUDE.md
  • crates/ironclaw_runner/Cargo.toml
  • crates/ironclaw_runner/src/lib.rs
  • crates/ironclaw_runner/src/loop_driver_host.rs
  • crates/ironclaw_runner/src/loop_driver_host/thread_scope_tests.rs
  • crates/ironclaw_runner/src/runtime.rs
  • crates/ironclaw_runner/src/tool_disclosure_bridge.rs
  • docs/reborn/target-architecture/CHECKLIST.md
  • docs/reborn/target-architecture/PLAN.md
  • docs/reborn/target-architecture/PROPOSAL.md
  • docs/reborn/target-architecture/families/loop.md
  • scripts/ci/ws12_workflow_contracts.py
  • scripts/no_panics_reborn_baseline.txt
  • scripts/reborn-e2e-rust.sh
  • tests/integration/coverage-floor.toml
  • tests/integration/support/builder.rs
  • tests/integration/support/group.rs
  • tests/integration/support/group_options.rs
  • tests/integration/support/triggered_submit.rs
  • tests/support/reborn_parity_qa/qa_trace.rs
💤 Files with no reviewable changes (1)
  • crates/ironclaw_runner/src/tool_disclosure_bridge.rs

Comment thread crates/ironclaw_architecture/tests/reborn_runner_sheds.rs Outdated
Comment thread crates/ironclaw_architecture/tests/reborn_runner_sheds.rs Outdated
Comment thread crates/ironclaw_architecture/tests/reborn_runner_sheds.rs
Comment thread crates/ironclaw_architecture/tests/reborn_runner_sheds.rs Outdated
Comment thread crates/ironclaw_loop_host/src/driver_host_port_adapters.rs
Comment on lines +17 to +23
use crate::{
HostManagedModelError, HostManagedModelErrorKind, HostManagedModelGateway,
HostManagedModelMessage, HostManagedModelMessageRole, HostManagedModelRequest,
HostManagedModelResponse, HostManagedModelRouteSnapshot, HostManagedModelStreamSink,
HostManagedToolResultContent, ModelCost, StaticModelCostTable, ThreadBackedLoopContextPort,
ThreadBackedLoopModelPort, ThreadContextWindowCache,
};

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 | 🟠 Major | ⚡ Quick win

Add a decomposition tracking issue for the relocated gateway file.

crates/ironclaw_loop_host/src/model_gateway.rs reaches at least Line [3604]. The file is therefore over 3,000 lines at its new owner.

Add a linked decomposition issue and identify boundaries for provider conversion, route handling, error mapping, and tests. Keep the split separate if this PR must remain behavior-preserving.

As per coding guidelines, files over 3,000 lines require a decomposition tracking issue.

🤖 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_loop_host/src/model_gateway.rs` around lines 17 - 23, Add a
linked decomposition tracking issue for model_gateway.rs, documenting planned
boundaries for provider conversion, route handling, error mapping, and tests.
Keep this decomposition separate from the current behavior-preserving change and
reference the issue using the repository’s established tracking convention.

Source: Coding guidelines

Comment thread crates/ironclaw_loop_host/src/thread_resolving_model_gateway.rs Outdated
Comment thread docs/reborn/target-architecture/PROPOSAL.md
Comment thread tests/integration/coverage-floor.toml

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

🤖 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_architecture/tests/reborn_runner_sheds.rs`:
- Around line 54-57: The MOVED_ITEMS documentation and entries disagree about
whether module-private disclosure helpers are pinned. Update the inventory
around MOVED_ITEMS and the entries for CapabilityCatalog, PromotedSet,
DisclosureCaps, ActiveSet, and ToolTier so the documented contract is
consistent: either describe the tool-disclosure cluster as an intentionally
pinned package or retain only package-level types in the inventory.
- Around line 471-486: Hoist the loop-invariant crate_directory(&root, RUNNER)
call out of the per-file loop and compute the crate directory once before
iterating over runner entries. Reuse that value in the relative(path, ...) call
while preserving the existing matching and insertion behavior.
- Around line 582-583: Update the negative assertion in the relevant self-test
to query the fixture’s actual impl-header name, LlmProviderModelGateway, rather
than the absent SomethingNeverDefined name. Keep the assertion verifying that
defines does not treat the impl header as a definition, while preserving the
existing positive definition coverage.
- Around line 405-415: Update the documentation above dependency_names to
accurately describe cargo metadata --no-deps output: dependency["name"] is the
package identity, while rename is the optional manifest alias; alternatively,
change the helper to assert the intended package-name field. Keep the
implementation and renamed-dependency behavior consistent with the corrected
documentation.

In `@crates/ironclaw_loop_host/src/driver_host_port_adapters.rs`:
- Around line 480-525: Add tests in turn_error_to_host_error_tests for the
missing mappings: verify TurnError::Unauthorized produces
AgentLoopHostErrorKind::Unauthorized, and verify both TurnError::InvalidRequest
and TurnError::InvalidRunOriginAdapter produce
AgentLoopHostErrorKind::InvalidInvocation.

In `@crates/ironclaw_loop_host/src/model_gateway.rs`:
- Around line 17-23: Add a linked decomposition tracking issue for
model_gateway.rs, documenting planned boundaries for provider conversion, route
handling, error mapping, and tests. Keep this decomposition separate from the
current behavior-preserving change and reference the issue using the
repository’s established tracking convention.

In `@crates/ironclaw_loop_host/src/model_routes.rs`:
- Around line 50-117: Remove the identity-mirror struct ActiveModelRouteSettings
and its constructors/accessors, including from_active_settings on ModelRoute.
Add ModelRoute::from_llm_config accepting ironclaw_llm::LlmConfig and construct
the route directly from active_provider_id() and active_model_name(), preserving
existing validation through ModelRoute::new.
- Around line 415-443: Update validate_route_component and ModelRouteError so
validation failures preserve the underlying reason from
validate_model_route_component_value, including whether the value exceeds the
limit or contains a disallowed character. Add or populate a reason field when
constructing ModelRouteError instead of mapping the error to a reasonless
InvalidRoute; ensure Display or equivalent error reporting exposes that
preserved detail for validate_provider_id, validate_model_id, and
validate_version_token.

In `@crates/ironclaw_loop_host/src/thread_resolving_model_gateway.rs`:
- Around line 18-40: Keep the fields of ThreadResolvingLoopModelGateway private
and replace external struct-literal construction with a public owning-crate
factory, preferably ThreadResolvingLoopModelGateway::new accepting a typed
configuration object. Update ironclaw_runner’s loop_driver_host.rs construction
site to call that factory while preserving all existing initialization values
and invariants.

In `@docs/reborn/target-architecture/PROPOSAL.md`:
- Line 84: Reconcile the two pre-move ironclaw_runner line counts in this bullet
by selecting the authoritative 33.2k baseline used by the loop_host arithmetic,
or explicitly label 33.1k as the older measurement. Update the opening count and
related “was” wording so §2.4 and the checklist derive from one consistent
baseline.

In `@tests/integration/coverage-floor.toml`:
- Around line 71-95: Restore artifact-derived absolute coverage ratchets in both
the ironclaw_runner entry and the ironclaw_loop_host entry: add
floor_covered_lines and captured_total_lines values from the merged coverage
artifact, without estimating them. Preserve the existing percentage floors and
rationale, or document an explicit bounded exception if artifact values cannot
be added.
🪄 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: 90d555a9-a0a0-4878-928f-ee31d5754a70

📥 Commits

Reviewing files that changed from the base of the PR and between e9e738c and 47869fa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (55)
  • .github/workflows/code_style.yml
  • crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • crates/ironclaw_architecture/tests/reborn_runner_sheds.rs
  • crates/ironclaw_architecture/tests/reborn_struct_test_support_ratchet.rs
  • crates/ironclaw_hooks/Cargo.toml
  • crates/ironclaw_loop_host/AGENTS.md
  • crates/ironclaw_loop_host/CLAUDE.md
  • crates/ironclaw_loop_host/Cargo.toml
  • crates/ironclaw_loop_host/src/context_shadow.rs
  • crates/ironclaw_loop_host/src/driver_host_port_adapters.rs
  • crates/ironclaw_loop_host/src/driver_host_port_adapters/tests.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_loop_host/src/model_gateway.rs
  • crates/ironclaw_loop_host/src/model_gateway/prompt_cache_activity.rs
  • crates/ironclaw_loop_host/src/model_gateway_error_mapping.rs
  • crates/ironclaw_loop_host/src/model_routes.rs
  • crates/ironclaw_loop_host/src/thread_resolving_model_gateway.rs
  • crates/ironclaw_loop_host/src/tool_disclosure.rs
  • crates/ironclaw_loop_host/src/tool_disclosure_mode.rs
  • crates/ironclaw_loop_host/src/tool_disclosure_port.rs
  • crates/ironclaw_loop_host/tests/llm_gateway.rs
  • crates/ironclaw_loop_host/tests/model_routes.rs
  • crates/ironclaw_product/tests/inbound_turn_contract.rs
  • crates/ironclaw_product/tests/support/planned_agent_loop.rs
  • crates/ironclaw_reborn_composition/src/factory/production_backend_assembly.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
  • crates/ironclaw_reborn_composition/src/model_gateway_assembly.rs
  • crates/ironclaw_reborn_composition/src/root/product_live_adapters.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/default_system_prompt.rs
  • crates/ironclaw_reborn_composition/src/runtime_input.rs
  • crates/ironclaw_reborn_composition/tests/product_live_adapters.rs
  • crates/ironclaw_reborn_composition/tests/trigger_poller_e2e.rs
  • crates/ironclaw_runner/AGENTS.md
  • crates/ironclaw_runner/CLAUDE.md
  • crates/ironclaw_runner/Cargo.toml
  • crates/ironclaw_runner/src/lib.rs
  • crates/ironclaw_runner/src/loop_driver_host.rs
  • crates/ironclaw_runner/src/loop_driver_host/thread_scope_tests.rs
  • crates/ironclaw_runner/src/runtime.rs
  • crates/ironclaw_runner/src/tool_disclosure_bridge.rs
  • docs/reborn/target-architecture/CHECKLIST.md
  • docs/reborn/target-architecture/PLAN.md
  • docs/reborn/target-architecture/PROPOSAL.md
  • docs/reborn/target-architecture/families/loop.md
  • scripts/ci/ws12_workflow_contracts.py
  • scripts/no_panics_reborn_baseline.txt
  • scripts/reborn-e2e-rust.sh
  • tests/integration/coverage-floor.toml
  • tests/integration/support/builder.rs
  • tests/integration/support/group.rs
  • tests/integration/support/group_options.rs
  • tests/integration/support/triggered_submit.rs
  • tests/support/reborn_parity_qa/qa_trace.rs
💤 Files with no reviewable changes (1)
  • crates/ironclaw_runner/src/tool_disclosure_bridge.rs
🛑 Comments failed to post (2)
crates/ironclaw_loop_host/src/model_routes.rs (2)

50-117: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether ActiveModelRouteSettings has an independent contract
# (e.g. persisted/serialized on its own) or is only ever bridged into ModelRoute.
rg -n -C3 'ActiveModelRouteSettings' --type=rust

Repository: nearai/ironclaw

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -u

echo "== Locate model_routes.rs and related symbols =="
fd -a 'model_routes\.rs$' . || true
rg -n "ActiveModelRouteSettings|ModelRoute|LlmConfig|LlmProvider" crates --type=rust || true

echo "== File tree snippets =="
for f in $(rg -l "ActiveModelRouteSettings|struct ModelRoute|impl ModelRoute" crates --type=rust || true); do
  echo "--- $f"
  sed -n '1,180p' "$f"
done

echo "== Dependency references =="
rg -n "ironclaw_llm|ModelRouter|LlmConfig|active_provider_id|active_model_name" crates/ironclaw_loop_host --type=rust || true

Repository: nearai/ironclaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -u

echo "== exact ActiveModelRouteSettings occurrences outside generated output =="
rg -n "ActiveModelRouteSettings" crates --type=rust || true

echo "== model_routes.rs source =="
sed -n '1,180p' crates/ironclaw_loop_host/src/model_routes.rs

echo "== model_gateway_assembly LlmConfig dependency source =="
sed -n '1,190p' crates/ironclaw_reborn_composition/src/model_gateway_assembly.rs

Repository: nearai/ironclaw

Length of output: 13411


Remove ActiveModelRouteSettings as an identity mirror.

ActiveModelRouteSettings and ModelRoute both hold provider_id/model_id, validate identically, and only bridge into each other. Per the repo rule: mirror structs are allowed only when wire/API and internal representations evolve independently; field-for-field identity mirrors without that rationale are a violation. Add ModelRoute::from_llm_config directly from ironclaw_llm::LlmConfig and remove ActiveModelRouteSettings.

The ActiveModelRouteSettings::from_llm_config dependency is the only current direct IronClaw LlmConfig usage from ironclaw_loop_host, so eliminating it keeps that dependency from spreading beyond where the model gateway is assembled.

🤖 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_loop_host/src/model_routes.rs` around lines 50 - 117, Remove
the identity-mirror struct ActiveModelRouteSettings and its
constructors/accessors, including from_active_settings on ModelRoute. Add
ModelRoute::from_llm_config accepting ironclaw_llm::LlmConfig and construct the
route directly from active_provider_id() and active_model_name(), preserving
existing validation through ModelRoute::new.

Sources: Coding guidelines, Learnings


415-443: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

validate_route_component drops the validation cause.

validate_model_route_component_value(...).map_err(|_| ModelRouteError::new(ModelRouteErrorKind::InvalidRoute)) discards the underlying reason (length vs. disallowed character) from all three callers (validate_provider_id, validate_model_id, validate_version_token). ModelRouteError has no reason field, so the detail is unrecoverable; Display only ever prints "invalid_route".

Preserve the cause: add a reason: String field to ModelRouteError (or log the source error before mapping) instead of discarding it with map_err(|_| ...).

🛠️ Proposed fix to preserve the cause
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ModelRouteError {
     kind: ModelRouteErrorKind,
+    reason: String,
 }

 impl ModelRouteError {
-    fn new(kind: ModelRouteErrorKind) -> Self {
-        Self { kind }
+    fn new(kind: ModelRouteErrorKind, reason: impl Into<String>) -> Self {
+        Self { kind, reason: reason.into() }
     }
 }

 impl fmt::Display for ModelRouteError {
     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-        formatter.write_str(self.kind.as_str())
+        write!(formatter, "{}: {}", self.kind.as_str(), self.reason)
     }
 }

 fn validate_route_component(
     label: &'static str,
     value: String,
     max_bytes: usize,
     allowed: impl Fn(char) -> bool,
 ) -> Result<String, ModelRouteError> {
     let trimmed = value.trim().to_string();
     validate_model_route_component_value(label, &trimmed, max_bytes, allowed)
-        .map_err(|_| ModelRouteError::new(ModelRouteErrorKind::InvalidRoute))?;
+        .map_err(|error| ModelRouteError::new(ModelRouteErrorKind::InvalidRoute, error.to_string()))?;
     Ok(trimmed)
 }
🤖 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_loop_host/src/model_routes.rs` around lines 415 - 443, Update
validate_route_component and ModelRouteError so validation failures preserve the
underlying reason from validate_model_route_component_value, including whether
the value exceeds the limit or contains a disallowed character. Add or populate
a reason field when constructing ModelRouteError instead of mapping the error to
a reasonless InvalidRoute; ensure Display or equivalent error reporting exposes
that preserved detail for validate_provider_id, validate_model_id, and
validate_version_token.

Source: Coding guidelines

@BenKurrek

Copy link
Copy Markdown
Collaborator Author

CI triage — both failures were gates catching real gaps, and both are fixed

The first run had two red lanes; Reborn E2E, Platform & Compat, IronClaw Stress, Regression Test Check and PR: History Check were all green. (Code Style is a roll-up — its only content was fast-checks failed, i.e. #2 below, not a separate defect.)

1. Detect Reborn test scope → unclassified pull-request path: .claude/commands/trace.md (1f8c0ba018)

Not caused by this PR's moves: scripts/ci/reborn_pr_test_plan.py has no rule for .claude/ on main either, and its fail-closed arm raises on the first unclassified path. The practical effect is that any PR editing a skill, a command, or a rule fails the whole Tests (Reborn) roll-up and skips every downstream Reborn lane — on a documentation-only change. The planner landed 2026-08-02 (#6952); #7037 touched four .claude/ files and merged the next day, so this has been live and unhit for about a day.

Fix: classify .claude/ alongside docs/ in IGNORED_PREFIXES. That is adding a missing classification, not loosening the gate — the fail-closed arm is untouched, and classify-test-scope.sh already treats the same paths as docs_only=true, so the two detectors now agree. Two regression tests, both verified red by reverting the classification: guidance paths are accepted and select no lane, and a guidance edit riding along with a crate change still selects that crate's lane (the ignore is per-path, not per-PR).

2. Fast deterministic checks → six fixture .unwrap()s read as production (4f46b2d635)

check_no_panics.py's has_cfg_test_module_declaration only recognises a flat #[path = "x.rs"]. A module declared in a non-mod.rs file must spell the directory (#[path = "loop_driver_host/thread_scope_tests.rs"]), which the regex misses — so the carved test file classified as production and its fixture helpers tripped the delta scan.

Fix: inline the module, which is also this file's own convention (loop_driver_host.rs already carries four inline #[cfg(test)] modules) and keeps the three test paths byte-identical. Runner roster still 259.

Reported, not fixed here: that scanner gap is pre-existing and latent for the two sibling files declared the same way (tests.rs, compaction_tests.rs). Neither has tripped it because their panics sit under item-level #[cfg(test)] attributes the scanner does track — mine were free helper fns gated only at the declaration site. Widening the regex changes a security-adjacent gate's classification and has panic-baseline implications, so it wants its own slice.

Re-verified locally on 4f46b2d635: cargo fmt --check, workspace cargo check --all-targets --all-features, per-crate clippy -D warnings on runner + loop_host, cargo test -p ironclaw_architecture 32/32, and all three check_no_panics.py modes (--base origin/main --head HEAD, --reborn-baseline, --self-test).

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7064 August 3, 2026 14:44 Destroyed
BenKurrek and others added 3 commits August 3, 2026 10:46
…oud inventory

Both pre-existing on main, both found by this PR: the PR test planner's
missing `.claude/` classification (fixed here) and `check_no_panics.py`'s
flat-only `#[path]` recognition (reported, sidestepped by inlining).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Round 2 surfaced the next `unmapped test or CI path`:
`scripts/no_panics_reborn_baseline.txt` and `scripts/reborn-e2e-rust.sh`.
That arm is deliberate — repo-root `scripts/` is not prefix-classified, so
each file gets a decision rather than a blanket ignore — and both decisions
are recorded beside the constant: the panic baseline is owned end-to-end by
Code Style's `check_no_panics.py --reborn-baseline`, and the E2E selector
script is driven by the `Reborn E2E` workflow, which has its own scope
detector and which this planner does not schedule.

The self-test asserts both halves: the two decided paths are accepted and
select no lane, AND an undecided sibling still refuses — so the fix cannot
drift into the blanket prefix the arm exists to prevent.

Process note: discovering these one CI round at a time is avoidable. Running
the planner locally over the PR's own changed-path set finds every unclassified
path in one pass; the whole set now plans as `selected` over 3 buckets, root
partition 0, and integration lanes 0 and 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-vacuous scan self-test

Five findings from the CodeRabbit pass, all accepted:

- **`ThreadResolvingLoopModelGateway` fields go back to private.** The shed had
  turned eleven `pub(super)` fields into `pub` so the runner's struct literal
  kept compiling — a real widening, and against root `CLAUDE.md`'s
  "module-specific initialization must live in the owning crate as a public
  factory". Replaced by `ThreadResolvingLoopModelGatewayParts` + a `new` that
  destructures it: the caller still gets a compile error when a field is added
  (the property the `pub`-fields shape had) without any downstream crate being
  able to assemble a gateway outside the host's construction path.
- **The definition scanner's impl-header self-test was vacuous.** It asserted
  on a name absent from the fixture, so a regression that accepted `impl X for
  Y` headers as definitions would have stayed green. The fixture now carries a
  name that appears ONLY as an impl target.
- **The `cargo metadata` rename comment described behaviour the code does not
  have.** It claimed the helper resolves through `rename`; it reads `name`,
  which under `--no-deps` is the package identity — which is exactly why a
  renamed edge cannot hide. Comment corrected to the real mechanism.
- **`MOVED_ITEMS`' doc contradicted its own contents** on the five `pub(crate)`
  tool-disclosure types. They are pinned deliberately: visibility is not the
  criterion, membership in the moved unit's contract is, and a half-move that
  left one behind would compile.
- **`turn_error_to_host_error` gained the two uncovered arms**, `Unauthorized`
  and the two request-shaped variants. The function moved crates and became
  `pub` in this PR, so the security-relevant arm was newly reachable and
  untested. Red-then-green verified by reclassifying `Unauthorized` to
  `InvalidInvocation`: that test alone fails, and only it.

Also hoisted a loop-invariant `crate_directory` walk out of the residue scan's
per-file loop.

Rosters: runner 259 (unchanged), loop_host 771 -> 773 (the two new tests).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Review pass — five findings, all accepted and fixed (dc190de227)

1. ThreadResolvingLoopModelGateway's fields were widened to pub. Reverted properly. This is the strongest of the five and it was my shortcut: the port implementation moved crates, the runner constructs it with a struct literal, and turning eleven pub(super) fields into pub was the path of least resistance. It is also against this repo's own rule — root CLAUDE.md: "Module-specific initialization logic … must live in the owning crate as a public factory function". Fixed with ThreadResolvingLoopModelGatewayParts + new(parts) that destructures it. The params struct keeps the property the pub-fields shape had — adding a field is a compile error at both call sites — while removing the one it should never have had: no downstream crate can now assemble a gateway outside the host's construction path, or mutate one after construction. A new with eleven positional arguments would have traded the widening for a too_many_arguments exemption, which is why it is a params struct.

2. The definition scanner's impl-header self-test was vacuous. It asserted !defines(…, "SomethingNeverDefined") — a name absent from the fixture — while claiming to prove that an impl X for Y header is a reference, not a definition. A regression that accepted impl headers would have stayed green. The fixture now carries impl ImplementedButNeverDefined for Thing, a name present only as an impl target, and the assertion names it. This is the same failure mode the PR body describes for doubles that ignore an argument; good catch.

3. The cargo metadata comment promised behaviour the code does not have. It said the helper resolves through rename; it reads dependency["name"]. Under --no-deps that field is the package identity and rename is the optional alias — so reading name is precisely what makes a renamed edge (llm = { package = "ironclaw_llm" }) unable to hide from the assertions. The behaviour was right and the comment was wrong; the comment now states the real mechanism.

4. MOVED_ITEMS' doc contradicted its own contents. It said module-private helpers are excluded, then pinned five pub(crate) tool-disclosure types. Those pins are deliberate — visibility is not the criterion, membership in the moved unit's contract is, and a half-move that left CapabilityCatalog behind in the runner would compile. Doc rewritten to say that.

5. turn_error_to_host_error gained its two uncovered arms. Unauthorized and the two request-shaped variants (InvalidRequest, InvalidRunOriginAdapter) had no test. That matters more after this PR than before it: the function moved crates and became pub, so the security-relevant arm is newly reachable from outside. Red-then-green verified by mutating the mapper — reclassifying Unauthorized to InvalidInvocation fails unauthorized_maps_to_unauthorized and only that test.

Also applied: hoisted a loop-invariant crate_directory walk out of the residue scan's per-file loop.

Not actioned, with reasons. The >3,000-line decomposition-issue suggestion on model_gateway.rs: the file is 3,634 lines and was already that size in ironclaw_runner; it carries the repo's own // arch-exempt: large_file, … plan #4088 marker, which travelled with the move, and splitting it in a move-only PR is exactly what the suggestion itself says to keep separate. CHECKLIST WS4's loop_host re-charter row already owns internal splits in this crate (it names capability_port.rs's five roles); the gateway now sits beside it as the second candidate.

Rosters after the fixes: runner 259 (unchanged), loop_host 771 → 773 (the two new tests, nothing removed). fmt clean; per-crate clippy -D warnings --all-targets --all-features clean on loop_host, runner, architecture; workspace cargo check --all-targets --all-features clean; reborn_runner_sheds 8/8.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7064 August 3, 2026 14:56 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

♻️ Duplicate comments (2)
docs/reborn/target-architecture/PROPOSAL.md (1)

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

Use one authoritative ironclaw_runner baseline.

The bullet still opens with 33.1k lines, but the same paragraph states that the post-shed crate was 22.0k, was 33.2k. The loop-host arithmetic also uses 33.2k. Change the opening figure to 33.2k or label 33.1k as an older measurement.

Proposed wording
-- **ironclaw_runner** (✎ **33.1k** lines, was 38.3k):
+- **ironclaw_runner** (✎ **33.2k** lines, was 38.3k):
🤖 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/reborn/target-architecture/PROPOSAL.md` at line 84, Update the opening
ironclaw_runner size in this bullet from 33.1k to the authoritative 33.2k
baseline, keeping the existing post-shed 22.0k figure and loop-host arithmetic
consistent.
crates/ironclaw_loop_host/src/model_gateway.rs (1)

25-31: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep the decomposition issue linked for the relocated gateway file.

crates/ironclaw_loop_host/src/model_gateway.rs still exceeds 3,000 lines; the file reaches at least Line [3604]. This PR changes the owner but does not show a decomposition tracking issue for the new owner. Add a linked issue with boundaries for provider conversion, route handling, error mapping, and tests.

As per coding guidelines, files over 3,000 lines require a decomposition tracking issue.

🤖 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_loop_host/src/model_gateway.rs` around lines 25 - 31, Add a
decomposition-tracking issue reference for the relocated model gateway owner,
covering separate boundaries for provider conversion, route handling, error
mapping, and tests. Place the reference near the module’s existing ownership or
documentation context in model_gateway.rs, without changing the imported symbols
or implementation behavior.

Source: Coding guidelines

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

Inline comments:
In `@scripts/ci/test_reborn_pr_test_plan.py`:
- Around line 366-387: Update both
`scripts/ci/classify-test-scope.sh:is_docs_only_path()` and
`scripts/ci/test-classify-test-scope.sh` so guidance files under `.claude/**`,
including nested Markdown and `CLAUDE.md`/`AGENTS.md` paths, are consistently
classified as docs-only. Preserve the resulting no-test-lane behavior validated
by `test_agent_guidance_is_classified_and_selects_no_rust_lane`.

In `@tests/integration/group_triggers/scenario_trigger_self_create_denied.rs`:
- Line 13: Update the module reference in the test documentation around
validate_provider_tool_call to point to its defining module,
ironclaw_loop_host::capability_port::provider_validation, or the corresponding
provider_validation.rs file, instead of ironclaw_loop_host::model_gateway.

---

Duplicate comments:
In `@crates/ironclaw_loop_host/src/model_gateway.rs`:
- Around line 25-31: Add a decomposition-tracking issue reference for the
relocated model gateway owner, covering separate boundaries for provider
conversion, route handling, error mapping, and tests. Place the reference near
the module’s existing ownership or documentation context in model_gateway.rs,
without changing the imported symbols or implementation behavior.

In `@docs/reborn/target-architecture/PROPOSAL.md`:
- Line 84: Update the opening ironclaw_runner size in this bullet from 33.1k to
the authoritative 33.2k baseline, keeping the existing post-shed 22.0k figure
and loop-host arithmetic consistent.
🪄 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: 60fc246b-c3ef-4215-a6ab-3375acebb1c3

📥 Commits

Reviewing files that changed from the base of the PR and between 47869fa and 4f46b2d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (10)
  • .claude/commands/trace.md
  • crates/ironclaw_loop_host/src/model_gateway.rs
  • crates/ironclaw_runner/src/loop_driver_host.rs
  • docs/reborn/engine-v2-to-reborn-parity.md
  • docs/reborn/target-architecture/CHECKLIST.md
  • docs/reborn/target-architecture/PROPOSAL.md
  • scripts/ci/reborn_pr_test_plan.py
  • scripts/ci/test_reborn_pr_test_plan.py
  • tests/integration/group_triggers/scenario_trigger_self_create_denied.rs
  • tests/integration/tool_disclosure.rs

Comment on lines +366 to +387
def test_agent_guidance_is_classified_and_selects_no_rust_lane(self) -> None:
"""`.claude/**` is prose, like `docs/**`.

Regression for the gap #7064 hit: the planner had no rule for
`.claude/`, so its fail-closed arm rejected any PR editing a skill, a
command, or a rule — failing the whole `Tests (Reborn)` roll-up on a
documentation-only change. The assertion is deliberately paired: the
path must be *accepted* AND must select no Rust lane, so a future
"classification" that quietly turns guidance edits into a full matrix
fails here too.
"""
for path in (
".claude/commands/trace.md",
".claude/rules/testing.md",
".claude/skills/reborn-feature/SKILL.md",
):
with self.subTest(path=path):
plan = self.plan("pull_request", [path])
self.assertEqual(plan["mode"], "none", path)
self.assertEqual(plan["crate_buckets"], [], path)
self.assertEqual(plan["root_partitions"], [], path)
self.assertEqual(plan["integration_lanes"], [], path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)(test_reborn_pr_test_plan\.py|classify-test-scope\.sh|.*no_rust_lane|.*reborn|.*scope|claude)' || true

echo
echo "== outline target =="
ast-grep outline scripts/ci/test_reborn_pr_test_plan.py --view expanded || true

echo
echo "== relevant lines test =="
sed -n '330,420p' scripts/ci/test_reborn_pr_test_plan.py

echo
echo "== classifier outline/lines =="
sed -n '1,240p' scripts/ci/classify-test-scope.sh

Repository: nearai/ironclaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== search classify-test-scope usage =="
rg -n "classify-test-scope|is_docs_only_path|TEST_PLAN|reborn|scope" scripts .github . -g '!**/*.csv' -g '!**/*.md' | head -n 250 || true

echo
echo "== deterministic shell path function probe =="
python3 - <<'PY'
from pathlib import Path

script = Path('scripts/ci/classify-test-scope.sh').read_text()
print('classify-test-scope exists:', Path('scripts/ci/classify-test-scope.sh').exists())
if 'is_docs_only_path' not in script:
    print('no is_docs_only_path found')
else:
    # Extract relevant shell control around is_docs_only_path until next top-level function or final fi? Use simple brace/line.
    lines = script.splitlines()
    start = next(i for i,l in enumerate(lines) if 'is_docs_only_path' in l)
    # Print with line numbers
    for i,l in enumerate(lines[max(0,start-20):start+120], start=max(0,start-20)-19):
        print(f'{i:04d}: {l}')
PY

Repository: nearai/ironclaw

Length of output: 30795


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' scripts/ci/classify-test-scope.sh

Repository: nearai/ironclaw

Length of output: 5237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== classify-test-scope tests relevant =="
sed -n '1,260p' scripts/ci/test-classify-test-scope.sh

echo
echo "== workflow references to classify-test-scope =="
rg -n "classify-test-scope|TEST_PLAN|changed_paths|only|workflow_dispatch|pull_request|merge_group|reborn-tests" .github/workflows scripts/ci -g '*.{yml,yaml,sh,py}' | head -n 220 || true

echo
echo "== shell is_docs_only_path behavior for nested paths =="
cat >/tmp/probe_shell.sh <<'BASH'
set -euo pipefail
is_docs_only_path() {
  local path="$1"
  case "$path" in
    docs/*|.github/ISSUE_TEMPLATE/*|.github/pull_request_template.md)
      return 0
      ;;
    *.md)
      case "$path" in
        */*) return 1 ;;
        *) return 0 ;;
      esac
      ;;
    *).
      return 1
      ;;
  esac
}
for path in .claude/commands/trace.md .claude/rules/testing.md .claude/skills/reborn-feature/SKILL.md docs/reborn/README.md README.md; do
  if is_docs_only_path "$path"; then echo "$path docs_only"; else echo "$path !docs_only"; fi
done
BASH
bash /tmp/probe_shell.sh
rm /tmp/probe_shell.sh

Repository: nearai/ironclaw

Length of output: 32418


Cover nested .claude/** in the live scope classifier.

scripts/ci/test-classify-test-scope.sh uses .claude/commands/trace.md as docs-only, but scripts/ci/classify-test-scope.sh:is_docs_only_path() treats nested Markdown as code and exits non-docs. Add the CLAUDE.md / AGENTS.md invariants for .claude/** guidance paths to both classifiers.

🤖 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 366 - 387, Update both
`scripts/ci/classify-test-scope.sh:is_docs_only_path()` and
`scripts/ci/test-classify-test-scope.sh` so guidance files under `.claude/**`,
including nested Markdown and `CLAUDE.md`/`AGENTS.md` paths, are consistently
classified as docs-only. Preserve the resulting no-test-lane behavior validated
by `test_agent_guidance_is_classified_and_selects_no_rust_lane`.

Source: Path instructions

//!
//! Traced, not assumed: denial happens at the model-gateway seam
//! (`ironclaw_runner::model_gateway`'s `validate_provider_tool_call`, via
//! (`ironclaw_loop_host::model_gateway`'s `validate_provider_tool_call`, via

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

bash scripts/codebase-graph.sh status || true
rg -n -C 2 '\bfn validate_provider_tool_call\b|mod provider_validation' \
  crates/ironclaw_loop_host/src

Repository: nearai/ironclaw

Length of output: 5564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'scenario_trigger_self_create_denied.rs' . | sed 's#^\./##'
file="$(fd 'scenario_trigger_self_create_denied.rs' . | head -n1)"
echo "FILE=$file"
wc -l "$file"
sed -n '1,80p' "$file"
rg -n 'validate_provider_tool_call|provider_validation|model_gateway' .

Repository: nearai/ironclaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== module declarations =="
sed -n '44,52p' crates/ironclaw_loop_host/src/capability_port.rs
sed -n '240,250p' crates/ironclaw_loop_host/src/capability_port.rs

echo
echo "== definition =="
sed -n '1,35p' crates/ironclaw_loop_host/src/capability_port/provider_validation.rs

echo
echo "== public symbols in crate lib =="
sed -n '1,220p' crates/ironclaw_loop_host/src/lib.rs | grep -n 'pub (super|crate|mod)|capability_port|model_gateway' | head -n 80
rg -n 'pub (use|mod|struct|enum|type|trait|fn)\b.*validate_provider_tool_call|\bvalidate_provider_tool_call\b' crates/ironclaw_loop_host/src/lib.rs crates/ironclaw_loop_host/src

echo
echo "== call sites in loop_host =="
rg -n --glob '*.rs' 'validate_provider_tool_call|provider_validation' crates/ironclaw_loop_host/src -C 2

Repository: nearai/ironclaw

Length of output: 2564


Correct the validate_provider_tool_call reference.

This integration test violates the “Module specs win ties” repo invariant by pointing validate_provider_tool_call at ironclaw_loop_host::model_gateway. The function is defined under ironclaw_loop_host::capability_port::provider_validation; use the defining module/filename or crates/ironclaw_loop_host/src/capability_port/provider_validation.rs.

🤖 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/group_triggers/scenario_trigger_self_create_denied.rs` at
line 13, Update the module reference in the test documentation around
validate_provider_tool_call to point to its defining module,
ironclaw_loop_host::capability_port::provider_validation, or the corresponding
provider_validation.rs file, instead of ironclaw_loop_host::model_gateway.

@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Round 3: the two fixed lanes are green. The one red lane is #7066's defect, reproduced on this tree — with local evidence that it is not this PR.

At head dc190de227: Code Style ✅, Platform & Compat ✅, IronClaw Stress ✅, Regression Test Check ✅, PR: History Check ✅. And the one that matters most — Detect Reborn test scope now SUCCEEDS, so the Reborn lanes actually ran for the first time on this PR (rounds 1 and 2 skipped all of them behind the two planner defects).

Reborn integration tests (0) and (1) fail with error: no such command: llvm-cov. That is #7066's defect exactly, and this tree has it: scripts/ci/run-hermetic-test-process.sh:61-62 allowlists REBORN_COV_LANE_INDEX|MODE|PARTITIONS|TEST_TIMEOUT but **not REBORN_COV_COLLECT``, and scripts/ci/reborn-coverage-lane-run.sh:53reads${REBORN_COV_COLLECT:-true}— so the stripped variable defaults coverage back **on** and callscargo llvm-cov, whose install step was correctly skipped because the plan resolved coverage_mode: none`. It fires only when coverage is off and the lane has a non-empty suite list, which is why it presents as "some lanes red, some green" and reads like a flaky integration test.

I am not duplicating the one-token fix — #7066 is MERGEABLE with 24/24 checks passing. This PR needs a merge of main once it lands.

What I did instead: ran the affected lanes locally, so this PR's evidence does not depend on another PR. Both lane-0 and lane-1 suite lists, computed from reborn-coverage-int-tier-tests.sh with the same modulo-4 partitioning the workflow uses:

suites passed failed
lane 0 + lane 1, complete 27 461 4

All four failures are the same environment gate, not behaviour: case_2_postgres variants in extension_delivery (×2), backend_matrix (×1) and extension_ingress (×1), each with the explicit "StorageMode::Postgres requires a reachable Docker daemon" message. CI has Docker; this laptop does not.

The suite that most directly exercises the moved code — reborn_integration_tool_disclosure, which sits in the failing lane 1 — is 26/26 green, as are tool_call (30), triggered_submit (17), mcp (22), profile (14), safety (15), subagent_await_edge (2), wiring_parity (18) and generated_gate_sequences (29).

One more thing worth recording for the next slice: Rust Reborn in Reborn E2E was skipped by scope, so the repointed selector (-p ironclaw_runner → -p ironclaw_loop_host for llm_gateway) has not run in CI. All 10 selectors were verified locally under bash … < /dev/null, each matching exactly one test, which is the stronger check — but it is local-only evidence and the PR body should be read that way.

BenKurrek added a commit that referenced this pull request Aug 4, 2026
Reconciles this lane with #7064, which landed the parallel WS3/WS4 runner
sheds and edited the same coordination files.

Five conflicts, all in shared coordination surfaces — no code move in this
PR was altered (all 117 PR-only files are byte-identical to the pre-merge
tip; the 5 apparent diffs are deletions absent on both sides).

- `reborn_dependency_boundaries.rs`: the array auto-merged to the union of
  both sides' removals; only `WS0_LAYER_MATRIX_EXCEPTION_BASELINE`
  conflicted. Recomputed as `len()` of the merged list — 13 base, minus
  #7064's three (`hooks -> wasm_limiter`, `runner -> agent_loop`,
  `runner -> loop_host`) and this PR's three (`mcp -> extensions`,
  `scripts -> extensions`, `scripts -> resources`), plus this PR's
  justified `ironclaw_sandbox -> ironclaw_resources` = **8**. Counted by
  parsing only the entries between the const and its closing `];`, so the
  struct definition and the four test fixtures are excluded.
- `loop_host/Cargo.toml`: both sides added a `[dev-dependencies]` line;
  kept both (`http` from main, `ironclaw_sandbox` from this PR).
- `CHECKLIST.md`: kept both dated amendments in date order — #7064's
  `13 -> 10` and this PR's, with its count corrected from the
  authored-in-isolation `11` to the merged `8` exactly as the §11.2.2 row
  instructs. Also kept this PR's two new coverage-gate rows beside main's
  amended loud-inventory row.
- `reborn_pr_test_plan.py`: both sides made the same `.claude/` fix; took
  main's landed wording (`startswith` makes tuple order irrelevant).
- `test_reborn_pr_test_plan.py`: union of both sides' new tests, no name
  collisions — 46 tests pass.

Also repointed the one PR-authored `changed-coverage-exemptions.toml`
entry the merge shifted: `hosted_mcp.rs` moved +1 because main added a
doc-comment line, so its line-keyed exemption now resolves to
byte-identical source lines. The other stale entries in that manifest are
inherited and already stale on main; left untouched.

Verified: architecture suite 206 passed / 0 failed, `cargo check
--all-targets` clean, `cargo fmt` a no-op, zero conflict markers, and both
sides' `coverage-floor.toml` recaptures intact (runner 82.53 from #7064;
host_runtime 88.65 and the new ironclaw_sandbox 87.09 from this PR).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Aug 4, 2026
Reconciles this lane with #7064, which landed the parallel WS3/WS4 runner
sheds and edited the same coordination files.

One conflict: `WS0_LAYER_MATRIX_EXCEPTION_BASELINE` in
`reborn_dependency_boundaries.rs`. The array itself auto-merged to the
union of both sides' removals — #7064's three (`hooks -> wasm_limiter`,
`runner -> agent_loop`, `runner -> loop_host`) and this PR's one
(`host_runtime -> skills`). Recomputed the constant as `len()` of the
merged list: main is at 10, so this slice takes it to **9**. Counted by
parsing only the entries between the const and its closing `];`, which
excludes the struct definition and the four test fixtures.

This slice was authored off 13 and computed `13 -> 12` in isolation; the
ratchet narrative and the two doc rows that quoted that figure (PLAN's
"First-party tools" bullet and CHECKLIST's W7-progress row) now read
`10 -> 9` and record why, per the union rule on the CHECKLIST §11.2.2 row.
The edge deleted is unchanged; only the total moved.

Everything else auto-merged and was verified rather than assumed: both
sides' `coverage-floor.toml` recaptures are intact (runner 82.53 and the
new `ironclaw_loop_host` 90.89 from #7064; `host_runtime` 88.59 from this
PR), and both sides' dated amendments survive in CHECKLIST, PROPOSAL and
PLAN. All 14 PR-only files are byte-identical to the pre-merge tip, so no
code move was altered.

Verified: architecture suite 206 passed / 0 failed, `cargo check
--all-targets` clean, `cargo fmt` a no-op, zero conflict markers, and the
changed-coverage manifest validates with no exemption stranded or shifted
by this merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Aug 4, 2026
`Detect Reborn test scope` exits 1 on any pull request whose diff holds a
path `reborn_pr_test_plan.py` has no rule for, which made this PR
unmergeable: it must edit `Dockerfile` (the moved directory's
`COPY wit/ wit/` no longer resolves) and `scripts/check-version-bumps.sh`
(the ABI gate would otherwise grep dead paths and silently stop
enforcing). 18 of its 46 paths were unclassified.

Same class as the `.claude/` gap #7064 fixed, and classified the same
way — one rule per class, recorded beside the constant:

  * `Dockerfile` / `.dockerignore` — `platform-and-compat.yml` keys
    `has_docker_risk` off exactly this pair and owns the image build.
  * `.githooks/**` — Code Style triggers on the tree and lints its
    contents (`test-ci-comm-locale-pin.sh`); no Reborn lane runs a hook.
  * `scripts/{build-wasm-extensions,check-version-bumps}.sh` —
    `platform-and-compat.yml`'s `has_direct_wasm_abi_risk` classifier
    both scopes and runs them.
  * markdown owned by no crate (`crates/AGENTS.md`,
    `test-tools/README.md`) — prose, like `docs/` and `.claude/`. A
    crate-resident doc still selects its own crate's lane.

The first-party extension package assets are deliberately NOT ignored.
`crates/extensions/packages/*/wasm/*.wasm` is a shipped artifact that
`ironclaw_extension_support` embeds with `include_bytes!`, and
`test-tools/*/manifest.toml` is `include_str!`d by
`ironclaw_extension_host`. Calling either prose would convert today's
loud failure into a silent under-schedule of a change to production
output — the WS10 failure mode. `EMBEDDED_ASSET_OWNERS` routes each tree
to the crate that compiles it instead, so this PR now additionally
schedules `ironclaw_extension_{support,host,manager}`: the crates that
consume the six rebuilt WASM artifacts.

Also fixes #7085 in a file this PR already touches. The WIT version
extractors used the GNU-only BRE `\+`, so on BSD sed (macOS) they matched
nothing, and because the `WIT_TOOL_VERSION` cross-check is guarded on a
non-empty version the hook printed "All version checks passed" having
compared nothing. `[[:space:]][[:space:]]*` is identical under GNU sed,
so the enforced Linux CI lane is unchanged; verified on BSD sed that both
`wit/tool.wit` (0.3.0) and `wit/channel.wit` (0.3.1) now extract.

Regression tests: every classified class gets a case in
`test_reborn_pr_test_plan.py`, including the paired assertion that the
embedded assets *select a lane* rather than merely being accepted (the
inverse of the `.claude/` prose test), and a staleness pin that fails if
an asset tree or its owning crate moves. All ten new cases fail against
the planner on `main`. `test_unclassified_build_input_fails_fast` moves
off `Dockerfile` onto a still-undecided input so the fail-closed arm
stays exercised.

Refs #7087, #7085

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Aug 4, 2026
Two review findings, both correct, both artifacts of this PR's own renames.

1. engine-v2-to-reborn-parity.md note 4 read "a native script/software
   execution lane (`ironclaw_sandbox`, `RuntimeKind::Script`) sandboxed via
   `ironclaw_sandbox`" -- self-referential after the merge collapsed
   ironclaw_scripts and ironclaw_process_sandbox into one crate, and it
   contradicts note 5 four paragraphs down ("no production execution backend
   is wired for it"). Re-stated as the typed runtime contract it is, citing
   the measurement: `with_script_runtime` has zero production callers
   (`rg` finds only the builder itself, docs, and 30 test call sites).

2. CHECKLIST WS10 ratchet note 2 said "raise the percentage floor ...; only
   the line count should fall". That generalises WS3's sandbox merge, where
   observed coverage happened to rise. It is wrong as guidance for WS7, and
   the counterexample is in this same file: the 2026-08-03 entry from #7064
   records ironclaw_runner falling 85.55% -> 82.53% because the shed removed
   the crate's better-covered half, holding the floor, and RATCHET FAILing in
   the merge queue. Note 2 now says re-capture from the merged artifact, and
   lower only with that entry's move-not-regression counterfactual (add the
   moved files back, confirm the union clears the old floor, plus a zero-tests-
   lost name set-diff).

cargo test -p ironclaw_architecture: 32 targets, 206 passed, 0 failed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@serrrfirat serrrfirat mentioned this pull request Aug 6, 2026
29 tasks

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants