Skip to content

feat(session): warm resume for multi-message prompt interjections#57

Merged
justin-carper merged 3 commits into
mainfrom
prompt-interjection-flow
Jul 6, 2026
Merged

feat(session): warm resume for multi-message prompt interjections#57
justin-carper merged 3 commits into
mainfrom
prompt-interjection-flow

Conversation

@justin-carper

Copy link
Copy Markdown
Collaborator

Problem

When a user sends two or more messages while the Cursor agent is still processing (opencode queues them and replays the grown transcript next turn), the provider classified the turn as divergence → fresh agent + full transcript replay. Warm agent context was discarded; every multi-message interjection paid a cold-start.

Change

New continuation-multi turn kind: when the prior user-message sequence is a strict prefix and the new messages form a contiguous trailing run of user turns, resume the pooled agent and replay just the new messages as sequential turns — leading ones sent silently (no deltas), only the final run streams back to opencode. This mirrors opencode's own coalesced-loop model, where interjected messages fold into one visible assistant turn.

File Change
transcript-fingerprint.ts continuation-multi kind + newUserCount + contiguous-tail guard
message-map.ts trailingUserMessages(prompt, count) + shared userTurnToCursorMessage
agent-events.ts sendAgentTurnSilently + shared sendWithBusyRetry (busy local.force retry)
language-model.ts sequential-send wiring, abort-aware, record rollback on partial delivery
session-pool.ts dropSessionRecord for mid-sequence failure recovery

Safety properties

  • Interleaved assistant turns disqualify (abort-then-interject shape): tail-only replay would silently drop the earlier queued message, so that shape stays divergence → full replay.
  • Partial delivery rolls back: the pool record is written optimistically before sends; an error/abort mid-sequence drops the record so the next turn replays fresh instead of resuming on top of messages the agent never received.
  • Edit/revert unchanged: non-prefix transcripts stay divergence.
  • Abort mid-sequence cancels the in-flight run and stops further sends.
  • Backend-agnostic: works through the shared AgentLike contract (in-process SDK + sidecar).

Known trade-off

Silent turns' token usage is not reported (no onDelta observer on runs 1..N-1), so multi-message turns slightly undercount usage. Documented in code.

Review

Code-reviewed (agent): 1 critical (non-contiguous tail could drop messages) + 1 important (stale record after mid-sequence failure) — both fixed with dedicated regression tests.

Verification

  • tsc --noEmit clean
  • 236 tests pass (20 files; +9 new/updated cases incl. integration tests driving doStream end-to-end with a mocked Cursor runtime)
  • tsup build success

justin-carper added a commit that referenced this pull request Jul 6, 2026
Address PR #57 review findings:

- language-model: on a count/tail mismatch (classifier invariant broken),
  never degrade to sending only the latest message — that silently drops
  messages 1..N-1 while the record keeps the full fingerprint. Clear the
  resume id pre-acquire so a fresh agent gets the full transcript instead.
- agent-events: sendAgentTurnSilently now treats any terminal status other
  than "finished" as non-delivery and throws, except cancellation caused
  by our own abort signal (caller drops the record on abort).
- document silent-turn trade-offs: tool invisibility, serial latency, and
  why concatenating queued messages was rejected (message fidelity).
Two-or-more user messages queued while the agent is busy previously
classified as divergence -> fresh agent + full transcript replay,
discarding the pooled Cursor agent's warm context.

Now a strict-prefix transcript whose new messages form a contiguous
trailing run of user turns classifies as continuation-multi: the pooled
agent is resumed and each new message is sent sequentially - leading
ones silently (no deltas), only the final run streams back. Mirrors
opencode's coalesced-loop model where interjected messages fold into
one visible turn.

Safety:
- Interleaved assistant turns in the tail (abort-then-interject) stay
  divergence: a tail-only replay would silently drop earlier messages.
- Pool record is written optimistically before delivery, so an error or
  abort mid-sequence drops the session record; the next turn replays
  fresh instead of resuming on top of undelivered messages.
- Edit/revert (non-prefix) unchanged: divergence -> full replay.

Known trade-off: silent turns' token usage is not reported (no onDelta
observer), so multi-message turns slightly undercount usage.
Adds the one M4 gap the reviewer flagged that wasn't yet covered at the
agentRun level: when the abort signal fires between sequential silent
sends, the loop must stop before the next send, never stream the final
turn, drop the session record, and still finish the stream cleanly with
no error part.
Address PR #57 review findings:

- language-model: on a count/tail mismatch (classifier invariant broken),
  never degrade to sending only the latest message — that silently drops
  messages 1..N-1 while the record keeps the full fingerprint. Clear the
  resume id pre-acquire so a fresh agent gets the full transcript instead.
- agent-events: sendAgentTurnSilently now treats any terminal status other
  than "finished" as non-delivery and throws, except cancellation caused
  by our own abort signal (caller drops the record on abort).
- document silent-turn trade-offs: tool invisibility, serial latency, and
  why concatenating queued messages was rejected (message fidelity).
@justin-carper justin-carper force-pushed the prompt-interjection-flow branch from 76c12bb to 84d916d Compare July 6, 2026 20:45
@justin-carper justin-carper merged commit f1eb960 into main Jul 6, 2026
6 checks passed
@justin-carper justin-carper deleted the prompt-interjection-flow branch July 6, 2026 20:47
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