dialogue: keep the remembered topic on the latest turn

rememberTurn runs after followUpMerge, which has already inherited a
Text slot from the previous same-intent turn, so the fill-if-empty rule
pinned the first topic of a run of query turns and never released it.
"во сколько у меня встреча", then "какие у меня планы", then "а завтра?"
continued the meeting — two turns stale.

Overwrite for system and query, where Text is a topic and not a payload.
A continuation is the exception and keeps what it inherited: its own
utterance is the ellipsis, and the topic it carries is the real one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrVSBKe3RFDF4fGYKWYQnX
This commit is contained in:
kami
2026-08-01 22:57:08 +04:00
parent 079cf689aa
commit 17964d1162
2 changed files with 39 additions and 1 deletions
+10 -1
View File
@@ -353,8 +353,17 @@ func (h *reactiveHandler) rememberTurn(prev *dialogue.Session, dec router.Decisi
// which knows the day but not what was asked ABOUT, so keep the raw
// utterance where continuation.go can find it. Only these two intents:
// everywhere else Text is a payload and must stay what the router put in.
//
// Overwritten, not filled: rememberTurn runs AFTER followUpMerge, which
// has already inherited a Text from the previous same-intent turn, so a
// fill-if-empty rule keeps the OLD topic for ever. Seen on the deployed
// daemon 01-08-2026 — "во сколько у меня встреча" then "какие у меня
// планы" then "а завтра?" continued the meeting, two turns stale.
//
// A continuation is the exception and keeps what it inherited: its
// utterance is the ellipsis, and the topic it carries is the real one.
slots := toDialogueSlots(dec.Slots)
if slots.Text == "" && (dec.Intent == router.IntentSystem || dec.Intent == router.IntentQuery) {
if !dec.Continued && (dec.Intent == router.IntentSystem || dec.Intent == router.IntentQuery) {
slots.Text = dec.Utterance
}
h.dialogueSessions.Put(voiceDialogueID, &dialogue.Session{
+29
View File
@@ -138,3 +138,32 @@ func TestReplySystemIgnoresAnInheritedTopic(t *testing.T) {
t.Fatalf("replySystem refused a real continuation")
}
}
// TestRememberTurnRefreshesTheTopic — rememberTurn runs after followUpMerge,
// which has already inherited a Text from the previous same-intent turn. A
// fill-if-empty rule therefore pins the FIRST topic of a run of query turns
// and never lets go, so a later "а завтра?" continues a question two turns
// old. Seen on the deployed daemon, 01-08-2026.
func TestRememberTurnRefreshesTheTopic(t *testing.T) {
h := &reactiveHandler{
now: func() time.Time { return contNow },
dialogueSessions: dialogue.NewSessionStore(2 * time.Minute),
}
h.rememberTurn(nil, router.Decision{
Intent: router.IntentQuery, Utterance: "во сколько у меня встреча",
}, contNow)
// The second turn arrives with the first turn's Text already merged in.
prev := h.dialogueSessions.Get(voiceDialogueID, contNow)
h.rememberTurn(prev, router.Decision{
Intent: router.IntentQuery,
Utterance: "какие у меня планы",
Slots: router.Slots{Text: "во сколько у меня встреча"},
}, contNow)
got := h.dialogueSessions.Get(voiceDialogueID, contNow)
if got == nil {
t.Fatal("no session")
}
if got.Slots.Text != "какие у меня планы" {
t.Fatalf("topic = %q, want the latest turn's", got.Slots.Text)
}
}