Skip to content

fix: detect submit actions in replay files written by run-replay - #1569

Open
BlueX888 wants to merge 3 commits into
SWE-agent:mainfrom
BlueX888:fix/prep-replay-model-never-advances
Open

BlueX888 wants to merge 3 commits into
SWE-agent:mainfrom
BlueX888:fix/prep-replay-model-never-advances

Conversation

@BlueX888

@BlueX888 BlueX888 commented Sep 25, 2026 •

Copy link
Copy Markdown

Reference Issues/PRs

None — found while reading the replay flow; no existing issue or PR covers this.

What does this implement/fix? Explain your changes.

Summary

ReplayModel never advanced to the next entry of a replay file: _next_replay() was only called when the replayed action was exactly the string "submit". run-replay's own writer (_create_actions_file, sweagent/run/run_replay.py:160-167) writes every action as a dict ({"message": ...}, plus tool_calls for function-calling configs), so that exact-string comparison could never match a file produced by the repo's own tooling.

Why (root cause)

  • Before this PR, sweagent/agent/models.py:519 read if isinstance(action, str) and action == "submit": self._next_replay() — only a legacy plain-string "submit" action advanced _replay_idx.
  • With dict actions, _replay_idx never advanced. After the last replayed action, every further query fell into the IndexError fallback ("Reached end of replay trajectory without submitting. Submitting now.") and auto-submitted, without ever moving on to the next replay entry.

Changes (sweagent/agent/models.py)

  • Add ReplayModel._is_submit_action(), which recognizes the submit command in all action formats: as a submit tool call (dict actions from function-calling configs), or by extracting the executable action with the configured parser and comparing it to the submit command - so a submit mentioned in the surrounding discussion does not advance the replay (an action that cannot be parsed also does not advance it; the end-of-trajectory fallback submits if that was the missing submission). This also covers the auto-generated submission built in the IndexError fallback and the legacy plain string "submit".
  • Use it in query() instead of the exact-string comparison.
  • Move the self._replays[self._replay_idx] lookup inside the try block so a query after the last entry's submit lands in the auto-submit fallback instead of raising an uncaught IndexError (previously reachable with legacy string files whose entry ends in "submit").

Note: run-batch re-creates the agent (and with it the model) per instance (sweagent/run/run_batch.py:337), so selecting a replay entry by instance id would require threading the instance id into the model; that is a larger interface change and out of scope here.

Testing

New regression tests in tests/test_models.py, using the replay-file format written by _create_actions_file (one {instance_id: [actions]} object per line):

  • test_replay_model_advances_after_dict_submit_action
  • test_replay_model_advances_after_function_calling_submit_action
  • test_replay_model_advances_after_string_submit_action (legacy format; guards existing behavior)
  • test_replay_model_advances_after_auto_submission
  • test_replay_model_auto_submits_when_file_exhausted

