From fa92a10eb7ac1102269972ad26c9ba38d92131d9 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 24 Aug 2026 18:34:27 +0300 Subject: [PATCH] feat: prefer semantic interpretation and audit reflexes --- cmd/core-runtime/cognitive_interpretation.go | 51 +++++++++++++++++-- .../cognitive_interpretation_test.go | 40 +++++++++++++++ cmd/core-runtime/main.go | 4 +- ...00292-development-mode-runtime-access.json | 2 +- 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/cmd/core-runtime/cognitive_interpretation.go b/cmd/core-runtime/cognitive_interpretation.go index fb8bdc1..cccb20e 100644 --- a/cmd/core-runtime/cognitive_interpretation.go +++ b/cmd/core-runtime/cognitive_interpretation.go @@ -5,8 +5,10 @@ import ( "context" "encoding/json" "fmt" + "hash/fnv" "net/http" "net/url" + "time" "git.cin.su/m/Assistant/contracts/authority" "git.cin.su/m/Assistant/contracts/capability" @@ -61,12 +63,53 @@ func (c cognitiveInterpretationClient) Interpret(ctx context.Context, request co } func shouldRequestBroaderInterpretation(result interactiondomain.SemanticResult) bool { - switch result.Interpretation.Applicability { - case interaction.SemanticNeedsBroaderInterpret, interaction.SemanticLowConfidence, interaction.SemanticMixedIntent: - return true - default: + return shouldRequestSemanticInterpretation(result) +} + +// shouldRequestSemanticInterpretation keeps the deterministic layer as a +// fast reflex, not as the meaning authority. Questions, requests, directives, +// mixed signals and capability-bearing messages go through cognitive +// interpretation; only stable low-risk conversational/assertive reflexes may +// answer without it. +func shouldRequestSemanticInterpretation(result interactiondomain.SemanticResult) bool { + if result.Interpretation.Clarification == interaction.ClarificationRequired { return false } + if result.NeedsCognition || result.InformationRequestKind != "" || len(result.CapabilityHints) > 0 { + return true + } + if result.Interpretation.Applicability != interaction.SemanticApplicable { + return true + } + switch result.Interpretation.SpeechAct { + case interaction.SpeechActConversational, interaction.SpeechActConfirmation, interaction.SpeechActRejection: + return false + case interaction.SpeechActAssertion: + return result.Interpretation.ConfidenceMillis < 850 + default: + return true + } +} + +const ( + deterministicReflexAuditInterval = 10 * time.Minute + deterministicReflexAuditSample = uint32(16) +) + +// shouldAuditDeterministicReflex samples stable reflexes in a time bucket. +// This is observability, not a correctness gate: a failed audit leaves the +// reflex usable, while a successful disagreement can replace it with the +// cognitive candidate and becomes durable evidence through the normal +// candidate store. +func shouldAuditDeterministicReflex(result interactiondomain.SemanticResult, utteranceID string, now time.Time) bool { + if shouldRequestSemanticInterpretation(result) || utteranceID == "" { + return false + } + bucket := now.Unix() / int64(deterministicReflexAuditInterval/time.Second) + hash := fnv.New32a() + _, _ = hash.Write([]byte(utteranceID)) + _, _ = hash.Write([]byte(fmt.Sprintf(":%d", bucket))) + return hash.Sum32()%deterministicReflexAuditSample == 0 } func isSimpleLocalQuestion(result interactiondomain.SemanticResult) bool { diff --git a/cmd/core-runtime/cognitive_interpretation_test.go b/cmd/core-runtime/cognitive_interpretation_test.go index 99223f8..d14563d 100644 --- a/cmd/core-runtime/cognitive_interpretation_test.go +++ b/cmd/core-runtime/cognitive_interpretation_test.go @@ -2,6 +2,7 @@ package main import ( "testing" + "time" "git.cin.su/m/Assistant/contracts/interaction" interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain" @@ -34,3 +35,42 @@ func TestOperationalQuestionStillUsesStrictInterpretation(t *testing.T) { t.Fatal("operational question must retain strict interpretation routing") } } + +func TestDirectiveAndInventoryPreferMeaningInterpretationEvenWhenDeterministicLooksApplicable(t *testing.T) { + result := interactiondomain.SemanticResult{ + Interpretation: interaction.UtteranceInterpretation{ + SpeechAct: interaction.SpeechActDirective, + Applicability: interaction.SemanticApplicable, + ConfidenceMillis: 980, + }, + InformationRequestKind: interaction.InformationRequestCognitiveModelInventory, + } + if !shouldRequestSemanticInterpretation(result) { + t.Fatal("model inventory directive must be interpreted by meaning, not only token reflex") + } +} + +func TestStableReflexIsPeriodicallySampledForDrift(t *testing.T) { + result := interactiondomain.SemanticResult{Interpretation: interaction.UtteranceInterpretation{ + SpeechAct: interaction.SpeechActConversational, Applicability: interaction.SemanticApplicable, + }} + found := false + for minute := 0; minute < 160; minute++ { + if shouldAuditDeterministicReflex(result, "utterance-drift-probe", time.Unix(int64(minute)*60, 0)) { + found = true + break + } + } + if !found { + t.Fatal("stable reflex was never sampled during the audit window") + } +} + +func TestMeaningfulSignalsAreNotReflexOnly(t *testing.T) { + for _, act := range []interaction.SpeechAct{interaction.SpeechActQuestion, interaction.SpeechActRequestToInvestigate, interaction.SpeechActDirective} { + result := interactiondomain.SemanticResult{Interpretation: interaction.UtteranceInterpretation{SpeechAct: act, Applicability: interaction.SemanticApplicable}} + if !shouldRequestSemanticInterpretation(result) { + t.Fatalf("%s must prefer cognitive interpretation", act) + } + } +} diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 104885c..36531e1 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -514,7 +514,9 @@ func main() { } cognitiveAttempted := false cognitiveAvailable := true - if shouldRequestBroaderInterpretation(semantic) { + semanticInterpretationRequested := shouldRequestSemanticInterpretation(semantic) + semanticReflexAudit := shouldAuditDeterministicReflex(semantic, string(utterance.ID), now) + if semanticInterpretationRequested || semanticReflexAudit { cognitiveAttempted = true projectNames := make([]string, 0, len(semantic.Projects)) for _, ref := range semantic.Projects { diff --git a/dev/tasks/DEV-000292-development-mode-runtime-access.json b/dev/tasks/DEV-000292-development-mode-runtime-access.json index b1f4317..60b0f2c 100644 --- a/dev/tasks/DEV-000292-development-mode-runtime-access.json +++ b/dev/tasks/DEV-000292-development-mode-runtime-access.json @@ -42,7 +42,7 @@ "persistence_and_migrations": ["dev volumes only; no production migration"], "failure_retry_restart": ["dev services remain independently restartable", "unsupported modality stays explicitly unavailable"], "assumptions": ["the developer starts the profile separately from production", "test credentials are disposable and never reused in production"], - "acceptance_scenarios": ["dev profile starts independently", "text, speech, image and OCR routes are discoverable or report a precise unavailable state", "diagnostic endpoints are reachable inside the dev network", "production compose remains unchanged", "no secret value appears in source or logs"], + "acceptance_scenarios": ["dev profile starts independently", "text, speech, image and OCR routes are discoverable or report a precise unavailable state", "diagnostic endpoints are reachable inside the dev network", "meaningful chat requests prefer cognitive interpretation over token reflexes", "stable reflexes are sampled periodically for cognitive drift and a disagreement is observable as a cognitive candidate", "production compose remains unchanged", "no secret value appears in source or logs"], "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", "docker compose -f deploy/docker-compose.dev.yml config", "git diff --check", "dev runtime health smoke"], "terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"] }