Skip to content

Async worker path: driving row is older than its own history window #330

Description

@enriquephl

On the async worker path, a turn can be assembled with the driving message older than every other message in its own history window. The model then answers the newest message it sees, while the generated reply is persisted against the older driving row.

Mechanism

build_reply_request (crates/eros-engine-server/src/pipeline/handlers.rs) fetches the newest HISTORY_WINDOW rows, then pins the driving row back if the fetch missed it:

if !rows.iter().any(|m| m.id == user_message_id) {
    if let Some(driving) = chat_repo
        .message_by_id_in_session(session_id, user_message_id)
        .await?
    {
        rows.insert(0, driving);
    }
}

The pin only fires when the driving row fell outside the newest-N fetch — i.e. a burst landed while the turn was queued and buried it. The row is therefore genuinely older than everything else in the window, and the existing comment says so: "older than everything in the window, so it goes first."

The pin is correct as far as it goes: without it the model answers a message it never sees. But the rows that accompany it are all causally after it. The prompt reads as though the conversation continued past the message being answered, because it did.

Failure shape

Chronological order [U_burst_1 … U_burst_N, U_driving] is impossible — the driving row is the older one. What the model actually receives is:

[U_driving(pinned), … newer rows …, newest row]

so the last thing in its context is not what it was asked to answer. The reply is then stored against U_driving. A user reading the transcript sees an answer to a later message attached to an earlier one.

Scope

Pre-existing; not introduced by the history-window work in #329. Before that change the whole fetched window was injected, so the same inversion existed with more rows around it. Narrowing the window changes how much unrelated context accompanies the driving row, not whether the inversion happens.

Frequency is bounded by how often a session accumulates more than HISTORY_WINDOW new rows while one turn sits queued, which needs measuring before deciding how much this deserves.

Directions, not a proposal

  • Drop rows newer than the driving row when assembling that turn, so the window is causally consistent with what is being answered.
  • Or keep them but mark the driving row explicitly in the prompt, so "answer this one" is stated rather than implied by position.
  • Or treat a buried driving row as stale and skip the turn, if the newer messages have already superseded it.

Which of these is right depends on whether a buried driving row is worth answering at all by the time it surfaces. Worth a measurement first.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions