Skip to content

feat(agent-loop): tools-capable completion nudge for interactive coding - #6013

Merged
pranavraja99 merged 9 commits into
mainfrom
feat/tools-capable-completion-nudge
Jul 14, 2026
Merged

pranavraja99 merged 9 commits into
mainfrom
feat/tools-capable-completion-nudge

Conversation

@pranavraja99

@pranavraja99 pranavraja99 commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

What

Makes the agent loop's driver-specific completion nudge tools-capable and turns it on for interactive coding. Two minimal, complementary parts:

  1. Enable the gate — run_profile/resolver.rs::interactive_profile() opts into SteeringPolicy.allow_driver_specific_nudges (the interactive-coding / reborn-LocalDev profile). It was false in every production profile, so the existing nudge never fired.
  2. Make the nudge tools-capable — the existing try_final_answer_nudge is terminal and tool-free (empty capability view → prose only). Instead, when a turn ends by trailing off on a GracefulStop (reply.trim().is_empty() || ends_with(':')) or on NoProgressDetected, the loop now re-enters for one more ordinary iteration with the full tool surface, injecting a "finish the task — write any required output file, then give your final answer" directive.

Reuses existing machinery: the drained-follow-up continue and the inline control-message injection used by RetryAlteration::RepairInvalidModelOutput. Capped at 2 nudges/run (COMPLETION_NUDGE_LIMIT), gated behind the same steering flag. A clean/complete reply is never nudged. The control-message constructor is fallible (no panic), matching FINAL_ANSWER_NUDGE.

Why

Turns that trail off mid-task (model narrates "let me write the file:" but emits no tool call, or a no-progress stop) otherwise end without finishing — the tool-free nudge can't write a required output artifact before answering; this one can. In-loop equivalent of nearai-bench's out-of-loop trailed_off_without_answer nudge (same trigger heuristic), so ironclaw finishes these tasks itself instead of relying on the harness.

Compatibility / replay

  • New LoopExecutionState fields are #[serde(default)] (older checkpoints decode).
  • Default-family fingerprint/digest unchanged → replay identity preserved. Confirmed by the Live Canary → Deterministic Replay lane passing on this branch.

Validation

Deterministic flip proof (control vs treatment), through the real CanonicalAgentLoopExecutor:

  • completion_nudge_lets_model_use_tools_to_finish_after_trailing_off — a trailed-off reply re-enters with tools + the directive; the model then executes its write tool (batch_invocations == 1) and completes.
  • completion_nudge_disabled_leaves_trailed_off_run_without_tool_use — same trajectory, gate off: the tool never runs (batch_invocations == 0). The failure this fixes.
  • completion_nudge_skipped_on_clean_reply — a complete reply is left untouched (no regression).

Green: agent-loop (379) + turns (143) + reborn_composition tests; Formatting / Code Style / No-panics / Clippy (all-features); Reborn E2E + recorded-fixture replay; Live Canary (deterministic-replay).

Live-model note (updated)

The production reborn path (NEAR AI cloud) runs this cleanly: the 2026-07-12 daily PinchBench completed 147/147 at avg 0.935 with 0 errored/hung tasks. An earlier caveat about a "mid-turn hang" was an artifact of local validation over OpenRouter's deepseek endpoint, which intermittently returns undecodable HTTP bodies on tool-heavy requests (HttpError: error decoding response body); ironclaw retries them as transient, but across a long task they can miss the turn timeout before reaching a stop. That's a provider-transport flake specific to that endpoint — not reborn, this branch, or main (the NEAR path shows 0 such errors). PinchBench is being run on this PR via the NEAR path to confirm.

🤖 Generated with Claude Code

The agent loop already had a driver-specific completion nudge
(`try_final_answer_nudge`), but it was (a) gated off in every production
run profile (`SteeringPolicy.allow_driver_specific_nudges = false`) and
(b) tool-free — it forces an empty capability view, so the model can only
synthesize prose and cannot write a required output artifact before it
answers. Turns that trail off mid-task (the model narrates "let me write
the file:" but emits no tool call, or a no-progress stop) therefore end
without finishing.

Make the nudge tools-capable and turn it on for interactive coding:

- resolver.rs: `interactive_profile()` now opts into
  `allow_driver_specific_nudges` (the reborn/LocalDev coding profile).
- On a `GracefulStop` whose closing reply trailed off (empty after trim,
  or ends with ':'), or a `NoProgressDetected` stop, the loop now
  re-enters for one more ordinary iteration with the FULL tool surface,
  injecting a "finish the task — write any required output file, then
  give your final answer" directive. It reuses the existing machinery:
  the drained-follow-up continue path and the inline control-message
  injection used by `RepairInvalidModelOutput`. Capped at 2 nudges/run,
  gated behind the same steering flag; a clean/complete reply is never
  nudged (no regression on correct answers).

This is the in-loop, tools-capable equivalent of nearai-bench's
out-of-loop `trailed_off_without_answer` nudge (same trigger heuristic),
so ironclaw finishes these tasks itself instead of relying on the harness.

New `LoopExecutionState` fields are `#[serde(default)]` (checkpoint
compatible) and the default-family fingerprint is unchanged (nudges are
not part of loop identity), preserving replay identity — consistent with
how the original nudge was added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 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: fd9341a9-56b2-454d-a738-3afbbecf6811

📥 Commits

Reviewing files that changed from the base of the PR and between ce9dde2 and cff7da5.

📒 Files selected for processing (1)
  • crates/ironclaw_agent_loop/src/executor/tests.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Interactive runs can now make a limited completion retry when the assistant stops mid-task or provides an unfinished response.
    • The retry retains access to tools to help complete required output artifacts before ending.
    • Interactive profiles now enable this behavior by default (with a bounded nudge cap).
  • Bug Fixes

    • Prevented trailing-off assistant replies from ending runs prematurely by preserving “trailed off” status for graceful stop handling.
  • Tests

    • Added end-to-end coverage for enabled, disabled, and unnecessary completion retries.

Walkthrough

The agent loop detects eligible trailing-off or no-progress stops, injects a bounded tool-capable completion prompt, and retries through the prompt pipeline. Persisted state, interactive-profile enablement, and regression tests cover enabled, disabled, and clean-completion behavior.

Changes

Completion nudge flow

Layer / File(s) Summary
Profile and execution state
crates/ironclaw_turns/src/run_profile/resolver.rs, crates/ironclaw_turns/tests/run_profile_contract.rs, crates/ironclaw_agent_loop/src/state.rs
Interactive profiles enable driver-specific nudges; execution state stores usage, pending, and trailing-off flags with serde defaults.
Nudge prompt and reply classification
crates/ironclaw_agent_loop/prompts/completion_nudge.md, crates/ironclaw_agent_loop/src/executor/loop_exit.rs, crates/ironclaw_agent_loop/src/executor/assistant_reply.rs
Adds the completion prompt, usage cap, trailing-off predicate, inline control message construction, and reply classification.
Stop decision and retry orchestration
crates/ironclaw_agent_loop/src/executor.rs, crates/ironclaw_agent_loop/src/executor/canonical.rs
Eligible no-progress and trailing-off graceful-stop outcomes schedule a bounded retry; aborted and ineligible outcomes retain termination behavior.
Prompt lifecycle and regression coverage
crates/ironclaw_agent_loop/src/executor/prompt.rs, crates/ironclaw_agent_loop/src/executor/tests.rs
The pending directive is injected once per iteration, with tests covering enabled nudges, disabled nudges, and clean completions.

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

Sequence Diagram(s)

sequenceDiagram
  participant AssistantReplyStage
  participant Executor
  participant PromptPipeline
  participant Model
  AssistantReplyStage->>Executor: Record trailing-off reply status
  Executor->>Executor: Evaluate stop kind and nudge budget
  Executor->>PromptPipeline: Mark completion nudge pending
  PromptPipeline->>Model: Inject completion nudge control message
  Model->>Executor: Return completion and tool work
Loading

Possibly related PRs

Suggested reviewers: copilot, think-in-universe, serrrfirat

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is specific, but it omits several required template sections such as Change Type, Linked Issue, Security Impact, and the Reborn checklist. Rewrite the PR body to match the repository template and add all required sections, especially Change Type, Linked Issue, Security Impact, Reborn checklist, rollback plan, and review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional-commits style and accurately summarizes the interactive completion-nudge change.
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.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 12, 2026 17:45 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 12, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 12, 2026 17:47 Destroyed

@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 a tools-capable completion nudge mechanism designed to prevent the agent loop from terminating prematurely when an assistant's reply trails off (e.g., ending with a colon or being empty). Instead of stopping, the loop re-enters for another iteration with the full tool surface available, allowing the model to finish its task. This feature is gated by the steering policy and capped at two nudges per run. The review feedback highlights a potential issue where state.last_reply_trailed_off is not reset to false during capability (tool execution) turns, which could incorrectly trigger an unwanted completion nudge on subsequent graceful stops.

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.

}
match kind {
StopKind::NoProgressDetected => true,
StopKind::GracefulStop => state.last_reply_trailed_off,

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

There is a potential edge case where state.last_reply_trailed_off is never reset to false when a capability (tool execution) turn is executed.

Issue

If a previous turn's reply trailed off (setting last_reply_trailed_off = true), but the model subsequently executes tools successfully in a later turn and the loop eventually stops gracefully (e.g., due to a tool's terminate_hint), state.last_reply_trailed_off will still be true. This will incorrectly trigger an unwanted completion nudge even though the task was completed successfully.

