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
This commit is contained in:
@@ -8,11 +8,13 @@ Handles:
|
||||
|
||||
import time
|
||||
import threading
|
||||
from concurrent.futures import Future
|
||||
from typing import Dict, Optional, Any
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from logging_config import StructuredLogger as _StructuredLogger
|
||||
# Use the project's structured logger so watchdog/state lines go to ajarbot.log.
|
||||
logger = _StructuredLogger("sub_agent_manager").logger
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -30,6 +32,9 @@ class SubAgentState:
|
||||
last_message_count: int = 0
|
||||
error_count: int = 0
|
||||
last_error: Optional[str] = None
|
||||
# Cancellation support
|
||||
future: Optional[Future] = field(default=None, repr=False)
|
||||
cancel_requested: bool = False
|
||||
|
||||
|
||||
class SubAgentManager:
|
||||
@@ -91,7 +96,14 @@ class SubAgentManager:
|
||||
started_at=now,
|
||||
last_activity=now
|
||||
)
|
||||
logger.info("[SubAgentManager] Registered sub-agent: %s - %s", agent_id, task_description)
|
||||
logger.info("[SubAgentManager] STATE[register] id=%s task=%s", agent_id, task_description[:80])
|
||||
|
||||
def attach_future(self, agent_id: str, future: Future) -> None:
|
||||
"""Attach the running Future for a sub-agent so it can be cancelled on timeout."""
|
||||
with self._lock:
|
||||
if agent_id in self.sub_agents:
|
||||
self.sub_agents[agent_id].future = future
|
||||
logger.info("[SubAgentManager] STATE[attach_future] id=%s", agent_id)
|
||||
|
||||
def update_activity(self, agent_id: str, message_count: Optional[int] = None) -> None:
|
||||
"""Update last activity timestamp and message count for a sub-agent.
|
||||
@@ -154,7 +166,7 @@ class SubAgentManager:
|
||||
self.sub_agents[agent_id].is_complete = True
|
||||
self.sub_agents[agent_id].result = result
|
||||
self.sub_agents[agent_id].error = error
|
||||
logger.info("[SubAgentManager] Sub-agent completed: %s (success=%s)",
|
||||
logger.info("[SubAgentManager] STATE[complete] id=%s success=%s",
|
||||
agent_id, error is None)
|
||||
|
||||
def get_hung_agents(self) -> list:
|
||||
@@ -221,8 +233,15 @@ class SubAgentManager:
|
||||
time.time() - state.last_activity
|
||||
)
|
||||
|
||||
# Mark as failed
|
||||
# Mark as failed and request cancellation
|
||||
state.is_complete = True
|
||||
state.cancel_requested = True
|
||||
if state.future is not None:
|
||||
cancelled = state.future.cancel()
|
||||
logger.info(
|
||||
"[SubAgentManager] STATE[cancel] id=%s cancel_returned=%s (False means already running — thread may linger but caller will unblock)",
|
||||
agent_id, cancelled
|
||||
)
|
||||
total_runtime = time.time() - state.started_at
|
||||
idle_time = time.time() - state.last_activity
|
||||
|
||||
|
||||
Reference in New Issue
Block a user