From 5db143df3b723275fb1a84fab22566fc593c5872 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 18 Aug 2026 17:15:56 +0300 Subject: [PATCH] Prefer structured planning hints over raw message heuristics --- modules/work/domain/planning.go | 13 +++++++------ modules/work/domain/planning_test.go | 10 ++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/modules/work/domain/planning.go b/modules/work/domain/planning.go index ae8d476..3b11acb 100644 --- a/modules/work/domain/planning.go +++ b/modules/work/domain/planning.go @@ -81,24 +81,25 @@ func DetermineHelpPath(input work.PlanningInput) work.HelpPath { hasInspectHint := hasPlanningHint(input, capability.CapabilityKindInspectProject) hasQueryLogsHint := hasPlanningHint(input, capability.CapabilityKindQueryLogs) hasVerifyHint := hasPlanningHint(input, capability.CapabilityKindVerifyFact) + rawTextFallbackAllowed := len(input.CapabilityHints) == 0 && !input.RequiresClarification && !input.NeedsCognition topSpeech := input.SpeechAct switch { case input.RequiresClarification: return work.HelpPath{Kind: work.HelpPathAnswer, Reason: "clarification is required before capability selection or durable work", Reply: "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."} - case hasReminderHint || isReminderRequest(lower): + case hasReminderHint || (rawTextFallbackAllowed && isReminderRequest(lower)): return work.HelpPath{Kind: work.HelpPathDirectCapability, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}, Reason: "reminder is a direct scheduling-capability path, not a coding/project workflow", Reply: "Понял: нужен reminder/scheduler capability. В DEV-000063 я фиксирую это как безопасный путь помощи; само напоминание ещё не зарегистрировано, потому что scheduling adapter не подключён."} case isEmailReviewRequest(lower) && hasReadEmailHint: return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "mail triage/review needs structured reading and reasoning without claiming outbound send"} - case hasSendEmailHint || isExplicitEmailGapRequest(lower): + case hasSendEmailHint || (rawTextFallbackAllowed && isExplicitEmailGapRequest(lower)): return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindSendEmail}, CapabilityGap: true, Reason: "email sending needs a communication capability implementation and explicit authority", Reply: "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксирую этот capability gap, чтобы позже к нему можно было вернуться."} case hasModifyHint || hasInspectHint || hasQueryLogsHint || hasReadEmailHint || hasRecallHint || hasVerifyHint || needsDurableWorkFromHints(input): return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "semantic hints indicate project/code work even if cognition may still be useful upstream"} - case requiresDurableWork(lower): + case rawTextFallbackAllowed && requiresDurableWork(lower): return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "request needs durable coordination, planning, investigation, or outcome tracking"} - case isUnsupportedCapabilityRequest(lower): + case rawTextFallbackAllowed && isUnsupportedCapabilityRequest(lower): return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{}, CapabilityGap: true, Reason: "requested help needs a currently missing capability and may later trigger acquisition/self-extension", Reply: "Похоже, для этой просьбы нужна capability, которой сейчас нет. Это не задача конкретного executor автоматически: в будущем Домовой сможет найти, подключить или создать подходящую capability и вернуться к исходной потребности."} - case containsAny(lower, "объясни", "расскажи", "что делает"): + case rawTextFallbackAllowed && containsAny(lower, "объясни", "расскажи", "что делает"): return work.HelpPath{Kind: work.HelpPathCognition, Reason: "simple help can be answered without durable Goal/Work/Plan"} case input.NeedsCognition && (topSpeech == work.SpeechActRef("QUESTION") || topSpeech == work.SpeechActRef("REQUEST_TO_INVESTIGATE") || hasRecallHint): return work.HelpPath{Kind: work.HelpPathCognition, Reason: "semantic layer marked this utterance as cognition-relevant and no durable work path outranks it"} @@ -334,4 +335,4 @@ func isExplicitEmailGapRequest(lower string) bool { func isUnsupportedCapabilityRequest(lower string) bool { return containsAny(lower, "не умеешь", "подключи интеграцию", "подключи сервис", "интеграц", "неизвестн", "нет capability", "создай adapter", "создай адаптер", "сервис, которого сейчас нет") -} \ No newline at end of file +} diff --git a/modules/work/domain/planning_test.go b/modules/work/domain/planning_test.go index 24e30f7..b08c239 100644 --- a/modules/work/domain/planning_test.go +++ b/modules/work/domain/planning_test.go @@ -109,6 +109,16 @@ func TestHelpPathCapabilityGapDoesNotBecomeCodexTask(t *testing.T) { } } +func TestHelpPathStructuredHintsOverrideRawCognitionPhrase(t *testing.T) { + input := planningInputForTest("Объясни, что делает этот код.", "QUESTION", epistemics.VerificationNotApplicable) + input.NeedsCognition = false + input.CapabilityHints = []capability.CapabilityKind{capability.CapabilityKindSendEmail} + path := DetermineHelpPath(input) + if path.Kind != work.HelpPathCapabilityGap || !path.CapabilityGap { + t.Fatalf("structured send-email hint should force capability gap path: %#v", path) + } +} + func planForTest(t *testing.T, message string, act string, verification epistemics.VerificationState) work.PlanningResult { t.Helper() result, err := BuildPlanningResult(planningInputForTest(message, act, verification), time.Unix(100, 0))