Skip to content

perf(turns): long-lived authority + remove redundant global commit_gate from the turn-state row store (#6263 Step 1) - #6281

Merged
ilblackdragon merged 2 commits into
mainfrom
refactor/turn-state-longlived-authority
Jul 19, 2026
Merged

ilblackdragon merged 2 commits into
mainfrom
refactor/turn-state-longlived-authority

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Step 1 of the turn-state consolidation in #6263. A pure latency/CPU + contention refactor of FilesystemTurnStateRowStore; durability and crash-recovery semantics are unchanged (write-through preserved).

What

  • Removed the process-wide commit_gate async mutex. It was held in addition to the snapshot_state mutex the mutation path already held across its read-seq → apply → enqueue window. Production wires a single shared row-store instance, so that redundant global lock serialized every user's turn-state transition. snapshot_state is now the single serialization point (held microseconds — CPU apply + delta build + mpsc send, never across the durable ack); journal-sequence ordering is preserved because enqueue order already equals reservation-seq order under it.
  • Stopped the per-op full engine rebuild on the non-hot apply() path (retry / submit_child / tree / recover / fail / relinquish / record_model_route): the long-lived cached InMemoryTurnStateStore authority is reused in place for the None/Run lease overlays instead of reconstructed from a full-snapshot clone per call. Only the All-overlay (expired-lease recovery, off the hot path) still rebuilds. The hot chat-turn ops (submit/claim/complete/block/cancel/resume) already used the targeted-delta path and are untouched.

Durability preserved

Every mutation still enqueue_delta → awaits the durable journal ack before returning. load_snapshot_from_rows, migrate_legacy_blob_if_needed, materialize_delta_log, remove_orphan_active_locks, backend seq assignment, and the entire runner-lease overlay/heartbeat surface are unmodified. This is not the async-write-behind change — that's #6263 Step 3, gated on the crash-consistency property suite.

Measured (chat-turn contention sweep, filesystem-row, 32-core box; before = byte-identical main)

conc p50 before→after goodput c100 turn_conflict c100
32 24.0 → 17.4 ms — —
64 136.6 → 107.8 ms — —
100 229.8 → 175.9 ms 277 → 625 ops 99 → 43

Store-isolated per-transition p50 down ~20-27% at c32-c100; goodput at c100 more than doubled; conflict errors roughly halved. The c8 p50 (~7 ms) is unchanged — it's the synchronous durable-ack floor, which only Step 3 (async write-behind) addresses.

Tests

cargo test -p ironclaw_turns: 599 passed / 0 failed (full 47-test row-store contract suite incl. concurrency/crash/reservation cases), independently re-verified. ironclaw_runner (filesystem-goal-store) and ironclaw_reborn_composition turn-state tests green. clippy -D warnings clean; pre-commit-safety exit 0.

🤖 Generated with Claude Code

…mit_gate from the row store (#6263 Step 1)

FilesystemTurnStateRowStore's mutation path carried a process-wide
`commit_gate` async mutex in addition to the `snapshot_state` mutex it
already held across the read-seq -> apply -> enqueue window. Because
production wires a single shared row-store instance, that redundant
global lock serialized every user's turn-state transition. This removes
`commit_gate` entirely: `snapshot_state` is the single serialization
point (held for microseconds — CPU apply + delta build + mpsc send,
never across the durable ack), and journal-sequence ordering is
preserved because enqueue order already equals reservation-seq order
under that lock.

Also stops the per-op full engine rebuild on the non-hot `apply()` path
(retry/submit_child/tree/recover/fail/relinquish/record_model_route):
the long-lived cached InMemoryTurnStateStore authority is now reused
in place for the None/Run lease overlays instead of reconstructed from
a full-snapshot clone each call. Only the All-overlay (expired-lease
recovery, off the hot path) still rebuilds. The hot chat-turn ops
(submit/claim/complete/block/cancel/resume) already used the targeted
-delta path and are unchanged.

Durability is UNCHANGED — write-through preserved: every mutation still
awaits the durable journal ack before returning, and load/materialize/
legacy-blob-migration/orphan-lock/runner-lease-overlay semantics are
byte-for-byte identical. This is a pure latency/CPU + contention
refactor, not the async-write-behind change (that is #6263 Step 3,
gated on the crash-consistency property suite).

Measured (chat-turn contention sweep, filesystem-row, 32-core box):
store-isolated per-transition p50 down ~20-27% at c32-c100; goodput at
c100 277 -> 625 ops; turn_conflict errors roughly halved (c100 99 ->
43). The c8 p50 (~7ms) is unchanged — it is the synchronous durable-ack
floor, which only Step 3 addresses.

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

ironloopai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 4faac1033589527adc49ef1f6a5fc2ef6239fa47
Result: One or more review results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-19T08:27:16.794Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-19T08:27:16.782Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: 4faac10. Previous verdict: Review declined.
Recent activity
Time Reviewer State Detail
2026-07-19T08:20:31.546Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head d588a37.
2026-07-19T08:20:31.546Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-19T08:20:31.604Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-19T08:20:34.982Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at 32b98b1.
2026-07-19T08:21:40.594Z ironloop/common-reviewer (reviewer) Result captured Skipped; 0 blocking findings.
2026-07-19T08:21:40.594Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-19T08:27:16.782Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (4faac10).
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6281 July 19, 2026 08:20 Destroyed
@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules labels Jul 19, 2026
@github-actions github-actions Bot added the contributor: core 20+ merged PRs label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff4e2c90-d5f5-4b8f-93b1-ea9c8b0674e2

📥 Commits

Reviewing files that changed from the base of the PR and between d588a37 and 4faac10.

📒 Files selected for processing (1)
  • crates/ironclaw_turns/src/filesystem_store/row_store/traits.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved consistency when saving durable checkpoints and updating in-memory state.
    • Reduced the risk of state mismatches by ensuring checkpoint deltas are applied safely, and stale cached state is cleared if an update fails.
  • Refactor

    • Streamlined internal state overlay acquisition across different update flows to improve reliability and avoid duplicated overlay logic.

Walkthrough

FilesystemTurnStateRowStore centralizes runner-lease overlay acquisition, removes commit_gate synchronization, and uses snapshot_state locking to coordinate durable checkpoint enqueue with in-memory delta application.

Changes

Filesystem store synchronization

Layer / File(s) Summary
Shared overlay acquisition and apply paths
crates/ironclaw_turns/src/filesystem_store/row_store.rs
Adds acquire_overlaid_store, removes commit_gate, and routes both apply paths through shared overlay logic.
Checkpoint snapshot synchronization
crates/ironclaw_turns/src/filesystem_store/row_store/traits.rs
Holds snapshot_state across durable enqueue and cached-state delta application, clearing the cache when application fails.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and validation, but it omits most required template sections and checklist details. Populate the missing template sections: Change Type, Linked Issue, Security Impact, Database Impact, Blast Radius, Rollback Plan, Review Follow-Through, and the trust-boundary checklist.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, matches the refactor, and follows conventional-commits style.
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.

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⏭️ IronLoop Review Declined: reviewer

Review at a glance

Disposition Head
⏭️ Review declined d588a37549d8

Head: d588a37549d8d60d081e57aade24354997a71041
Reason: The comparison changes 158 files across 19+ crates/subsystems (3,020 additions and 8,891 deletions), including CLI, auth/secrets, host runtime, WebUI, Slack WASM, fixtures, and scripts. This materially exceeds and conflicts with the PR description’s focused row-store change.
Next: Split or rebase the PR so base..head contains only the intended turn-state row-store refactor, with unrelated cross-subsystem changes removed or submitted separately; then request a new scoped review.

Run details

Status: Current
Trustworthy review produced: no

Summary

Skipped: the supplied base-to-head comparison is a mega, mixed-scope diff that cannot be reliably reviewed as the stated turn-state refactor within this review budget.

@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 refactors FilesystemTurnStateRowStore by removing the commit_gate mutex and instead serializing enqueues on the shared snapshot_state lock. It also introduces a helper method acquire_overlaid_store to deduplicate the overlay-store acquisition logic across the apply and apply_with_targeted_delta paths. Feedback was provided regarding the need to invalidate the snapshot_state cache if apply_delta fails during a loop checkpoint operation, preventing the cache from being left in an inconsistent state.

Important

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

Comment on lines +378 to +380
if let Some(state) = guard.as_mut() {
state.apply_delta(delta, state.journal_seq)?;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If state.apply_delta fails here, the in-memory snapshot_state cache (guard) is left in a partially-mutated, inconsistent state. To ensure correctness and follow defensive programming practices, we should invalidate the cache by setting *guard = None on failure, matching the pattern used in apply and apply_with_targeted_delta.

                if let Some(state) = guard.as_mut() {
                    if let Err(error) = state.apply_delta(delta, state.journal_seq) {
                        *guard = None;
                        return Err(error);
                    }
                }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed and fixed — the new put_loop_checkpoint path now sets *guard = None before propagating a failed apply_delta, matching the invariant apply and apply_with_targeted_delta enforce at every mutation-error site (8+ *guard = None points). So a later read rebuilds the cache from durable state instead of serving a partially-mutated snapshot. The durable enqueue has already succeeded at that point, so recovery replays it — the in-memory cache is the only thing to invalidate.

…oop_checkpoint apply

If the in-memory apply_delta fails, the cached snapshot can be left
partially mutated; the new put_loop_checkpoint path propagated the
error via ? without dropping the cache, unlike apply /
apply_with_targeted_delta (which *guard = None on every mutation
error). Now matches that invariant — a later read rebuilds from durable
state rather than serving a corrupt cache. The durable enqueue already
succeeded, so recovery replays it; the cache is the only thing to
invalidate. The failure arm is an internal-invariant apply error not
reachable from the public store API without fault injection the store
doesn't expose, so it follows the tested sibling pattern rather than a
new drivable test. [skip-regression-check]

Reported-by: gemini-code-assist (PR #6281 review)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6281 July 19, 2026 08:27 Destroyed
@railway-app

railway-app Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 19, 2026 at 8:38 am

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_turns/src/filesystem_store/row_store/traits.rs (1)

364-382: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Stale (non-advanced) sequence number passed to apply_delta.

Line 379 calls state.apply_delta(delta, state.journal_seq) — the current, already-consumed sequence, not the next one. Every other call site that applies a delta to the cached state advances first: apply() in row_store.rs uses let reservation_seq = current_journal_seq.next(); before RowSnapshotState::new(new_snapshot, store, reservation_seq), and apply_with_targeted_delta() computes state.journal_seq.next() before calling state.apply_delta(delta, reservation_seq). Reusing the stale seq here risks stamping the loop-checkpoint row with a seq that collides with — or fails to reflect — the seq the delta journal actually assigned, and leaves state.journal_seq under-counting relative to the durable head that enqueue_delta just advanced, which refresh_snapshot_cache_after_stale_mutation_error's state.journal_seq < head_seq check relies on.

🐛 Proposed fix
                 if let Some(state) = guard.as_mut() {
-                    state.apply_delta(delta, state.journal_seq)?;
+                    let next_seq = state.journal_seq.next();
+                    state.apply_delta(delta, next_seq)?;
                 }

Per repo guidance, this bug fix should ship with a regression test through the caller (e.g. asserting journal_seq/row seq advances by exactly one across a put_loop_checkpoint call, or that a subsequent mutation's reservation seq doesn't collide) rather than only a helper-level check. Want me to draft that test?

#!/bin/bash
# Confirm apply_delta's seq contract and whether journal_seq is mutated internally.
rg -n -B2 -A15 'fn apply_delta' crates/ironclaw_turns
🤖 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_turns/src/filesystem_store/row_store/traits.rs` around lines
364 - 382, Advance the sequence before applying the loop-checkpoint delta in the
put_loop_checkpoint flow: update the block around enqueue_delta and
RowSnapshotState::apply_delta to compute the next journal sequence and pass that
reservation instead of state.journal_seq. Add a regression test through the
put_loop_checkpoint caller verifying the journal/row sequence advances exactly
once and subsequent mutations do not reuse the sequence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/ironclaw_turns/src/filesystem_store/row_store/traits.rs`:
- Around line 364-382: Advance the sequence before applying the loop-checkpoint
delta in the put_loop_checkpoint flow: update the block around enqueue_delta and
RowSnapshotState::apply_delta to compute the next journal sequence and pass that
reservation instead of state.journal_seq. Add a regression test through the
put_loop_checkpoint caller verifying the journal/row sequence advances exactly
once and subsequent mutations do not reuse the sequence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 94555689-be91-4f95-b354-6016ba12173f

📥 Commits

Reviewing files that changed from the base of the PR and between c1f005a and d588a37.

📒 Files selected for processing (2)
  • crates/ironclaw_turns/src/filesystem_store/row_store.rs
  • crates/ironclaw_turns/src/filesystem_store/row_store/traits.rs

@github-actions

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.59% (313150 / 365872 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 365872 lines now vs 320188 at floor capture (+45684 lines, +14.27%) — material change (>5%)

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.59% — 313150 / 365872 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 31.75% 80 / 252
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.46% 604 / 967
ironclaw_dispatcher 62.88% 83 / 132
ironclaw_mcp 64.89% 595 / 917
ironclaw_triggers 65.44% 2142 / 3273
ironclaw_filesystem 67.78% 3957 / 5838
ironclaw_channel_host 68.65% 219 / 319
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 71.64% 1551 / 2165
ironclaw_trust 72.88% 661 / 907
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.67% 958 / 1283
ironclaw_extractors 74.72% 538 / 720
ironclaw_capabilities 75.58% 2092 / 2768
ironclaw_projects 76.48% 400 / 523
ironclaw_reborn_cli 77.07% 10249 / 13298
ironclaw_llm 78.36% 20306 / 25915
ironclaw_product_context 78.57% 11 / 14
ironclaw_run_state 79.25% 424 / 535
ironclaw_telegram_extension 80.18% 4842 / 6039
ironclaw_wasm_product_adapters 80.36% 1448 / 1802
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_first_party_extensions 81.06% 5965 / 7359
ironclaw_memory_native 81.17% 3195 / 3936
ironclaw_events 81.95% 1594 / 1945
ironclaw_network 82.98% 673 / 811
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_processes 83.76% 939 / 1121
ironclaw_secrets 83.79% 2548 / 3041
ironclaw_wasm 84.44% 1069 / 1266
ironclaw_auth 84.81% 3233 / 3812
ironclaw_product_workflow 84.91% 11031 / 12992
ironclaw_reborn_config 85.2% 2055 / 2412
ironclaw_turns 85.58% 14398 / 16825
ironclaw_channel_delivery 85.79% 1383 / 1612
ironclaw_common 86.13% 1714 / 1990
ironclaw_threads 86.93% 4708 / 5416
ironclaw_slack_v2_adapter 87.3% 1491 / 1708
ironclaw_skills 87.58% 4470 / 5104
ironclaw_hooks 87.78% 9921 / 11302
ironclaw_reborn_composition 87.97% 70290 / 79904
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_host_api 88.1% 3894 / 4420
ironclaw_product_adapters 88.1% 3384 / 3841
ironclaw_reborn_traces 88.2% 11946 / 13544
ironclaw_host_runtime 88.69% 18060 / 20363
ironclaw_webui 88.9% 7652 / 8607
ironclaw_extensions 89.38% 2971 / 3324
ironclaw_reborn_openai_compat 89.5% 3778 / 4221
ironclaw_runner 89.65% 17365 / 19370
ironclaw_telegram_v2_adapter 89.7% 2717 / 3029
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_resources 91.65% 4476 / 4884
ironclaw_loop_host 92.28% 15577 / 16880
ironclaw_attachments 93.06% 630 / 677
ironclaw_agent_loop 94.88% 9184 / 9680
ironclaw_safety 95.09% 3682 / 3872
ironclaw_outbound 95.52% 3451 / 3613
ironclaw_first_party_extension_ports 95.62% 3672 / 3840

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

@ilblackdragon

Copy link
Copy Markdown
Member Author

✅ Ready for merge — turn-state row-store perf refactor (#6263 Step 1: long-lived authority + removed the redundant global commit_gate).

Review + thermo-nuclear pass complete:

  • Gemini's HIGH-priority finding fixed — put_loop_checkpoint now invalidates the in-memory snapshot cache (*guard = None; return Err(error)) on a failed apply_delta, matching the ~8 other mutation-error sites in row_store/traits.rs; previously it propagated via ? leaving a stale cached snapshot.
  • All three clippy lanes (default / libsql-only / all-features) clean; 25 row_store tests pass.

CI green 59/59 (9 skipped). Ready to merge.

This branch was successfully deployed

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

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant