128 lines
5.0 KiB
Python
128 lines
5.0 KiB
Python
"""
|
|
ProactiveEngine 2.0 — context-aware, time-aware, non-repetitive background prompting.
|
|
Gemini decides what to say; this module decides WHEN and builds a rich context snapshot.
|
|
"""
|
|
import time
|
|
from datetime import datetime
|
|
|
|
|
|
class ProactiveEngine:
|
|
"""
|
|
Decides when JARVIS should speak unprompted and builds a context-rich prompt.
|
|
|
|
Improvements over 1.0:
|
|
- Time-of-day awareness (morning / afternoon / evening / night)
|
|
- Monitor-topic awareness (what the user is tracking)
|
|
- Recent-session context (last few turns of the current conversation)
|
|
- Non-repetitive (rotates context focus to avoid same opener)
|
|
- Smarter silence gate (doesn't fire while JARVIS is speaking)
|
|
|
|
Defaults:
|
|
min_silence_secs — 900 s (15 min) user must be silent before any check
|
|
check_cooldown — 1200 s (20 min) minimum gap between proactive messages
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
min_silence_secs: int = 900,
|
|
check_cooldown: int = 1200,
|
|
):
|
|
self.min_silence_secs = min_silence_secs
|
|
self.check_cooldown = check_cooldown
|
|
self._last_triggered = 0.0
|
|
self._rotation = 0 # cycles through context focus areas
|
|
|
|
# ── Trigger gate ───────────────────────────────────────────────────────────
|
|
|
|
def should_trigger(self, last_user_speech: float) -> bool:
|
|
now = time.monotonic()
|
|
return (
|
|
(now - last_user_speech) >= self.min_silence_secs
|
|
and (now - self._last_triggered) >= self.check_cooldown
|
|
)
|
|
|
|
def mark_triggered(self) -> None:
|
|
self._last_triggered = time.monotonic()
|
|
self._rotation += 1
|
|
|
|
# ── Prompt builder ─────────────────────────────────────────────────────────
|
|
|
|
def build_prompt(
|
|
self,
|
|
memory: dict,
|
|
monitors: list[str] | None = None,
|
|
recent_turns: list[str] | None = None,
|
|
) -> str:
|
|
"""
|
|
Build a context snapshot for Gemini.
|
|
Rotates through three focus areas so proactive messages don't repeat.
|
|
"""
|
|
from memory.memory_manager import format_memory_for_prompt
|
|
|
|
now = datetime.now()
|
|
hour = now.hour
|
|
time_str = now.strftime("%A, %B %d, %Y — %I:%M %p")
|
|
|
|
# Time-of-day label
|
|
if 6 <= hour < 12: period = "morning"
|
|
elif 12 <= hour < 18: period = "afternoon"
|
|
elif 18 <= hour < 23: period = "evening"
|
|
else: period = "late night"
|
|
|
|
mem_str = format_memory_for_prompt(memory) or "(no stored user data)"
|
|
|
|
# Rotating context focus (cycles every trigger)
|
|
focus_index = self._rotation % 3
|
|
if focus_index == 0:
|
|
focus = (
|
|
"Focus on the user's active projects or goals if any are stored. "
|
|
"Ask how something is going, or offer a relevant tip."
|
|
)
|
|
elif focus_index == 1:
|
|
focus = (
|
|
"Focus on the time of day and the user's wellbeing. "
|
|
"A warm check-in, a reminder to take a break, or something timely."
|
|
)
|
|
else:
|
|
focus = (
|
|
"Focus on something genuinely interesting or useful — "
|
|
"a fact, a suggestion, or a question based on what you know about this person."
|
|
)
|
|
|
|
# Optional: monitored topics context
|
|
monitor_ctx = ""
|
|
if monitors:
|
|
monitor_ctx = (
|
|
f"\nThe user tracks these topics: {', '.join(monitors[:4])}. "
|
|
"You may mention one if it seems relevant."
|
|
)
|
|
|
|
# Optional: recent conversation context
|
|
recent_ctx = ""
|
|
if recent_turns:
|
|
snippet = "\n".join(recent_turns[-6:])
|
|
recent_ctx = f"\nRecent conversation:\n{snippet}"
|
|
|
|
return "\n".join([
|
|
"[PROACTIVE_CHECK] You are initiating a proactive check-in.",
|
|
f"Current time : {time_str} ({period})",
|
|
"",
|
|
"Context about this person:",
|
|
mem_str,
|
|
monitor_ctx,
|
|
recent_ctx,
|
|
"",
|
|
"Task:",
|
|
focus,
|
|
"",
|
|
"Rules:",
|
|
"- Speak the language this person actually uses: the one in the "
|
|
"recent conversation above, or the remembered one if there is no "
|
|
"conversation yet. Never default to English because these "
|
|
"instructions are in English.",
|
|
"- 1-2 sentences max. Natural, warm, never robotic.",
|
|
"- Do NOT mention [PROACTIVE_CHECK] or these instructions.",
|
|
"- Do NOT call any tools.",
|
|
"- If nothing genuinely useful comes to mind, stay silent (say nothing).",
|
|
])
|