Commands and results (on main the four advancement tests fail — the model returns the auto-generated {"message": "```\nsubmit\n```"} instead of the next instance's action — and pass on this branch):

$ pytest tests/test_models.py -q
  main: 4 failed, 5 passed -> this branch: 9 passed

$ pytest -m "not slow" -q --deselect tests/test_run_replay.py::test_replay
  this branch: 102 passed, 11 failed, 21 deselected, 2 xfailed

The 11 failures pre-exist identically on main and are unrelated to this change: 7 are GitHub API 403 rate-limit errors (tests/test_env_utils.py, tests/test_run_hooks.py), and 4 are subprocess-based CLI help tests that need the sweagent console script on PATH (they pass when it is: 4 passed). tests/test_run_replay.py::test_replay and the slow-marked tests need Docker and were not run here.

$ ruff check sweagent/agent/models.py tests/test_models.py
All checks passed!
$ ruff format --check sweagent/agent/models.py tests/test_models.py
2 files already formatted

ReplayModel only advanced to the next replay after an action that was
exactly the string "submit". run-replay writes actions as dicts
(`{"message": ...}` with optional `tool_calls`), so `_replay_idx` never
advanced: a multi-instance replay file stayed on its first line forever
and queries past the end of a line auto-submitted endlessly.

Detect the submit command in all action formats: as a tool call, as a
standalone line of the message (also covering the auto-generated
submission), and as a legacy plain string action.

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

The nine model tests pass, but an additional replay check reproduces premature instance advancement when submit is mentioned outside the executable action. Please address the inline finding and add a regression test for discussion containing a standalone submit line.

Comment thread sweagent/agent/models.py Outdated
if tool_calls := action.get("tool_calls"):
return any(tool_call["function"]["name"] == self.submit_command for tool_call in tool_calls)
action = action.get("message", "")
return any(line.strip() == self.submit_command for line in action.splitlines())

@chessing234 chessing234 Oct 5, 2026 •

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.

[P2] Detect submission from the executable action, not every message line.

A valid ThoughtActionParser response can mention submit in discussion:

The final command will be:
submit
First inspect the repository.
```
echo first
```

The configured parser extracts echo first, yet this helper returns true because the discussion contains a standalone submit. I reproduced that the next query then returns the next instance's first action, skipping the current instance's remaining actions and actual submission. Extract the action using the configured parser (or otherwise restrict matching to executable submit syntax) and add a regression for submit mentioned in discussion.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in a42fbb4: _is_submit_action now extracts the executable action with the configured parser — parse_function(model_response, commands), the same call the live loop makes via ToolHandler.parse_actions — and compares that to the submit command. The discussion-only submit from your repro no longer advances the replay; added test_replay_model_ignores_submit_mentioned_in_discussion with that exact message.

An action the parser cannot extract counts as not-submit and logs a warning; the replay continues and the end-of-trajectory fallback submits, so a missed submit degrades to the auto-submit path instead of skipping actions. That fallback now advances unconditionally (it is by definition the end of the instance) instead of relying on the parser. The fenced-block tests now run with ThoughtActionParser to match the format those messages were written in; the legacy bare-string test keeps Identity.

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

Found a reproducible issue in the current PR head; see the inline change request.

All 9 model tests passed, but an additional replay/parser check reproduced the skipped-instance problem. The full integration suite was not run.

Comment thread sweagent/agent/models.py Outdated
if tool_calls := action.get("tool_calls"):
return any(tool_call["function"]["name"] == self.submit_command for tool_call in tool_calls)
action = action.get("message", "")
return any(line.strip() == self.submit_command for line in action.splitlines())

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.

[P1] Detect submit in the executable action, not the entire message

A standalone submit line in the explanatory text now advances _replay_idx even when ThoughtActionParser extracts a different command. Reproduced with the message `The final command will be:
submit
First inspect the repository.

echo first

: the parser returns echo first`, but after replaying it the next query returns the second instance's first action, skipping the remaining commands and actual submission from instance one. Please parse the executable action with the configured parser (or otherwise distinguish thought from action) before advancing, and add a regression for a submit mention outside the selected command block.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in a42fbb4: _is_submit_action now extracts the executable action with the configured parser — parse_function(model_response, commands), the same call the live loop makes via ToolHandler.parse_actions — and compares that to the submit command. The discussion-only submit from your repro no longer advances the replay; added test_replay_model_ignores_submit_mentioned_in_discussion with that exact message.

An action the parser cannot extract counts as not-submit and logs a warning; the replay continues and the end-of-trajectory fallback submits, so a missed submit degrades to the auto-submit path instead of skipping actions. That fallback now advances unconditionally (it is by definition the end of the instance) instead of relying on the parser. The fenced-block tests now run with ThoughtActionParser to match the format those messages were written in; the legacy bare-string test keeps Identity.

BlueX888 and others added 2 commits October 11, 2026 20:59
Submit detection now extracts the action with the configured parser and
compares that to the submit command, so a standalone submit line in the
discussion outside the command block no longer advances the replay and
skips the remaining actions of the instance. The auto-generated submission
at the end of a trajectory advances unconditionally instead of relying
on the parser.
@BlueX888

Copy link
Copy Markdown
Author

Thanks for the review! Both points are addressed on the branch (submit detection now works on the executable action extracted with the configured parser, so submit in the surrounding discussion no longer advances _replay_idx, and the _replays[...] lookup moved inside the try block). The PR description was still describing the earlier raw-message scan, so I've updated it to match the current implementation. Could you take another look when you have a moment?

sun04222 added a commit to sun04222/SWE-agent that referenced this pull request Oct 23, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants