From 515cabb5cfc78fcc093dbab1ce59b2c6dcbac7fb Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 14:58:32 +0300 Subject: [PATCH 1/3] Expand semantic memory decomposition checks --- cmd/core-runtime/household_scenario_test.go | 14 +++++++++++ modules/interaction/domain/chat_test.go | 20 ++++++++++++++++ .../interaction/domain/task_decomposition.go | 24 +++++++++++++++---- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/cmd/core-runtime/household_scenario_test.go b/cmd/core-runtime/household_scenario_test.go index 923fa17..806d0d6 100644 --- a/cmd/core-runtime/household_scenario_test.go +++ b/cmd/core-runtime/household_scenario_test.go @@ -48,6 +48,20 @@ func TestHouseholdMarketplaceBrandSearchUsesResearchRoute(t *testing.T) { } } +func TestHouseholdSemanticMemoryControlsMarketplaceAlias(t *testing.T) { + withoutMemory := interactiondomain.DecomposeTask("найди на WB бренд marka-wear") + if withoutMemory.Source != "" || withoutMemory.Object != "" { + t.Fatalf("core invented marketplace meaning without memory: %+v", withoutMemory) + } + withMemory := interactiondomain.DecomposeTaskWithLexicon("поищи марку marka-wear в Wildberries", interactiondomain.TaskLexicon{ + ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "марку", Value: "BRAND"}}, + SourceAliases: []interactiondomain.TaskLexiconEntry{{Alias: "wildberries", Value: "WILDBERRIES"}}, + }) + if withMemory.Intent != interactiondomain.TaskIntentSearch || withMemory.Object != "BRAND" || withMemory.Source != "WILDBERRIES" || withMemory.Target != "marka-wear" || !withMemory.NeedsResearch { + t.Fatalf("semantic memory did not control marketplace decomposition: %+v", withMemory) + } +} + func TestHouseholdBearDrawingUsesCreativeLocalRoute(t *testing.T) { candidate := interactiondomain.BuildDeterministicCandidate("нарисуй бурого медведя", "", time.Unix(10, 0).UnixMilli()) decomposition := interactiondomain.DecomposeTaskWithLexicon("нарисуй бурого медведя", interactiondomain.TaskLexicon{ObjectAliases: []interactiondomain.TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}}) diff --git a/modules/interaction/domain/chat_test.go b/modules/interaction/domain/chat_test.go index ba8b914..d351cba 100644 --- a/modules/interaction/domain/chat_test.go +++ b/modules/interaction/domain/chat_test.go @@ -181,6 +181,26 @@ func TestDecomposeMarketplaceBrandRequestIntoExecutionAtoms(t *testing.T) { } } +func TestDecomposeDoesNotInventMutableAliases(t *testing.T) { + got := DecomposeTask("найди на WB бренд marka-wear") + if got.Source != "" || got.Object != "" || got.NeedsResearch { + t.Fatalf("parser invented a mutable alias without memory: %+v", got) + } +} + +func TestDecomposeUsesRememberedAliasesAcrossWordings(t *testing.T) { + lexicon := TaskLexicon{ + ObjectAliases: []TaskLexiconEntry{{Alias: "бренд", Value: "BRAND"}, {Alias: "марку", Value: "BRAND"}}, + SourceAliases: []TaskLexiconEntry{{Alias: "wb", Value: "WILDBERRIES"}, {Alias: "wildberries", Value: "WILDBERRIES"}}, + } + for _, message := range []string{"найди на WB бренд marka-wear", "поищи марку marka-wear в Wildberries"} { + got := DecomposeTaskWithLexicon(message, lexicon) + if got.Intent != TaskIntentSearch || got.Object != "BRAND" || got.Source != "WILDBERRIES" || got.Target != "marka-wear" || !got.NeedsResearch { + t.Fatalf("remembered aliases failed for %q: %+v", message, got) + } + } +} + func TestDecomposeCreativeRequestDoesNotInventResearch(t *testing.T) { lexicon := TaskLexicon{ObjectAliases: []TaskLexiconEntry{{Alias: "медвед", Value: "IMAGE"}}} got := DecomposeTaskWithLexicon("нарисуй бурого медведя", lexicon) diff --git a/modules/interaction/domain/task_decomposition.go b/modules/interaction/domain/task_decomposition.go index 594be6e..abe6c67 100644 --- a/modules/interaction/domain/task_decomposition.go +++ b/modules/interaction/domain/task_decomposition.go @@ -53,13 +53,13 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi result.Intent = TaskIntentCreate } for _, entry := range lexicon.ObjectAliases { - if containsStemInTokens(tokens, entry.Alias) { + if lexiconAliasMatches(tokens, entry.Alias) { result.Object = entry.Value break } } for _, entry := range lexicon.SourceAliases { - if containsStemInTokens(tokens, entry.Alias) { + if lexiconAliasMatches(tokens, entry.Alias) { result.Source = entry.Value break } @@ -87,6 +87,14 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi return result } +func lexiconAliasMatches(tokens []string, alias string) bool { + alias = strings.TrimSpace(strings.ToLower(alias)) + if len([]rune(alias)) < 2 { + return false + } + return containsStemInTokens(tokens, alias) +} + func valueAfterAnyCue(message string, cues ...string) string { for _, cue := range cues { if value := valueAfterCue(message, cue); value != "" { @@ -103,8 +111,14 @@ func valueAfterCue(message, cue string) string { } value := strings.TrimSpace(message[index+len(cue):]) value = strings.Trim(value, " \t:,-") - if end := strings.IndexAny(value, "?!,.;"); end >= 0 { - value = value[:end] + end := len(value) + if punctuation := strings.IndexAny(value, "?!,.;"); punctuation >= 0 && punctuation < end { + end = punctuation } - return strings.TrimSpace(value) + for _, boundary := range []string{" в ", " на ", " из ", " для ", " около "} { + if index := strings.Index(value, boundary); index >= 0 && index < end { + end = index + } + } + return strings.TrimSpace(value[:end]) } -- 2.54.0 From 4cac361762ebad60b70ac422c8687b1cee717d08 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 14:59:03 +0300 Subject: [PATCH 2/3] Use matched alias for task target --- modules/interaction/domain/task_decomposition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/interaction/domain/task_decomposition.go b/modules/interaction/domain/task_decomposition.go index abe6c67..b55e2e9 100644 --- a/modules/interaction/domain/task_decomposition.go +++ b/modules/interaction/domain/task_decomposition.go @@ -72,7 +72,7 @@ func DecomposeTaskWithLexicon(message string, lexicon TaskLexicon) TaskDecomposi } if result.Target == "" && result.Object != "" { for _, entry := range lexicon.ObjectAliases { - if entry.Value == result.Object { + if entry.Value == result.Object && lexiconAliasMatches(tokens, entry.Alias) { result.Target = valueAfterCue(lower, entry.Alias) break } -- 2.54.0 From 1a8934f88bf4440543df12c4e572369ecb6b3e66 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 15:04:39 +0300 Subject: [PATCH 3/3] Keep creative requests out of travel continuation --- cmd/core-runtime/main.go | 50 ++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index d7ea517..3c918d0 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -510,6 +510,7 @@ func main() { return } var effectiveTaskMessage = interactiondomain.StripAssistantVocative(utterance.Content) + creativeUserRequest := interactiondomain.IsCreativeRequest(utterance.Content) continuationMessage := "" travelContinuation := false var previousTask *interaction.HumanUtterance @@ -531,13 +532,15 @@ func main() { log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory)) } conversationHistory = append(conversationHistory, recentUserHistory...) - if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" { - continuationMessage = continuation - effectiveTaskMessage = continuation - } else if continuation := interactiondomain.ContinueTravelOrigin(effectiveTaskMessage, conversationHistory); continuation != "" { - continuationMessage = continuation - travelContinuation = true - effectiveTaskMessage = continuation + if !creativeUserRequest { + if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" { + continuationMessage = continuation + effectiveTaskMessage = continuation + } else if continuation := interactiondomain.ContinueTravelOrigin(effectiveTaskMessage, conversationHistory); continuation != "" { + continuationMessage = continuation + travelContinuation = true + effectiveTaskMessage = continuation + } } if request.ConversationID != "" && interactiondomain.IsRetryRequest(utterance.Content) { if len(conversationHistory) > 0 { @@ -567,12 +570,12 @@ func main() { semantic = continuedSemantic } } - if travelContinuation && interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) == "" { + if !creativeUserRequest && travelContinuation && interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) == "" { semantic.CapabilityHints = append(semantic.CapabilityHints, string(capability.CapabilityKindWebResearch)) semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } - creativeRequest := interactiondomain.IsCreativeRequest(utterance.Content) || interactiondomain.IsCreativeRequest(effectiveTaskMessage) + creativeRequest := creativeUserRequest || interactiondomain.IsCreativeRequest(effectiveTaskMessage) creativeReply := interactiondomain.CreativeDeterministicReply(utterance.Content) if creativeReply == "" { creativeReply = interactiondomain.CreativeDeterministicReply(effectiveTaskMessage) @@ -652,13 +655,15 @@ func main() { cognitiveAvailable = false } } - if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { - semantic.Interpretation.Clarification = interaction.ClarificationRequired - semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify - semantic.Reply = travelReply - deterministicReply = travelReply - semantic.NeedsCognition = false - semantic.Interpretation.NeedsCognition = false + if !creativeRequest { + if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { + semantic.Interpretation.Clarification = interaction.ClarificationRequired + semantic.Interpretation.Applicability = interaction.SemanticCandidateClarify + semantic.Reply = travelReply + deterministicReply = travelReply + semantic.NeedsCognition = false + semantic.Interpretation.NeedsCognition = false + } } if err := interactionRepository.AppendUtterance(r.Context(), conversation, utterance, intent, relations); err != nil { http.Error(w, "save chat message", http.StatusInternalServerError) @@ -794,9 +799,11 @@ func main() { planningReply = helpPath.Reply needsCognition = false } - if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { - planningReply = travelReply - needsCognition = false + if !creativeRequest { + if travelReply := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory); travelReply != "" { + planningReply = travelReply + needsCognition = false + } } if planningReply == "" && needsCognition && cognitiveAttempted && !cognitiveAvailable { planningReply = humanCognitiveDegradedReply(utterance.Content) @@ -907,7 +914,10 @@ func main() { } if identityReply == "" && creativeReply == "" && creativeFallbackReply == "" { stableAct := interactiondomain.ClassifySpeechAct(utterance.Content, utterance.CorrectsUtteranceID) - travelClarification := interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) + travelClarification := "" + if !creativeRequest { + travelClarification = interactiondomain.TravelSearchClarification(effectiveTaskMessage, conversationHistory) + } if stableReply, stable := interactiondomain.StableConversationalReply(utterance.Content, stableAct); stable && travelClarification == "" { planningReply = stableReply needsCognition = false -- 2.54.0