feat(classic): preserve action history across task continuations - #12673
Conversation
Stop clearing episodes when the user enters a new task after finishing. The compression system (4 recent episodes full, older ones summarized, 1024 token budget) already handles context overflow. Keeping history lets the agent build on prior work instead of starting from zero. Restart the process for a clean slate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughEvent history handling during task transitions was changed to preserve event episodes (registering an ActionSuccessResult) instead of clearing episodes and resetting the cursor; tests were reorganized to validate cursor semantics and history persistence across task switches. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #12673 +/- ##
==========================================
- Coverage 74.71% 70.81% -3.91%
==========================================
Files 2537 2477 -60
Lines 192315 199299 +6984
Branches 18925 18874 -51
==========================================
- Hits 143697 141140 -2557
- Misses 44493 54557 +10064
+ Partials 4125 3602 -523
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@classic/original_autogpt/autogpt/app/main.py`:
- Around line 757-759: Before assigning agent.state.task = next_task, check for
an active "finish" episode (agent.state.finish) whose result is still None and
finalize it so it can't be reused; either call the existing episode finalizer
(e.g., agent.finalize_episode(), agent.finish_episode(), or
agent.state.finish.complete()) if available, or set agent.state.finish.result to
a non-None sentinel (e.g., an empty result or "skipped") to mark it complete,
then proceed to set agent.state.task = next_task so the new loop iteration won't
reuse the stale finish episode.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc487d41-454a-4fd6-ae7c-789b7bd6d6b6
📒 Files selected for processing (2)
classic/forge/tests/test_action_history_cursor.pyclassic/original_autogpt/autogpt/app/main.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (dev)
- GitHub Check: test
- GitHub Check: build (release)
- GitHub Check: test
- GitHub Check: test
- GitHub Check: benchmark-tests
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (typescript)
- GitHub Check: Check PR Status
- GitHub Check: Seer Code Review
- GitHub Check: Cursor Bugbot
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-03-17T10:57:12.953Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.
Applied to files:
classic/original_autogpt/autogpt/app/main.py
📚 Learning: 2026-03-19T15:10:50.676Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12483
File: autogpt_platform/backend/backend/copilot/tools/test_dry_run.py:298-303
Timestamp: 2026-03-19T15:10:50.676Z
Learning: When using Python’s `unittest.mock.patch` in tests, choose the patch target based on how the imported name is resolved:
- If the code under test uses an **eager/module-level import** (e.g., `from foo.bar import baz` at module top), patch **the module where the name is looked up** (i.e., where it is used in the SUT), e.g. `patch("mymodule.baz")`.
- If the code under test uses a **lazy import** executed later (e.g., `from foo.bar import baz` inside a function/branch), patch **the source module** (e.g., `patch("foo.bar.baz")`) because the late `from ... import` will read the (potentially patched) name from the source module at call time.
For a concrete example: if `simulate_block` is imported inside an `if dry_run:` block in the SUT, then the correct test patch target is the source module path for `simulate_block` as it exists at call time (e.g., `patch("backend.executor.simulator.simulate_block")`), not the test file’s import location.
Applied to files:
classic/forge/tests/test_action_history_cursor.py
🔇 Additional comments (1)
classic/forge/tests/test_action_history_cursor.py (1)
42-54: Good coverage additions for cursor safety and task-continuation history behavior.These tests clearly pin the
current_episodebounds behavior and the “preserve history across task changes” contract.Also applies to: 57-81
AgentFinished is caught before execute() registers a result, leaving the finish episode with result=None. The interaction loop sees this as "episode in progress" and reuses the old finish proposal instead of calling the LLM for the new task. Register a success result before continuing so the loop calls propose_action() for the new task. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6aed43d. Configure here.
majdyz
left a comment
There was a problem hiding this comment.
LGTM. The fix correctly closes the dangling finish episode via register_result so the main loop's if not (_ep := current_episode) or _ep.result check proposes a fresh action on the next task rather than replaying the stored finish proposal. Verified the flow:
propose_action→after_parse→register_actioncreates episode with result=Noneexecute→ finish tool raisesAgentFinishedbeforeafter_executeruns → episode left with result=None- main.py catches
AgentFinished, callsregister_result(ActionSuccessResult(...)), cursor advances tolen(episodes) - Next iteration:
current_episodeis None → proposes fresh action ✓
Preserving history across task continuations is the right call — the token-budget trimming in ActionHistoryComponent.get_messages (max_tokens=1024) bounds prompt size even if episodes grows, and compression summarizes older entries lazily.
Left a few minor comments (defensive guard on register_result, inline-import nit echoing Cursor, and a suggestion for integration-level test coverage) — none blocking.
CI black --check flagged this line; collapse the register_result call onto a single line (87 chars, under the 88 limit) to match black. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
- Move ActionSuccessResult to the top-level forge.models.action import instead of importing it inline (flagged by Cursor Bugbot and majdyz). - Guard register_result with `if (ep := current_episode) and not ep.result` so it never raises RuntimeError if AgentFinished ever propagates from a path where register_action didn't run first (defensive, per majdyz). - Add an integration-style test mirroring the AgentFinished -> new-task flow in main.py: finish episode closed via register_result, next current_episode is None (fresh proposal), prior history preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
Access finish_episode.result directly instead of current_episode.result — the current_episode property returns Episode | None, which pyright cannot narrow (reportOptionalMemberAccess). Same object, type-safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
OpenAI's images.generate now requires an explicit `model`; the call omitted it and failed with 400 "Missing required parameter: 'model'", breaking test_dalle. Add a configurable `dalle_model` (default "dall-e-2", which supports the 256/512/1024 sizes and b64_json output this component uses) and pass it through. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
The live images.generate API rejects response_format ("Unknown
parameter"), so stop sending it. Without it the image comes back as
either base64 JSON or a temporary URL depending on the model — handle
both: decode b64_json when present, otherwise download the url.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
OpenAI removed dall-e-2 and dall-e-3 from the API on 2026-05-12
("The model 'dall-e-2' does not exist"), so DALL-E image generation was
fully broken for real users, not just CI. Migrate the OpenAI image
provider to gpt-image-1 (the current recommended model):
- Default model -> gpt-image-1
- gpt-image-1 returns base64 PNGs and supports 1024x1024 square output
(it rejects response_format and the old 256/512 sizes), so clamp the
square size to 1024 and read b64_json from the response
- test_dalle now exercises the only supported square size (1024)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
…ward) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🔴 Merge Conflicts DetectedThe following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.
🟢 Low Risk — File Overlap OnlyThese PRs touch the same files but different sections (click to expand)
Summary: 5 conflict(s), 0 medium risk, 3 low risk (out of 8 PRs with file overlap) Auto-generated on push. Ignores: |
…carryforward)" This reverts commit 53565f1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2WvfqfXHpq4CpSypnu9f9

Summary
Preserve action history across task continuations (the core change):
finish— the agent can build on prior work instead of starting from zero each task.ActionSuccessResultwhenAgentFinishedleftresult=None, so the loop proposes a fresh action instead of replaying the stale finish proposal. (Restart the process for a fully clean slate.)Review feedback addressed:
ActionSuccessResultto the top-levelforge.models.actionimport (Cursor Bugbot, majdyz).register_resultwithif (ep := current_episode) and not ep.resultso it can't raise ifAgentFinishedever propagates without a priorregister_action(majdyz).AgentFinished→register_result→ fresh proposal (majdyz).Required fix — image generation migrated to
gpt-image-1: OpenAI removeddall-e-2/dall-e-3from the API on 2026-05-12 ("The model 'dall-e-2' does not exist"), so the classic image-generation command — andtest_dalle— was fully broken. Migrating the OpenAI image provider togpt-image-1is required for image generation to function at all: adalle_modelconfig default, 1024×1024 square output,model=passed toimages.generate, dropping the now-rejectedresponse_format, and reading the image from the base64 (or URL) response.Test plan
poetry run pytest forge/tests/test_action_history_cursor.py -v— 8 tests pass (cursor safety, history retention across tasks, finish→continuation flow)gpt-image-1test_dalle🤖 Generated with Claude Code
Note
Medium Risk
Changes agent loop behavior and prompt context after task continuation, which can affect planning quality; image API migration is localized but touches live external calls in tests.
Overview
Task continuation no longer wipes episodic history when the user starts a follow-up after
finish; prior episodes stay in context (compression still caps prompt size). On that path,main.pynow closes the open finish episode with anActionSuccessResultwhenAgentFinishedleftresult=None, so the loop proposes a new action instead of reusing the stale finish proposal, with a guard soregister_resultonly runs when there is an open episode.OpenAI image generation is updated for removed DALL·E models: default
gpt-image-1, 1024×1024 square sizing,model=onimages.generate, noresponse_format, and decoding from base64 or URL. Tests cover cursor safety, retained history, finish→continuation flow, and a fixedtest_dalleat 1024.Adds a one-line backend README heading.
Reviewed by Cursor Bugbot for commit bcf50f8. Bugbot is set up for automated code reviews on this repo. Configure here.