feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments

Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager

Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users

Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks

Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response

Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence

Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)

Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
This commit is contained in:
2026-04-23 07:54:01 -06:00
parent 1232490c3b
commit 916f86725d
70 changed files with 10945 additions and 187 deletions

View File

@@ -5,17 +5,19 @@ tasks:
# Morning briefing - sent to Slack/Telegram
- name: morning-weather
prompt: |
Check the user profile (Jordan.md) for the location (Centennial, CO). Use the get_weather tool with OpenWeatherMap API to fetch the current weather.
Also use web_fetch to get today's high/low from a weather service:
Fetch the current weather for Centennial, CO using web_fetch:
https://wttr.in/Centennial,CO?format=j1
Parse the JSON response to extract:
- maxtempF (today's high)
- mintempF (today's low)
- current_condition[0].temp_F (current temp)
- current_condition[0].FeelsLikeF (feels like)
- weather[0].maxtempF (today's high)
- weather[0].mintempF (today's low)
- current_condition[0].weatherDesc[0].value (conditions)
- current_condition[0].windspeedMiles (wind speed)
Format the report as:
🌤️ **Weather Report for Centennial, CO**
- Current: [current]°F (feels like [feels_like]°F)
- Today's High: [high]°F
@@ -23,7 +25,7 @@ tasks:
- Conditions: [conditions]
- Wind: [wind speed] mph
- Recommendation: [brief clothing/activity suggestion]
Keep it brief and friendly!
schedule: "daily 06:00"
enabled: true
@@ -59,7 +61,7 @@ tasks:
send_to_platform: "telegram"
send_to_channel: "8088983654"
# Daily API cost report
# Daily API cost report — DISABLED (bot runs on Max subscription, no per-token billing)
- name: daily-cost-report
prompt: |
Generate a daily API usage and cost report:
@@ -88,6 +90,181 @@ tasks:
Keep it clear and actionable!
schedule: "daily 23:00"
enabled: false
send_to_platform: "telegram"
send_to_channel: "8088983654"
# RSO Phase 2 — Weekly Reflection Agent
- name: rso-weekly-reflection
prompt: |
You are the Weekly Reflection Agent for the RSO (Reflective Self-Optimization) system.
Your job is to analyze the past 7 days of interaction logs and produce a structured report.
## Step 1 — Collect the data
Run this command to gather and parse the last 7 days of logs:
```
python3 -c "
import json, os, glob
from datetime import datetime, timedelta
from collections import Counter
log_dir = 'memory_workspace/observation/logs'
error_dir = 'memory_workspace/observation/errors'
cutoff = datetime.now() - timedelta(days=7)
interactions, signals, errors = [], [], []
for path in sorted(glob.glob(f'{log_dir}/*.jsonl')):
date_str = os.path.basename(path).replace('.jsonl','')
try:
file_date = datetime.strptime(date_str, '%Y-%m-%d')
if file_date < cutoff:
continue
except ValueError:
continue
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
r = json.loads(line)
if r.get('record_type') == 'interaction':
interactions.append(r)
elif r.get('record_type') == 'signal_patch':
signals.append(r)
for path in sorted(glob.glob(f'{error_dir}/*.jsonl')):
date_str = os.path.basename(path).replace('.jsonl','')
try:
file_date = datetime.strptime(date_str, '%Y-%m-%d')
if file_date < cutoff:
continue
except ValueError:
continue
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
errors.append(json.loads(line))
durations = [r['response']['duration_ms'] for r in interactions]
task_types = Counter(r['request']['task_type'] for r in interactions)
complexity = Counter(r['request']['complexity'] for r in interactions)
all_tools = []
for r in interactions:
all_tools.extend(t['tool'] for t in r['response'].get('tool_calls', []))
tool_counts = Counter(all_tools)
sig_pos = sum(1 for r in signals if isinstance(r.get('signal'), dict) and r['signal'].get('explicit_positive'))
sig_neg = sum(1 for r in signals if isinstance(r.get('signal'), dict) and r['signal'].get('explicit_negative'))
sig_correction = sum(1 for r in signals if isinstance(r.get('signal'), dict) and r['signal'].get('correction_followed'))
follow_types = Counter(r['signal']['follow_up_type'] for r in signals if isinstance(r.get('signal'), dict))
slow = [r for r in interactions if r['response']['duration_ms'] > 60000]
timeouts = [e for e in errors if 'timed out' in e.get('message','').lower()]
print('=== STATS ===')
print(f'Total interactions: {len(interactions)}')
print(f'Total signals: {len(signals)}')
print(f'Total errors: {len(errors)}, Timeouts: {len(timeouts)}')
print(f'Avg duration: {sum(durations)/len(durations)/1000:.1f}s' if durations else 'No data')
print(f'Max duration: {max(durations)/1000:.1f}s' if durations else '')
print(f'Slow (>60s): {len(slow)} ({len(slow)*100//len(interactions) if interactions else 0}%)')
print(f'Task types: {dict(task_types)}')
print(f'Complexity: {dict(complexity)}')
print(f'Signals — pos:{sig_pos} neg:{sig_neg} correction:{sig_correction} follow_types:{dict(follow_types)}')
print(f'Top 12 tools:')
for tool, count in tool_counts.most_common(12):
print(f' {tool}: {count}')
print(f'Slow interactions:')
for r in sorted(slow, key=lambda x: x['response']['duration_ms'], reverse=True)[:10]:
print(f' {r[\"response\"][\"duration_ms\"]//1000}s — {r[\"request\"][\"message_preview\"][:70]}')
print(f'Errors:')
for e in errors:
print(f' [{e.get(\"timestamp\",\"\")[:10]}] {e.get(\"error_type\",\"?\")} — {e.get(\"message\",\"\")[:120]}')
"
```
## Step 2 — Run the memory relevance scorer
Run this to score all indexed memory files:
```
python -c "
import sys
sys.path.insert(0, '.')
from observation.memory_scorer import MemoryRelevanceScorer
scorer = MemoryRelevanceScorer('./memory_workspace')
report = scorer.score_all(lookback_days=30)
s = report['summary']
print('=== MEMORY SCORES ===')
print(f'Files scored: {report[\"files_scored\"]} cold_start: {report[\"cold_start\"]}')
print(f'Core: {s[\"core_memory\"]} Active: {s[\"active_memory\"]} Archive: {s[\"archive_candidates\"]} Stale: {s[\"stale_candidates\"]}')
for e in report['archive_recommendations'][:10]:
flags = ','.join(e['staleness_flags']) or 'none'
print(f' ARCHIVE {e[\"path\"]} score={e[\"score\"]:.1f} age={e[\"age_days\"]:.0f}d [{flags}]')
scorer.write_report(lookback_days=30)
"
```
## Step 3 — Write the analysis report
Using the stats above, write a report answering these five questions. Be specific — use the actual numbers.
**Q1: What went well?**
- Interactions with positive signals and fast completions
- Tools and task types that completed efficiently
**Q2: What went wrong?**
- Timeouts, errors, corrections from the user
- Any recurring failure patterns
**Q3: What patterns emerged?**
- Most common task types and tools
- Any repeated tool chains worth noting
**Q4: What is being wasted?**
- Slow interactions that could be faster
- Redundant tool usage patterns
- Any scheduled tasks producing no value
- Include memory archive candidates from Step 2 (files with score <3 and age >=30d)
**Q5: Recommendations (35 max, data-backed)**
- Each one tagged: prompt | tool_usage | memory | code | config
- Include the supporting number ("X of Y interactions showed...")
- If there are memory archive candidates, include a `memory` recommendation
## Step 4 — Save the report
Determine the ISO week number:
```
python3 -c "from datetime import datetime; d=datetime.now(); print(f'week-{d.year}-{d.isocalendar()[1]:02d}')"
```
Save the report to:
- `memory_workspace/observation/summaries/week-YYYY-WW.md` (use write_file)
- Obsidian: use permanent_note to save it under `Projects/RSO/Reflections/week-YYYY-WW`
## Step 5 — Send Telegram summary
Send a brief summary (not the full report) to Jordan:
Weekly Reflection Report -- Week WW
[X] interactions | [Y]% positive signals | [Z] timeouts
Memory: [A] archive candidates, [B] stale files
Top findings:
- [Finding 1]
- [Finding 2]
- [Finding 3]
Full report saved to Obsidian -> Projects/RSO/Reflections/week-WW
schedule: "weekly sun 20:00"
enabled: true
send_to_platform: "telegram"
send_to_channel: "8088983654"

View File

@@ -0,0 +1,4 @@
{
"morning-weather": "2026-04-23T06:00:31.656886",
"zettelkasten-daily-review": "2026-04-22T20:00:25.320736"
}