Skip to content

Studio: Let the agent run a command again after it edits a file - #10810

Open
NilayYadav wants to merge 3 commits into
unslothai:mainfrom
NilayYadav:agent-rerun-after-edit
Open

Studio: Let the agent run a command again after it edits a file#10810
NilayYadav wants to merge 3 commits into
unslothai:mainfrom
NilayYadav:agent-rerun-after-edit

Conversation

@NilayYadav

Copy link
Copy Markdown
Collaborator

Fixes #10792. When the agent ran a command, edited a file, and then ran the same command again, the second run was skipped. Studio remembered every successful call for the whole reply and treated any repeat as a duplicate, even when a file had changed in between. The agent was told the command had already run, so it could not check its own change, and trying again removed its tools and ended the reply.

Now when a python, terminal, or edit_file call succeeds, Studio forgets the earlier calls to those three tools, so a later identical command runs again. Running the exact same call twice in a row is still skipped, the limit on repeated duplicates from #5962 is kept, and web search still skips repeats.

Tested in the Studio chat with an agent that runs cat notes.txt, edits the file, and runs cat notes.txt again. Before, the second cat was skipped and the reply never finished. After, it ran and showed the edited file. This is separate from #10658, which only lets its own project task tools repeat.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T21:21:48.407255Z 8da4fc4 Manual request
ℹ️ About Codex in GitHub

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

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

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02682d496c

ℹ️ About Codex in GitHub

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

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

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

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

)
)
if not failed:
if decision.tool_name in _WORKSPACE_TOOLS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate cached workspace calls after side-effecting failures

When a Python or terminal invocation modifies a file and then exits nonzero, is_tool_error() marks the result as failed, so this invalidation never runs. For example, after a successful cat x, a Python call can write new contents to x and then raise an exception; the next identical cat x remains in _successful_keys and is incorrectly suppressed, potentially forcing the reply to finish without verifying the changed file. Clear stale workspace keys whenever one of these tools actually executes, while retaining the current success check only for caching the just-completed call.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 56ccabc: python, terminal and edit_file now clear earlier workspace results whether the call succeeds or fails, so a read after a failed write runs again. Covered by test_failed_workspace_execution_invalidates_previous_reads.

)
)
if not failed:
if decision.tool_name in _WORKSPACE_TOOLS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve workspace repeats within multi-call turns

