Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

Problem

When users interrupt a stream and then resume, we inject a synthetic [INTERRUPTED] message to give the model context. However, this wording can confuse models into thinking there was an error or that they should stop.

Solution

Change the sentinel text from [INTERRUPTED] to [CONTINUE] to make it clearer that the model should continue its previous work.

This is a quick UX improvement while we work on more comprehensive handling of interrupted messages with thinking (see #investigate-thinking-error branch).

Changes

  • Updated sentinel text: [INTERRUPTED][CONTINUE]
  • Updated all comments and tests to reflect the new wording
  • All 297 unit tests pass ✅

Testing

  • ✅ Unit tests updated and passing
  • Verified with make test

Generated with cmux

The [CONTINUE] sentinel is clearer to models, indicating they should
continue their previous work rather than viewing it as an error state.

This is a quick UX improvement while we work on more comprehensive
handling of interrupted messages with thinking.
@ammario ammario enabled auto-merge October 10, 2025 20:02
@ammario ammario disabled auto-merge October 10, 2025 20:04
@ammario ammario merged commit 9aa4007 into main Oct 10, 2025
7 checks passed
@ammario ammario deleted the quick-fix-continue-sentinel branch October 10, 2025 20:04
ammario pushed a commit that referenced this pull request Oct 11, 2025
## Problem

Currently we always add the `[CONTINUE]` sentinel after partial
messages:
```
[user1] → [partial assistant] → [CONTINUE] → [user2]
```

But when a user message already follows the partial, the sentinel is
**redundant** - the user message itself provides the continuation
signal.

## Solution

**Conditionally skip the sentinel when unnecessary:**

```typescript
// Before: Always add sentinel
[user] → [partial] → [CONTINUE] → [user] → ...

// After: Only add when needed
[user] → [partial] → [user] → ...              // Skip - user follows
[user] → [partial] → [CONTINUE]                // Keep - last message
[user] → [partial] → [CONTINUE] → [assistant]  // Keep - assistant follows
```

**Implementation:**
```typescript
if (msg.role === "assistant" && msg.metadata?.partial) {
  const nextMsg = messages[i + 1];
  
  // Only add sentinel if NO user message follows
  if (!nextMsg || nextMsg.role !== "user") {
    result.push(createSentinel());
  }
}
```

## Benefits

- ✅ **Saves tokens** - One fewer message per resume-with-user-message
(common case!)
- ✅ **More natural** - Models handle "user interrupts then continues"
well
- ✅ **Cleaner history** - No redundant synthetic messages
- ✅ **Stateless logic** - Just looks at message array structure

## When Sentinel Is Added

- Partial is **last message** (empty resume)
- Partial followed by **assistant message** (rare but possible)

## When Sentinel Is Skipped  

- Partial followed by **user message** (most common!)
  - User interrupts stream and adds follow-up
  - This is the typical resume pattern

## Testing

**Updated tests:**
- ✅ Multi-partial test now expects 5 messages (not 6)
- ✅ New test specifically for skip-sentinel behavior
- ✅ All 298 unit tests pass

**Test coverage:**
1. Sentinel added when partial is last message ✅
2. Sentinel skipped when user follows partial ✅
3. No synthetic messages when sentinel skipped ✅

## Risk Assessment

**Very low risk:**
- Only affects case where sentinel was already redundant
- Models naturally understand "user continues conversation"
- Existing behavior unchanged for empty resumes
- Stateless transform, no history dependencies

## Related

Builds on #170 which changed sentinel text from `[INTERRUPTED]` to
`[CONTINUE]`.

_Generated with `cmux`_
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