Skip to content

feat(reborn): add host runtime contract facade#3095

Merged
serrrfirat merged 4 commits into
reborn-integrationfrom
reborn-land-04f-host-runtime-contract
Apr 30, 2026
Merged

feat(reborn): add host runtime contract facade#3095
serrrfirat merged 4 commits into
reborn-integrationfrom
reborn-land-04f-host-runtime-contract

Conversation

@serrrfirat
Copy link
Copy Markdown
Collaborator

Summary

Adds PR4f-a: a contract-first ironclaw_host_runtime facade for Reborn.

This is intentionally not production host-runtime composition yet. It gives upper Reborn layers (TurnCoordinator, AgentLoopHost, model gateway, adapter plumbing) one stable API to build against without depending directly on dispatcher/runtime/process/network/secret/filesystem internals.

Scope

New crate:

crates/ironclaw_host_runtime

Adds:

  • HostRuntime high-level trait
  • runtime request/status/cancel/surface types
  • structured RuntimeCapabilityOutcome
  • scripted testkit::FakeHostRuntime
  • contract tests for outcomes, visible surface, cancellation/status/health, and sanitized failures

Design decisions

This PR is contract-first only:

  • no production composition helper yet
  • no direct RuntimeDispatcher, CapabilityHost, or ProcessHost handles exposed
  • no v1/product wiring
  • reuses ironclaw_host_api vocabulary where possible
  • preserves approval/auth/resource waits as structured suspension outcomes, not errors or JSON strings
  • includes FakeHostRuntime so upper-stack contracts can start before PR4f-b production wiring lands

Follow-up PR4f-b should wire concrete Reborn services behind this facade after the remaining runtime/capability work is ready.

Links

Verification

cargo test -p ironclaw_host_runtime
cargo clippy -p ironclaw_host_runtime --all-targets -- -D warnings
cargo test -p ironclaw_architecture
cargo fmt --all -- --check
git diff --check

All passed locally.

@github-actions github-actions Bot added scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Apr 29, 2026
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the ironclaw_host_runtime crate, establishing a contract-first facade for the IronClaw Reborn host runtime. It defines essential data structures for capability invocations, suspension states (approvals, auth, resources), and work management, alongside a HostRuntime trait and a FakeHostRuntime testkit. Feedback focuses on improving code maintainability and test utility: specifically, extracting shared validation logic for IdempotencyKey and CapabilitySurfaceVersion to reduce duplication and optimize performance, enhancing the FakeHostRuntime to support scripting full cancellation outcomes, and using more idiomatic import patterns in test files.

