package main import ( "context" "log" "strconv" "strings" "time" "git.cin.su/m/Assistant/contracts/capability" "git.cin.su/m/Assistant/contracts/continuity" "git.cin.su/m/Assistant/contracts/epistemics" "git.cin.su/m/Assistant/contracts/interaction" capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain" epistemicsdomain "git.cin.su/m/Assistant/modules/epistemics/domain" ) const reminderResponseSkillID = capability.SkillProfileID("skill-obvious-reminder-response-v1") type learningEpistemicsStore interface { SaveSystemExpectation(context.Context, epistemics.SystemExpectation) error SaveExperienceRecord(context.Context, epistemics.ExperienceRecord) error SaveReflectionCandidate(context.Context, epistemics.ReflectionCandidate) error } type learningCapabilityStore interface { LoadSkillProfile(context.Context, capability.SkillProfileID) (capability.SkillProfile, error) SaveSkillProfile(context.Context, capability.SkillProfile) error } type learningRecorder struct { epistemics learningEpistemicsStore capability capabilityLearningStore } type capabilityLearningStore interface { learningCapabilityStore } type reminderResponseLearningInput struct { Utterance interaction.HumanUtterance Intent interaction.OriginalHumanIntent Result continuity.HumanResponseResult At time.Time } type correctionLearningInput struct { Utterance interaction.HumanUtterance Intent interaction.OriginalHumanIntent At time.Time } type fastPathDeclineLearningInput struct { Utterance interaction.HumanUtterance Intent interaction.OriginalHumanIntent Result continuity.HumanResponseResult Reason capability.FastPathDeclineReason At time.Time } type capabilityLearningInput struct { Utterance interaction.HumanUtterance Intent interaction.OriginalHumanIntent ImplementationID capability.CapabilityImplementationID UsageObservation capability.CapabilityUsageObservationID GapID capability.CapabilityGapID Outcome capability.UsageOutcomeClass OutcomeRef string At time.Time } type delegationLearningInput struct { Intent interaction.OriginalHumanIntent Utterance interaction.HumanUtterance DelegationID string RunID string ImplementationID capability.CapabilityImplementationID OutcomeRef string VerificationRefs []epistemics.EvidenceRef Verified bool ExecutionWindows int HumanBlocked bool At time.Time } func (r learningRecorder) recordReminderResponse(ctx context.Context, input reminderResponseLearningInput) { if r.epistemics == nil || r.capability == nil || input.Intent.ID == "" || input.Utterance.ID == "" || input.Result.Response.ID == "" { return } expectedEffect := "resolve obvious reminder continuation using deterministic target resolver" if input.Result.Response.TargetProspectiveID != "" { expectedEffect += " for prospective item " + string(input.Result.Response.TargetProspectiveID) } expectation := epistemics.SystemExpectation{ OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID), SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), ExpectedOutcome: epistemics.OutcomeSuccess, ExpectedEffect: expectedEffect, ConfidenceMillis: 850, EvidenceBasis: []epistemics.EvidenceRef{"continuity:response-target-resolution"}, RecordedAtUnixMillis: input.At.UnixMilli(), } expectation, err := epistemicsdomain.NewSystemExpectation(expectation) if err != nil { log.Printf("learning reminder expectation invalid: %v", err) return } if err := r.epistemics.SaveSystemExpectation(ctx, expectation); err != nil { log.Printf("learning reminder expectation unavailable: %v", err) return } actual, confidence := outcomeForHumanResponse(input.Result.Response) experience := epistemics.ExperienceRecord{ SourceKind: epistemics.ExperienceSourceContinuity, OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID), SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), ContextRefs: contextRefs( "continuity:human-response:"+string(input.Result.Response.ID), "continuity:prospective:"+string(input.Result.Response.TargetProspectiveID), "continuity:attention:"+string(input.Result.Response.TargetAttentionID), ), HelpMethod: epistemics.HelpMethodDeterministicFastPath, HelpMethodRef: "continuity:human-response-target-resolver-v1", SkillRef: epistemics.SkillRef(reminderResponseSkillID), ExpectationID: expectation.ID, ExpectedOutcome: expectation.ExpectedOutcome, ActualOutcome: actual, OutcomeRef: "continuity:human-response:" + string(input.Result.Response.ID), OutcomeDetail: string(input.Result.Response.Operation) + " -> " + string(input.Result.Response.Outcome), HumanResponseRef: epistemics.HumanResponseRef(input.Result.Response.ID), VerificationEvidence: []epistemics.EvidenceRef{epistemics.EvidenceRef("human-response:" + string(input.Result.Response.Outcome))}, ConfidenceMillis: confidence, RecordedAtUnixMillis: input.At.UnixMilli(), } r.saveExperienceAndReflection(ctx, experience) r.updateReminderSkill(ctx, experience, skillOutcomeForExperience(experience), false) } func (r learningRecorder) recordCorrection(ctx context.Context, input correctionLearningInput) { if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" { return } experience := epistemics.ExperienceRecord{ SourceKind: epistemics.ExperienceSourceInteraction, OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID), SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), HelpMethod: epistemics.HelpMethodClarification, ExpectedOutcome: epistemics.OutcomeSuccess, ActualOutcome: epistemics.OutcomeUserCorrected, OutcomeDetail: "human correction indicates prior interpretation or target resolution may be wrong", CorrectionUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), VerificationEvidence: []epistemics.EvidenceRef{"interaction:human-correction"}, ConfidenceMillis: 1000, RecordedAtUnixMillis: input.At.UnixMilli(), } r.saveExperienceAndReflection(ctx, experience) } func (r learningRecorder) recordFastPathDecline(ctx context.Context, input fastPathDeclineLearningInput) { if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" { return } experience, err := epistemicsdomain.FastPathDeclineExperience( epistemics.OriginalIntentRef(input.Intent.ID), epistemics.SourceUtteranceRef(input.Utterance.ID), epistemics.SkillRef(reminderResponseSkillID), string(outcomeForDecline(input.Reason)), input.At.UnixMilli(), ) if err != nil { return } if input.Result.Response.ID != "" { experience.ContextRefs = contextRefs("continuity:response-candidate:" + string(input.Result.Response.Operation)) } saved, ok := r.saveExperienceAndReflection(ctx, experience) if ok { r.updateReminderSkill(ctx, saved, capability.UsageSkipped, false) } } func (r learningRecorder) recordCapabilityUse(ctx context.Context, input capabilityLearningInput) { if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" { return } actual := epistemics.OutcomeSuccess switch input.Outcome { case capability.UsageFailed: actual = epistemics.OutcomeFailed case capability.UsageSkipped: actual = epistemics.OutcomeNotApplicable case capability.UsageUnknown: actual = epistemics.OutcomeUnknown } experience := epistemics.ExperienceRecord{ SourceKind: epistemics.ExperienceSourceCapability, OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID), SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), HelpMethod: epistemics.HelpMethodCapabilityExecution, CapabilityImplementationRef: epistemics.CapabilityImplementationRef(input.ImplementationID), CapabilityUsageObservationRef: epistemics.CapabilityUsageObservationRef(input.UsageObservation), CapabilityGapRef: epistemics.CapabilityGapRef(input.GapID), ExpectedOutcome: epistemics.OutcomeSuccess, ActualOutcome: actual, OutcomeRef: input.OutcomeRef, OutcomeDetail: "capability outcome " + string(input.Outcome), VerificationEvidence: []epistemics.EvidenceRef{"capability:usage-observation"}, ConfidenceMillis: 900, RecordedAtUnixMillis: input.At.UnixMilli(), } r.saveExperienceAndReflection(ctx, experience) } func (r learningRecorder) recordDelegationOutcome(ctx context.Context, input delegationLearningInput) { if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" || input.ImplementationID == "" { return } actual := epistemics.OutcomeSuccess confidence := int64(950) if !input.Verified { actual = epistemics.OutcomeFailed confidence = 850 } detailParts := []string{"delegation outcome recorded"} if input.ExecutionWindows > 1 { detailParts = append(detailParts, "execution-window-continuations="+strconv.Itoa(input.ExecutionWindows-1)) } if input.HumanBlocked { detailParts = append(detailParts, "human-blocker-occurred") } experience := epistemics.ExperienceRecord{ SourceKind: epistemics.ExperienceSourceCapability, OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID), SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID), ContextRefs: contextRefs("work:delegation:"+input.DelegationID, "work:run:"+input.RunID), HelpMethod: epistemics.HelpMethodCapabilityExecution, HelpMethodRef: "work:agent-delegation-v1", CapabilityImplementationRef: epistemics.CapabilityImplementationRef(input.ImplementationID), ExpectedOutcome: epistemics.OutcomeSuccess, ActualOutcome: actual, OutcomeRef: input.OutcomeRef, OutcomeDetail: strings.Join(detailParts, "; "), VerificationEvidence: input.VerificationRefs, ConfidenceMillis: confidence, RecordedAtUnixMillis: input.At.UnixMilli(), } r.saveExperienceAndReflection(ctx, experience) } func (r learningRecorder) saveExperienceAndReflection(ctx context.Context, value epistemics.ExperienceRecord) (epistemics.ExperienceRecord, bool) { value, err := epistemicsdomain.NewExperienceRecord(value) if err != nil { log.Printf("learning experience invalid: %v", err) return epistemics.ExperienceRecord{}, false } if err := r.epistemics.SaveExperienceRecord(ctx, value); err != nil { log.Printf("learning experience unavailable: %v", err) return epistemics.ExperienceRecord{}, false } if candidate, ok, err := epistemicsdomain.ReflectionForExperience(value); err == nil && ok { if err := r.epistemics.SaveReflectionCandidate(ctx, candidate); err != nil { log.Printf("learning reflection unavailable: %v", err) } } else if err != nil { log.Printf("learning reflection invalid: %v", err) } return value, true } func (r learningRecorder) updateReminderSkill(ctx context.Context, experience epistemics.ExperienceRecord, outcome capability.UsageOutcomeClass, corrected bool) { if r.capability == nil || experience.ID == "" { return } profile, err := r.capability.LoadSkillProfile(ctx, reminderResponseSkillID) if err != nil { log.Printf("learning skill profile unavailable: %v", err) return } next, err := capabilitydomain.ObserveSkillOutcome(profile, capability.SkillOutcomeObservation{ ExperienceRef: capability.SkillEvidenceRef("epistemics:experience:" + string(experience.ID)), Outcome: outcome, UserCorrected: corrected, ObservedAtUnixMillis: experience.RecordedAtUnixMillis, }) if err != nil { log.Printf("learning skill observation invalid: %v", err) return } if err := r.capability.SaveSkillProfile(ctx, next); err != nil { log.Printf("learning skill profile save unavailable: %v", err) return } } func outcomeForHumanResponse(response continuity.HumanResponse) (epistemics.OutcomeClass, int64) { if response.ResolutionState != continuity.ResponseResolved { switch response.ResolutionState { case continuity.ResponseAmbiguous: return epistemics.OutcomeConflict, 850 case continuity.ResponseNoTarget: return epistemics.OutcomeOutOfDistribution, 850 default: return epistemics.OutcomeUnexpected, 700 } } switch response.Outcome { case continuity.ResponseOutcomeUserReportedComplete, continuity.ResponseOutcomeSnoozed, continuity.ResponseOutcomeCancelled, continuity.ResponseOutcomeDismissed: return epistemics.OutcomeSuccess, 950 case continuity.ResponseOutcomeClarificationNeeded: return epistemics.OutcomeConflict, 850 default: return epistemics.OutcomeUnknown, 500 } } func skillOutcomeForExperience(experience epistemics.ExperienceRecord) capability.UsageOutcomeClass { switch experience.ActualOutcome { case epistemics.OutcomeSuccess, epistemics.OutcomePartialSuccess: return capability.UsageSucceeded case epistemics.OutcomeFailed, epistemics.OutcomeUnexpected, epistemics.OutcomeUserCorrected: return capability.UsageFailed case epistemics.OutcomeUnknown: return capability.UsageUnknown default: return capability.UsageSkipped } } func outcomeForDecline(reason capability.FastPathDeclineReason) epistemics.OutcomeClass { switch reason { case capability.FastPathLowConfidence: return epistemics.OutcomeLowConfidence case capability.FastPathOutOfDistribution: return epistemics.OutcomeOutOfDistribution case capability.FastPathConflict: return epistemics.OutcomeConflict case capability.FastPathStale: return epistemics.OutcomeStale default: return epistemics.OutcomeNotApplicable } } func contextRefs(values ...string) []epistemics.ContextRef { refs := make([]epistemics.ContextRef, 0, len(values)) for _, value := range values { value = strings.TrimSpace(value) if value == "" || strings.HasSuffix(value, ":") { continue } refs = append(refs, epistemics.ContextRef(value)) } return refs } func looksLikeUnsupportedContinuation(message string, result continuity.HumanResponseResult) bool { lower := strings.ToLower(strings.TrimSpace(message)) if result.Handled { return false } if result.Response.Operation == continuity.ResponseContinue || result.Response.Operation == continuity.ResponseReject { return true } return strings.Contains(lower, "это") && (strings.Contains(lower, "напомин") || strings.Contains(lower, "перенеси") || strings.Contains(lower, "закрой") || strings.Contains(lower, "передвинь")) } func declineReasonForResponse(result continuity.HumanResponseResult) capability.FastPathDeclineReason { switch result.Response.Operation { case continuity.ResponseReject: return capability.FastPathConflict case continuity.ResponseContinue: return capability.FastPathNotApplicable default: return capability.FastPathOutOfDistribution } }