2026-02-13 19:06:28 -07:00
|
|
|
"""AI Agent with Memory and LLM Integration."""
|
|
|
|
|
|
2026-03-03 20:48:43 -07:00
|
|
|
import random
|
2026-02-13 23:38:44 -07:00
|
|
|
import threading
|
2026-03-01 10:46:43 -07:00
|
|
|
import time
|
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
|
|
|
from concurrent.futures import Future, TimeoutError as FutureTimeoutError
|
|
|
|
|
from datetime import datetime
|
2026-03-01 10:46:43 -07:00
|
|
|
from typing import Any, List, Optional, Callable
|
2026-02-13 19:06:28 -07:00
|
|
|
|
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
|
|
|
from logging_config import StructuredLogger as _StructuredLogger
|
|
|
|
|
# Use the project's structured logger so STATE[...] lines go to ajarbot.log, not /dev/null.
|
|
|
|
|
_agent_logger = _StructuredLogger("agent").logger
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
from hooks import HooksSystem
|
|
|
|
|
from llm_interface import LLMInterface
|
|
|
|
|
from memory_system import MemorySystem
|
2026-02-14 18:03:42 -07:00
|
|
|
from self_healing import SelfHealingSystem
|
2026-02-13 19:06:28 -07:00
|
|
|
from tools import TOOL_DEFINITIONS, execute_tool
|
2026-03-01 10:46:43 -07:00
|
|
|
from sub_agent_manager import SubAgentManager
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
# Maximum number of recent messages to include in LLM context
|
2026-02-15 10:22:23 -07:00
|
|
|
MAX_CONTEXT_MESSAGES = 20 # Optimized for Agent SDK flat-rate subscription
|
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
|
|
|
CHILD_MAX_CONTEXT_MESSAGES = 10 # Reduced window for restricted child sessions
|
2026-02-13 23:38:44 -07:00
|
|
|
# Maximum conversation history entries before pruning
|
2026-03-01 10:46:43 -07:00
|
|
|
MAX_CONVERSATION_HISTORY = 50 # Conservative limit to prevent Agent SDK JSON buffer overflow (1MB max)
|
2026-02-16 07:43:31 -07:00
|
|
|
# Maximum tool execution iterations (generous limit for complex operations like zettelkasten)
|
|
|
|
|
MAX_TOOL_ITERATIONS = 30 # Allows complex multi-step workflows with auto-linking, hybrid search, etc.
|
2026-02-13 19:06:28 -07:00
|
|
|
|
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
|
|
|
import uuid as _uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _classify_task_type(message: str) -> str:
|
|
|
|
|
"""Heuristic task-type classifier for RSO observation entries.
|
|
|
|
|
|
|
|
|
|
Returns one of: "query" | "action" | "analysis" | "creative"
|
|
|
|
|
"""
|
|
|
|
|
text = message.lower()
|
|
|
|
|
if any(w in text for w in ("write", "create", "draft", "compose", "generate", "make", "build")):
|
|
|
|
|
return "creative"
|
|
|
|
|
if any(w in text for w in ("analyze", "analyse", "review", "summarize", "compare", "evaluate", "explain")):
|
|
|
|
|
return "analysis"
|
|
|
|
|
if any(w in text for w in ("run", "execute", "send", "update", "delete", "move", "deploy", "schedule", "set")):
|
|
|
|
|
return "action"
|
|
|
|
|
return "query"
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
class Agent:
|
2026-02-15 09:57:39 -07:00
|
|
|
"""AI Agent with memory, LLM, and hooks."""
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
provider: str = "claude",
|
|
|
|
|
workspace_dir: str = "./memory_workspace",
|
2026-02-16 07:43:31 -07:00
|
|
|
is_sub_agent: bool = False,
|
|
|
|
|
specialist_prompt: Optional[str] = None,
|
2026-02-13 19:06:28 -07:00
|
|
|
) -> None:
|
|
|
|
|
self.memory = MemorySystem(workspace_dir)
|
|
|
|
|
self.llm = LLMInterface(provider)
|
|
|
|
|
self.hooks = HooksSystem()
|
|
|
|
|
self.conversation_history: List[dict] = []
|
2026-02-13 23:38:44 -07:00
|
|
|
self._chat_lock = threading.Lock()
|
2026-02-14 18:03:42 -07:00
|
|
|
self.healing_system = SelfHealingSystem(self.memory, self)
|
2026-02-18 20:31:32 -07:00
|
|
|
self._progress_callback: Optional[Callable[[str], None]] = None
|
|
|
|
|
self._progress_timer: Optional[threading.Timer] = None
|
2026-02-13 19:06:28 -07:00
|
|
|
|
2026-02-16 07:43:31 -07:00
|
|
|
# Sub-agent orchestration
|
|
|
|
|
self.is_sub_agent = is_sub_agent
|
|
|
|
|
self.specialist_prompt = specialist_prompt
|
2026-03-04 17:45:40 -07:00
|
|
|
self.sub_agent_manager = SubAgentManager() # Default: 5 min idle, 15 min total
|
2026-03-01 10:46:43 -07:00
|
|
|
if not is_sub_agent:
|
|
|
|
|
self.sub_agent_manager.start_watchdog() # Only main agent runs watchdog
|
2026-02-16 07:43:31 -07:00
|
|
|
self.sub_agents: dict = {} # Cache for spawned sub-agents
|
2026-03-01 10:46:43 -07:00
|
|
|
self.agent_id: Optional[str] = None # Set when this is a sub-agent
|
2026-02-16 07:43:31 -07:00
|
|
|
|
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
|
|
|
# RSO observation (main agent only — sub-agents never log)
|
|
|
|
|
self._interaction_logger = None
|
|
|
|
|
self._last_interaction_id: Optional[str] = None
|
|
|
|
|
self._last_interaction_ts: Optional[float] = None
|
|
|
|
|
if not is_sub_agent:
|
|
|
|
|
try:
|
|
|
|
|
from observation.interaction_logger import InteractionLogger
|
|
|
|
|
self._interaction_logger = InteractionLogger(self.memory.workspace_dir)
|
|
|
|
|
self._interaction_logger.cleanup_old_logs()
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Agent] Observation logger unavailable: {_e}")
|
|
|
|
|
|
|
|
|
|
# Child safety config (main agent only — controls restricted-user prompt path)
|
|
|
|
|
self._child_safety_config = None
|
|
|
|
|
if not is_sub_agent:
|
|
|
|
|
try:
|
|
|
|
|
from child_safety import ChildSafetyConfig
|
|
|
|
|
from pathlib import Path as _Path
|
|
|
|
|
_cs_path = _Path("config/adapters.local.yaml")
|
|
|
|
|
if _cs_path.exists():
|
|
|
|
|
self._child_safety_config = ChildSafetyConfig.from_yaml(_cs_path)
|
|
|
|
|
if self._child_safety_config:
|
|
|
|
|
print(f"[Agent] Child safety active for: {self._child_safety_config.restricted_users}")
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Agent] Child safety config not loaded: {_e}")
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
self.memory.sync()
|
2026-02-16 07:43:31 -07:00
|
|
|
if not is_sub_agent: # Only trigger hooks for main agent
|
|
|
|
|
self.hooks.trigger("agent", "startup", {"workspace_dir": workspace_dir})
|
|
|
|
|
|
|
|
|
|
def spawn_sub_agent(
|
|
|
|
|
self,
|
|
|
|
|
specialist_prompt: str,
|
|
|
|
|
agent_id: Optional[str] = None,
|
|
|
|
|
share_memory: bool = True,
|
|
|
|
|
) -> 'Agent':
|
|
|
|
|
"""Spawn a sub-agent with specialized system prompt.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
specialist_prompt: Custom system prompt for the specialist
|
|
|
|
|
agent_id: Optional ID for caching (reuse same specialist)
|
|
|
|
|
share_memory: Whether to share memory workspace with main agent
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Agent instance configured as a specialist
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
sub = agent.spawn_sub_agent(
|
|
|
|
|
specialist_prompt="You are a zettelkasten expert. Focus ONLY on note-taking.",
|
|
|
|
|
agent_id="zettelkasten_processor"
|
|
|
|
|
)
|
|
|
|
|
result = sub.chat("Process my fleeting notes", username="jordan")
|
|
|
|
|
"""
|
|
|
|
|
# Check cache if agent_id provided
|
|
|
|
|
if agent_id and agent_id in self.sub_agents:
|
|
|
|
|
return self.sub_agents[agent_id]
|
|
|
|
|
|
|
|
|
|
# Create new sub-agent
|
|
|
|
|
workspace = self.memory.workspace_dir if share_memory else f"{self.memory.workspace_dir}/sub_agents/{agent_id}"
|
|
|
|
|
sub_agent = Agent(
|
|
|
|
|
provider=self.llm.provider,
|
|
|
|
|
workspace_dir=workspace,
|
|
|
|
|
is_sub_agent=True,
|
|
|
|
|
specialist_prompt=specialist_prompt,
|
|
|
|
|
)
|
2026-03-01 10:46:43 -07:00
|
|
|
|
2026-03-03 20:48:43 -07:00
|
|
|
# DEFENSIVE: Ensure sub-agent never inherits main event loop
|
|
|
|
|
# Sub-agents run in dedicated threads with isolated loops
|
|
|
|
|
sub_agent.llm._event_loop = None
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Set agent_id for activity tracking
|
|
|
|
|
sub_agent.agent_id = agent_id
|
2026-02-16 07:43:31 -07:00
|
|
|
|
|
|
|
|
# Cache if ID provided
|
|
|
|
|
if agent_id:
|
|
|
|
|
self.sub_agents[agent_id] = sub_agent
|
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
|
|
|
# Register with sub-agent manager for monitoring.
|
|
|
|
|
# Register nested sub-agents against the main agent's manager too, so deep
|
|
|
|
|
# delegations are visible rather than running as ghosts.
|
|
|
|
|
manager = self.sub_agent_manager
|
|
|
|
|
manager.register_sub_agent(agent_id, specialist_prompt[:100])
|
2026-02-16 07:43:31 -07:00
|
|
|
|
|
|
|
|
return sub_agent
|
|
|
|
|
|
|
|
|
|
def delegate(
|
|
|
|
|
self,
|
|
|
|
|
task: str,
|
|
|
|
|
specialist_prompt: str,
|
|
|
|
|
username: str = "default",
|
|
|
|
|
agent_id: Optional[str] = None,
|
2026-03-01 10:46:43 -07:00
|
|
|
max_retries: int = 1,
|
2026-02-16 07:43:31 -07:00
|
|
|
) -> str:
|
2026-03-01 10:46:43 -07:00
|
|
|
"""Delegate a task to a specialist sub-agent with automatic retry on hang."""
|
2026-03-03 20:48:43 -07:00
|
|
|
# Generate unique agent IDs to prevent caching race conditions in parallel delegations
|
|
|
|
|
if not agent_id:
|
|
|
|
|
agent_id = f"sub_{int(time.time()*1000)}_{random.randint(1000,9999)}"
|
|
|
|
|
else:
|
|
|
|
|
# Add timestamp to user-provided ID to ensure uniqueness
|
|
|
|
|
agent_id = f"{agent_id}_{int(time.time()*1000)}"
|
|
|
|
|
|
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
|
|
|
# Enforce the watchdog's total_timeout at the caller. Add a small buffer so
|
|
|
|
|
# the watchdog gets first crack at marking the agent hung before we TimeoutError.
|
|
|
|
|
total_timeout = self.sub_agent_manager.total_timeout_seconds + 30
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
for attempt in range(max_retries + 1):
|
|
|
|
|
if attempt > 0:
|
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
|
|
|
_agent_logger.warning("[Agent] STATE[retry] id=%s attempt=%d/%d", agent_id, attempt + 1, max_retries + 1)
|
|
|
|
|
|
|
|
|
|
retry_id = f"{agent_id}_r{attempt}" if attempt > 0 else agent_id
|
2026-03-01 10:46:43 -07:00
|
|
|
sub_agent = self.spawn_sub_agent(specialist_prompt, agent_id=retry_id)
|
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
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Heartbeat for activity tracking
|
|
|
|
|
heartbeat_running = [True]
|
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
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
def heartbeat():
|
|
|
|
|
while heartbeat_running[0]:
|
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
|
|
|
msg_count = getattr(sub_agent.llm, '_last_message_count', 0)
|
|
|
|
|
self.sub_agent_manager.update_activity(retry_id, message_count=msg_count)
|
2026-03-01 10:46:43 -07:00
|
|
|
time.sleep(10)
|
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
|
|
|
|
|
|
|
|
heartbeat_thread = threading.Thread(target=heartbeat, daemon=True, name=f"hb-{retry_id}")
|
|
|
|
|
heartbeat_thread.start()
|
|
|
|
|
|
|
|
|
|
# Manual Future + daemon thread: lets the worker be orphaned cleanly on timeout
|
|
|
|
|
# without keeping the process alive at shutdown.
|
|
|
|
|
future: Future = Future()
|
|
|
|
|
|
|
|
|
|
def _worker(f=future, sa=sub_agent, t=task, u=username):
|
|
|
|
|
if not f.set_running_or_notify_cancel():
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
f.set_result(sa.chat(t, username=u))
|
|
|
|
|
except BaseException as exc:
|
|
|
|
|
f.set_exception(exc)
|
|
|
|
|
|
|
|
|
|
worker_thread = threading.Thread(target=_worker, daemon=True, name=f"sub-{retry_id}")
|
2026-03-01 10:46:43 -07:00
|
|
|
try:
|
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
|
|
|
worker_thread.start()
|
|
|
|
|
self.sub_agent_manager.attach_future(retry_id, future)
|
|
|
|
|
_agent_logger.info("[Agent] STATE[dispatch] id=%s timeout=%ds", retry_id, total_timeout)
|
|
|
|
|
try:
|
|
|
|
|
result = future.result(timeout=total_timeout)
|
|
|
|
|
except FutureTimeoutError:
|
|
|
|
|
future.cancel()
|
|
|
|
|
err = f"delegate() hit total timeout ({total_timeout}s) for {retry_id}"
|
|
|
|
|
_agent_logger.error("[Agent] STATE[timeout] id=%s", retry_id)
|
|
|
|
|
self.sub_agent_manager.mark_complete(retry_id, error=err)
|
|
|
|
|
if attempt >= max_retries:
|
|
|
|
|
raise TimeoutError(err)
|
|
|
|
|
continue
|
|
|
|
|
self.sub_agent_manager.mark_complete(retry_id, result=result)
|
2026-03-01 10:46:43 -07:00
|
|
|
return result
|
|
|
|
|
except Exception as e:
|
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
|
|
|
# future.result() re-raises worker exceptions here.
|
|
|
|
|
self.sub_agent_manager.mark_complete(retry_id, error=str(e))
|
|
|
|
|
_agent_logger.warning("[Agent] STATE[error] id=%s err=%s", retry_id, str(e)[:200])
|
2026-03-01 10:46:43 -07:00
|
|
|
if attempt >= max_retries:
|
|
|
|
|
raise
|
|
|
|
|
finally:
|
|
|
|
|
heartbeat_running[0] = False
|
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
|
|
|
heartbeat_thread.join(timeout=1)
|
|
|
|
|
# Don't join worker_thread — it may be stuck in sub_agent.chat(). Daemon=True
|
|
|
|
|
# means the process can still exit cleanly; a timed-out worker is orphaned.
|
2026-02-13 19:06:28 -07:00
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
def _get_context_messages(self, max_messages: int) -> List[dict]:
|
|
|
|
|
"""Get recent messages without breaking tool_use/tool_result pairs.
|
|
|
|
|
|
|
|
|
|
Ensures that:
|
|
|
|
|
1. A tool_result message always has its preceding tool_use message
|
|
|
|
|
2. A tool_use message always has its following tool_result message
|
|
|
|
|
3. The first message is never a tool_result without its tool_use
|
|
|
|
|
"""
|
|
|
|
|
if len(self.conversation_history) <= max_messages:
|
|
|
|
|
return list(self.conversation_history)
|
|
|
|
|
|
|
|
|
|
# Start with the most recent messages
|
|
|
|
|
start_idx = len(self.conversation_history) - max_messages
|
|
|
|
|
# Track original start_idx before adjustments for end-of-list check
|
|
|
|
|
original_start_idx = start_idx
|
|
|
|
|
|
|
|
|
|
# Check if we split a tool pair at the start
|
|
|
|
|
if start_idx > 0:
|
|
|
|
|
candidate = self.conversation_history[start_idx]
|
|
|
|
|
# If first message is a tool_result, include the tool_use before it
|
|
|
|
|
if candidate["role"] == "user" and isinstance(candidate.get("content"), list):
|
|
|
|
|
if any(isinstance(block, dict) and block.get("type") == "tool_result"
|
|
|
|
|
for block in candidate["content"]):
|
|
|
|
|
start_idx -= 1
|
|
|
|
|
|
|
|
|
|
# Build result slice using adjusted start
|
|
|
|
|
result = list(self.conversation_history[start_idx:])
|
|
|
|
|
|
|
|
|
|
# Check if we split a tool pair at the end
|
|
|
|
|
# Use original_start_idx + max_messages to find end of original slice
|
|
|
|
|
original_end_idx = original_start_idx + max_messages
|
|
|
|
|
if original_end_idx < len(self.conversation_history):
|
|
|
|
|
end_msg = self.conversation_history[original_end_idx - 1]
|
|
|
|
|
if end_msg["role"] == "assistant" and isinstance(end_msg.get("content"), list):
|
|
|
|
|
has_tool_use = any(
|
|
|
|
|
(hasattr(block, "type") and block.type == "tool_use") or
|
|
|
|
|
(isinstance(block, dict) and block.get("type") == "tool_use")
|
|
|
|
|
for block in end_msg["content"]
|
|
|
|
|
)
|
|
|
|
|
if has_tool_use:
|
|
|
|
|
# The tool_result at original_end_idx is already in result
|
|
|
|
|
# if start_idx was adjusted, so only add if it's not there
|
|
|
|
|
next_msg = self.conversation_history[original_end_idx]
|
|
|
|
|
if next_msg not in result:
|
|
|
|
|
result.append(next_msg)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
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
|
|
|
def _cap_old_message_content(self, messages: List[dict], keep_full_turns: int = 4, cap_chars: int = 600) -> List[dict]:
|
|
|
|
|
"""Cap the content of older messages to reduce prompt size.
|
|
|
|
|
|
|
|
|
|
The most recent `keep_full_turns` turns (2 messages per turn) are kept
|
|
|
|
|
in full. Older messages have their text content capped at `cap_chars`
|
|
|
|
|
characters — enough to preserve the gist without the full prose.
|
|
|
|
|
|
|
|
|
|
Returns a new list; does NOT mutate conversation_history.
|
|
|
|
|
"""
|
|
|
|
|
keep_full_messages = keep_full_turns * 2 # user + assistant per turn
|
|
|
|
|
if len(messages) <= keep_full_messages:
|
|
|
|
|
return messages
|
|
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
result = []
|
|
|
|
|
cutoff = len(messages) - keep_full_messages
|
|
|
|
|
|
|
|
|
|
for i, msg in enumerate(messages):
|
|
|
|
|
if i >= cutoff:
|
|
|
|
|
# Recent turns — include in full
|
|
|
|
|
result.append(msg)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Older turn — cap text content
|
|
|
|
|
msg_copy = copy.copy(msg)
|
|
|
|
|
content = msg.get("content", "")
|
|
|
|
|
|
|
|
|
|
if isinstance(content, str):
|
|
|
|
|
if len(content) > cap_chars:
|
|
|
|
|
msg_copy["content"] = content[:cap_chars] + "... [truncated]"
|
|
|
|
|
else:
|
|
|
|
|
msg_copy["content"] = content
|
|
|
|
|
elif isinstance(content, list):
|
|
|
|
|
new_blocks = []
|
|
|
|
|
for block in content:
|
|
|
|
|
if isinstance(block, dict) and block.get("type") == "text":
|
|
|
|
|
text = block.get("text", "")
|
|
|
|
|
if len(text) > cap_chars:
|
|
|
|
|
new_blocks.append({**block, "text": text[:cap_chars] + "... [truncated]"})
|
|
|
|
|
else:
|
|
|
|
|
new_blocks.append(block)
|
|
|
|
|
else:
|
|
|
|
|
new_blocks.append(block)
|
|
|
|
|
msg_copy["content"] = new_blocks
|
|
|
|
|
else:
|
|
|
|
|
msg_copy["content"] = content
|
|
|
|
|
|
|
|
|
|
result.append(msg_copy)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
def _prune_conversation_history(self) -> None:
|
|
|
|
|
"""Prune conversation history to prevent unbounded growth.
|
|
|
|
|
|
|
|
|
|
Removes oldest messages while preserving tool_use/tool_result pairs.
|
|
|
|
|
"""
|
|
|
|
|
if len(self.conversation_history) <= MAX_CONVERSATION_HISTORY:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Keep the most recent half
|
|
|
|
|
keep_count = MAX_CONVERSATION_HISTORY // 2
|
|
|
|
|
start_idx = len(self.conversation_history) - keep_count
|
|
|
|
|
|
|
|
|
|
# Ensure we don't split a tool pair
|
|
|
|
|
if start_idx > 0:
|
|
|
|
|
candidate = self.conversation_history[start_idx]
|
|
|
|
|
if candidate["role"] == "user" and isinstance(candidate.get("content"), list):
|
|
|
|
|
if any(isinstance(block, dict) and block.get("type") == "tool_result"
|
|
|
|
|
for block in candidate["content"]):
|
|
|
|
|
start_idx -= 1
|
|
|
|
|
|
|
|
|
|
self.conversation_history = self.conversation_history[start_idx:]
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
def _strip_images_from_history(self) -> None:
|
|
|
|
|
"""Remove image blocks from conversation history to prevent token bloat.
|
|
|
|
|
|
|
|
|
|
Images are huge (tens of thousands of tokens) and rarely needed after
|
|
|
|
|
the initial response. This prevents old images from polluting context
|
|
|
|
|
and confusing the agent about which image the user is referring to.
|
|
|
|
|
"""
|
|
|
|
|
images_removed = 0
|
|
|
|
|
for msg in self.conversation_history:
|
|
|
|
|
if isinstance(msg.get("content"), list):
|
|
|
|
|
original_len = len(msg["content"])
|
|
|
|
|
# Keep only non-image blocks
|
|
|
|
|
msg["content"] = [
|
|
|
|
|
block for block in msg["content"]
|
|
|
|
|
if not (isinstance(block, dict) and block.get("type") == "image")
|
|
|
|
|
]
|
|
|
|
|
images_removed += original_len - len(msg["content"])
|
|
|
|
|
|
|
|
|
|
# If all blocks were images and there's no text, add placeholder
|
|
|
|
|
if not msg["content"] and msg["role"] == "user":
|
|
|
|
|
msg["content"] = "[Image was removed from history]"
|
|
|
|
|
# If content is now a single text block, simplify to string
|
|
|
|
|
elif len(msg["content"]) == 1 and isinstance(msg["content"][0], dict) and msg["content"][0].get("type") == "text":
|
|
|
|
|
msg["content"] = msg["content"][0]["text"]
|
|
|
|
|
|
|
|
|
|
if images_removed > 0:
|
|
|
|
|
print(f"[Agent] Removed {images_removed} image(s) from conversation history")
|
|
|
|
|
|
|
|
|
|
def _prune_old_tool_results(self, keep_recent: int = 10) -> None:
|
|
|
|
|
"""Remove old tool_result blocks to prevent buffer overflow during diagram generation.
|
|
|
|
|
|
|
|
|
|
When creating complex diagrams, each add_element call creates a tool_result.
|
|
|
|
|
These accumulate quickly and can exceed the Agent SDK's 1MB JSON buffer.
|
|
|
|
|
We keep only the most recent tool results.
|
|
|
|
|
"""
|
|
|
|
|
if len(self.conversation_history) < keep_recent:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
tool_results_removed = 0
|
|
|
|
|
# Process all but the most recent messages
|
|
|
|
|
for msg in self.conversation_history[:-keep_recent]:
|
|
|
|
|
if isinstance(msg.get("content"), list):
|
|
|
|
|
original_blocks = msg["content"][:]
|
|
|
|
|
# Remove tool_result blocks but keep text, tool_use, etc.
|
|
|
|
|
msg["content"] = [
|
|
|
|
|
block for block in msg["content"]
|
|
|
|
|
if not (isinstance(block, dict) and block.get("type") == "tool_result")
|
|
|
|
|
]
|
|
|
|
|
tool_results_removed += len(original_blocks) - len(msg["content"])
|
|
|
|
|
|
|
|
|
|
# If all blocks were tool_results, add placeholder
|
|
|
|
|
if not msg["content"] and msg["role"] == "user":
|
|
|
|
|
msg["content"] = "[Tool results removed from history]"
|
|
|
|
|
# Simplify single text blocks
|
|
|
|
|
elif len(msg["content"]) == 1 and isinstance(msg["content"][0], dict) and msg["content"][0].get("type") == "text":
|
|
|
|
|
msg["content"] = msg["content"][0]["text"]
|
|
|
|
|
|
|
|
|
|
if tool_results_removed > 0:
|
|
|
|
|
print(f"[Agent] Pruned {tool_results_removed} old tool_result(s) from conversation history")
|
|
|
|
|
|
2026-02-18 20:31:32 -07:00
|
|
|
def chat(
|
|
|
|
|
self,
|
|
|
|
|
user_message: str,
|
|
|
|
|
username: str = "default",
|
2026-03-01 10:46:43 -07:00
|
|
|
progress_callback: Optional[Callable[[str], None]] = None,
|
|
|
|
|
inbound_message: Optional['InboundMessage'] = None
|
2026-02-18 20:31:32 -07:00
|
|
|
) -> str:
|
2026-02-13 23:38:44 -07:00
|
|
|
"""Chat with context from memory and tool use.
|
|
|
|
|
|
|
|
|
|
Thread-safe: uses a lock to prevent concurrent modification of
|
|
|
|
|
conversation history from multiple threads (e.g., scheduled tasks
|
|
|
|
|
and live messages).
|
2026-02-18 20:31:32 -07:00
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
user_message: The user's message
|
|
|
|
|
username: The user's name (default: "default")
|
|
|
|
|
progress_callback: Optional callback for sending progress updates during long operations
|
2026-03-01 10:46:43 -07:00
|
|
|
inbound_message: Optional full message object (for file/image handling)
|
2026-02-13 23:38:44 -07:00
|
|
|
"""
|
2026-03-01 10:46:43 -07:00
|
|
|
# Update activity if this is a sub-agent
|
|
|
|
|
if self.is_sub_agent and self.agent_id:
|
|
|
|
|
# Find parent agent to update activity
|
|
|
|
|
# (parent has the sub_agent_manager)
|
|
|
|
|
# For now, we'll add this in delegate() instead
|
|
|
|
|
pass
|
|
|
|
|
|
2026-02-18 20:31:32 -07:00
|
|
|
# Store the callback for use during the chat
|
|
|
|
|
self._progress_callback = progress_callback
|
|
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
# Handle model switching commands (no lock needed, read-only on history)
|
2026-02-13 19:06:28 -07:00
|
|
|
if user_message.lower().startswith("/model "):
|
|
|
|
|
model_name = user_message[7:].strip()
|
|
|
|
|
self.llm.set_model(model_name)
|
|
|
|
|
return f"Switched to model: {model_name}"
|
|
|
|
|
elif user_message.lower() == "/sonnet":
|
|
|
|
|
self.llm.set_model("claude-sonnet-4-5-20250929")
|
|
|
|
|
return "Switched to Claude Sonnet 4.5 (more capable, higher cost)"
|
|
|
|
|
elif user_message.lower() == "/haiku":
|
|
|
|
|
self.llm.set_model("claude-haiku-4-5-20251001")
|
|
|
|
|
return "Switched to Claude Haiku 4.5 (faster, cheaper)"
|
|
|
|
|
elif user_message.lower() == "/status":
|
|
|
|
|
current_model = self.llm.model
|
|
|
|
|
is_sonnet = "sonnet" in current_model.lower()
|
|
|
|
|
cache_status = "enabled" if is_sonnet else "disabled (Haiku active)"
|
|
|
|
|
return (
|
|
|
|
|
f"Current model: {current_model}\n"
|
|
|
|
|
f"Prompt caching: {cache_status}\n"
|
|
|
|
|
f"Context messages: {MAX_CONTEXT_MESSAGES}\n"
|
|
|
|
|
f"Memory results: 2\n\n"
|
|
|
|
|
f"Commands: /sonnet, /haiku, /status"
|
|
|
|
|
)
|
|
|
|
|
|
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
|
|
|
# RSO: classify signal from this message relative to the prior interaction
|
|
|
|
|
if (
|
|
|
|
|
self._interaction_logger is not None
|
|
|
|
|
and self._last_interaction_id is not None
|
|
|
|
|
and not self.is_sub_agent
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
from observation.signal_detector import classify_signal
|
|
|
|
|
_now = time.time()
|
|
|
|
|
_delta = (_now - self._last_interaction_ts) if self._last_interaction_ts else None
|
|
|
|
|
_signal_type = classify_signal(user_message, time_delta_seconds=_delta)
|
|
|
|
|
self._interaction_logger.update_signal(
|
|
|
|
|
self._last_interaction_id,
|
|
|
|
|
{
|
|
|
|
|
"follow_up_type": _signal_type,
|
|
|
|
|
"explicit_positive": _signal_type == "positive",
|
|
|
|
|
"explicit_negative": _signal_type in ("negative", "correction"),
|
|
|
|
|
"correction_followed": _signal_type == "correction",
|
|
|
|
|
"time_delta_seconds": round(_delta, 1) if _delta is not None else None,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Agent] Signal detection failed: {_e}")
|
|
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
with self._chat_lock:
|
2026-02-18 20:31:32 -07:00
|
|
|
try:
|
2026-03-01 10:46:43 -07:00
|
|
|
return self._chat_inner(user_message, username, inbound_message)
|
2026-02-18 20:31:32 -07:00
|
|
|
finally:
|
|
|
|
|
# Clear the callback when done
|
|
|
|
|
self._progress_callback = None
|
2026-02-13 23:38:44 -07:00
|
|
|
|
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
|
|
|
def _build_system_prompt(self, user_message: str, username: str, platform: str = "unknown") -> str:
|
2026-02-18 20:31:32 -07:00
|
|
|
"""Build the system prompt with SOUL, user profile, and memory context."""
|
2026-02-16 07:43:31 -07:00
|
|
|
if self.specialist_prompt:
|
2026-02-18 20:31:32 -07:00
|
|
|
return (
|
2026-02-16 07:43:31 -07:00
|
|
|
f"{self.specialist_prompt}\n\n"
|
2026-02-18 20:31:32 -07:00
|
|
|
f"You have access to tools for file operations, command execution, "
|
|
|
|
|
f"web fetching, note-taking, and Google services. "
|
|
|
|
|
f"Use them to accomplish your specialized task efficiently."
|
2026-02-16 07:43:31 -07:00
|
|
|
)
|
2026-02-13 19:06:28 -07:00
|
|
|
|
2026-02-18 20:31:32 -07:00
|
|
|
soul = self.memory.get_soul()
|
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
|
|
|
context = self.memory.get_context()
|
2026-02-18 20:31:32 -07:00
|
|
|
user_profile = self.memory.get_user(username)
|
|
|
|
|
relevant_memory = self.memory.search_hybrid(user_message, max_results=5)
|
|
|
|
|
|
|
|
|
|
memory_lines = [f"- {mem['snippet']}" for mem in relevant_memory]
|
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
|
|
|
parts = [soul]
|
|
|
|
|
if context:
|
|
|
|
|
parts.append(f"Operational Context:\n{context}")
|
|
|
|
|
parts.append(f"User Profile:\n{user_profile}")
|
|
|
|
|
parts.append("Relevant Memory:\n" + "\n".join(memory_lines))
|
|
|
|
|
|
|
|
|
|
# Inject platform-specific formatting and tone reminder
|
|
|
|
|
if platform == "slack":
|
|
|
|
|
parts.append(
|
|
|
|
|
"FORMATTING (Slack): Use *text* for bold, _text_ for italic. "
|
|
|
|
|
"No ## headers, no --- dividers. Tables in triple backtick code blocks. "
|
|
|
|
|
"Links as <url|text>.\n\n"
|
|
|
|
|
"TONE: You are JARVIS — dry British wit, deadpan delivery, calm competence. "
|
|
|
|
|
"Lead with the result. Wit lives in word choice, not length. "
|
|
|
|
|
"Occasional 'Sir'. Never chipper. Never flat."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
parts.append(
|
|
|
|
|
"You have access to tools for file operations, command execution, "
|
|
|
|
|
"web fetching, note-taking, and Google services (Gmail, Calendar, Contacts). "
|
|
|
|
|
"Use them freely to help the user."
|
2026-02-18 20:31:32 -07:00
|
|
|
)
|
|
|
|
|
|
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
|
|
|
parts.append(
|
|
|
|
|
"DELEGATION: Call `delegate_task` when a request involves any of: "
|
|
|
|
|
"(a) reading/analyzing more than ~3 independent files or sources, "
|
|
|
|
|
"(b) multiple independent research threads that can run in parallel, "
|
|
|
|
|
"(c) a scoped sub-task you expect to take more than ~5 tool calls. "
|
|
|
|
|
"Inline tool loops past ~10 steps degrade quality and context. "
|
|
|
|
|
"Sequentially dependent work (e.g. server provisioning where each step gates the next) "
|
|
|
|
|
"stays inline — delegation only helps when subtasks are independent."
|
|
|
|
|
)
|
|
|
|
|
return "\n\n".join(parts)
|
|
|
|
|
|
|
|
|
|
def _build_child_system_prompt(self, username: str) -> str:
|
|
|
|
|
"""Build a stripped system prompt for restricted child users.
|
|
|
|
|
|
|
|
|
|
Skips SOUL.md, context.md, hybrid memory search, and delegation block
|
|
|
|
|
to save ~1,500 tokens per turn. Injects gabriel_context.md if present.
|
|
|
|
|
"""
|
|
|
|
|
from child_safety import (
|
|
|
|
|
CHILD_GUARDRAIL_BLOCK,
|
|
|
|
|
CHILD_TUTOR_IDENTITY,
|
|
|
|
|
FIRST_RUN_BLOCK,
|
|
|
|
|
SESSION_UPDATE_INSTRUCTION,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
user_profile = self.memory.get_user(username)
|
|
|
|
|
|
|
|
|
|
context_path = self.memory.workspace_dir / "users" / "gabriel_context.md"
|
|
|
|
|
gabriel_context = (
|
|
|
|
|
context_path.read_text(encoding="utf-8") if context_path.exists() else None
|
|
|
|
|
)
|
|
|
|
|
is_first_run = gabriel_context is None
|
|
|
|
|
|
|
|
|
|
parts = [CHILD_TUTOR_IDENTITY, f"User Profile:\n{user_profile}"]
|
|
|
|
|
if gabriel_context:
|
|
|
|
|
parts.append(f"Project Context & Skills:\n{gabriel_context}")
|
|
|
|
|
if is_first_run:
|
|
|
|
|
parts.append(FIRST_RUN_BLOCK)
|
|
|
|
|
parts.append(CHILD_GUARDRAIL_BLOCK)
|
|
|
|
|
parts.append(SESSION_UPDATE_INSTRUCTION)
|
|
|
|
|
parts.append(
|
|
|
|
|
"You have access to file tools. Use them to update gabriel_context.md "
|
|
|
|
|
"at the end of this conversation."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return "\n\n".join(parts)
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
def _prepare_message_content(
|
|
|
|
|
self,
|
|
|
|
|
user_message: str,
|
|
|
|
|
inbound_message: Optional['InboundMessage']
|
|
|
|
|
) -> tuple[Any, bool]:
|
|
|
|
|
"""Prepare message content, embedding images if present.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
user_message: The text message
|
|
|
|
|
inbound_message: Optional message object with file metadata
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Tuple of (content, has_images)
|
|
|
|
|
- content: str (text only) or List[Dict] (text + images)
|
|
|
|
|
- has_images: bool
|
|
|
|
|
"""
|
|
|
|
|
import base64
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
if not inbound_message or "files" not in inbound_message.metadata:
|
|
|
|
|
return user_message, False
|
|
|
|
|
|
|
|
|
|
files = inbound_message.metadata.get("files", [])
|
|
|
|
|
if not files:
|
|
|
|
|
return user_message, False
|
|
|
|
|
|
|
|
|
|
# Separate images from documents
|
|
|
|
|
images = [f for f in files if f.get("mimetype", "").startswith("image/")]
|
|
|
|
|
documents = [f for f in files if not f.get("mimetype", "").startswith("image/")]
|
|
|
|
|
|
|
|
|
|
# If no images AND no documents, return early
|
|
|
|
|
if not images and not documents:
|
|
|
|
|
return user_message, False
|
|
|
|
|
|
|
|
|
|
# Build multi-modal content
|
|
|
|
|
content_blocks = []
|
|
|
|
|
|
|
|
|
|
# Add text
|
|
|
|
|
if user_message.strip():
|
|
|
|
|
content_blocks.append({"type": "text", "text": user_message})
|
|
|
|
|
|
|
|
|
|
# Add images (base64 encoded)
|
|
|
|
|
for img in images:
|
|
|
|
|
try:
|
|
|
|
|
file_path = Path(img["file_path"])
|
|
|
|
|
|
|
|
|
|
# Check file exists
|
|
|
|
|
if not file_path.exists():
|
|
|
|
|
print(f"[Agent] Image file not found: {file_path}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Check file size (Claude max: 5MB per image)
|
|
|
|
|
file_size = file_path.stat().st_size
|
|
|
|
|
if file_size > 5 * 1024 * 1024: # 5MB
|
|
|
|
|
print(f"[Agent] Image too large ({file_size / 1024 / 1024:.1f}MB): {file_path.name}")
|
|
|
|
|
content_blocks.append({
|
|
|
|
|
"type": "text",
|
|
|
|
|
"text": f"\n[Image {file_path.name} is too large ({file_size / 1024 / 1024:.1f}MB). Max: 5MB]"
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Read and encode
|
|
|
|
|
image_data = file_path.read_bytes()
|
|
|
|
|
base64_data = base64.standard_b64encode(image_data).decode("utf-8")
|
|
|
|
|
|
|
|
|
|
# Validate mimetype
|
|
|
|
|
mimetype = img["mimetype"]
|
|
|
|
|
if mimetype not in ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"]:
|
|
|
|
|
print(f"[Agent] Unsupported image format: {mimetype}")
|
|
|
|
|
content_blocks.append({
|
|
|
|
|
"type": "text",
|
|
|
|
|
"text": f"\n[Image {file_path.name} has unsupported format: {mimetype}]"
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Verify base64 data is not empty
|
|
|
|
|
if not base64_data:
|
|
|
|
|
print(f"[Agent ERROR] Base64 encoding failed for {file_path.name}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
print(f"[Agent DEBUG] Embedding image: {file_path.name} ({mimetype}, {file_size / 1024:.1f}KB)")
|
|
|
|
|
print(f"[Agent DEBUG] Base64 encoded: {len(base64_data)} chars, content_blocks count: {len(content_blocks)}")
|
|
|
|
|
|
|
|
|
|
image_block = {
|
|
|
|
|
"type": "image",
|
|
|
|
|
"source": {
|
|
|
|
|
"type": "base64",
|
|
|
|
|
"media_type": mimetype,
|
|
|
|
|
"data": base64_data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
content_blocks.append(image_block)
|
|
|
|
|
print(f"[Agent DEBUG] Image block added to content_blocks (new count: {len(content_blocks)})")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[Agent] Failed to load image {img.get('file_path')}: {e}")
|
|
|
|
|
|
|
|
|
|
# Add note about documents
|
|
|
|
|
if documents:
|
|
|
|
|
doc_list = "\n".join(
|
|
|
|
|
f"- {d['filename']} at {d['file_path']}"
|
|
|
|
|
for d in documents
|
|
|
|
|
)
|
|
|
|
|
content_blocks.append({
|
|
|
|
|
"type": "text",
|
|
|
|
|
"text": f"\n\nAttached documents:\n{doc_list}\n(Use read_file tool to access)"
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Final validation
|
|
|
|
|
image_block_count = sum(1 for block in content_blocks if block.get("type") == "image")
|
|
|
|
|
text_block_count = sum(1 for block in content_blocks if block.get("type") == "text")
|
|
|
|
|
print(f"[Agent DEBUG] Final content_blocks: {image_block_count} image(s), {text_block_count} text block(s)")
|
|
|
|
|
|
|
|
|
|
# Return has_images=True only if there are actual image blocks
|
|
|
|
|
has_images = image_block_count > 0
|
|
|
|
|
return content_blocks, has_images
|
|
|
|
|
|
|
|
|
|
def _chat_inner(self, user_message: str, username: str, inbound_message: Optional['InboundMessage'] = None) -> str:
|
2026-02-18 20:31:32 -07:00
|
|
|
"""Inner chat logic, called while holding _chat_lock."""
|
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
|
|
|
_rso_start_time = time.time()
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Prepare content (may include images)
|
|
|
|
|
content, has_images = self._prepare_message_content(user_message, inbound_message)
|
|
|
|
|
|
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
|
|
|
# Extract platform for formatting hints
|
|
|
|
|
platform = inbound_message.platform if inbound_message else "unknown"
|
|
|
|
|
|
|
|
|
|
# Determine if this is a restricted child session
|
|
|
|
|
is_child = (
|
|
|
|
|
not self.is_sub_agent
|
|
|
|
|
and self._child_safety_config is not None
|
|
|
|
|
and self._child_safety_config.is_restricted(username)
|
|
|
|
|
)
|
|
|
|
|
context_cap = CHILD_MAX_CONTEXT_MESSAGES if is_child else MAX_CONTEXT_MESSAGES
|
|
|
|
|
|
|
|
|
|
# Build system prompt (child gets a stripped prompt without SOUL/context/memory)
|
|
|
|
|
if is_child:
|
|
|
|
|
system = self._build_child_system_prompt(username)
|
|
|
|
|
else:
|
|
|
|
|
system = self._build_system_prompt(user_message, username, platform)
|
2026-02-18 20:31:32 -07:00
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Enhance prompt for images
|
|
|
|
|
if has_images:
|
|
|
|
|
system += (
|
|
|
|
|
"\n\nThe user has shared one or more images. "
|
|
|
|
|
"Analyze the visual content and respond helpfully."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Add to conversation history (content may be str or List[Dict])
|
2026-02-13 19:06:28 -07:00
|
|
|
self.conversation_history.append(
|
2026-03-01 10:46:43 -07:00
|
|
|
{"role": "user", "content": content}
|
2026-02-13 19:06:28 -07:00
|
|
|
)
|
|
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
self._prune_conversation_history()
|
|
|
|
|
|
2026-02-18 20:31:32 -07:00
|
|
|
# In Agent SDK mode, query() handles tool calls automatically via MCP.
|
|
|
|
|
# The tool loop is only needed for Direct API mode.
|
|
|
|
|
if self.llm.mode == "agent_sdk":
|
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
|
|
|
return self._chat_agent_sdk(user_message, system, username, _rso_start_time, context_cap=context_cap)
|
2026-02-18 20:31:32 -07:00
|
|
|
else:
|
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
|
|
|
return self._chat_direct_api(user_message, system, username, _rso_start_time, context_cap=context_cap)
|
2026-02-18 20:31:32 -07:00
|
|
|
|
|
|
|
|
def _send_progress_update(self, elapsed_seconds: int):
|
|
|
|
|
"""Send a progress update if callback is set."""
|
|
|
|
|
if self._progress_callback:
|
|
|
|
|
messages = [
|
|
|
|
|
f"⏳ Still working... ({elapsed_seconds}s elapsed)",
|
|
|
|
|
f"🔄 Processing your request... ({elapsed_seconds}s)",
|
|
|
|
|
f"⚙️ Working on it, this might take a moment... ({elapsed_seconds}s)",
|
|
|
|
|
]
|
|
|
|
|
# Rotate through messages
|
|
|
|
|
message = messages[(elapsed_seconds // 90) % len(messages)]
|
|
|
|
|
try:
|
|
|
|
|
self._progress_callback(message)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[Agent] Failed to send progress update: {e}")
|
|
|
|
|
|
|
|
|
|
def _start_progress_updates(self):
|
|
|
|
|
"""Start periodic progress updates (every 90 seconds)."""
|
|
|
|
|
def send_update(elapsed: int):
|
|
|
|
|
self._send_progress_update(elapsed)
|
|
|
|
|
# Schedule next update
|
|
|
|
|
self._progress_timer = threading.Timer(90.0, send_update, args=[elapsed + 90])
|
|
|
|
|
self._progress_timer.daemon = True
|
|
|
|
|
self._progress_timer.start()
|
|
|
|
|
|
|
|
|
|
# Send first update after 90 seconds
|
|
|
|
|
self._progress_timer = threading.Timer(90.0, send_update, args=[90])
|
|
|
|
|
self._progress_timer.daemon = True
|
|
|
|
|
self._progress_timer.start()
|
|
|
|
|
|
|
|
|
|
def _stop_progress_updates(self):
|
|
|
|
|
"""Stop progress updates."""
|
|
|
|
|
if self._progress_timer:
|
|
|
|
|
self._progress_timer.cancel()
|
|
|
|
|
self._progress_timer = None
|
|
|
|
|
|
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
|
|
|
def _chat_agent_sdk(self, user_message: str, system: str, username: str = "default", _rso_start: float = 0.0, context_cap: int = MAX_CONTEXT_MESSAGES) -> str:
|
2026-02-18 20:31:32 -07:00
|
|
|
"""Chat using Agent SDK. Tools are handled automatically by MCP."""
|
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
|
|
|
context_messages = self._cap_old_message_content(
|
|
|
|
|
self._get_context_messages(context_cap)
|
|
|
|
|
)
|
2026-02-18 20:31:32 -07:00
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# DEBUG: Count images in conversation history
|
|
|
|
|
image_count = 0
|
|
|
|
|
for msg in context_messages:
|
|
|
|
|
if isinstance(msg.get("content"), list):
|
|
|
|
|
image_count += sum(1 for block in msg["content"] if isinstance(block, dict) and block.get("type") == "image")
|
|
|
|
|
if image_count > 0:
|
|
|
|
|
print(f"[Agent DEBUG] Sending {len(context_messages)} messages to Claude with {image_count} total image(s) in context")
|
|
|
|
|
|
2026-02-18 20:31:32 -07:00
|
|
|
# Start progress updates
|
|
|
|
|
self._start_progress_updates()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# chat_with_tools() in Agent SDK mode returns a string directly.
|
|
|
|
|
# The SDK handles all tool calls via MCP servers internally.
|
|
|
|
|
response = self.llm.chat_with_tools(
|
|
|
|
|
context_messages,
|
|
|
|
|
tools=[], # Ignored in Agent SDK mode; tools come from MCP
|
|
|
|
|
system=system,
|
|
|
|
|
)
|
|
|
|
|
except TimeoutError as e:
|
2026-03-01 16:17:37 -07:00
|
|
|
# Use the detailed timeout message from llm_interface.py
|
|
|
|
|
error_msg = str(e) if str(e) else "⏱️ Task timed out - consider breaking it into smaller steps or using delegate_task."
|
2026-02-18 20:31:32 -07:00
|
|
|
print(f"[Agent] TIMEOUT: {error_msg}")
|
|
|
|
|
self.healing_system.capture_error(
|
|
|
|
|
error=e,
|
|
|
|
|
component="agent.py:_chat_agent_sdk",
|
|
|
|
|
intent="Calling Agent SDK for chat response (TIMEOUT)",
|
|
|
|
|
context={
|
|
|
|
|
"model": self.llm.model,
|
|
|
|
|
"message_preview": user_message[:100],
|
|
|
|
|
"error_type": "timeout",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return error_msg
|
|
|
|
|
except Exception as e:
|
2026-03-01 16:17:37 -07:00
|
|
|
# Include actual error message for better debugging
|
2026-02-18 20:31:32 -07:00
|
|
|
error_msg = f"Agent SDK error: {e}"
|
|
|
|
|
print(f"[Agent] {error_msg}")
|
|
|
|
|
self.healing_system.capture_error(
|
|
|
|
|
error=e,
|
|
|
|
|
component="agent.py:_chat_agent_sdk",
|
|
|
|
|
intent="Calling Agent SDK for chat response",
|
|
|
|
|
context={
|
|
|
|
|
"model": self.llm.model,
|
|
|
|
|
"message_preview": user_message[:100],
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-03-01 16:17:37 -07:00
|
|
|
# Return the actual error message instead of generic text
|
|
|
|
|
return f"Sorry, I encountered an error: {str(e)[:500]}"
|
2026-02-18 20:31:32 -07:00
|
|
|
finally:
|
|
|
|
|
# Always stop progress updates when done
|
|
|
|
|
self._stop_progress_updates()
|
|
|
|
|
|
|
|
|
|
# In Agent SDK mode, response is always a string
|
|
|
|
|
final_response = response if isinstance(response, str) else str(response)
|
|
|
|
|
|
|
|
|
|
if not final_response.strip():
|
|
|
|
|
final_response = "(No response generated)"
|
|
|
|
|
|
|
|
|
|
self.conversation_history.append(
|
|
|
|
|
{"role": "assistant", "content": final_response}
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Remove images from conversation history to prevent token bloat and confusion
|
|
|
|
|
self._strip_images_from_history()
|
|
|
|
|
|
|
|
|
|
# Prune old tool results to prevent buffer overflow during diagram generation
|
|
|
|
|
self._prune_old_tool_results(keep_recent=10)
|
|
|
|
|
|
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
|
|
|
# Write memory entry — one-liner for scheduled tasks, rich/compact for real turns
|
|
|
|
|
if username == "scheduler":
|
|
|
|
|
timestamp = datetime.now().strftime('%H:%M')
|
|
|
|
|
summary = f"**Scheduled**: {user_message[:60]}... → delivered at {timestamp}"
|
|
|
|
|
elif self.memory.is_high_signal(user_message):
|
|
|
|
|
summary = self.memory.rich_conversation(user_message, final_response)
|
|
|
|
|
else:
|
|
|
|
|
summary = self.memory.compact_conversation(
|
|
|
|
|
user_message=user_message,
|
|
|
|
|
assistant_response=final_response,
|
|
|
|
|
tools_used=None # SDK handles tools internally; we don't track them here
|
|
|
|
|
)
|
|
|
|
|
self.memory.write_memory(summary, daily=True)
|
|
|
|
|
|
|
|
|
|
# RSO Phase 1: log interaction entry (agent_sdk mode)
|
|
|
|
|
if self._interaction_logger is not None:
|
|
|
|
|
try:
|
|
|
|
|
_iid = str(_uuid.uuid4())
|
|
|
|
|
_duration_ms = int((time.time() - _rso_start) * 1000) if _rso_start else 0
|
|
|
|
|
_tool_names = list(getattr(self.llm, '_last_tool_names', []) or [])
|
|
|
|
|
_total_cost = getattr(self.llm, '_last_total_cost_usd', 0) or 0
|
|
|
|
|
_num_turns = getattr(self.llm, '_last_num_turns', 0) or 0
|
|
|
|
|
_tool_count = len(_tool_names)
|
|
|
|
|
_complexity = (
|
|
|
|
|
"simple" if _tool_count < 3 else
|
|
|
|
|
"moderate" if _tool_count <= 6 else
|
|
|
|
|
"complex"
|
|
|
|
|
)
|
|
|
|
|
_entry = {
|
|
|
|
|
"record_type": "interaction",
|
|
|
|
|
"interaction_id": _iid,
|
|
|
|
|
"timestamp": datetime.now().astimezone().isoformat(),
|
|
|
|
|
"session_id": id(self),
|
|
|
|
|
"request": {
|
|
|
|
|
"source": "agent_sdk",
|
|
|
|
|
"user": username,
|
|
|
|
|
"message_preview": user_message[:100],
|
|
|
|
|
"task_type": _classify_task_type(user_message),
|
|
|
|
|
"complexity": _complexity,
|
|
|
|
|
},
|
|
|
|
|
"response": {
|
|
|
|
|
"duration_ms": _duration_ms,
|
|
|
|
|
"tool_calls": [{"tool": t} for t in _tool_names],
|
|
|
|
|
"total_tool_calls": _tool_count,
|
|
|
|
|
"tokens_in": 0, # not available in agent_sdk mode
|
|
|
|
|
"tokens_out": 0, # not available in agent_sdk mode
|
|
|
|
|
"cost_usd": _total_cost,
|
|
|
|
|
"num_turns": _num_turns,
|
|
|
|
|
"error": None,
|
|
|
|
|
},
|
|
|
|
|
"user_signal": {
|
|
|
|
|
"explicit_positive": False,
|
|
|
|
|
"explicit_negative": False,
|
|
|
|
|
"correction_followed": False,
|
|
|
|
|
"follow_up_type": None,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
self._interaction_logger.log_interaction(_entry)
|
|
|
|
|
self._last_interaction_id = _iid
|
|
|
|
|
self._last_interaction_ts = time.time()
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Agent] RSO log failed: {_e}")
|
2026-02-18 20:31:32 -07:00
|
|
|
|
|
|
|
|
return final_response
|
|
|
|
|
|
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
|
|
|
def _chat_direct_api(self, user_message: str, system: str, username: str = "default", _rso_start: float = 0.0, context_cap: int = MAX_CONTEXT_MESSAGES) -> str:
|
2026-02-18 20:31:32 -07:00
|
|
|
"""Chat using Direct API with manual tool execution loop."""
|
2026-02-16 07:43:31 -07:00
|
|
|
max_iterations = MAX_TOOL_ITERATIONS
|
2026-02-13 19:06:28 -07:00
|
|
|
use_caching = "sonnet" in self.llm.model.lower()
|
2026-02-18 20:31:32 -07:00
|
|
|
tools_used = []
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
for iteration in range(max_iterations):
|
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
|
|
|
context_messages = self._cap_old_message_content(
|
|
|
|
|
self._get_context_messages(context_cap)
|
|
|
|
|
)
|
2026-02-13 23:38:44 -07:00
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# DEBUG: Count images in conversation history (only log on first iteration)
|
|
|
|
|
if iteration == 0:
|
|
|
|
|
image_count = 0
|
|
|
|
|
for msg in context_messages:
|
|
|
|
|
if isinstance(msg.get("content"), list):
|
|
|
|
|
image_count += sum(1 for block in msg["content"] if isinstance(block, dict) and block.get("type") == "image")
|
|
|
|
|
if image_count > 0:
|
|
|
|
|
print(f"[Agent DEBUG] Sending {len(context_messages)} messages to Claude with {image_count} total image(s) in context")
|
|
|
|
|
|
2026-02-13 23:38:44 -07:00
|
|
|
try:
|
|
|
|
|
response = self.llm.chat_with_tools(
|
|
|
|
|
context_messages,
|
|
|
|
|
tools=TOOL_DEFINITIONS,
|
|
|
|
|
system=system,
|
|
|
|
|
use_cache=use_caching,
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"LLM API error: {e}"
|
|
|
|
|
print(f"[Agent] {error_msg}")
|
2026-02-14 18:03:42 -07:00
|
|
|
self.healing_system.capture_error(
|
|
|
|
|
error=e,
|
2026-02-18 20:31:32 -07:00
|
|
|
component="agent.py:_chat_direct_api",
|
|
|
|
|
intent="Calling Direct API for chat response",
|
2026-02-14 18:03:42 -07:00
|
|
|
context={
|
|
|
|
|
"model": self.llm.model,
|
|
|
|
|
"message_preview": user_message[:100],
|
|
|
|
|
"iteration": iteration,
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-03-01 16:17:37 -07:00
|
|
|
# Return actual error message instead of generic text
|
|
|
|
|
return f"Sorry, I encountered an error: {str(e)[:500]}"
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
if response.stop_reason == "end_turn":
|
|
|
|
|
text_content = []
|
|
|
|
|
for block in response.content:
|
|
|
|
|
if block.type == "text":
|
|
|
|
|
text_content.append(block.text)
|
|
|
|
|
|
|
|
|
|
final_response = "\n".join(text_content)
|
2026-02-13 23:38:44 -07:00
|
|
|
|
|
|
|
|
if not final_response.strip():
|
|
|
|
|
final_response = "(No response generated)"
|
|
|
|
|
|
2026-02-13 19:06:28 -07:00
|
|
|
self.conversation_history.append(
|
|
|
|
|
{"role": "assistant", "content": final_response}
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-01 10:46:43 -07:00
|
|
|
# Remove images from conversation history to prevent token bloat and confusion
|
|
|
|
|
self._strip_images_from_history()
|
|
|
|
|
|
|
|
|
|
# Prune old tool results to prevent buffer overflow during diagram generation
|
|
|
|
|
self._prune_old_tool_results(keep_recent=10)
|
|
|
|
|
|
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
|
|
|
if username == "scheduler":
|
|
|
|
|
timestamp = datetime.now().strftime('%H:%M')
|
|
|
|
|
summary = f"**Scheduled**: {user_message[:60]}... → delivered at {timestamp}"
|
|
|
|
|
elif self.memory.is_high_signal(user_message):
|
|
|
|
|
summary = self.memory.rich_conversation(
|
|
|
|
|
user_message, final_response,
|
|
|
|
|
tools_used=tools_used if tools_used else None
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
summary = self.memory.compact_conversation(
|
|
|
|
|
user_message=user_message,
|
|
|
|
|
assistant_response=final_response,
|
|
|
|
|
tools_used=tools_used if tools_used else None
|
|
|
|
|
)
|
|
|
|
|
self.memory.write_memory(summary, daily=True)
|
|
|
|
|
|
|
|
|
|
# RSO Phase 1: log interaction entry (direct_api mode)
|
|
|
|
|
if self._interaction_logger is not None:
|
|
|
|
|
try:
|
|
|
|
|
_iid = str(_uuid.uuid4())
|
|
|
|
|
_duration_ms = int((time.time() - _rso_start) * 1000) if _rso_start else 0
|
|
|
|
|
_tool_count = len(tools_used)
|
|
|
|
|
_complexity = (
|
|
|
|
|
"simple" if _tool_count < 3 else
|
|
|
|
|
"moderate" if _tool_count <= 6 else
|
|
|
|
|
"complex"
|
|
|
|
|
)
|
|
|
|
|
_entry = {
|
|
|
|
|
"record_type": "interaction",
|
|
|
|
|
"interaction_id": _iid,
|
|
|
|
|
"timestamp": datetime.now().astimezone().isoformat(),
|
|
|
|
|
"session_id": id(self),
|
|
|
|
|
"request": {
|
|
|
|
|
"source": "direct_api",
|
|
|
|
|
"user": username,
|
|
|
|
|
"message_preview": user_message[:100],
|
|
|
|
|
"task_type": _classify_task_type(user_message),
|
|
|
|
|
"complexity": _complexity,
|
|
|
|
|
},
|
|
|
|
|
"response": {
|
|
|
|
|
"duration_ms": _duration_ms,
|
|
|
|
|
"tool_calls": [{"tool": t} for t in tools_used],
|
|
|
|
|
"total_tool_calls": _tool_count,
|
|
|
|
|
"tokens_in": 0,
|
|
|
|
|
"tokens_out": 0,
|
|
|
|
|
"cost_usd": 0,
|
|
|
|
|
"error": None,
|
|
|
|
|
},
|
|
|
|
|
"user_signal": {
|
|
|
|
|
"explicit_positive": False,
|
|
|
|
|
"explicit_negative": False,
|
|
|
|
|
"correction_followed": False,
|
|
|
|
|
"follow_up_type": None,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
self._interaction_logger.log_interaction(_entry)
|
|
|
|
|
self._last_interaction_id = _iid
|
|
|
|
|
self._last_interaction_ts = time.time()
|
|
|
|
|
except Exception as _e:
|
|
|
|
|
print(f"[Agent] RSO log failed: {_e}")
|
2026-02-13 19:06:28 -07:00
|
|
|
|
|
|
|
|
return final_response
|
|
|
|
|
|
|
|
|
|
elif response.stop_reason == "tool_use":
|
|
|
|
|
assistant_content = []
|
|
|
|
|
tool_uses = []
|
|
|
|
|
|
|
|
|
|
for block in response.content:
|
|
|
|
|
if block.type == "text":
|
|
|
|
|
assistant_content.append({
|
|
|
|
|
"type": "text",
|
|
|
|
|
"text": block.text
|
|
|
|
|
})
|
|
|
|
|
elif block.type == "tool_use":
|
|
|
|
|
assistant_content.append({
|
|
|
|
|
"type": "tool_use",
|
|
|
|
|
"id": block.id,
|
|
|
|
|
"name": block.name,
|
|
|
|
|
"input": block.input
|
|
|
|
|
})
|
|
|
|
|
tool_uses.append(block)
|
|
|
|
|
|
|
|
|
|
self.conversation_history.append({
|
|
|
|
|
"role": "assistant",
|
|
|
|
|
"content": assistant_content
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
tool_results = []
|
|
|
|
|
for tool_use in tool_uses:
|
2026-02-18 20:31:32 -07:00
|
|
|
if tool_use.name not in tools_used:
|
|
|
|
|
tools_used.append(tool_use.name)
|
2026-02-14 18:03:42 -07:00
|
|
|
result = execute_tool(tool_use.name, tool_use.input, healing_system=self.healing_system)
|
2026-02-13 23:38:44 -07:00
|
|
|
if len(result) > 5000:
|
|
|
|
|
result = result[:5000] + "\n... (output truncated)"
|
2026-02-13 19:06:28 -07:00
|
|
|
print(f"[Tool] {tool_use.name}: {result[:100]}...")
|
|
|
|
|
tool_results.append({
|
|
|
|
|
"type": "tool_result",
|
|
|
|
|
"tool_use_id": tool_use.id,
|
|
|
|
|
"content": result
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
self.conversation_history.append({
|
|
|
|
|
"role": "user",
|
|
|
|
|
"content": tool_results
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
return f"Unexpected stop reason: {response.stop_reason}"
|
|
|
|
|
|
|
|
|
|
return "Error: Maximum tool use iterations exceeded"
|
|
|
|
|
|
|
|
|
|
def switch_model(self, provider: str) -> None:
|
|
|
|
|
"""Switch LLM provider."""
|
|
|
|
|
self.llm = LLMInterface(provider)
|
|
|
|
|
|
|
|
|
|
def shutdown(self) -> None:
|
|
|
|
|
"""Cleanup and stop background services."""
|
|
|
|
|
self.memory.close()
|
|
|
|
|
self.hooks.trigger("agent", "shutdown", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
agent = Agent(provider="claude")
|
|
|
|
|
response = agent.chat("What's my current project?", username="alice")
|
|
|
|
|
print(response)
|