Comment on lines +29 to +47
pub fn new(value: impl Into<String>) -> Result<Self, HostRuntimeError> {
let value = value.into();
if value.is_empty() {
return Err(HostRuntimeError::invalid_request(
"idempotency key must not be empty",
));
}
if value.len() > 256 {
return Err(HostRuntimeError::invalid_request(
"idempotency key must be at most 256 bytes",
));
}
if value.chars().any(|c| c == '\0' || c.is_control()) {
return Err(HostRuntimeError::invalid_request(
"idempotency key must not contain NUL/control characters",
));
}
Ok(Self(value))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The validation logic in IdempotencyKey::new is very similar to the logic in CapabilitySurfaceVersion::new (lines 119-137). Consider extracting this logic into a shared private helper function. Additionally, ensure the helper performs cheaper validation checks (like length, emptiness, and character set) on a trimmed slice before performing more expensive operations like replace that allocate a new string to avoid unnecessary allocations.

References
  1. When canonicalizing a string, perform cheaper validation checks (like length, emptiness, and character set) on a trimmed slice before performing more expensive operations like replace that allocate a new string.

Comment thread crates/ironclaw_host_runtime/src/lib.rs Outdated
Comment on lines +562 to +574
async fn cancel_work(
&self,
request: CancelRuntimeWorkRequest,
) -> Result<CancelRuntimeWorkOutcome, HostRuntimeError> {
let mut state = self.state.lock().expect("fake mutex poisoned");
state.cancellations.push(request);
let cancelled = state.cancelled_work.pop_front().unwrap_or_default();
Ok(CancelRuntimeWorkOutcome {
cancelled,
already_terminal: Vec::new(),
unsupported: Vec::new(),
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation of cancel_work in FakeHostRuntime only allows scripting the cancelled portion of the CancelRuntimeWorkOutcome. The already_terminal and unsupported fields are always empty.

To make the testkit more powerful and cover more scenarios, I suggest modifying it to allow scripting the entire CancelRuntimeWorkOutcome.

This would involve:

  1. Changing cancelled_work: VecDeque<Vec<RuntimeWorkId>> in FakeHostRuntimeState (line 439) to cancel_outcomes: VecDeque<CancelRuntimeWorkOutcome>.
  2. Replacing with_cancelled_work (line 482) with a new method like with_cancel_outcome(self, outcome: CancelRuntimeWorkOutcome) -> Self.
  3. Updating cancel_work to pop from the new queue and return an error if it's empty, for consistency with invoke_capability and visible_capabilities.

});
let runtime = FakeHostRuntime::new()
.with_outcome(RuntimeCapabilityOutcome::ApprovalRequired(
ironclaw_host_runtime::RuntimeApprovalGate {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The type RuntimeApprovalGate is used with its full path ironclaw_host_runtime::RuntimeApprovalGate. For consistency and to follow idiomatic Rust patterns, consider adding it to a use statement. If importing from a parent module, use super:: for the relative import instead of the full crate path.

References
  1. When importing items from a parent module into a child module in Rust, use super:: for relative imports instead of the full crate path.

@henrypark133
Copy link
Copy Markdown
Collaborator

I like the direction of introducing a host-runtime facade here. The contract is moving toward the right shape: upper Reborn services get stable runtime outcomes (Completed, ApprovalRequired, AuthRequired, ResourceBlocked, SpawnedProcess, Failed) instead of having to understand every lower subsystem directly.

One boundary concern I’d like to tighten before this becomes contract vocabulary: RuntimeCaller currently mixes a few different axes:

  • authority identity: already carried by ExecutionContext, Principal, grants, leases, and policy
  • projection surface: already partly represented by CapabilitySurfaceKind
  • request origin / workflow owner: TurnCoordinator, SystemService, etc.

Because of that, AgentLoopHost and Adapter feel ambiguous as top-level host-runtime callers. If the agent loop is just the tool-execution portion of a turn, I’d prefer it stay under TurnCoordinator rather than become a separate caller. Likewise, Adapter is overloaded in this codebase and could mean runtime adapter, bridge adapter, gateway adapter, etc.; if the distinction is about what capability surface we render, that seems better modeled through CapabilitySurfaceKind or an explicit surface/request-source field.

Suggested direction:

  • keep authority exclusively in ExecutionContext / principals / grants / leases / policy
  • keep model/admin/gateway projection differences in CapabilitySurfaceKind
  • either remove RuntimeCaller for now, or shrink it to a request-origin concept with only clearly product-owned origins, e.g. turn/mission/system service
  • document that request origin must never grant or bypass authority

That would make the dependency story cleaner: upper workflow calls HostRuntime; HostRuntime composes authorization, approvals, resources, secrets, network, processes, dispatcher, and events; runtime adapters remain callees and do not decide caller-facing authorization or approval.

@serrrfirat serrrfirat force-pushed the reborn-land-04f-host-runtime-contract branch from 73e56d7 to 466e6ac Compare April 29, 2026 21:22
Copy link
Copy Markdown
Collaborator

@henrypark133 henrypark133 left a comment

Choose a reason for hiding this comment

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

Review: remove production panic paths from the host runtime testkit

The contract facade is directionally clean: it is narrowly scoped, keeps the Reborn layering intact, and models approval/auth/resource waits as structured outcomes instead of errors. The focused contract tests also cover the important facade behavior.

What looks good:

  • The new crate depends only on ironclaw_host_api, which matches the Reborn dependency-boundary direction.
  • The public facade keeps substrate handles hidden behind structured runtime outcomes.
  • The test coverage exercises invocation recording, visible surfaces, cancellation/status/health, and sanitized failures.

Concerning: exported testkit introduces panic-style calls in production code

crates/ironclaw_host_runtime/src/lib.rs exports testkit::FakeHostRuntime from the library, and its mutex accessors call .expect("fake mutex poisoned") in multiple places. Because this lives under src/lib.rs, the repo's production no-panics gate flags it as production code rather than test-only code.

I reproduced the CI failure locally with:

python3 scripts/check_no_panics.py --base origin/reborn-integration --head HEAD

That reports 15 violations in crates/ironclaw_host_runtime/src/lib.rs, starting at line 467. Please either make the testkit locking fallible/non-panicking, or move/gate the fake so it is not part of production-checked library code.

Local verification:

  • cargo test -p ironclaw_host_runtime passed.
  • cargo clippy -p ironclaw_host_runtime --all-targets --all-features passed.
  • cargo test -p ironclaw_architecture reborn_crate_dependency_boundaries_hold passed.
  • scripts/check_no_panics.py --base origin/reborn-integration --head HEAD failed with the 15 new .expect() violations above.

@serrrfirat
Copy link
Copy Markdown
Collaborator Author

@henrypark133 I checked the current head (466e6ace) against your review notes. Both items are addressed in the current PR diff:

  • RuntimeCaller was removed in favor of RuntimeRequestOrigin with only product-owned origins (TurnCoordinator, MissionService, SystemService). The contract docs now state request origin is audit/correlation metadata only and must never grant/bypass authority; projection differences stay in CapabilitySurfaceKind.
  • The exported FakeHostRuntime testkit no longer uses .expect()/.unwrap() for mutex access. lock_state() handles poisoned mutexes via poisoned.into_inner(), so the no-panics gate no longer reports host-runtime violations.

Verification run from an isolated worktree:

/Users/firatsertgoz/.local/bin/python3.11 scripts/check_no_panics.py --base origin/reborn-integration --head HEAD
OK: No panic-inducing calls in changed production code.

cargo test -p ironclaw_host_runtime
7 passed

cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings
finished successfully

cargo test -p ironclaw_architecture reborn_crate_dependency_boundaries_hold
1 passed

cargo fmt --check
passed

bash scripts/pre-commit-safety.sh still flags an out-of-scope UTF-8 slicing warning in crates/ironclaw_scripts/src/lib.rs, which is not part of PR #3095’s changed files.

@serrrfirat
Copy link
Copy Markdown
Collaborator Author

Addressed the boundary concern by removing the request-origin vocabulary from the host runtime contract entirely.

Why: TurnCoordinator / MissionService / SystemService are upper workflow concepts. Keeping them in ironclaw_host_runtime made the lower facade know about product-owned callers, and could invite future code to branch on origin as if it were authority. The contract now has no RuntimeRequestOrigin / request_origin field; authority stays in ExecutionContext, principals, grants, leases, and policy, while projection remains explicit through CapabilitySurfaceKind.

Verification:

cargo fmt --check
cargo test -p ironclaw_host_runtime
cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings
cargo test -p ironclaw_architecture reborn_crate_dependency_boundaries_hold
python3.11 scripts/check_no_panics.py --base origin/reborn-integration --head HEAD

All passed. scripts/pre-commit-safety.sh still reports the existing out-of-scope UTF-8 slicing warning in crates/ironclaw_scripts/src/lib.rs, unrelated to this PR's changed files.

@serrrfirat serrrfirat added the reborn IronClaw Reborn architecture and landing work label Apr 30, 2026
@serrrfirat serrrfirat force-pushed the reborn-land-04f-host-runtime-contract branch from 42e9e81 to 4ff3a2f Compare April 30, 2026 11:19
@serrrfirat
Copy link
Copy Markdown
Collaborator Author

Rebased onto reborn-integration at 4ff3a2ff8; merged the workspace Cargo.toml member-list collision with #3097 (ironclaw_wasm).

Re: @henrypark133's review — the testkit panic-paths concern was already addressed by commit 136403d9 ("fix(reborn): address host runtime review feedback"). Verified locally:

$ python3.13 scripts/check_no_panics.py --base origin/reborn-integration --head HEAD
OK: No panic-inducing calls in changed production code.

Plus:

  • cargo test -p ironclaw_host_runtime — 7/7 pass
  • cargo test -p ironclaw_architecture reborn_crate_dependency_boundaries_hold — pass
  • cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings — clean

@serrrfirat serrrfirat force-pushed the reborn-land-04f-host-runtime-contract branch 4 times, most recently from 0213c13 to 76becc4 Compare April 30, 2026 12:58
Replaces the FakeHostRuntime testkit with DefaultHostRuntime, the
production composition that wraps ironclaw_capabilities::CapabilityHost
behind the HostRuntime contract.

DefaultHostRuntime composes:
- ExtensionRegistry (capability descriptors)
- CapabilityDispatcher (already-authorized runtime dispatch)
- TrustAwareCapabilityDispatchAuthorizer (host-policy-aware authorization)
- RunStateStore + ApprovalRequestStore (invocation lifecycle + approval
  prompts; required for approval-gated paths)
- CapabilityLeaseStore (resume paths)
- ProcessManager (future spawn paths)

Trust is host-validated and supplied per request via the new
RuntimeCapabilityRequest::trust_decision field — callers must not
synthesize trust decisions or override host policy.

Capability invocation outcomes translate as:
- Ok(result) -> Completed
- Err(AuthorizationRequiresApproval) -> ApprovalRequired (looks up the
  persisted approval_request_id from run-state)
- Err(other) -> Failed with sanitized RuntimeFailureKind

Layering hygiene: the projection-surface dimension is now an opaque
SurfaceKind bounded string newtype rather than a CapabilitySurfaceKind
enum baking in upper-stack vocabulary (agent_loop, adapter, admin). The
host treats the label as a cache/version dimension only — caller-authority
filtering of which surface a particular UI or upper service may render
stays an upper-layer concern.

The four request structs (RuntimeCapabilityRequest, VisibleCapabilityRequest,
CancelRuntimeWorkRequest, RuntimeStatusRequest) are now #[non_exhaustive]
with explicit ::new() constructors so future fields can be added without
breaking external callers.

Tests now drive DefaultHostRuntime against InMemoryRunStateStore,
InMemoryApprovalRequestStore, InMemoryCapabilityLeaseStore, and a
recording dispatcher — exercising completed, approval-required, failed,
visible-surface, status, cancel, and health paths.
@serrrfirat serrrfirat force-pushed the reborn-land-04f-host-runtime-contract branch from 76becc4 to 721f889 Compare April 30, 2026 13:13
@serrrfirat serrrfirat merged commit 0d363d7 into reborn-integration Apr 30, 2026
18 checks passed
@serrrfirat serrrfirat deleted the reborn-land-04f-host-runtime-contract branch April 30, 2026 13:25
This was referenced May 7, 2026
@ironclaw-ci ironclaw-ci Bot mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs reborn IronClaw Reborn architecture and landing work risk: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants