Files
ajarbot/observation/signal_detector.py

110 lines
3.5 KiB
Python
Raw Normal View History

feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments 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
2026-04-23 07:54:01 -06:00
"""
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"