Suggestion

Reset state.last_reply_trailed_off to false whenever a capability turn is executed. For example, in canonical.rs where ParentLoopOutput::CapabilityCalls is matched (around line 228):

ParentLoopOutput::CapabilityCalls(calls) => {
    state.last_reply_trailed_off = false;
    latency::stage!(
        "capabilities",
        ...
    )?
}

@github-actions

github-actions Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.57% (302542 / 353574 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 353574 lines now vs 320188 at floor capture (+33386 lines, +10.43%) — 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.57% — 302542 / 353574 lines

Per-crate breakdown (63 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_run_state 53.07% 225 / 424
ironclaw_authorization 53.89% 464 / 861
ironclaw_triggers 59.89% 1792 / 2992
ironclaw_observability 61.54% 16 / 26
ironclaw_webui_v2 62.93% 2679 / 4257
ironclaw_mcp 63.03% 578 / 917
ironclaw_reborn_cli 66.18% 4488 / 6781
ironclaw_filesystem 67.1% 3833 / 5712
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 71.57% 1551 / 2167
ironclaw_trust 72.88% 661 / 907
ironclaw_capabilities 74.39% 1685 / 2265
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.67% 958 / 1283
ironclaw_extractors 74.72% 538 / 720
ironclaw_llm 78.36% 20328 / 25941
ironclaw_product_context 78.57% 11 / 14
ironclaw_first_party_extensions 78.81% 5576 / 7075
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_wasm_product_adapters 80.71% 1448 / 1794
ironclaw_memory_native 81.22% 3205 / 3946
ironclaw_secrets 82.7% 2791 / 3375
ironclaw_events 82.86% 1765 / 2130
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_wasm 83.97% 1011 / 1204
ironclaw_auth 83.99% 3147 / 3747
ironclaw_reborn_config 84.06% 1814 / 2158
ironclaw_processes 84.44% 993 / 1176
ironclaw_common 84.85% 1490 / 1756
ironclaw_turns 85.08% 13690 / 16090
ironclaw_host_api 85.13% 2663 / 3128
ironclaw_product_workflow 85.57% 10845 / 12674
ironclaw_projects 85.92% 659 / 767
ironclaw_network 86.12% 670 / 778
ironclaw_threads 86.7% 4594 / 5299
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_product_adapters 87.18% 3265 / 3745
ironclaw_skills 87.6% 4471 / 5104
ironclaw_hooks 87.78% 9921 / 11302
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_reborn_traces 88.19% 11946 / 13546
ironclaw_host_runtime 88.59% 17395 / 19635
ironclaw_reborn_composition 89.29% 80188 / 89811
ironclaw_extensions 89.38% 2971 / 3324
ironclaw_approvals 89.41% 1587 / 1775
ironclaw_runner 89.41% 16916 / 18919
ironclaw_reborn_openai_compat 89.55% 3798 / 4241
ironclaw_conversations 90.33% 3121 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_loop_host 92.52% 14811 / 16008
ironclaw_resources 92.83% 4736 / 5102
ironclaw_attachments 93.06% 630 / 677
ironclaw_reborn_webui_ingress 93.19% 2217 / 2379
ironclaw_telegram_v2_adapter 93.62% 2511 / 2682
ironclaw_agent_loop 94.79% 9199 / 9705
ironclaw_safety 95.04% 3677 / 3869
ironclaw_first_party_extension_ports 95.24% 3343 / 3510
ironclaw_outbound 95.59% 3556 / 3720

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

@railway-app

railway-app Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 14, 2026 at 4:24 pm

Add a control/treatment pair through the real CanonicalAgentLoopExecutor:
- WITH the gate on, a trailed-off reply ('...write it to the file:') re-enters
  with tools + the directive; the model then EXECUTES its write tool
  (batch_invocations == 1) and completes — the artifact it trailed off on gets
  produced.
- WITH the gate off (production default), the same trajectory ends right after
  the trail-off; the tool never runs (batch_invocations == 0). This is the
  failure the nudge fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 12, 2026 20:13 Destroyed
… panic)

Match the FINAL_ANSWER_NUDGE construction: return Result and propagate a
PlannerContract error instead of .expect(), satisfying the no-panics-in-
production-code check. Plus rustfmt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pranavraja99
pranavraja99 marked this pull request as ready for review July 12, 2026 21:30
Copilot AI review requested due to automatic review settings July 12, 2026 21:30

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pranavraja99

Copy link
Copy Markdown
Contributor Author

/benchmark pinchbench --framework ironclaw-reborn --model deepseek-ai/DeepSeek-V4-Flash

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Started pinchbench on ironclaw-reborn (model deepseek-ai/DeepSeek-V4-Flash) against ironclaw 5caadbae17 — watch run.

@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.

Code Review (multi-agent)

Intent: Enable tools-capable, driver-specific completion nudges for interactive coding when runs trail off or stall, while preserving clean replies and replay compatibility.

Shape: normal; reviewed the direct PR layer against main. No modifiers were selected: the exact comparison is neither mega, mechanical, nor generated/vendor-heavy, and its file list matches the captured GitHub snapshot.

Coverage: complete; exact local-git diff, 10 files (production 8, tests 1, docs 1, generated/vendor 0, CI 0, config 0), 0 packets, no oversized files, no failed reviewers, no limitations.

Stats: 9 findings (from 18 raw, 9 after dedup) across 6 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach. Reviewers failed: none. Body-only: 0.

The body lists every reviewer finding; overlapping findings are merged only for inline comments.

Security

  1. Medium Stale trail-off state reopens tools after a terminal capability result (crates/ironclaw_agent_loop/src/executor/canonical.rs:620, confidence 98) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:620
    last_reply_trailed_off is set by an admitted assistant reply but is never cleared when a later turn completes via capability calls. A trailing reply followed by drained queued input continues the loop with this flag still true; if the next capability batch sets terminate_hint, the stop strategy returns GracefulStop, and this branch mistakes the stale flag for a trail-off on the current turn. The executor consequently grants another full-tool iteration after the capability explicitly requested termination, allowing unintended repeated or mutating tool activity.

Bugs / Correctness

  1. Medium Pending nudge is cleared before it survives the model boundary (crates/ironclaw_agent_loop/src/executor/prompt.rs:363-368, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/prompt.rs:368
    The flag is cleared before the BeforeModel checkpoint and model call. If the call is retried, model.rs rebuilds the request from this state and replaces inline_messages with a bundle that no longer contains the completion directive; recovery from the checkpoint likewise resumes with completion_nudges_used incremented but no pending nudge. A transient model failure or lease/process recovery can therefore silently consume the bounded nudge and complete from an ordinary prompt without performing the unfinished tool work.
  2. Medium Completion conversion misses resume and compaction stop paths (crates/ironclaw_agent_loop/src/executor/canonical.rs:324-341, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:330
    The new completion_nudge_should_fire conversion exists only in the PromptStep::Prepared stop branch. The equivalent StopStep::Stop branches after approval/auth/external-tool resume and SkipModel still call ExitStage directly. A resumed capability that supplies the third NoChange result, or a compaction-only turn whose existing output-token window triggers NoProgressDetected, therefore receives only the old terminal/tool-free behavior instead of the promised tools-capable retry.
  3. Medium Stale trailed-off state can nudge an unrelated graceful stop (crates/ironclaw_agent_loop/src/executor/canonical.rs:618-621, confidence 95) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:620
    GracefulStop is produced both for a current ReplyOnly turn and for an all-terminate-hint capability batch, but this predicate consults only the sticky last_reply_trailed_off bit. If a colon-ending reply is preempted by queued follow-up input, the executor continues without clearing that bit; a capability batch for the follow-up that ends via terminate hints is then incorrectly treated as a newly trailed-off reply and gets an extra model/tool iteration.

Performance / Concurrency

No findings.

Tests

  1. Medium Production-wired interactive nudge lacks an integration test (crates/ironclaw_turns/src/run_profile/resolver.rs:332, confidence 100) — anchor: crates/ironclaw_turns/src/run_profile/resolver.rs:332
    The executor test manually enables nudges on MockHost, while the resolver test only checks the flag value. No tests/integration case drives the default interactive profile through product workflow, scheduler, agent loop, and a real capability seam, so production wiring could drop the flag or tool surface without failing these tests.
  2. Medium No-progress completion-nudge branch is untested (crates/ironclaw_agent_loop/src/executor/canonical.rs:619, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:619
    The new NoProgressDetected arm always requests a tools-capable retry, but existing enabled-gate no-progress tests invoke ExitStage directly and therefore bypass completion_nudge_should_fire and the canonical re-entry path. A regression could leave stalled runs on the old terminal, tool-free behavior while all current tests pass.
  3. Medium Two-nudge hard cap has no boundary coverage (crates/ironclaw_agent_loop/src/executor/canonical.rs:615-616, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:615
    No test reaches completion_nudges_used == COMPLETION_NUDGE_LIMIT. The added trail-off test exercises only the first nudge, so removing or weakening the cap would permit unbounded extra model and tool iterations without failing the suite.
  4. Medium Legacy checkpoint defaults for new nudge state are untested (crates/ironclaw_agent_loop/src/state.rs:87-103, confidence 100) — anchor: crates/ironclaw_agent_loop/src/state.rs:87
    The PR relies on serde(default) for three new LoopExecutionState fields, but the only legacy-checkpoint nudge test removes final_answer_nudges_used. No test strips completion_nudges_used, completion_nudge_pending, and last_reply_trailed_off and verifies that an older checkpoint still decodes with safe defaults.
  5. Low One-shot prompt consumption is not asserted (crates/ironclaw_agent_loop/src/executor/prompt.rs:368, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/prompt.rs:368
    The three-call completion test confirms that the second prompt contains the directive but never checks the third prompt. If completion_nudge_pending stopped being cleared, every later prompt in the same tool flow would repeat the control message and the current test would still pass.

Conventions

  1. Medium Move completion-nudge mechanics out of the lifecycle spine (crates/ironclaw_agent_loop/src/executor/canonical.rs:329-349, confidence 100) — anchor: crates/ironclaw_agent_loop/CLAUDE.md:42
    The new branch directly decides completion-nudge eligibility, mutates nudge state, and reroutes the stop transition inside canonical.rs. The crate contract requires canonical.rs to remain the ordered lifecycle spine and explicitly says lifecycle mechanics belong in their owning executor stage, not as branch logic here.
  2. Medium Preserve the control-message validation error (crates/ironclaw_agent_loop/src/executor/loop_exit.rs:58-62, confidence 100) — anchor: .claude/rules/error-handling.md:16
    The added map_err(|_| ...) discards the LoopInlineMessageBody validation failure. Repository error-handling rules explicitly reject cause-discarding map_err(|_| ...) mappings and require carrying or logging the bound source error.
  3. Medium Add required Reborn integration-tier coverage (crates/ironclaw_agent_loop/src/executor/tests.rs:1954-1955, confidence 100) — anchor: AGENTS.md:100
    This production-wired Reborn behavior is covered only by crate-local MockHost tests. The changed-file set contains no tests/integration/ test, and the PR body does not explain why the integration harness cannot reach the path. The repository rule requires production-wired Reborn behavior to ship with an integration-tier seam assertion, with crate-tier fallback allowed only when the PR states why integration cannot reach it.
  4. Medium Update the lightweight-loop contract for post-reply tool use (crates/ironclaw_agent_loop/src/executor/canonical.rs:330-340, confidence 100) — anchor: AGENTS.md:96
    The new path can persist an assistant Reply, then re-enter the loop and execute tools. The authoritative lightweight-loop contract still says every Reply completes and stops the run (docs/reborn/contracts/lightweight-agent-loop.md:102) and its contract-test list says a final reply completes without side effects (docs/reborn/contracts/lightweight-agent-loop.md:502). The diff changes that behavior without updating the relevant spec, contrary to the repository documentation rule. The new test comment at executor/tests.rs:2022 also calls gate-off the production default even though this PR enables it for the interactive production profile.

Local Patterns

  1. Low Gate comments still describe nudges as disabled in production (crates/ironclaw_turns/src/run_profile/resolver.rs:326-332, confidence 100) — anchor: Changed line crates/ironclaw_turns/src/run_profile/resolver.rs:332 enables the gate; crates/ironclaw_turns/tests/run_profile_contract.rs:53-54 confirms that interactive coding now opts in.
    Enabling allow_driver_specific_nudges for the default interactive profile makes several local comments stale: executor/loop_exit.rs:76 and executor/tests.rs:1766,2022 still call the disabled gate the production default, while executor/tests/support.rs:138 describes the shared gate as covering only the final-answer nudge. These comments now give maintainers the wrong activation model for both nudge paths.
  2. Nit Comment points to a nonexistent PromptStage::run method (crates/ironclaw_agent_loop/src/executor/prompt.rs:698-701, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/prompt.rs:193-214 shows PromptStage::process delegating to PromptPlanningPipeline::run, while changed line 701 names the nonexistent method.
    The comment says the flag is consumed by PromptStage::run, but PromptStage only implements process; the clearing happens in PromptPlanningPipeline::run. Searching the named symbol therefore leads nowhere when auditing the one-shot lifecycle.

Maintainability

  1. Medium Keep the completion-nudge transition inside StopStage (crates/ironclaw_agent_loop/src/executor/canonical.rs:330-341, confidence 100) — anchor: crates/ironclaw_agent_loop/src/executor/canonical.rs:330; crates/ironclaw_agent_loop/CLAUDE.md:44
    The nudge policy and Stop-to-Continue state transition are embedded in only the prepared-model arm's StopStep handler, even though canonical.rs has separate handlers for prepared, resumed-capability, and skip-model turns. This makes identical StopStep outcomes depend on which lifecycle arm produced them and forces future nudge changes to be repeated across the canonical spine. StopStage already owns the stop decision and receives the host context, state, summary, and pending ack needed to perform this transition.
  2. Low Keep reply-shape metadata on the completed-turn summary (crates/ironclaw_agent_loop/src/state.rs:97-103, confidence 90) — anchor: crates/ironclaw_agent_loop/src/state.rs:97; crates/ironclaw_agent_loop/src/strategies/stop.rs:40
    last_reply_trailed_off describes only the reply represented by the current TurnSummary, but it is stored in long-lived checkpoint state, written by AssistantReplyStage, read by stop handling, and manually reset in canonical.rs. That creates a three-module synchronization invariant and checkpoint compatibility surface for transient data that is consumed during the same completed-turn flow.

Approach

  1. Medium The opt-in also reaches subagent and scheduled-trigger profiles (crates/ironclaw_turns/src/run_profile/resolver.rs:326-332, confidence 96) — anchor: crates/ironclaw_turns/src/run_profile/resolver.rs:326-332; inherited via crates/ironclaw_turns/src/run_profile/resolver.rs:216-223 and crates/ironclaw_runner/src/planned_driver_factory.rs:224-287
    Enabling the flag in interactive_profile() is broader than the stated interactive-coding goal because RunProfileDefinition::interactive_like() clones this definition, and the runner uses that helper for the planned default, subagent, and scheduled-trigger profiles. Consequently background scheduled-trigger runs and subagent runs also gain bounded extra model/tool iterations even though this PR only validates the interactive profile. Make the nudge policy an explicit choice when constructing an interactive-like profile so only the intended interactive/default profile opts in; this preserves the existing behavior of background profiles and keeps the feature's runtime blast radius aligned with its goal.

)? {
StopStep::Stop {
state,
state: stop_state,

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.

Medium — Completion conversion misses resume and compaction stop paths.

The new completion_nudge_should_fire conversion exists only in the PromptStep::Prepared stop branch. The equivalent StopStep::Stop branches after approval/auth/external-tool resume and SkipModel still call ExitStage directly. A resumed capability that supplies the third NoChange result, or a compaction-only turn whose existing output-token window triggers NoProgressDetected, therefore receives only the old terminal/tool-free behavior instead of the promised tools-capable retry.

Fix: Extract shared StopStep::Stop handling and invoke the completion-nudge conversion from Prepared, resume, and SkipModel branches.

Also flagged by: conventions/Medium (Move completion-nudge mechanics out of the lifecycle spine), conventions/Medium (Update the lightweight-loop contract for post-reply tool use), maintainability/Medium (Keep the completion-nudge transition inside StopStage)

// `build_prompt_bundle_for_surface` (with the full tool surface still
// available). Clearing here bounds the nudge to exactly this iteration and
// keeps a later model-error retry from re-injecting it.
self.state.completion_nudge_pending = false;

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.

Medium — Pending nudge is cleared before it survives the model boundary.

The flag is cleared before the BeforeModel checkpoint and model call. If the call is retried, model.rs rebuilds the request from this state and replaces inline_messages with a bundle that no longer contains the completion directive; recovery from the checkpoint likewise resumes with completion_nudges_used incremented but no pending nudge. A transient model failure or lease/process recovery can therefore silently consume the bounded nudge and complete from an ordinary prompt without performing the unfinished tool work.

Fix: Keep completion_nudge_pending set through checkpoints and model retries, and clear it only after a successful model response has consumed the nudged request.

Also flagged by: tests/Low (One-shot prompt consumption is not asserted)

assert!(matches!(exit, LoopExit::Failed(_)));
}

#[tokio::test]

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.

Medium — Add required Reborn integration-tier coverage.

This production-wired Reborn behavior is covered only by crate-local MockHost tests. The changed-file set contains no tests/integration/ test, and the PR body does not explain why the integration harness cannot reach the path. The repository rule requires production-wired Reborn behavior to ship with an integration-tier seam assertion, with crate-tier fallback allowed only when the PR states why integration cannot reach it.

Fix: Add a tests/integration/ harness test that observes the nudged model request and capability execution, or document a concrete integration-tier reachability blocker in the PR.

pub(super) fn completion_nudge_control_message() -> Result<LoopInlineMessage, AgentLoopExecutorError>
{
let safe_body =
LoopInlineMessageBody::new(COMPLETION_NUDGE.trim().to_string()).map_err(|_| {

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.

Medium — Preserve the control-message validation error.

The added map_err(|_| ...) discards the LoopInlineMessageBody validation failure. Repository error-handling rules explicitly reject cause-discarding map_err(|_| ...) mappings and require carrying or logging the bound source error.

Fix: Bind the validation error and preserve it in the executor error or log it before returning the sanitized planner-contract error.

{
return false;
}
if state.completion_nudges_used >= COMPLETION_NUDGE_LIMIT {

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.

Medium — Two-nudge hard cap has no boundary coverage.

No test reaches completion_nudges_used == COMPLETION_NUDGE_LIMIT. The added trail-off test exercises only the first nudge, so removing or weakening the cap would permit unbounded extra model and tool iterations without failing the suite.

Fix: tests::executor::completion_nudge_stops_after_two_retries covering three consecutive trailed-off replies and asserting exactly two injected nudges, three model calls, and completion_nudges_used == 2

/// required output file) before answering. Capped so the loop can't issue
/// unbounded extra iterations. `#[serde(default)]` keeps older checkpoints
/// decodable.
#[serde(default)]

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.

Medium — Legacy checkpoint defaults for new nudge state are untested.

The PR relies on serde(default) for three new LoopExecutionState fields, but the only legacy-checkpoint nudge test removes final_answer_nudges_used. No test strips completion_nudges_used, completion_nudge_pending, and last_reply_trailed_off and verifies that an older checkpoint still decodes with safe defaults.

Fix: tests::state::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults covering omission of all three new fields and asserting 0/false/false after decoding

Also flagged by: maintainability/Low (Keep reply-shape metadata on the completed-turn summary)

return false;
}
match kind {
StopKind::NoProgressDetected => true,

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.

Medium — No-progress completion-nudge branch is untested.

The new NoProgressDetected arm always requests a tools-capable retry, but existing enabled-gate no-progress tests invoke ExitStage directly and therefore bypass completion_nudge_should_fire and the canonical re-entry path. A regression could leave stalled runs on the old terminal, tool-free behavior while all current tests pass.

Fix: tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected covering repeated no-change outcomes through CanonicalAgentLoopExecutor, followed by a nudged capability call and closing reply

Also flagged by: security/Medium (Stale trail-off state reopens tools after a terminal capability result), bugs/Medium (Stale trailed-off state can nudge an unrelated graceful stop)

// completion nudge to re-enter with the full tool surface rather than
// ending mid-task. Best-effort and capped; see the completion-nudge
// path in `ironclaw_agent_loop`.
allow_driver_specific_nudges: true,

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.

Medium — Production-wired interactive nudge lacks an integration test.

The executor test manually enables nudges on MockHost, while the resolver test only checks the flag value. No tests/integration case drives the default interactive profile through product workflow, scheduler, agent loop, and a real capability seam, so production wiring could drop the flag or tool surface without failing these tests.

Fix: tests::integration::completion_nudge::interactive_profile_executes_tool_after_trailed_off_reply covering a whole Reborn turn that trails off, receives the completion directive, invokes a recorded capability, and persists the closing reply

Also flagged by: local-patterns/Low (Gate comments still describe nudges as disabled in production), approach/Medium (The opt-in also reaches subagent and scheduled-trigger profiles)

.inline_messages
.push(invalid_model_output_repair_control_message());
}
// Tools-capable completion nudge scheduled by the stop handling on the prior

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.

Nit — Comment points to a nonexistent PromptStage::run method.

The comment says the flag is consumed by PromptStage::run, but PromptStage only implements process; the clearing happens in PromptPlanningPipeline::run. Searching the named symbol therefore leads nowhere when auditing the one-shot lifecycle.

Fix: Replace PromptStage::run with PromptPlanningPipeline::run (or say that the planning pipeline clears the flag after building the final bundle).

@github-actions

Copy link
Copy Markdown
Contributor

🧪 nearai-bench pinchbench — run complete (no baseline)

Ironclaw 5caadbae17 on ironclaw-reborn: 45.2% pass, avg score 0.909 across 147 tasks. No baseline exists under baselines/pinchbench/ to compare against — add one (e.g. via the nightly refresh job) to enable regression detection.

🔍 browse run + per-task trajectories · download results

@pranavraja99

Copy link
Copy Markdown
Contributor Author

/benchmark pinchbench --framework ironclaw-reborn --model deepseek-ai/DeepSeek-V4-Flash

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Started pinchbench on ironclaw-reborn (model deepseek-ai/DeepSeek-V4-Flash) against ironclaw 5caadbae17 — watch run.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 nearai-bench pinchbench — run complete (no baseline)

Ironclaw 5caadbae17 on ironclaw-reborn: 23.8% pass, avg score 0.421 across 147 tasks. No baseline exists under baselines/pinchbench/ to compare against — add one (e.g. via the nightly refresh job) to enable regression detection.

🔍 browse run + per-task trajectories · download results

@pranavraja99

Copy link
Copy Markdown
Contributor Author

/benchmark pinchbench --framework ironclaw-reborn

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Started pinchbench on ironclaw-reborn against ironclaw 5caadbae17 — watch run.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 nearai-bench pinchbench — run complete (no baseline)

Ironclaw 5caadbae17 on ironclaw-reborn: 55.1% pass, avg score 0.926 across 147 tasks. No baseline exists under baselines/pinchbench/ to compare against — add one (e.g. via the nightly refresh job) to enable regression detection.

🔍 browse run + per-task trajectories · download results

serrrfirat
serrrfirat previously approved these changes Jul 14, 2026

@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 (12)
crates/ironclaw_agent_loop/src/state.rs (2)

88-112: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Medium — Legacy checkpoint defaults for new nudge state are untested.

The PR relies on serde(default) for three new LoopExecutionState fields (completion_nudges_used, completion_nudge_pending, last_reply_trailed_off), but the only legacy-checkpoint nudge test removes final_answer_nudges_used. No test strips the new completion fields and verifies that an older checkpoint still decodes with safe defaults.

Fix: Add tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults covering omission of all three new fields and asserting 0/false/false after decoding.

🤖 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_agent_loop/src/state.rs` around lines 88 - 112, Add
tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults
alongside the existing legacy checkpoint tests. Remove completion_nudges_used,
completion_nudge_pending, and last_reply_trailed_off from the serialized
checkpoint payload, decode it into LoopExecutionState, and assert the fields
default to 0, false, and false respectively.

88-112: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Medium — Legacy checkpoint defaults for new nudge state are untested.

The PR relies on serde(default) for three new LoopExecutionState fields (completion_nudges_used, completion_nudge_pending, last_reply_trailed_off), but the only legacy-checkpoint nudge test removes final_answer_nudges_used. No test strips the new completion fields and verifies that an older checkpoint still decodes with safe defaults.

Fix: Add tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults covering omission of all three new fields and asserting 0/false/false after decoding.

🤖 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_agent_loop/src/state.rs` around lines 88 - 112, Add
tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults
alongside the existing legacy checkpoint test, removing completion_nudges_used,
completion_nudge_pending, and last_reply_trailed_off from the serialized payload
before decoding. Assert the decoded LoopExecutionState restores these fields to
0, false, and false respectively.
crates/ironclaw_agent_loop/src/executor/tests.rs (6)

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

Medium — No-progress completion-nudge branch is untested.

The new NoProgressDetected arm always requests a tools-capable retry, but existing enabled-gate no-progress tests invoke ExitStage directly and therefore bypass completion_nudge_should_fire and the canonical re-entry path. A regression could leave stalled runs on the old terminal, tool-free behavior while all current tests pass.

Fix: Add tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected covering repeated no-change outcomes through CanonicalAgentLoopExecutor, followed by a nudged capability call and closing reply.

🤖 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_agent_loop/src/executor/tests.rs` at line 2345, Add an async
test named
tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected
that drives repeated no-change outcomes through CanonicalAgentLoopExecutor,
exercises the NoProgressDetected completion-nudge path, verifies re-entry
requests tool capabilities, and finishes with a closing reply.

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

Medium — No-progress completion-nudge branch is untested.

The new NoProgressDetected arm always requests a tools-capable retry, but existing enabled-gate no-progress tests invoke ExitStage directly and therefore bypass completion_nudge_should_fire and the canonical re-entry path. A regression could leave stalled runs on the old terminal, tool-free behavior while all current tests pass.

Fix: Add tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected covering repeated no-change outcomes through CanonicalAgentLoopExecutor, followed by a nudged capability call and closing reply.

🤖 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_agent_loop/src/executor/tests.rs` at line 2345, Add the test
tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected
in the executor tests, exercising repeated no-change outcomes through
CanonicalAgentLoopExecutor, then verifying completion_nudge_should_fire handles
NoProgressDetected by re-entering with tools enabled and accepting the closing
reply.

2279-2279: 📐 Maintainability & Code Quality | 🟠 Major | ⚖️ Poor tradeoff

Medium — Add required Reborn integration-tier coverage.

This production-wired Reborn behavior is covered only by crate-local MockHost tests. The changed-file set contains no tests/integration/ test, and the PR body does not explain why the integration harness cannot reach the path. The repository rule requires production-wired Reborn behavior to ship with an integration-tier seam assertion, with crate-tier fallback allowed only when the PR states why integration cannot reach it.

Fix: Add a tests/integration/ harness test that observes the nudged model request and capability execution, or document a concrete integration-tier reachability blocker in the PR.

🤖 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_agent_loop/src/executor/tests.rs` at line 2279, Add
integration-tier coverage under tests/integration/ for the production-wired
Reborn path exercised by the test near the #[tokio::test] in the executor tests.
Assert that the harness observes both the nudged model request and subsequent
capability execution; if the path cannot be reached through the integration
harness, document the specific reachability blocker in the PR instead.

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

Medium — Two-nudge hard cap has no boundary coverage.

No test reaches completion_nudges_used == COMPLETION_NUDGE_LIMIT. The added trail-off test exercises only the first nudge, so removing or weakening the cap would permit unbounded extra model and tool iterations without failing the suite.

Fix: Add tests::executor::completion_nudge_stops_after_two_retries covering three consecutive trailed-off replies and asserting exactly two injected nudges, three model calls, and completion_nudges_used == 2.

🤖 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_agent_loop/src/executor/tests.rs` at line 2377, Add the async
test completion_nudge_stops_after_two_retries in tests::executor, using three
consecutive trailed-off replies to exercise the completion nudge limit. Assert
exactly two injected nudges, three model calls, and completion_nudges_used == 2,
preserving the existing executor test setup and assertions for the first-nudge
behavior.

2279-2279: 📐 Maintainability & Code Quality | 🟠 Major | ⚖️ Poor tradeoff

Medium — Add required Reborn integration-tier coverage.

This production-wired Reborn behavior is covered only by crate-local MockHost tests. The changed-file set contains no tests/integration/ test, and the PR body does not explain why the integration harness cannot reach the path. The repository rule requires production-wired Reborn behavior to ship with an integration-tier seam assertion, with crate-tier fallback allowed only when the PR states why integration cannot reach it.

Fix: Add a tests/integration/ harness test that observes the nudged model request and capability execution, or document a concrete integration-tier reachability blocker in the PR.

🤖 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_agent_loop/src/executor/tests.rs` at line 2279, Add an
integration-tier test under tests/integration/ that exercises the
production-wired Reborn flow and asserts both the nudged model request and
resulting capability execution, rather than relying only on the crate-local
MockHost test around the tokio test. If the integration harness genuinely cannot
reach this path, document the specific reachability blocker in the PR instead.

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

Medium — Two-nudge hard cap has no boundary coverage.

No test reaches completion_nudges_used == COMPLETION_NUDGE_LIMIT. The added trail-off test exercises only the first nudge, so removing or weakening the cap would permit unbounded extra model and tool iterations without failing the suite.

Fix: Add tests::executor::completion_nudge_stops_after_two_retries covering three consecutive trailed-off replies and asserting exactly two injected nudges, three model calls, and completion_nudges_used == 2.

🤖 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_agent_loop/src/executor/tests.rs` at line 2377, Add the async
test completion_nudge_stops_after_two_retries under tests::executor, configuring
three consecutive trailed-off replies and asserting exactly two injected nudges,
three model calls, and completion_nudges_used == 2. Use the existing trail-off
test setup and helpers, extending it only to cover the COMPLETION_NUDGE_LIMIT
boundary.
crates/ironclaw_agent_loop/src/executor/canonical.rs (2)

331-377: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Medium — Completion conversion misses resume and compaction stop paths.

The completion_nudge_should_fire transition exists only in the PromptStep::Prepared stop branch. The equivalent StopStep::Stop branches after ResumeApproval/ResumeAuth/ResumeExternalTool (lines 460-478) and SkipModel (lines 548-567) still call ExitStage directly. A resumed capability that supplies a NoChange result, or a compaction-only turn triggering NoProgressDetected, receives the old terminal, tool-free behavior instead of the tools-capable retry.

Fix: Extract the shared StopStep::Stop handling (including the nudge check) and invoke it from the Prepared, resume, and SkipModel branches.

🤖 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_agent_loop/src/executor/canonical.rs` around lines 331 - 377,
The completion-nudge transition is implemented only for the Prepared stop path,
leaving resume and SkipModel StopStep branches terminal. Extract the shared
StopStep::Stop handling, including completion_nudge_should_fire and its
tools-capable retry state updates, and reuse it from Prepared,
ResumeApproval/ResumeAuth/ResumeExternalTool, and SkipModel branches while
preserving normal ExitStage processing.

331-377: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Medium — Completion conversion misses resume and compaction stop paths.

The completion_nudge_should_fire transition exists only in the PromptStep::Prepared stop branch. The equivalent StopStep::Stop branches after ResumeApproval/ResumeAuth/ResumeExternalTool (lines 460-478) and SkipModel (lines 548-567) still call ExitStage directly. A resumed capability that supplies a NoChange result, or a compaction-only turn triggering NoProgressDetected, receives the old terminal, tool-free behavior instead of the tools-capable retry.

Fix: Extract the shared StopStep::Stop handling (including the nudge check) and invoke it from the Prepared, resume, and SkipModel branches.

🤖 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_agent_loop/src/executor/canonical.rs` around lines 331 - 377,
Extract the shared StopStep::Stop handling into a reusable path that performs
completion_nudge_should_fire before invoking ExitStage, preserving ack deferral
and stop-state updates. Replace the direct terminal handling in the
PromptStep::Prepared, ResumeApproval/ResumeAuth/ResumeExternalTool, and
SkipModel branches so resumed NoChange and compaction-only NoProgressDetected
stops receive the same tools-capable completion nudge.
crates/ironclaw_agent_loop/src/executor/prompt.rs (2)

373-378: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Medium — Pending nudge is cleared before it survives the model boundary.

The completion_nudge_pending flag is cleared before the BeforeModel checkpoint and model call. If the model call fails and is retried, model.rs rebuilds the request from this state and replaces inline_messages with a bundle that no longer contains the completion directive. Recovery from the checkpoint likewise resumes with completion_nudges_used incremented but no pending nudge. A transient model failure or lease/process recovery can therefore silently consume the bounded nudge and complete from an ordinary prompt without performing the unfinished tool work.

Fix: Keep completion_nudge_pending set through checkpoints and model retries, and clear it only after a successful model response has consumed the nudged request.

🤖 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_agent_loop/src/executor/prompt.rs` around lines 373 - 378,
Keep completion_nudge_pending set through the BeforeModel checkpoint, model
call, retries, and recovery paths; clear it only after a successful model
response has consumed the nudged request. Update the completion-nudge handling
around build_prompt_bundle_for_surface and the model response flow so failed
calls or resumed checkpoints preserve and re-inject the directive.

373-378: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Medium — Pending nudge is cleared before it survives the model boundary.

The completion_nudge_pending flag is cleared before the BeforeModel checkpoint and model call. If the model call fails and is retried, model.rs rebuilds the request from this state and replaces inline_messages with a bundle that no longer contains the completion directive. Recovery from the checkpoint likewise resumes with completion_nudges_used incremented but no pending nudge. A transient model failure or lease/process recovery can therefore silently consume the bounded nudge and complete from an ordinary prompt without performing the unfinished tool work.

Fix: Keep completion_nudge_pending set through checkpoints and model retries, and clear it only after a successful model response has consumed the nudged request.

🤖 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_agent_loop/src/executor/prompt.rs` around lines 373 - 378,
Keep state.completion_nudge_pending set through the BeforeModel checkpoint and
model retry/recovery paths, rather than clearing it immediately after building
the prompt bundle. Clear it only after the model call returns successfully and
the nudged request has been consumed, preserving the directive when model.rs
rebuilds the request or execution resumes from a checkpoint.
🤖 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_agent_loop/src/executor/canonical.rs`:
- Around line 331-377: The completion-nudge transition is implemented only for
the Prepared stop path, leaving resume and SkipModel StopStep branches terminal.
Extract the shared StopStep::Stop handling, including
completion_nudge_should_fire and its tools-capable retry state updates, and
reuse it from Prepared, ResumeApproval/ResumeAuth/ResumeExternalTool, and
SkipModel branches while preserving normal ExitStage processing.
- Around line 331-377: Extract the shared StopStep::Stop handling into a
reusable path that performs completion_nudge_should_fire before invoking
ExitStage, preserving ack deferral and stop-state updates. Replace the direct
terminal handling in the PromptStep::Prepared,
ResumeApproval/ResumeAuth/ResumeExternalTool, and SkipModel branches so resumed
NoChange and compaction-only NoProgressDetected stops receive the same
tools-capable completion nudge.

In `@crates/ironclaw_agent_loop/src/executor/prompt.rs`:
- Around line 373-378: Keep completion_nudge_pending set through the BeforeModel
checkpoint, model call, retries, and recovery paths; clear it only after a
successful model response has consumed the nudged request. Update the
completion-nudge handling around build_prompt_bundle_for_surface and the model
response flow so failed calls or resumed checkpoints preserve and re-inject the
directive.
- Around line 373-378: Keep state.completion_nudge_pending set through the
BeforeModel checkpoint and model retry/recovery paths, rather than clearing it
immediately after building the prompt bundle. Clear it only after the model call
returns successfully and the nudged request has been consumed, preserving the
directive when model.rs rebuilds the request or execution resumes from a
checkpoint.

In `@crates/ironclaw_agent_loop/src/executor/tests.rs`:
- Line 2345: Add an async test named
tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected
that drives repeated no-change outcomes through CanonicalAgentLoopExecutor,
exercises the NoProgressDetected completion-nudge path, verifies re-entry
requests tool capabilities, and finishes with a closing reply.
- Line 2345: Add the test
tests::executor::completion_nudge_reenters_with_tools_after_no_progress_detected
in the executor tests, exercising repeated no-change outcomes through
CanonicalAgentLoopExecutor, then verifying completion_nudge_should_fire handles
NoProgressDetected by re-entering with tools enabled and accepting the closing
reply.
- Line 2279: Add integration-tier coverage under tests/integration/ for the
production-wired Reborn path exercised by the test near the #[tokio::test] in
the executor tests. Assert that the harness observes both the nudged model
request and subsequent capability execution; if the path cannot be reached
through the integration harness, document the specific reachability blocker in
the PR instead.
- Line 2377: Add the async test completion_nudge_stops_after_two_retries in
tests::executor, using three consecutive trailed-off replies to exercise the
completion nudge limit. Assert exactly two injected nudges, three model calls,
and completion_nudges_used == 2, preserving the existing executor test setup and
assertions for the first-nudge behavior.
- Line 2279: Add an integration-tier test under tests/integration/ that
exercises the production-wired Reborn flow and asserts both the nudged model
request and resulting capability execution, rather than relying only on the
crate-local MockHost test around the tokio test. If the integration harness
genuinely cannot reach this path, document the specific reachability blocker in
the PR instead.
- Line 2377: Add the async test completion_nudge_stops_after_two_retries under
tests::executor, configuring three consecutive trailed-off replies and asserting
exactly two injected nudges, three model calls, and completion_nudges_used == 2.
Use the existing trail-off test setup and helpers, extending it only to cover
the COMPLETION_NUDGE_LIMIT boundary.

In `@crates/ironclaw_agent_loop/src/state.rs`:
- Around line 88-112: Add
tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults
alongside the existing legacy checkpoint tests. Remove completion_nudges_used,
completion_nudge_pending, and last_reply_trailed_off from the serialized
checkpoint payload, decode it into LoopExecutionState, and assert the fields
default to 0, false, and false respectively.
- Around line 88-112: Add
tests::checkpoint_payload_without_completion_nudge_fields_decodes_to_defaults
alongside the existing legacy checkpoint test, removing completion_nudges_used,
completion_nudge_pending, and last_reply_trailed_off from the serialized payload
before decoding. Assert the decoded LoopExecutionState restores these fields to
0, false, and false respectively.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 905c4697-7641-4008-8434-a2e21cd38f6f

📥 Commits

Reviewing files that changed from the base of the PR and between 5caadba and ce9dde2.

📒 Files selected for processing (7)
  • crates/ironclaw_agent_loop/src/executor.rs
  • crates/ironclaw_agent_loop/src/executor/assistant_reply.rs
  • crates/ironclaw_agent_loop/src/executor/canonical.rs
  • crates/ironclaw_agent_loop/src/executor/loop_exit.rs
  • crates/ironclaw_agent_loop/src/executor/prompt.rs
  • crates/ironclaw_agent_loop/src/executor/tests.rs
  • crates/ironclaw_agent_loop/src/state.rs

Copilot AI review requested due to automatic review settings July 14, 2026 07:28
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 14, 2026 07:28 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…rift CI break)

main added model_observation to CapabilityResultMessage; the completion-nudge
test's batch-outcome literal predates it, so the merge-with-main didn't compile
(E0063), failing every Reborn test bucket + clippy. Set None to match the
current struct. Nudge logic unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 14, 2026 07:44
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 14, 2026 07:44 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pranavraja99
pranavraja99 disabled auto-merge July 14, 2026 14:07
Copilot AI review requested due to automatic review settings July 14, 2026 14:07
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6013 July 14, 2026 14:07 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pranavraja99
pranavraja99 enabled auto-merge July 14, 2026 14:55
Copilot AI review requested due to automatic review settings July 14, 2026 16:08

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@henrypark133 henrypark133 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.

Code Review (multi-agent)

Intent: Make interactive coding completion nudges tools-capable so trailing-off or no-progress turns can finish required tasks before answering.

Stats: 0 net-new findings (4 candidate findings, 3 after overlap deduplication, all covered by existing unresolved review threads) across 0 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach (parent-context fallback because the agent thread limit was reached). Reviewers failed: none. Body-only: 0.

No new actionable findings remain after comparing the forced-pass candidates with the 10 unresolved current-head review threads. The focused completion-nudge tests also pass: 3 passed.

@pranavraja99
pranavraja99 added this pull request to the merge queue Jul 14, 2026
Merged via the queue into main with commit 15f06e1 Jul 14, 2026
65 checks passed
@pranavraja99
pranavraja99 deleted the feat/tools-capable-completion-nudge branch July 14, 2026 16:52
henrypark133 added a commit that referenced this pull request Jul 16, 2026
…ntaining "Secretary" aren't scrubbed as "secret" on replay — undoes #5902 tool-result eviction / re-fetch loop (+ 24KB/48KB caps) (#6129)

* docs: add design spec for enabling Reborn nudges on 2 profiles

Scopes allow_driver_specific_nudges to interactive_default and
scheduled_trigger via a builder method, avoiding a shared-base flip
that would leak into planned_default/subagent.

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

* docs: correct nudge-enable spec target from interactive_default to planned_default

Real production interactive/chat/CLI turns request no explicit run
profile (submit_user_turn passes requested_run_profile: None) and the
production resolver defaults that to planned_default, not the literal
interactive_profile() construct. Retargets the design accordingly and
simplifies the implementation (no shared-base change needed at all).

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

* docs: add implementation plan for enabling planned_default/scheduled_trigger nudges

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

* docs: extract shared test helpers in nudge-enable plan per thermo-nuclear review

Tasks 6/7 previously copy-pasted the same scripted scenario and
completion assertion; extract no_progress_script()/
assert_completed_via_nudge() once, following the file's existing
run_request/run_context_for_driver helper-extraction pattern.

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

* feat(ironclaw_turns): add RunProfileDefinition::with_driver_specific_nudges builder

* Revert "feat(ironclaw_turns): add RunProfileDefinition::with_driver_specific_nudges builder"

This reverts commit 326a16c.

* docs: agent-loop canonical.rs slop cleanup design spec

Scopes fix for two recurring anti-pattern instances (completion-nudge
branching from #6013, latency-span boilerplate from #5487) plus a new
guardrail rule file, per Slack discussion on ironclaw_agent_loop growth.

* fix(threads): raise result_read preview/chunk cap to undo #5902 benchmark regression

PR #5902 (fixing #5838's context-compaction crash) cut the model-visible
tool-result preview from 100,000 to 2,048 bytes and capped result_read
pagination at the same 2,048-byte chunk with no bulk-fetch option --
recovering a 100KB tool result now takes ~49 manual result_read calls,
which most agent policies won't do reliably. This is a likely cause of
the reported benchmark score regression.

Raises TOOL_RESULT_RECORD_READ_MAX_BYTES to 40KB (still per-call bounded,
which is the property #5838 actually needed -- unbounded accumulation in
the compacted transcript caused the crash, not single-call size). Derives
MAX_MODEL_OBSERVATION_BYTES from that constant (was an independent 4096
literal) so the whole-envelope validation cap can't silently fall behind
the preview cap and start dropping large observations to bare summaries.

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

* fix(tests): size large-echo fixture against raised result_read cap

TOOL_RESULT_RECORD_READ_MAX_BYTES was raised 2KiB -> 40KiB earlier in
this PR; local_dev_runtime_safe_preview_observer_receives_bounded_payload
used a hardcoded ~2.5KB fixture sized for the old cap, so it no longer
exceeded the new cap and the truncation path it exercises stopped
firing, failing the "raw tail must remain out of the model replay"
assertion. Size the fixture relative to the cap instead, matching the
fix already applied to the other 3 hardcoded-size fixtures in this PR.

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

* fix(threads): word-boundary match for sensitive markers (undo "Secretary" scrub)

The replay validator matched SENSITIVE/PROMPT_INJECTION markers as raw
substrings, so `secret` matched the ordinary word `secretary`. Any tool
result containing "Secretary of the Treasury" (i.e. every OfficeQA read,
and PinchBench doc tasks) had its model-observation scrubbed on replay and
fell back to a stub — evicting the content from the transcript and sending
the model into a re-fetch loop (identical read/grep re-issued 16-26x,
result_read storms, 3-10x calls/cost, timeouts). This is the residual
#5902 regression the preview/observation cap bump alone did not fix.

Match markers on word boundaries instead: a marker only trips as a
standalone alphanumeric token. Delimiter-bounded markers (`bearer `,
`authorization:`) and standalone credentials (`client secret`) still match.

Verified on the two hardest-hit OfficeQA tasks (UID0072, UID0034): scrub
events 16+/task -> 0, result_read 12-16 -> 0-2, calls 45-173 -> 18-29,
score 0.0 -> 1.0, cost at/below pre-5902.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(threads): lower caps to 16KB/32KB + retention regression tests

Follow-up on the same PR:

- Preview cap 40KB -> 16KB and envelope cap derived as 2x (32KB). 40KB
  retained per result was too much context; 16KB holds a normal table read
  while staying bounded, and 32KB envelope comfortably fits a 16KB preview
  of ordinary text plus schema fields.

- With the preview lower, more results overflow onto the result_read path,
  so the retention of paged content matters more. That path was never a
  separate bug: a result_read chunk's observation flows through the SAME
  normalize/validate scrub as the first-look preview, so the word-boundary
  marker fix already keeps paged chunks intact (confirmed: at 16KB, tasks
  page result_read and still resolve 1.0). No InlineOnly change needed.

- Comprehensive retention tests so this can't regress:
  * document_content_preview_is_retained_on_replay_not_scrubbed — an 8KB
    "Secretary of the Treasury" preview stays intact through replay.
  * observation_envelope_cap_covers_the_preview_cap — structural guard that
    the envelope cap can never fall below the preview cap (the #5902 drift).
  * full_cap_preview_survives_replay — a max-size preview still fits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(threads): pin result_read chunk retention + bump preview cap to 24KB

- Add `result_read_chunk_observation_with_document_content_is_retained`:
  proves a paged result_read CHUNK observation (same shape
  result_read_observation emits) carrying "Secretary of the Treasury"
  survives replay intact, not scrubbed to a stub. Chunk observations run
  through the same normalize/scrub as first-look previews, so this pins
  that the paged-retrieval path ("get the rest") stays fixed too — the
  deterministic guarantee that paged content can't silently vanish.

- Preview cap 16KB -> 24KB (envelope derives to 48KB). 16KB was tight
  enough that overflow-heavy reads paged hard; 24KB holds a normal
  multi-table read while staying bounded. Retention is guaranteed by the
  tests regardless of the exact cap; 24KB is the tuned middle.

Deterministic coverage now spans all three retention failure modes:
first-look preview (document_content_preview_is_retained...), paged chunk
(this test), and envelope-vs-preview drift
(observation_envelope_cap_covers_the_preview_cap). ironclaw_threads 82/82.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(threads): retain result-read previews on replay

* fix(threads): satisfy replay CI guards

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Pranav Raja <pranav.raja@near.ai>

This branch is being deployed

1 in progress deployment
ironclaw-ci-preview / ironclaw-pr-6013 — c0847b68 Deployed Jul 14, 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 scope: docs Documentation size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants