Skip to content

fix: let the newer of two overlapping prompts win - #88

Merged
ThomasK33 merged 10 commits into
mainfrom
fix-newer-prompt-wins
Nov 5, 2026
Merged

ThomasK33 merged 10 commits into
mainfrom
fix-newer-prompt-wins

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Why

When a client sends two prompts of one session back to back, the newer prompt must replace the older one. The SDK did the opposite most of the time. Each prompt runs on its own goroutine, and the newer one usually registered first. registerPrompt then let the older prompt cancel it. The user's latest prompt ended at once as canceled, and the stale turn kept working. A probe on #86's code hit this in 284 of 300 runs. Main has the same bug. verify-session-cancel listed the case as uncovered, and checking that gap found it.

What

  • receive numbers each request from the peer (c.inboundRequests, which only the receive goroutine touches) and puts the number in the handlingRequest ctx value. requestArrival reads it. receive also records each in-flight request's arrival (c.inflightArrivals), and oldestInflightArrival returns the smallest.
  • The agent keeps the newest registered arrival per session (sessionNewest). registerPrompt cancels a prompt at once when a later prompt of the session has registered, even one that has already returned. Otherwise it replaces the registered prompt, as before. pruneCancelMarkers drops an entry once no older request is in flight, as it does for the cancel markers, with the same 1024 cap.
  • verify-session-cancel gets two promises, NewerPromptSurvives and OlderPromptEnds, and two facts, KeepNewerBy and NewestDropsWhen. Removing the newest check makes check.sh exit 1. A changed prune guard gives DRIFT (exit 2), and the model shows that dropping entries too early breaks NewerPromptSurvives.
  • Follow-ups from the verify-session-cancel: add a TLA+ check of session/cancel handling #85 readiness review:
    • check.sh reads a changed peer-wait loop as DRIFT, not as a removed release.
    • The statechart labels name a pending cleanup or response.
    • Two tests wait until the cancel handler returns, instead of a 1-second timer.
  • Both check.sh scripts pass -Djava.io.tmpdir per run. Parallel TLC runs raced on /tmp/Naturals.tla, and one run failed with a parse error.
  • verify-notification-barrier moves its sync commit forward. receive only gained the counter, so the barrier model is unchanged. All 8 configs pass, and the SKILL.md line citations are remapped to the new lines.

No protocol or schema change. agent_gen.go is unchanged.

Tests

  • TestAgentSideConnection_NewerPromptWinsWhicheverRegistersFirst and TestAgentSideConnection_PromptAfterCancelSurvivesLateOlderRegister (20 rounds per run) fail on fix: limit session/cancel to the prompts sent before it #86's code within the 20 rounds, mostly at round 0, and pass on this branch, 5 runs each at -cpu 1,2,8.
  • TestAgentSideConnection_LateOlderPromptKeepsNewerCancellable, TestAgentSideConnection_SessionNewestIsCapped and TestAgentSideConnection_ArrivalStateDrains pin the early return, the 1024-entry cap and the cleanup of the arrival state. Each one fails when its part of the code is mutated.
  • TestAgentSideConnection_OlderPromptEndsAfterNewerReturned drives registerPrompt directly for the path where the newer prompt returns before the older one registers. No black-box path can stall a handler that long.
  • Mutation checks: removing the newest check makes check.sh exit 1 (KeepNewerBy = "never", overlapping-prompts breaks NewerPromptSurvives) and fails 3 tests. A prune that never deletes fails the prune assertion. In the model, KeepNewerBy = "prev" (compare only with the registered entry) breaks OlderPromptEnds.
  • The two rewritten tests still fail on main's code.
  • verify-session-cancel/check.sh: 6 configs as expected, exit 0. replay.sh: 0 replays, exit 0. verify-notification-barrier/check.sh: 8 configs pass.
  • make test, go test -race -count=3 ., the generator (no change), gofmt, shellcheck, and treefmt without mdsh: pass.
  • verify-acp-go-sdk wire journeys (cancel-session, cancel-request, prompt-allow, prompt-reject, errors): 42 of 42 steps pass.

Refs #72

#86 is merged.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

A session/cancel must cancel every prompt of its session that the client
sent before it, whichever handler runs first, and must not cancel a prompt
that the client sent after it. The new skill model-checks both promises
for cancelPrompt, registerPrompt and pruneCancelMarkers, the generated
session/prompt and session/cancel cases, and the parts of receive and the
inbound barrier that they rely on.

check.sh reads four facts from the code, refuses to run when a shape that
the model assumes changes, and fingerprints the covered code. replay.sh
replays each counterexample against the real code.

The current code breaks both promises. check.sh expects these results:

- Overlapping prompts lose the cancel (#83, #72 item 4).
- The peer-wait release lets a cancel hit a later prompt (#72 item 1),
  also when the agent's Cancel calls the client.

The barrier and wire-level skills now point at the new skill.

---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
Two bugs that verify-session-cancel found:

- Overlapping prompts of one session lost the cancel. When an older prompt
  returned, its cleanup deleted the sessionCancels entry, which by then
  held the newer prompt's cancel func. registerPrompt now returns a func
  that deletes the entry only while it is still that prompt's.
- A session/cancel could cancel a prompt that the client sent after it.
  The inbound barrier starts a request while the running notification
  handler waits on the client, so a later prompt could register before an
  earlier cancel ran. Each entry now keeps the prompt's watermark, and
  cancelPrompt skips a prompt whose watermark is at or above the cancel's
  seq. This also covers an agent Cancel that calls the client.

The skill reads both fixes as facts, so a revert fails check.sh. The
replays move into agent_session_cancel_test.go as regression tests.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
Two bugs that verify-session-cancel found:

- Overlapping prompts of one session lost the cancel. When an older prompt
  returned, its cleanup deleted the sessionCancels entry, which by then
  held the newer prompt's cancel func. registerPrompt now returns a func
  that deletes the entry only while it is still that prompt's.
- A session/cancel could cancel a prompt that the client sent after it.
  The inbound barrier starts a request while the running notification
  handler waits on the client, so a later prompt could register before an
  earlier cancel ran. Each entry now keeps the prompt's watermark, and
  cancelPrompt skips a prompt whose watermark is at or above the cancel's
  seq. This also covers an agent Cancel that calls the client.

The skill reads both fixes as facts, so a revert fails check.sh. The
replays move into agent_session_cancel_test.go as regression tests.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
When the client sent two prompts of one session back to back, the newer
prompt's handler often registered first. registerPrompt then let the
older prompt cancel it, so the user's latest prompt ended at once and
the stale turn kept running (284 of 300 runs). receive now numbers each
request from the peer, and registerPrompt keeps a registered prompt that
arrived later and cancels the older one at once.

verify-session-cancel gets a third promise, NewerPromptSurvives, and a
fact RegisterKeepsNewer. It also takes three review follow-ups: check.sh
reads a changed peer-wait loop as DRIFT, the statechart labels name a
pending cleanup or response, and two tests wait for the cancel handler
instead of a timer. Both check.sh scripts give TLC its own temp folder,
so parallel runs no longer race on /tmp/Naturals.tla.

verify-notification-barrier moves its sync commit forward: receive only
gained the counter, which the receive goroutine alone touches, and all 8
configs pass. Its line citations are remapped.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
…ix-newer-prompt-wins

# Conflicts:
#	.agents/skills/verify-session-cancel/SKILL.md
#	.agents/skills/verify-session-cancel/SessionCancel.tla
#	.agents/skills/verify-session-cancel/cancel-waits-later-prompt.cfg
#	.agents/skills/verify-session-cancel/chart.cfg
#	.agents/skills/verify-session-cancel/check.sh
#	.agents/skills/verify-session-cancel/fingerprint.txt
#	.agents/skills/verify-session-cancel/one-prompt-at-a-time.cfg
#	.agents/skills/verify-session-cancel/overlapping-prompts.cfg
#	.agents/skills/verify-session-cancel/peer-wait-earlier-prompt.cfg
#	.agents/skills/verify-session-cancel/peer-wait-later-prompt.cfg
#	.agents/skills/verify-session-cancel/statechart-view.mjs
#	.agents/skills/verify-session-cancel/two-sessions.cfg
#	agent.go
#	agent_session_cancel_test.go
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33
ThomasK33 added this pull request to stack #87 November 5, 2026 10:05
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 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-11-05T11:20:30.732636Z d69a29c Manual request
🔒 Security Review ✅ Completed 2026-11-05T11:35:37.644236Z d69a29c 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.

…late

Adds the verifier's path for #88 and fixes the fact count in SKILL.md.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_

@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: aa39304318

ℹ️ 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 thread agent.go Resolved
Codex review of #88: registerPrompt compared only with the prompt that
was registered. When the newer prompt registered, returned and cleaned
up before a delayed older prompt registered, the older one ran with a
live ctx. The agent now keeps the newest arrival per session in
sessionNewest, and prunes it like the cancel markers: receive records
each in-flight request's arrival, and an entry goes once no older
request is in flight.

The model gets OlderPromptEnds and the facts KeepNewerBy and
NewestDropsWhen. The barrier skill's line citations are remapped from
the original text, so ranges no longer run backward (Codex review).

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 827f9412fe

ℹ️ 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".

Base automatically changed from fix-session-cancel-order to main November 5, 2026 10:51
Review of #88: two range ends in the barrier SKILL.md kept their old
numbers. The remap now uses git's diff hunks and also moves a number at
the end of a sentence. All 129 citations hold the same code text as at
the previous sync commit.

New tests pin the early return in registerPrompt, the sessionNewest cap
and the cleanup of the arrival state. Each one fails when its code is
mutated.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
# Conflicts:
#	.agents/skills/verify-session-cancel/SKILL.md
#	.agents/skills/verify-session-cancel/SessionCancel.tla
#	.agents/skills/verify-session-cancel/check.sh
#	.agents/skills/verify-session-cancel/fingerprint.txt
#	.agents/skills/verify-session-cancel/overlapping-prompts.cfg
#	.agents/skills/verify-session-cancel/peer-wait-earlier-prompt.cfg
#	acp_test.go
#	agent.go
#	agent_session_cancel_test.go
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 4bed7872f7

ℹ️ 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".

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 4bed7872f7

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Review of #88: a weaker sessionNewest prune guard kept entries longer and
no test failed. The new test registers a prompt with no older request in
flight and asserts that no entry stays.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: d69a29c208

ℹ️ 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".

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: d69a29c208

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 merged commit 5ef49d0 into main Nov 5, 2026
1 check passed
@ThomasK33
ThomasK33 deleted the fix-newer-prompt-wins branch November 5, 2026 11:41
ThomasK33 added a commit that referenced this pull request Nov 5, 2026
Readiness review of #88: the PlainHandlerReturns row in the barrier
SKILL.md cited the wrong lines since #73. Verifier note on #89: the
Register row named the steps of registerPrompt out of order.

_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
ThomasK33 added a commit that referenced this pull request Nov 5, 2026
## Why

A `session/cancel` must cancel every prompt of its session that the client sent before it, and never one that it sent after it. The protocol defines `session/cancel` as a notification, but a client can send it as a request, with an id. The SDK then had no notification seq for it. It recorded no marker and canceled the registered prompt whenever it arrived. A verifier measured both harms on #88's code: in 300 runs of "prompt, then cancel request", the earlier prompt kept running 278 times. In 300 runs of "cancel request, then prompt", the later prompt was canceled 268 times.

## What

- One clock: `receive` gives every request and every queued notification the next arrival number (`c.inboundMessages`). The number goes into the `handlingRequest` or `handlingNotification` ctx value, and `messageArrival` reads either one.
- `cancelPrompt` and `registerPrompt` compare arrivals for both cancel forms. The cancel marker keeps the latest cancel: two cancel requests run on their own goroutines, and an older one that runs last must not lower it.
- The clock replaces the watermark bookkeeping that only the cancel markers used. `inflightWatermarks`, `oldestInflightWatermark`, `requestWatermark` and the watermark in `promptCancel` are gone. The barrier still uses its watermark, which `receive` keeps as before.
- `verify-session-cancel` models cancel requests (`cancel-requests.cfg`: prompts, two cancel requests and one cancel notification, 134,169 states) and reads a new fact, `MarkerKeepsLatest`. The model caught the marker overwrite before any test did.
- `verify-notification-barrier` gets the new queue line and handler ctx line in `check.sh`, remapped citations and a new sync commit. Its model is unchanged, and all 8 configs pass.
- The `PlainHandlerReturns` row in the barrier `SKILL.md` cited the wrong lines since #73. It now cites the `handlerCtx` and `handleInbound` lines.

No protocol or schema change. `agent_gen.go` is unchanged.

## Tests

- `TestAgentSideConnection_SessionCancelRequestIsOrdered` (both orders, 20 rounds each) fails on #88's code at round 0 and passes here at `-cpu 1,2,8`.
- `TestAgentSideConnection_OlderCancelRequestKeepsLaterMarker` drives the two cancel requests in the bad order directly. It fails when the marker guard is a plain `if hasArrival {`.
- `verify-session-cancel/check.sh`: 7 configs as expected, exit 0. With `MarkerKeepsLatest = FALSE`, `cancel-requests` breaks `CancelReachesEarlierPrompts`. `verify-notification-barrier/check.sh`: 8 configs pass.
- `make test`, `go test -race -count=3 .`, the generator (no change), gofmt, shellcheck, and treefmt without mdsh: pass.
- `verify-acp-go-sdk` wire journeys (cancel-session, cancel-request, prompt-allow, prompt-reject, errors): 42 of 42 steps pass.

Refs #72

#88 is merged.

---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
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.

1 participant