From 5a439f7ba86c16d01160637ce9c5800d99e3723c Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 11:29:54 +0300 Subject: [PATCH] Route creative chat by meaning --- cmd/core-runtime/main.go | 21 +++++++++- dev/ACTIVE_TASK.json | 4 +- .../DEV-000294-creative-chat-routing.json | 42 +++++++++++++++++++ modules/interaction/domain/chat.go | 35 ++++++++++++++++ modules/interaction/domain/chat_test.go | 39 +++++++++++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 dev/tasks/DEV-000294-creative-chat-routing.json diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 0ead2ec..c29eb8b 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -572,6 +572,20 @@ func main() { semantic.NeedsCognition = false semantic.Interpretation.NeedsCognition = false } + creativeRequest := interactiondomain.IsCreativeRequest(effectiveTaskMessage) + creativeReply := interactiondomain.CreativeDeterministicReply(effectiveTaskMessage) + if creativeRequest { + semantic.CapabilityHints = interactiondomain.RemoveCapabilityHint(semantic.CapabilityHints, string(capability.CapabilityKindWebResearch)) + if creativeReply != "" { + semantic.Reply = creativeReply + semantic.NeedsCognition = false + semantic.Interpretation.NeedsCognition = false + } else { + semantic.NeedsCognition = true + semantic.Interpretation.NeedsCognition = true + semantic.CapabilityHints = append(semantic.CapabilityHints, string(capability.CapabilityKindCognitiveReasoning)) + } + } locationReply := interactiondomain.LocationContextReply(utterance.Content) if locationReply != "" { semantic.Reply = locationReply @@ -868,7 +882,7 @@ func main() { ConfidenceMillis: semantic.Interpretation.ConfidenceMillis, }) cancelReview() - stableSemanticReply := identityReply != "" || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection + stableSemanticReply := identityReply != "" || creativeReply != "" || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection if reviewErr != nil { log.Printf("cognitive response review unavailable: %v", reviewErr) } else if !stableSemanticReply && (!researchBacked || reviewPreservesResearchEvidence(reviewedReply, planningReply)) { @@ -890,6 +904,11 @@ func main() { needsCognition = false planningReply = identityReply } + if creativeReply != "" { + semantic.Reply = creativeReply + needsCognition = false + planningReply = creativeReply + } if strings.TrimSpace(planningReply) != "" { semantic.Reply = planningReply } diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index e8d6130..4480043 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1,5 +1,5 @@ { - "task_file": "dev/tasks/DEV-000293-site-browser-capability.json", - "task_id": "DEV-000293", + "task_file": "dev/tasks/DEV-000294-creative-chat-routing.json", + "task_id": "DEV-000294", "task_revision": 1 } diff --git a/dev/tasks/DEV-000294-creative-chat-routing.json b/dev/tasks/DEV-000294-creative-chat-routing.json new file mode 100644 index 0000000..ee3a367 --- /dev/null +++ b/dev/tasks/DEV-000294-creative-chat-routing.json @@ -0,0 +1,42 @@ +{ + "task_id": "DEV-000294", + "task_revision": 1, + "architecture_revision": "ADR-0019", + "base_repo_revision": "origin/main", + "risk_tier": "E", + "intent": "Route creative requests by meaning: render simple geometric sketches deterministically and send rich creative requests to cognition without web research or truth-evidence fallback.", + "requirement_ids": ["creative conversation", "no irrelevant web research", "short deterministic drawing"], + "allowed_change_scope": { + "modules": ["interaction", "core-runtime"], + "paths": [ + "cmd/core-runtime/**", + "modules/interaction/domain/**", + "dev/ACTIVE_TASK.json", + "dev/tasks/DEV-000294-creative-chat-routing.json" + ] + }, + "forbidden_change_scope": { + "paths": ["architecture/**", "security/**", "database/**", "deploy/docker-compose.prod.yml", "LLM-local/**"] + }, + "allowed_architecture_delta": { + "new_cross_module_dependencies": 0, + "new_canonical_owners": 0, + "new_privileges": 0, + "new_third_party_dependencies": 0, + "new_database_owner_roles": 0 + }, + "contracts": { + "consume": ["existing interaction semantic candidate", "existing cognitive gateway", "existing web research route"], + "may_change": ["creative capability hints and core route selection"] + }, + "semantic_owner": "interaction owns meaning classification; core-runtime owns route orchestration", + "canonical_state": ["existing utterance and candidate history only"], + "state_transitions": ["creative request -> deterministic sketch or cognitive conversation route"], + "side_effects_and_privileges": ["no new privilege or external mutation"], + "persistence_and_migrations": ["no migration"], + "failure_retry_restart": ["creative cognition failure reports unavailable cognition; never falls back to web research"], + "assumptions": ["simple circle rendering is a text-safe projection; richer images remain a separate capability"], + "acceptance_scenarios": ["нарисуй круг returns a circle and no source links", "расскажи сказку о колобке uses cognition and no web route", "ordinary weather and price requests keep web research", "cognitive review cannot replace deterministic circle"], + "required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check"], + "terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"] +} diff --git a/modules/interaction/domain/chat.go b/modules/interaction/domain/chat.go index 1fd6882..e1b3e5d 100644 --- a/modules/interaction/domain/chat.go +++ b/modules/interaction/domain/chat.go @@ -495,6 +495,9 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin if sourceDependentQuestion(primary, secondary, tokens) { add("WEB_RESEARCH") } + if isCreativeRequestTokens(tokens) { + add("COGNITIVE_REASONING") + } if containsToken(tokens, "напомни") || containsToken(tokens, "напомин") { add("SCHEDULE_REMINDER") } @@ -536,6 +539,14 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin return values } +func isCreativeRequestTokens(tokens []string) bool { + if containsStemInTokens(tokens, "нарис", "рису", "изобраз") { + return true + } + return containsStemInTokens(tokens, "расска", "раска", "сочин", "придум") && + containsStemInTokens(tokens, "сказк", "истор", "стих", "шутк", "песн", "басн") +} + // sourceDependentQuestion identifies semantic classes whose answer depends on // a current or externally observed source. It intentionally uses no named // place, person or one-off question branch; when the cue is absent the @@ -639,6 +650,24 @@ func LocationContextReply(message string) string { return "" } +// IsCreativeRequest recognizes generation requests rather than retrieval of +// externally verifiable facts. The cognitive route remains responsible for +// the requested style and content. +func IsCreativeRequest(message string) bool { + tokens := semanticTokens(message) + return isCreativeRequestTokens(tokens) +} + +// CreativeDeterministicReply covers simple geometric sketches that do not +// need a model or external source. Rich creative content remains cognitive. +func CreativeDeterministicReply(message string) string { + tokens := semanticTokens(message) + if containsStemInTokens(tokens, "нарис", "рису", "изобраз") && containsStemInTokens(tokens, "круг", "окруж") { + return "◯" + } + return "" +} + func boundedConfidence(primaryScore, secondaryScore int, scores map[interaction.SpeechAct]int) int64 { total := 0 for _, score := range scores { @@ -818,3 +847,9 @@ func removeCapabilityHint(values []string, denied string) []string { } return result } + +// RemoveCapabilityHint lets orchestration boundaries suppress a route that +// contradicts an already understood semantic class. +func RemoveCapabilityHint(values []string, denied string) []string { + return removeCapabilityHint(values, denied) +} diff --git a/modules/interaction/domain/chat_test.go b/modules/interaction/domain/chat_test.go index bdd41ef..ab4ef80 100644 --- a/modules/interaction/domain/chat_test.go +++ b/modules/interaction/domain/chat_test.go @@ -215,6 +215,45 @@ func TestMemoryInventoryRequestIsSemantic(t *testing.T) { } } +func TestCreativeRequestsDoNotRouteToWebResearch(t *testing.T) { + for _, message := range []string{"нарисуй круг", "расскажи сказку о колобке"} { + candidate := BuildDeterministicCandidate(message, "", time.Now().UnixMilli()) + t.Logf("%q candidate=%+v", message, candidate) + for _, hint := range candidate.CapabilityHints { + if hint == "WEB_RESEARCH" { + t.Fatalf("creative request %q unexpectedly selected web research: %+v", message, candidate) + } + } + } +} + +func TestInterpretCreativeRequestDoesNotRequireExternalEvidence(t *testing.T) { + utterance := interaction.HumanUtterance{Content: "нарисуй круг"} + result, err := InterpretUtterance(utterance, nil, nil, time.Now()) + if err != nil { + t.Fatal(err) + } + t.Logf("semantic result=%+v", result) + if result.Interpretation.SpeechAct == interaction.SpeechActQuestion || containsString(result.CapabilityHints, "WEB_RESEARCH") { + t.Fatalf("creative request was routed as external research: %+v", result) + } + if got := CreativeDeterministicReply("нарисуй круг"); got != "◯" { + t.Fatalf("circle reply = %q", got) + } + if !IsCreativeRequest("расскажи сказку о колобке") { + t.Fatal("story request was not recognized as creative") + } +} + +func containsString(values []string, expected string) bool { + for _, value := range values { + if value == expected { + return true + } + } + return false +} + func TestLocationContextReply(t *testing.T) { if got := LocationContextReply("я сейчас в Москве"); got != "Запомнил: сейчас вы в Москве." { t.Fatalf("unexpected location reply %q", got) -- 2.54.0