feat(agent-loop): content-digest plumbing for output-aware progress (PR2) - #5000
Conversation
…mpletion When the runaway-loop safety guard fires (StopKind::NoProgressDetected), the executor finalized a canned "I stopped because I was repeating the same step" assistant reply and returned the run as Completed — a runtime control decision leaking into the conversation as a fake successful turn. This hid real blockers (auth, broken connector, empty search) and marked incomplete tasks as done. The ExitStage NoProgressDetected arm now: - keeps the #4837 final-answer-nudge path bit-for-bit: when the gate is enabled and the model synthesizes a real closing answer, complete with that answer (PinchBench path unchanged); - otherwise writes the Final checkpoint and returns a typed failed_exit(LoopFailureKind::NoProgressDetected) instead of the canned reply. The product layer already maps "no_progress_detected" to deterministic copy, so the user sees an honest failure on every channel. Deletes NO_PROGRESS_FALLBACK_REPLY and finalize_no_progress_fallback. PR1 of the output-aware no-progress redesign (exit honesty). PR2 (content-digest progress signal) and PR3 (failures off the no-progress axis) follow. Wire behavior: gate-off no-progress runs now arrive as Failed{no_progress_detected} instead of Completed with a canned reply. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…PR2) Inert plumbing for output-aware no-progress detection (PR3 will consume it). Adds a ContentDigest of each completed capability output so a later change can treat "the output moved" as real progress. This PR does NOT change any detection/stop behavior — it is observably inert. - ContentDigest newtype (keyed-blake3, mirrors ArgsHash) in ironclaw_turns; normalize_for_hash moved there and re-exported from agent_loop::strategies so CapabilityCallSignature hashing is unchanged (no checkpoint-signature break). - output_digest: Option<ContentDigest> on CapabilityResultMessage (serde default None = back-compat); computed host-side in the write_capability_result chokepoint alongside byte_len. Best-effort: a digest-compute failure degrades to None and never fails an otherwise-successful capability write. - seen_capability_output_digests: BoundedRing<_, 64> on LoopExecutionState, populated at append_completed_capability_result but NOT consumed. Additive #[serde(default)] checkpoint field (legacy checkpoints decode to an empty ring; round-trip + legacy-decode tests added). - record_result still receives the host's progress unchanged — detection is byte-identical (inertness test drives the executor and asserts no behavior change for repeated identical-output completed calls). Review fixes folded in (multi-agent review): from_output is fail-open; a caller-level test asserts the digest is recorded into the ring through a real run; boundary note documents why the digest impl lives in ironclaw_turns. PR2 of the no-progress redesign. PR1 (#4993) = honest typed failure; PR3 = consume the digest (NoChange when output repeats) + take failures off the no-progress axis. Benchmark-gated at PR3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
🚅 Deployed to the ironclaw-pr-5000 environment in ironclaw-ci-preview
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a ChangesCapability Output Digest Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
…nto firat/no-progress-pr2-content-digest # Conflicts: # crates/ironclaw_product_workflow/tests/inbound_turn_contract.rs # crates/ironclaw_reborn/tests/loop_driver_host.rs # crates/ironclaw_reborn_composition/tests/product_live_adapters.rs # tests/support/reborn/harness.rs
…-content-digest # Conflicts: # crates/ironclaw_agent_loop/src/executor/loop_exit.rs
…PR2) (nearai#5000) * fix(agent-loop): no-progress stop fails honestly instead of faking completion When the runaway-loop safety guard fires (StopKind::NoProgressDetected), the executor finalized a canned "I stopped because I was repeating the same step" assistant reply and returned the run as Completed — a runtime control decision leaking into the conversation as a fake successful turn. This hid real blockers (auth, broken connector, empty search) and marked incomplete tasks as done. The ExitStage NoProgressDetected arm now: - keeps the nearai#4837 final-answer-nudge path bit-for-bit: when the gate is enabled and the model synthesizes a real closing answer, complete with that answer (PinchBench path unchanged); - otherwise writes the Final checkpoint and returns a typed failed_exit(LoopFailureKind::NoProgressDetected) instead of the canned reply. The product layer already maps "no_progress_detected" to deterministic copy, so the user sees an honest failure on every channel. Deletes NO_PROGRESS_FALLBACK_REPLY and finalize_no_progress_fallback. PR1 of the output-aware no-progress redesign (exit honesty). PR2 (content-digest progress signal) and PR3 (failures off the no-progress axis) follow. Wire behavior: gate-off no-progress runs now arrive as Failed{no_progress_detected} instead of Completed with a canned reply. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(agent-loop): content-digest plumbing for output-aware progress (PR2) Inert plumbing for output-aware no-progress detection (PR3 will consume it). Adds a ContentDigest of each completed capability output so a later change can treat "the output moved" as real progress. This PR does NOT change any detection/stop behavior — it is observably inert. - ContentDigest newtype (keyed-blake3, mirrors ArgsHash) in ironclaw_turns; normalize_for_hash moved there and re-exported from agent_loop::strategies so CapabilityCallSignature hashing is unchanged (no checkpoint-signature break). - output_digest: Option<ContentDigest> on CapabilityResultMessage (serde default None = back-compat); computed host-side in the write_capability_result chokepoint alongside byte_len. Best-effort: a digest-compute failure degrades to None and never fails an otherwise-successful capability write. - seen_capability_output_digests: BoundedRing<_, 64> on LoopExecutionState, populated at append_completed_capability_result but NOT consumed. Additive #[serde(default)] checkpoint field (legacy checkpoints decode to an empty ring; round-trip + legacy-decode tests added). - record_result still receives the host's progress unchanged — detection is byte-identical (inertness test drives the executor and asserts no behavior change for repeated identical-output completed calls). Review fixes folded in (multi-agent review): from_output is fail-open; a caller-level test asserts the digest is recorded into the ring through a real run; boundary note documents why the digest impl lives in ironclaw_turns. PR2 of the no-progress redesign. PR1 (nearai#4993) = honest typed failure; PR3 = consume the digest (NoChange when output repeats) + take failures off the no-progress axis. Benchmark-gated at PR3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(reborn): stabilize turn runner operator log capture --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Stacked on #4993 (PR1). Review against the base branch
firat/elastic-driscoll-86596fso the diff shows only this change; retarget tomainonce #4993 merges.What
PR2 of the no-progress redesign. This is inert plumbing: it adds a
ContentDigestof each completed capability output so a later change (PR3) can treat "the output moved" as real progress. This PR does not change any detection/stop behavior.ContentDigestnewtype (keyed-BLAKE3, low-8-bytes-LE — mirrorsArgsHash) inironclaw_turns::run_profile::content_digest.normalize_for_hashis moved there and re-exported fromagent_loop::strategies::progress, soCapabilityCallSignaturehashing is byte-for-byte unchanged (no checkpoint-signature break).output_digest: Option<ContentDigest>onCapabilityResultMessage,#[serde(default)](None= back-compat). Computed host-side in the singlewrite_capability_resultchokepoint alongsidebyte_len. Best-effort: a digest-compute failure degrades toNoneand never fails an otherwise-successful capability write.seen_capability_output_digests: BoundedRing<_, 64>onLoopExecutionState, populated atappend_completed_capability_resultbut not consumed. Additive#[serde(default)]checkpoint field — legacy checkpoints decode to an empty ring (round-trip + legacy-decode tests added).record_resultstill receives the host'sprogressunchanged. An executor-level test asserts repeated identical-output completed calls do not newly trip no-progress.Review fixes folded in (multi-agent review)
from_outputis fail-open (digest failure →None, never fails the write).ironclaw_turns.Roadmap
NoChangewhen output repeats) + take failures off the no-progress axis. Benchmark-gated at PR3.Validation
Local gate was run partially under heavy machine load:
cargo fmtclean; full-workspace--all-targetsbuild was at 0 errors (compiling final binaries) when stopped to free the machine. Deferring the authoritativeclippy --all-features+ test gate to CI — please watch CI before merge.🤖 Generated with Claude Code