Skip to content

arch(ws-0): state, checkpoints, BoundedRing, CapabilityCallSignature, NoProgressDetected - #3550

Merged
henrypark133 merged 7 commits into
reborn-integrationfrom
arch/ws-0
May 15, 2026
Merged

henrypark133 merged 7 commits into
reborn-integrationfrom
arch/ws-0

Conversation

@henrypark133

@henrypark133 henrypark133 commented May 13, 2026 •

Copy link
Copy Markdown
Collaborator

Context

Foundation workstream for the Reborn agent-loop framework. This branch establishes the new ironclaw_agent_loop crate and extends ironclaw_turns with the minimal host/request surface the later strategy, executor, and runtime branches need.

Master spec: docs/reborn/agent-loop-skeleton.md
Workstream brief: docs/reborn/agent-loop-briefs/state-and-checkpoints.md
Stack base: reborn-integration

Latest stack maintenance on 2026-05-14:

  • Rebased this branch onto its current stack base after the WS0 prompt-authority fix and the follow-up WS8/WS14-parent/WS16/WS17 conflict resolutions.
  • Pushed the updated branch with force-with-lease where the remote already existed, or published it as a new branch where it did not.
  • Verified the final ancestry chain from origin/reborn-integration through WS17 before publishing the PR descriptions.

What landed

  • New crates/ironclaw_agent_loop crate with the value-immutable LoopExecutionState model, per-strategy state slots, checkpoint marker/types, and crate-level ownership guardrails.
  • BoundedRing<T, N> with a deserialization guard that rejects over-capacity checkpoint payloads instead of rehydrating invalid state.
  • CapabilityCallSignature and stable argument hashing for no-progress detection without storing raw capability arguments.
  • LoopFailureKind::NoProgressDetected and LoopFailureKind::PolicyDenied plus sanitized failure mapping.
  • LoopPromptBundleRequest.inline_messages, LoopInlineMessage, optional LoopContextMessage.message_ref, and prompt-port support for summary-only context rows.
  • Checkpoint payload staging through LoopCheckpointPort, with run-scoped state refs and schema/kind validation boundaries.
  • Prompt authority fixes in Reborn callers so summary-only context and inline message behavior is consistent between ironclaw_turns and the Reborn host adapter.

Reviewer focus

  • BoundedRing serialization/deserialization invariants.
  • Stable hashing and JSON canonicalization behavior in the call-signature helpers.
  • Checkpoint payload staging versus checkpoint metadata writes; the branch intentionally keeps those concerns split.
  • Summary-only prompt rows: callers must use safe summaries and must not silently drop rows without durable refs.
  • The crate boundary: ironclaw_agent_loop defines loop state and framework types, while runner/host execution still belongs to downstream branches.

Non-goals / deferred work

  • Strategy traits and default strategy behavior are introduced by WS1, WS2, WS3, and WS5.
  • The planner facade and sealed family registry land in WS4 and WS3.5.
  • The canonical executor, planned driver, real host ports, and product-live wiring land later in the stack.

Validation

  • cargo check -p ironclaw_turns -p ironclaw_loop_support -p ironclaw_reborn
  • cargo test -p ironclaw_turns host_managed_prompt_port --lib
  • cargo test -p ironclaw_reborn --test loop_driver_host text_only_host
  • cargo test -p ironclaw_reborn --features root-llm-provider --test llm_gateway
  • git diff --check

Stack position

[#3550 ws-0] state/checkpoint foundation -> reborn-integration
   |-- #3551 ws-1 strategy alpha -> ws-0
   |-- #3552 ws-2 strategy beta -> ws-0
   |-- #3553 ws-3 strategy gamma -> ws-0
   |-- #3643 ws-3.5 loop family registry -> ws-0
   '-- #3554 level1-merged -> ws-0
         |-- #3555 ws-4 planner facade -> level1
         |-- #3556 ws-5 default strategies -> level1
         '-- #3557 level2-merged -> level1
               '-- #3596 ws-6a canonical executor -> level2
                     '-- #3597 ws-7 PlannedDriver adapter -> ws-6a
                           '-- #3598 ws-8 integration/test support -> ws-7
                                 |-- #3644 ws-9 capability host wiring -> ws-8
                                 |-- #3645 ws-10 checkpoint load/resume -> ws-8
                                 |-- #3646 ws-11 input port -> ws-8
                                 |-- #3647 ws-12 progress port -> ws-8
                                 |-- #3648 ws-13 cancellation accessor -> ws-8
                                 |-- #3649 ws-15 prompt/identity context -> ws-8
                                 '-- #3650 ws-14-parent integrated host ports -> ws-8
                                       '-- #3651 ws-14 planned default registration -> ws-14-parent
                                             '-- #3652 ws-16 live runtime wiring -> ws-14
                                                   '-- #3653 ws-17 product live cutover -> ws-16

@github-actions github-actions Bot added size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules scope: docs Documentation scope: dependencies Dependency updates contributor: core 20+ merged PRs and removed risk: medium Business logic, config, or moderate-risk modules labels May 13, 2026
@henrypark133
henrypark133 changed the base branch from reborn/agent-loop-skeleton to reborn-integration May 13, 2026 03:51

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the ironclaw_agent_loop crate, which establishes the framework for agent loop execution state and strategy contracts. Key components include the LoopExecutionState for managing iteration data, a BoundedRing for tracking recent failures and signatures, and a canonicalization mechanism for stable hashing of capability calls. Additionally, the PR extends ironclaw_turns with support for LoopInlineMessage and a new NoProgressDetected failure kind. Review feedback highlights opportunities to improve performance by avoiding unnecessary clones during deserialization and canonicalization, and identifies a potential resource exhaustion vulnerability in the BoundedRing deserialization logic.

Comment thread crates/ironclaw_agent_loop/src/state.rs Outdated
let state = object
.get("state")
.ok_or(CheckpointPayloadError::MissingField { field: "state" })?;
serde_json::from_value(state.clone()).map_err(|error| {

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

Avoid cloning the state value here. Since &serde_json::Value implements Deserializer, you can deserialize directly from the reference, which is more efficient for large execution states.

Suggested change
serde_json::from_value(state.clone()).map_err(|error| {
Self::deserialize(state).map_err(|error| {
References
  1. To improve performance, avoid unnecessary heap allocations and clones when processing data structures.

Comment thread crates/ironclaw_agent_loop/src/state/bounded_ring.rs
Comment on lines +64 to +76
let mut keys = object.keys().collect::<Vec<_>>();
keys.sort();
for (index, key) in keys.into_iter().enumerate() {
if index > 0 {
out.push(',');
}
out.push_str(&serde_json::Value::String(key.clone()).to_string());
out.push(':');
if let Some(child) = object.get(key) {
canonicalize(child, out);
}
}
out.push('}');

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 object canonicalization loop is inefficient. It collects and sorts keys, then performs a redundant lookup for each value, and uses an expensive way to JSON-escape keys (cloning into a Value then formatting). Refactoring to iterate over entries directly and using serde_json::to_string for escaping reduces overhead.

            let mut entries: Vec<_> = object.iter().collect();
            entries.sort_by_key(|&(k, _)| k);
            for (index, (key, value)) in entries.into_iter().enumerate() {
                if index > 0 {
                    out.push(',');
                }
                out.push_str(&serde_json::to_string(key).unwrap());
                out.push(':');
                canonicalize(value, out);
            }
            out.push('}');
References
  1. To improve performance, avoid unnecessary heap allocations and use iterators directly instead of collecting them into a Vec when possible.

@henrypark133
henrypark133 changed the base branch from reborn-integration to reborn/agent-loop-skeleton May 13, 2026 03:52
@henrypark133
henrypark133 requested a review from serrrfirat May 13, 2026 04:21
@github-actions github-actions Bot added the risk: medium Business logic, config, or moderate-risk modules label May 13, 2026
nickpismenkov added a commit that referenced this pull request May 14, 2026
PR #3590 originally wired the Reborn ProductAdapter / Telegram v2 channel
into the v1 agent binary at src/channels/reborn/, gated only by a runtime
flag. Per @serrrfirat's review the v1 agent should not be the host for
Reborn-experimental code at all. This commit removes that coupling
entirely.

The Reborn host is now a separate workspace crate
(crates/ironclaw_reborn_telegram_v2_host/) with its own binary
(ironclaw-reborn-telegram-host). The v1 ironclaw binary has zero
awareness it exists: no Reborn crate dependencies in v1's Cargo.toml,
no wiring code, no shared in-process state, no runtime flag, no v1/v2
exclusivity guard.

Reply-path stub
---------------

The current PR's tracer bridged through v1's in-process ChannelManager
to produce an actual Telegram reply. That bridge cannot exist across
processes, and no Reborn agent loop ships in src/ yet (PRs #3544 /
#3550 / #3586 still open). The new host terminates inbound at the
durable ledger / binding write and acks 200 to Telegram; no reply is
produced until the Reborn loop lands, at which point swapping
StubInboundTurnService for DefaultInboundTurnService is the only
required change.

zmanian's review items
----------------------

Fixed in this commit alongside the extraction (verified by tests):

1. TOCTOU in IdempotencyLedger::begin_or_replay (Major) — both libSQL
   and Postgres ledgers used SELECT-then-INSERT, racing the UNIQUE
   constraint on concurrent webhook retries. Both switched to
   INSERT-first patterns (libSQL catches SqliteFailure(2067), Postgres
   uses ON CONFLICT DO NOTHING RETURNING). New concurrent regression
   test spawns 8 racing callers; exactly one wins New, rest surface as
   Transient. Bonus: fixed the same wrong-error-code bug in
   binding_libsql.rs which was matching code 19 (primary
   SQLITE_CONSTRAINT) when libsql 0.6 actually surfaces 2067
   (extended SQLITE_CONSTRAINT_UNIQUE); the existing concurrent
   handler was silently never firing.

3. bot_token / webhook_secret lifecycle (Major) — wrapped in
   secrecy::SecretString in HostConfig so they zeroize on drop and
   accidental Debug prints reveal [REDACTED]. Residual exposure
   inside StaticCredentialResolver / SharedSecretHeaderAuth
   documented inline; full fix requires re-reading through
   EgressCredentialResolver, flagged as follow-up.

5. parse_phase/phase_to_str duplicated between ledger files (Minor)
   — extracted into crates/ironclaw_product_workflow_storage/src/phase.rs
   with roundtrip + reject tests.

11. with_base_url_for_test was #[doc(hidden)] but not compile-gated
    (Minor) — added a `test-support` feature; the helper now
    physically does not exist in release builds without it.

Items 2, 6, 7, 10 (ProductChannel-related) made moot by removing the
in-process bridge entirely.

Diff shape
----------

V1 source tree: 22 files changed, 60 insertions, 2810 deletions —
net subtraction. Removed src/channels/reborn/ (7 files), the
register_reborn_channels call in main.rs, the reborn_telegram_v2_enabled
config field + parser, validate_telegram_v1_v2_exclusivity + all its
tests, the v1/v2 hot-activation guard in ExtensionManager + 3 tests,
the V28 Postgres migration, the V26 libSQL migration entry + 2 tests,
and 9 optional Reborn workspace deps.

New crate: 12 files. Owns its own migrations (no entry in v1's
migration set), boot path, config (env-driven, no shared Config type
with v1), webhook router, composition root, stubbed inbound turn
service, and e2e tests.

Verification
------------

  cargo check                                                # clean
  cargo check --no-default-features --features libsql        # clean
  cargo check --all-features                                 # clean
  cargo build -p ironclaw_reborn_telegram_v2_host --bin ironclaw-reborn-telegram-host  # clean
  cargo clippy --all --tests --benches --examples --all-features  # zero warnings
  cargo deny check                                           # advisories/bans/licenses/sources ok
  cargo fmt --all -- --check                                 # clean
  cargo test -p ironclaw_product_workflow_storage --features libsql --lib  # 16/16
  cargo test -p ironclaw_reborn_telegram_v2_host             # 5/5 e2e
  cargo test --lib                                           # 4951/4952 (1 pre-existing
                                                             #            Postgres-connection
                                                             #            failure, unrelated)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Base automatically changed from reborn/agent-loop-skeleton to reborn-integration May 14, 2026 09:40

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed as paranoid architect. Approving with one non-blocking defense-in-depth note:

StageCheckpointPayloadRequest currently derives Debug, Serialize, and Deserialize while exposing raw payload: Vec<u8>, whereas PutCheckpointStateRequest keeps payload private and custom-redacts Debug. Since checkpoint bytes are intended to remain host-owned/internal, consider making payload private, adding constructor/accessor validation, custom redacted Debug, and reconsidering serde derives.

No blocking correctness/security findings found.

… on rebase)

- Split ControlStrategyState into StopStrategyState + GateStrategyState
  (slots.rs); LoopExecutionState now carries both as independent slots.
- Add LoopFailureKind::PolicyDenied with snake_case serde + #[non_exhaustive]
  on the enum; downstream matchers in reborn updated to handle the
  non-exhaustive shape with a fail-closed wildcard.
- JCS RFC 8785 canonicalization for CapabilityCallSignature::from_call via
  the serde_jcs crate; from_call is now fallible (returns Result) and
  rejects non-finite numbers (NaN/Infinity) via an explicit guard.
- LoopExecutionState::from_checkpoint_payload signature flipped from
  &serde_json::Value to (&[u8], kind: CheckpointKind); envelope carries
  schema_id + kind metadata so the boundary the checkpoint was taken at is
  authenticated on resume.
- LoopContextMessage.message_ref is now Option<LoopMessageRef>; None means
  "summary-only entry; prompt port MUST NOT resolve content from
  safe_summary." Call sites in loop_support, reborn, and tests updated to
  wrap in Some(...) on writes and filter_map on reads.
- LoopCheckpointPort: removed the premature load_checkpoint_payload stub
  (WS-10 owns it); added stage_checkpoint_payload(
  StageCheckpointPayloadRequest { schema_id, payload }
  ) -> LoopCheckpointStateRef with a fail-closed default impl.
- ConcurrencyHint { SafeForParallel, Exclusive } added to
  ironclaw_turns::run_profile::host; CapabilityDescriptorView gains a
  concurrency_hint field. All struct-literal constructors updated
  (defaulting to Exclusive until WS-9 derives from CapabilityDescriptor.effects).
- Tests: JCS-stable across pretty/minified, nested-shuffled, key-reordering;
  grep-style assertion that LoopExecutionState has no control_state;
  StopStrategyState / GateStrategyState default round-trip; PolicyDenied
  serializes as "policy_denied"; checkpoint kind-mismatch path.
- Cargo.toml: add serde_jcs + blake3 deps; drop siphasher (the hand-rolled
  canonicalization is replaced wholesale).

Rebased onto docs HEAD 93f0865 to incorporate:
ebd2dc9 ca648b3 49d1506 2b20998 4c12192 1f808fa e48a584 93f0865
…xes)

Address two P2 findings from codex review:
- StageCheckpointPayloadRequest now carries LoopCheckpointKind so adapters
  can bridge to CheckpointStateStore::put_checkpoint_state without guessing.
- HostManagedLoopCheckpointPort and RebornLoopDriverHost both implement
  stage_checkpoint_payload; the trait's default Unavailable body remains as
  defense-in-depth.
…text (codex iter 2 fixes)

Three findings from codex review:
- from_checkpoint_payload now reads raw state bytes (matches the
  staging contract); metadata stays out of the payload.
- stage_checkpoint_payload returns a run-scoped LoopCheckpointStateRef
  (checkpoint:{run_id}:{token}); is_for_run validators no longer reject.
- HostManagedLoopPromptPort materializes summary-only LoopContextMessage
  entries from safe_summary instead of dropping them via filter_map.
@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

Reviewed WS0 PR #3550 only.
Base 19f08a5609b7207d0b752cdac6826e5e80d1a887 → head 3db2d97bf1718554c8936e2ad4346794f02b7ec3.

Highest risk: prompt/model surface-version validation bypass. Merge stance: block until fixed.

Tests run:

  • cargo test -p ironclaw_agent_loop --lib ✅
  • cargo test -p ironclaw_reborn --test llm_gateway --features root-llm-provider ✅

Findings

# Sev Category File:Line Issue Fix suggestion
1 High Security/Correctness crates/ironclaw_reborn/src/model_gateway.rs:152-168 issue_host_prompt_bundle() rebuilds prompt with surface_version: None, then compares only messages. Caller can keep same message refs but swap stale/forged request.surface_version. Prompt-port stale-surface validation at crates/ironclaw_turns/src/run_profile/prompt.rs:168-186 never runs, while original request.surface_version is forwarded downstream at crates/ironclaw_loop_support/src/lib.rs:569-573. Rebuild with surface_version: request.surface_version.clone(). Reject unless prompt-bound fields match, at minimum messages and surface_version.

Security/data-flow notes

  • Source: LoopModelRequest.surface_version.
  • Trust crossing: model gateway reissues host prompt authority.
  • Sink: HostManagedModelRequest.surface_version forwarded to model gateway.
  • Missing auth check: stale/current surface validation skipped because rebuilt prompt request hardcodes surface_version: None.

Correctness/invariant notes

  • BoundedRing capacity guard checked: bounded_ring.rs:83-90, tests pass.
  • Checkpoint staging validates schema/kind via store path; no separate finding.
  • JCS args hashing covered by unit tests; no finding.

Missing tests

  • crates/ironclaw_reborn/tests/llm_gateway.rs — add test where messages match valid host-built bundle, but LoopModelRequest.surface_version is changed to stale/unknown. Assert provider not called and error is StaleSurface or InvalidInvocation.

Suggested fixes

  1. Pass request.surface_version.clone() into LoopPromptBundleRequest.
  2. Compare prompt_bundle.surface_version == request.surface_version alongside messages.
  3. Add regression test above.

Comment thread crates/ironclaw_turns/src/run_profile/host.rs
Comment thread crates/ironclaw_turns/src/run_profile/host.rs
@zmanian

zmanian commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Review notes — WS0 foundation

The contract surface here is load-bearing for the rest of the stack (WS1–WS17). The shape is solid; flagging four forward-compat items worth doing before downstream PRs lock the shape in.

Forward-compat hardening

  • #[non_exhaustive] sweep on the new public types: LoopExecutionState, CheckpointMarker, LoopInlineMessage, StageCheckpointPayloadRequest, LoopPromptBundleGrant, and the slot structs (ContextStrategyState, CapabilityStrategyState, GateStrategyState, etc.). Each WS PR that adds a field is otherwise a breaking-shaped change. publish = false softens this, but the marker is cheap and signals intent.
  • BoundedRing<T, 8> capacity is locked-in by hardcoding 8 in LoopExecutionState. WS-2 default strategies and WS-5 may want a different window for repetition detection. Either commit to 8 as a framework constant (and document it next to CHECKPOINT_SCHEMA_ID) or thread N through LoopExecutionState's type — the latter cascades into the strategy traits, so the decision belongs here.
  • StageCheckpointPayloadRequest.schema_id: String — the crate already has CheckpointSchemaId; using a raw String invites caller-side drift across WS6a/WS10.

LoopPromptBundleAuthority::shared() global

The process-global OnceLock keyed by run_id.to_string() is load-bearing for model-gateway authorization. It only grows — no eviction on run completion. For long-lived processes this is a slow memory leak; for tests sharing the static across cases it risks cross-run grant leakage. The builder method with_prompt_bundle_authority already supports injection. Strongly recommend converting to an injected dependency now; backing it out after WS-7/WS-16 wire through is hard.

Equivalence-class note (no fix needed)

CapabilityCallSignature hashes {"x":1} and {"x":1.0} equal under JCS — fine for no-progress detection, but the brief should state it explicitly so WS-2 doesn't assume otherwise.

No-progress detection, schema-id round-trip, JCS canonicalization, fail-closed _ => "driver_bug" arms — all the right calls. None of the above blocks landing; they're cheap to do now and expensive after the stack lands.

@henrypark133

Copy link
Copy Markdown
Collaborator Author

Addressed the surface-version validation finding in d299ca3. The Reborn model gateway now rebuilds the host prompt bundle with request.surface_version, verifies the rebuilt bundle surface matches the model request, and rejects stale/unvalidated surfaces before provider invocation. Verified with cargo test -p ironclaw_reborn --test llm_gateway --features root-llm-provider, plus cargo fmt --check and clippy over the touched crates.

@henrypark133
henrypark133 marked this pull request as ready for review May 15, 2026 00:20
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@henrypark133
henrypark133 merged commit 3ebb797 into reborn-integration May 15, 2026
15 checks passed
@henrypark133
henrypark133 deleted the arch/ws-0 branch May 15, 2026 00:21

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d299ca3ec1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +153 to +157
.build_prompt_bundle(LoopPromptBundleRequest {
mode: PromptMode::TextOnly,
context_cursor: None,
surface_version: request.surface_version.clone(),
checkpoint_state_ref: None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Configure surface validation before rebuilding prompt bundle

This path forwards request.surface_version into build_prompt_bundle without configuring a current-surface lookup on HostManagedLoopPromptPort, so any non-None surface version is rejected as InvalidInvocation before provider execution. In HostManagedLoopPromptPort::validate_request (crates/ironclaw_turns/src/run_profile/prompt.rs, around lines 168-179), a Some(surface_version) requires current_surface_version to be set; this gateway never sets it, so valid host-issued surface versions will be blocked.

Useful? React with 👍 / 👎.

Comment on lines +879 to +880
self.lock_state()?.latest_by_run.insert(
context.run_id.to_string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add eviction for abandoned prompt grants

The shared authority is process-global (OnceLock) and stores full prompt grants per run in latest_by_run, but entries are only removed by authorize_latest_model_request. If a prompt is built and the run exits before model authorization (e.g., cancellation/error path), that grant remains for the process lifetime, causing unbounded growth of retained message vectors over time. This should be bounded or explicitly cleaned up on run end.

Useful? React with 👍 / 👎.

theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
… NoProgressDetected (nearai#3550)

* arch: ws-0 — state, checkpoints, BoundedRing, CapabilityCallSignature, NoProgressDetected (iter 4, approved)

* arch(ws-0): align state + checkpoints with evolved spec (12 gap-fixes on rebase)

- Split ControlStrategyState into StopStrategyState + GateStrategyState
  (slots.rs); LoopExecutionState now carries both as independent slots.
- Add LoopFailureKind::PolicyDenied with snake_case serde + #[non_exhaustive]
  on the enum; downstream matchers in reborn updated to handle the
  non-exhaustive shape with a fail-closed wildcard.
- JCS RFC 8785 canonicalization for CapabilityCallSignature::from_call via
  the serde_jcs crate; from_call is now fallible (returns Result) and
  rejects non-finite numbers (NaN/Infinity) via an explicit guard.
- LoopExecutionState::from_checkpoint_payload signature flipped from
  &serde_json::Value to (&[u8], kind: CheckpointKind); envelope carries
  schema_id + kind metadata so the boundary the checkpoint was taken at is
  authenticated on resume.
- LoopContextMessage.message_ref is now Option<LoopMessageRef>; None means
  "summary-only entry; prompt port MUST NOT resolve content from
  safe_summary." Call sites in loop_support, reborn, and tests updated to
  wrap in Some(...) on writes and filter_map on reads.
- LoopCheckpointPort: removed the premature load_checkpoint_payload stub
  (WS-10 owns it); added stage_checkpoint_payload(
  StageCheckpointPayloadRequest { schema_id, payload }
  ) -> LoopCheckpointStateRef with a fail-closed default impl.
- ConcurrencyHint { SafeForParallel, Exclusive } added to
  ironclaw_turns::run_profile::host; CapabilityDescriptorView gains a
  concurrency_hint field. All struct-literal constructors updated
  (defaulting to Exclusive until WS-9 derives from CapabilityDescriptor.effects).
- Tests: JCS-stable across pretty/minified, nested-shuffled, key-reordering;
  grep-style assertion that LoopExecutionState has no control_state;
  StopStrategyState / GateStrategyState default round-trip; PolicyDenied
  serializes as "policy_denied"; checkpoint kind-mismatch path.
- Cargo.toml: add serde_jcs + blake3 deps; drop siphasher (the hand-rolled
  canonicalization is replaced wholesale).

Rebased onto docs HEAD 878a119 to incorporate:
341c8ab edbc72e c945926 6f3a750 81c429a b4971df ef839f2 878a119

* arch(ws-0): wire stage_checkpoint_payload end-to-end (codex iter 1 fixes)

Address two P2 findings from codex review:
- StageCheckpointPayloadRequest now carries LoopCheckpointKind so adapters
  can bridge to CheckpointStateStore::put_checkpoint_state without guessing.
- HostManagedLoopCheckpointPort and RebornLoopDriverHost both implement
  stage_checkpoint_payload; the trait's default Unavailable body remains as
  defense-in-depth.

* arch(ws-0): align checkpoint contracts + materialize summary-only context (codex iter 2 fixes)

Three findings from codex review:
- from_checkpoint_payload now reads raw state bytes (matches the
  staging contract); metadata stays out of the payload.
- stage_checkpoint_payload returns a run-scoped LoopCheckpointStateRef
  (checkpoint:{run_id}:{token}); is_for_run validators no longer reject.
- HostManagedLoopPromptPort materializes summary-only LoopContextMessage
  entries from safe_summary instead of dropping them via filter_map.

* fix(ws-0): bind model requests to prompt bundles

* fix(ws-0): issue prompt authority in reborn callers

* fix(ws-0): address review feedback
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: 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