Files
ajarbot/config/scheduled_tasks.yaml
Jordan Ramos 916f86725d 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

342 lines
12 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Scheduled Tasks Configuration
# Tasks that require the Agent/LLM to execute
tasks:
# Morning briefing - sent to Slack/Telegram
- name: morning-weather
prompt: |
Fetch the current weather for Centennial, CO using web_fetch:
https://wttr.in/Centennial,CO?format=j1
Parse the JSON response to extract:
- 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
- Today's Low: [low]°F
- Conditions: [conditions]
- Wind: [wind speed] mph
- Recommendation: [brief clothing/activity suggestion]
Keep it brief and friendly!
schedule: "daily 06:00"
enabled: true
send_to_platform: "telegram"
send_to_channel: "8088983654" # Your Telegram user ID
# Daily Zettelkasten Review
- name: zettelkasten-daily-review
prompt: |
Time for your daily zettelkasten review! Help Jordan process fleeting notes:
1. Use search_by_tags to find all notes tagged with "fleeting"
2. Show Jordan the list of fleeting notes captured today/recently
3. For each note, ask: "Would you like to:
a) Process this into a permanent note
b) Keep as fleeting for now
c) Delete (not useful)"
Format:
📝 **Daily Zettelkasten Review**
You have [X] fleeting notes to review:
1. [Title] - [first line of content]
2. [Title] - [first line of content]
...
Reply with the number to process, or 'skip' to review later.
Keep it conversational and low-pressure!
schedule: "daily 20:00"
enabled: true
send_to_platform: "telegram"
send_to_channel: "8088983654"
# 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:
Read the usage_data.json file to get today's API usage statistics.
Format the report as follows:
📊 **Daily API Usage Report**
**Today's Stats:**
- Total API calls: [count]
- Input tokens: [count]
- Output tokens: [count]
- Cache hits: [count] (if any)
**Costs:**
- Today: $[amount]
- Model breakdown: [breakdown by model]
**Budget Tracking:**
- Remaining budget: $19.86
- 75% threshold: $14.90 (⚠️ WARN IF EXCEEDED)
- Status: [On track / Warning - approaching 75% / Critical - over 75%]
⚠️ **IMPORTANT:** If cumulative cost exceeds $14.90 (75% of $19.86), display a clear warning message.
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"
# Evening summary
- name: evening-report
prompt: |
Good evening! Time for the daily wrap-up:
1. What was accomplished today?
2. Any tasks still pending?
3. Preview of tomorrow's priorities
4. Weather forecast for tomorrow (infer or say API needed)
Keep it concise and positive.
schedule: "daily 18:00"
enabled: false
send_to_platform: "telegram"
send_to_channel: "123456789" # Replace with chat ID
# Hourly health check (no message sending)
- name: system-health-check
prompt: |
Quick health check:
1. Are there any tasks that have been pending > 24 hours?
2. Is the memory system healthy?
3. Any alerts or issues?
Respond with "HEALTHY" if all is well, otherwise describe the issue.
schedule: "hourly"
enabled: false
username: "health-checker"
# Weekly review on Friday
- name: weekly-summary
prompt: |
It's Friday! Time for the weekly review:
1. Major accomplishments this week
2. Challenges faced and lessons learned
3. Key metrics (tasks completed, etc.)
4. Goals for next week
5. Team shoutouts (if applicable)
Make it comprehensive but engaging.
schedule: "weekly fri 17:00"
enabled: false
send_to_platform: "slack"
send_to_channel: "C12345"
# Custom: Midday standup
- name: midday-standup
prompt: |
Midday check-in! Quick standup report:
1. Morning accomplishments
2. Current focus
3. Any blockers?
4. Afternoon plan
Keep it brief - standup style.
schedule: "daily 12:00"
enabled: false
send_to_platform: "slack"
send_to_channel: "C12345"
# Configuration notes:
# - schedule formats:
# - "hourly" - Every hour on the hour
# - "daily HH:MM" - Every day at specified time (24h format)
# - "weekly day HH:MM" - Every week on specified day (mon, tue, wed, thu, fri, sat, sun)
# - send_to_platform: null = don't send to messaging (only log)
# - username: Agent memory username to use for this task