When a model emits terminal(cat x), edit_file(x, ...), and the same terminal(cat x) in one turn, this invalidation cannot take effect in the safetensors loop or the GGUF textual-fallback path: those paths remove repeated (tool name, arguments) pairs before calls reach ToolLoopController (safetensors_agentic.py:1305-1324 and llama_cpp.py:32356-32377). Consequently the second read is still discarded even though the intervening edit makes its result stale, so the fix only works when each call arrives in a separate model turn. Those prefilters need to retain non-adjacent workspace repeats or defer their deduplication to this controller.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 56ccabc: the safetensors and GGUF text-fallback prefilters no longer drop repeated workspace calls within a batch, so the controller decides them and a read after an edit in the same turn runs. Covered by test_workspace_read_edit_read_in_one_turn and test_textual_workspace_read_edit_read_in_one_turn.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Sep 11, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56ccabc127

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines 1313 to 1316
if _key in seen_keys and _fn.get("name") not in _WORKSPACE_TOOLS:
continue
seen_keys.add(_key)
deduped.append(_tc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep redundant workspace calls from consuming the batch cap

When a textual turn contains repeated workspace calls before a later useful call—for example, eight identical terminal(cat x) calls followed by edit_file—this exception retains every duplicate until _MAX_TOOL_CALLS_PER_TURN is reached, so the edit is discarded even though seven reads become controller no-ops. Collapse workspace duplicates that have no intervening workspace execution, or apply the cap after no-op classification; the mirrored condition in llama_cpp.py has the same failure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 8da4fc4: both text-parsed prefilters now collapse a workspace call that repeats the previous workspace call in the batch, since the controller would no-op it anyway, so repeats no longer fill the batch cap and drop a later edit. A repeat after a different workspace call is still kept. Covered by test_repeated_workspace_reads_do_not_crowd_out_a_later_edit and test_textual_repeated_workspace_reads_do_not_crowd_out_a_later_edit.

Comment on lines +1130 to +1132
self._successful_keys -= stale
for key in stale:
self._duplicate_noop_counts.pop(key, None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset the stale duplicate final-answer latch

If a successful workspace read is repeated twice, record_noop sets _force_final_answer; when an edit_file call later executes in the same batch, this block removes the now-stale duplicate counter but leaves that latch set. Both local loops then enter their no-tools final pass (safetensors_agentic.py:1586 and llama_cpp.py:33317), so the agent still cannot rerun the read to verify the edit. The duplicate-driven latch must be recomputed when these workspace entries are invalidated without clearing terminal states caused by unrelated disabled or one-shot calls.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changing this one. The latch is only set after the model repeats an identical call that already ran, twice in one reply despite the duplicate nudge, which is the #5962 loop guard working as intended. On main the same sequence also ends the reply and additionally skips the read after the edit; with this PR a read after the edit in the same batch still runs, so this is not a regression, and clearing the latch on an edit would weaken that guard.

@NilayYadav

Copy link
Copy Markdown
Collaborator Author

Reproduction on GitHub Actions: before and after

I ran the same flow in a real Studio install on ubuntu-latest with Chromium at 1280×1400. Both branches start from fresh main (bf87e29) and use an identical workflow, harness and tests. The only difference is whether the fix is included.

A scripted OpenAI-compatible connection sends four real calls through the Studio tool loop with Code enabled: edit_file creates notes.txt, terminal runs cat notes.txt, edit_file edits the file, then the same cat notes.txt runs again.

Tool call Without fix: run 34645585151 With PR: run 34645587086
edit_file creates notes.txt Created notes.txt (2 lines) Created notes.txt (2 lines)
cat notes.txt version one version one
edit_file edits notes.txt Edited notes.txt (1 replacement) Edited notes.txt (1 replacement)
cat notes.txt after the edit Unsloth did not run this call because an identical one had already completed. version two
Tool results sent back to the model 3 4

PR 10810 before and after

Unit tests, same two runs:

  • Without fix: the 8 new test cases fail ('duplicate' == 'execute', and the second read is missing from the executed calls). The other 567 tests in the three files pass.
  • With PR: 8 of 8 new cases pass, and all 575 tests in test_tool_loop_controller.py, test_safetensors_tool_loop.py and test_llama_cpp_tool_loop.py pass.

Review:

  • First Codex round: both items are fixed in 56ccabc.
  • Second Codex round:
    • Batch cap (fixed in 8da4fc4): repeated workspace calls in one text-parsed turn could fill the 8-call cap and drop a later edit. The prefilters now collapse only back-to-back workspace repeats, and both loops have a regression test that fails on 56ccabc and passes on 8da4fc4.
    • Stale latch (not changed): it only triggers when the model repeats an identical call twice after the duplicate nudge, and main ends the reply in that case too.
  • Other findings: none blocking.

The two red upstream Backend CI jobs fail the same tests on main (test_deepseek_r1_huge_fenceless_body_is_linear and test_tauri_collapse_removes_the_icon_rail_but_web_keeps_it). They are unrelated to this change.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Sep 11, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8da4fc4dfc

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines 1313 to +1316
_key = (_fn.get("name", ""), str(_fn.get("arguments", "")))
if _key in seen_keys:
if _fn.get("name") in _WORKSPACE_TOOLS:
# A workspace repeat only matters after a different workspace call.
if _key == last_workspace_key:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Canonicalize workspace calls before collapsing repeats

When textual output alternates equivalent edit_file arguments whose JSON object keys have different orders, str(arguments) gives different _key values even though ToolLoopController canonicalizes them to the same call. Consequently eight alternating serializations all survive this filter, the first executes and the rest become controller no-ops, and _MAX_TOOL_CALLS_PER_TURN can discard a later verification command. Fresh evidence after 8da4fc4 is that its back-to-back check compares raw serialization rather than the controller's canonical key; the mirrored GGUF filter has the same issue.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changing this one. It needs a single turn with eight or more equivalent calls whose JSON keys alternate order before a later call, which is degenerate model output rather than a normal session. The prefilter on main already compares the same raw argument string for every tool, so this PR does not introduce it, and the controller still turns those repeats into no-ops.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Sep 11, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 8da4fc4dfc

ℹ️ About Codex in GitHub

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

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

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

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

@danielhanchen

Copy link
Copy Markdown
Member

Confirmed this hits tool_loop_controller.py where record_result keeps every successful key for the whole reply, so a command after an edit_file comes back as a duplicate and the model never sees its own change. Will get this reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Unsloth Bug] Duplicate tool-call guard blocks re-running a command after files changed (e.g. re-running tests after an edit)

2 participants