Core agent improvements: - RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector - Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection - Rich conversation storage for notable turns; compact_conversation truncates long user messages - Task-type classifier (query/action/analysis/creative) for observation tagging - Nested sub-agent visibility: deep delegations now register against the main agent's manager Child safety (Gabriel profile): - child_safety.py: filtering, audit logging, prompt constants for restricted sessions - .kiro/specs/child-safety-profile: requirements, design, tasks specs - GABRIEL_BOT_PROPOSAL.md: initial proposal doc - Reduced context window (10 msgs) and tutor-mode identity for restricted users Telegram adapter: - Polling watchdog: auto-restarts updater if polling drops unexpectedly - get_me() with exponential-backoff retry on NetworkError at startup - Correct stop() ordering: signal watchdog before cancelling tasks Email / Gmail: - send_email: supports file attachments (attachments list param) - get_email: surfaces attachment metadata in response Scheduled tasks / weather: - Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively - New scheduled tasks and scheduler state persistence Discord: - adapters/discord/__init__.py scaffold - discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config) Infrastructure: - n8n workflow exports (garvis_webhook, content_pipeline variants) - memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs - UCS C240 migration plan doc - requirements.txt: new deps - .claude/settings.json, fix_hooks.py: hook/permission tuning
110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
"""
|
|
User Signal Detector — heuristic classifier for follow-up messages.
|
|
|
|
Classifies the user's next message (after Garvis responded) into one of:
|
|
"positive" — explicit praise / satisfaction
|
|
"negative" — explicit dissatisfaction / error report
|
|
"correction" — user corrects or rephrases Garvis
|
|
"refinement" — user extends the prior request
|
|
"neutral" — new topic or unclassifiable
|
|
|
|
This is intentionally heuristic. Aggregate patterns matter more than
|
|
any individual classification. The analysis layer must account for noise.
|
|
|
|
No project imports — safe to use anywhere without circular-dep risk.
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Keyword tables (extend here without touching classify_signal logic)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_POSITIVE_WORDS = frozenset({
|
|
"perfect", "great", "excellent", "exactly", "thanks", "thank",
|
|
"awesome", "good", "nice", "nailed", "correct",
|
|
"yes", "yep", "sure", "right", "wonderful",
|
|
"brilliant", "fantastic", "helpful", "appreciate",
|
|
})
|
|
|
|
_NEGATIVE_WORDS = frozenset({
|
|
"no", "nope", "wrong", "incorrect", "bad", "terrible", "awful",
|
|
"failed", "broken", "error", "mistake", "off",
|
|
})
|
|
|
|
_CORRECTION_WORDS = frozenset({
|
|
"actually", "wait", "sorry", "clarify",
|
|
})
|
|
|
|
_CORRECTION_PHRASES = frozenset({
|
|
"i meant", "i mean", "what i meant", "not that",
|
|
"let me clarify", "to clarify", "scratch that",
|
|
"hold on", "my bad", "that's not", "not what i",
|
|
"try again", "you missed",
|
|
})
|
|
|
|
_REFINEMENT_PHRASES = frozenset({
|
|
"can you also", "what about", "and also", "additionally",
|
|
"could you also", "one more", "another thing", "on top of that",
|
|
"while you're at it", "in addition",
|
|
"can you add", "please add", "add to that",
|
|
})
|
|
|
|
# Time threshold under which a quick reply skews toward correction.
|
|
_REPHRASE_THRESHOLD_S: float = 30.0
|
|
|
|
|
|
def classify_signal(
|
|
follow_up_text: str,
|
|
time_delta_seconds: Optional[float] = None,
|
|
) -> str:
|
|
"""Classify a follow-up message as a user feedback signal.
|
|
|
|
Args:
|
|
follow_up_text: The user's next message after Garvis responded.
|
|
time_delta_seconds: Seconds elapsed since the previous response.
|
|
If provided and < 30, rapid replies without positive signals
|
|
skew toward "correction".
|
|
|
|
Returns:
|
|
One of: "positive", "negative", "correction", "refinement", "neutral"
|
|
"""
|
|
if not follow_up_text or not follow_up_text.strip():
|
|
return "neutral"
|
|
|
|
text_lower = follow_up_text.lower().strip()
|
|
words = set(text_lower.split())
|
|
|
|
# --- Explicit positive ---
|
|
if words & _POSITIVE_WORDS:
|
|
return "positive"
|
|
|
|
# --- Multi-word correction phrases (check before single words) ---
|
|
for phrase in _CORRECTION_PHRASES:
|
|
if phrase in text_lower:
|
|
return "correction"
|
|
|
|
# --- Single-word correction signals ---
|
|
if words & _CORRECTION_WORDS:
|
|
return "correction"
|
|
|
|
# --- Explicit negative ---
|
|
if words & _NEGATIVE_WORDS:
|
|
return "negative"
|
|
|
|
# --- Refinement patterns ---
|
|
for phrase in _REFINEMENT_PHRASES:
|
|
if phrase in text_lower:
|
|
return "refinement"
|
|
|
|
# --- Rapid rephrase heuristic ---
|
|
# If the user responds very quickly and no other signal matched,
|
|
# treat it as a soft correction (likely dissatisfied with the answer).
|
|
if (
|
|
time_delta_seconds is not None
|
|
and time_delta_seconds < _REPHRASE_THRESHOLD_S
|
|
):
|
|
return "correction"
|
|
|
|
return "neutral"
|