Skip to content

fix(core): retire instruction notices after revert resync - #62028

Open
yyytttwww888-beep wants to merge 2 commits into
anomalyco:devfrom
yyytttwww888-beep:revert-instructions
Open

yyytttwww888-beep wants to merge 2 commits into
anomalyco:devfrom
yyytttwww888-beep:revert-instructions

Conversation

@yyytttwww888-beep

@yyytttwww888-beep yyytttwww888-beep commented Nov 5, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #62027

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Undoing an instruction update rebuilt the current baseline while replaying older notices that contradicted it. When the revert actually clears the instruction fold, mark surviving instruction notices as superseded and omit them from model context. Keep their rows and IDs for transcript reads and later revert boundaries. Reverts that preserve the fold still replay their valid updates.

No feature flag: this fixes existing revert behavior. The free-form metadata gains instructionSuperseded: true on affected notices. Earlier committed reverts are not backfilled. Related: #61551 uses the same marker for copied fork notices; this PR handles revert and can land independently.

How did you verify your code works?

  • Both new runner assertions failed on the base.
  • Runner, projector and instruction-state files pass (304 tests).
  • Four targeted cases pass ten repetitions (40 tests), including unchanged-fold behavior, new updates, replay, API context preview, other notices/sessions and message IDs reused as revert boundaries.
  • bun run check (35 tasks) and git diff --check pass.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72"><g fill="none" stroke="currentColor" opacity=".4"><rect x="8" y="14" width="64" height="44" rx="7"/><rect x="95" y="14" width="137" height="44" rx="7"/><path d="M76 36h15"/></g><g fill="currentColor" font-family="sans-serif" text-anchor="middle"><text x="40" y="40" font-size="12">Undo</text><text x="163" y="33" font-size="12">Current context</text><g opacity=".3"><text x="163" y="49" font-size="10">Old update</text><path d="M131 45h64" stroke="currentColor"/><animate attributeName="opacity" values=".3;.1;.3" dur="2s" repeatCount="indefinite"/></g></g></svg>

Screenshots / recordings

Not applicable; session history behavior.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #61550

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

Looks correct. I reproduced #62027 on the base with the PR's runner scenarios: the real runner with the simulated model still sent the old "Changed target" / "Changed context" notices after the undo. With this change they're gone. The rebuilt baseline is used, and instruction updates made after the revert still reach the model. Projector and runner tests (292), instruction-state and session-instructions tests (19) and tsc -b for packages/core pass.

rewind runs while the revert event is being applied, after the rows from the boundary onward are deleted. So marking every instruction notice in the session only affects the ones that survive, and rebuilding the session from its events sets the marker again.

Public API: no SDK, HTTP, plugin, config or CLI changes. Instruction notice messages may now carry metadata.instructionSuperseded: true in their free-form metadata, which is backwards compatible. #61551 uses the same marker for forks, so the two PRs should agree on its shape and on where it's filtered in history.ts.

No blocking concerns.

Related: #61550

@swf-cmd

swf-cmd commented Nov 7, 2026

Copy link
Copy Markdown

One thing this leaves in place: after the undo, the baseline is rebuilt from the current values, so the system prompt changes and the next request writes the whole conversation to the prompt cache again.

An alternative that fixes the same contradiction and keeps the cache: when the undo removes an update but keeps the epoch start, reset the fold to its baseline instead of deleting it. The surviving notices stay valid, the next step restates what changed since, and the cached prefix is still read. The same idea covers forks (#61550). The change is here: dev...swf-cmd:opencode-upstream:fork-instruction-baseline

I can't open it as a PR right now because I'm at the repository's open-PR limit, but I'm happy to once maintainers say which direction they prefer.

@yyytttwww888-beep

Copy link
Copy Markdown
Author

@swf-cmd Thanks, I agree that preserving the baseline can improve prefix reuse. I tested your alternative at 44fff3b and found the same retained-notice mismatch on undo:

  • A → B → A, then undo that keeps B but removes the later A update. Resetting current_values to baseline A means the next observation of A emits no correction for the surviving B notice.
  • A → B → C, then a native checkpoint, followed by undo past the checkpoint while B survives. The fresh C baseline is accompanied by B without a corrective C notice.

I added the A → B → A case and the checkpoint case in b42e774a. Both pass here; the four related test files pass (313 tests), each new case passes 10 repetitions, and bun run check passes.

To retain the notices safely, the tracked state needs to reflect the surviving history and its instruction epoch before calculating the current update. Resetting it to the baseline alone leaves these cases inconsistent.

AI-assisted testing and analysis.

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

The new commit only adds tests: one reverts after instructions return to their original value, the other reverts to a point before a native compaction checkpoint. Both are short and clear. They fail on the base and pass here. The source is unchanged since my last review. The issue's reproduction script fails on the base and passes with this branch. Runner and projector tests (294) and tsc -b for packages/core pass.

Public API: unchanged, apart from the backwards-compatible metadata.instructionSuperseded: true on instruction notice messages noted before.

No new concerns.

@swf-cmd

swf-cmd commented Nov 7, 2026

Copy link
Copy Markdown

Right on both, thanks for testing it. Resetting to the baseline forgets what the surviving B notice told the model. And once the revert goes past a checkpoint, my version just clears the state the way dev does, which is the bug this PR fixes.

Keeping the prefix would need the state rebuilt from the surviving notices on their epoch's baseline, which instruction_state doesn't keep for earlier epochs. That's more than this PR should take on, so please consider my alternative withdrawn here too. Any prefix-keeping change would come later, as a follow-up on top of this.

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.

Undo can replay stale instruction notices after rebuilding the current baseline

2 participants