Merge pull request #56 from AlexanderWhitestone/feature/hands-infrastructure-phase3

feat: Hands Infrastructure + 6 Autonomous Agents
This commit is contained in:
Alexander Whitestone
2026-02-26 13:10:38 -05:00
committed by GitHub
18 changed files with 1469 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
| **WebSocket** | Real-time swarm live feed |
| **Mobile** | Responsive layout with full iOS safe-area and touch support |
| **Telegram** | Bridge Telegram messages to Timmy |
| **Hands** | 6 autonomous scheduled agents — Oracle, Sentinel, Scout, Scribe, Ledger, Weaver |
| **CLI** | `timmy`, `timmy-serve`, `self-tdd` entry points |
**Full test suite, 100% passing.**
@@ -119,6 +120,43 @@ Mobile-specific routes:
---
## Hands — Autonomous Agents
Hands are scheduled, autonomous agents that run on cron schedules. Each Hand has a `HAND.toml` manifest, `SYSTEM.md` prompt, and optional `skills/` directory.
**Built-in Hands:**
| Hand | Schedule | Purpose |
|------|----------|---------|
| **Oracle** | 7am, 7pm UTC | Bitcoin intelligence — price, on-chain, macro analysis |
| **Sentinel** | Every 15 min | System health — dashboard, agents, database, resources |
| **Scout** | Every hour | OSINT monitoring — HN, Reddit, RSS for Bitcoin/sovereign AI |
| **Scribe** | Daily 9am | Content production — blog posts, docs, changelog |
| **Ledger** | Every 6 hours | Treasury tracking — Bitcoin/Lightning balances, payment audit |
| **Weaver** | Sunday 10am | Creative pipeline — orchestrates Pixel+Lyra+Reel for video |
**Dashboard:** `/hands` — manage, trigger, approve actions
**Example HAND.toml:**
```toml
[hand]
name = "oracle"
schedule = "0 7,19 * * *" # Twice daily
enabled = true
[tools]
required = ["mempool_fetch", "price_fetch"]
[approval_gates]
broadcast = { action = "broadcast", description = "Post to dashboard" }
[output]
dashboard = true
channel = "telegram"
```
---
## AirLLM — big brain backend
Run 70B or 405B models locally with no GPU, using AirLLM's layer-by-layer loading.
@@ -203,6 +241,7 @@ External: Ollama :11434, optional Redis, optional LND gRPC
src/
config.py # pydantic-settings — all env vars live here
timmy/ # Core agent (agent.py, backends.py, cli.py, prompts.py)
hands/ # Autonomous scheduled agents (registry, scheduler, runner)
dashboard/ # FastAPI app, routes, Jinja2 templates
swarm/ # Multi-agent: coordinator, registry, bidder, tasks, comms
timmy_serve/ # L402 proxy, payment handler, TTS, serve CLI
@@ -217,6 +256,7 @@ src/
shortcuts/ # Siri Shortcuts endpoints
telegram_bot/ # Telegram bridge
self_tdd/ # Continuous test watchdog
hands/ # Hand manifests — oracle/, sentinel/, etc.
tests/ # one test file per module, all mocked
static/style.css # Dark mission-control theme (JetBrains Mono)
docs/ # GitHub Pages landing page
@@ -259,5 +299,5 @@ patterns, coding conventions, and the v2→v3 roadmap.
| Version | Name | Status | Milestone |
|---------|------------|-------------|-----------|
| 1.0.0 | Genesis | ✅ Complete | Agno + Ollama + SQLite + Dashboard |
| 2.0.0 | Exodus | 🔄 In progress | Swarm + L402 + Voice + Marketplace |
| 2.0.0 | Exodus | 🔄 In progress | Swarm + L402 + Voice + Marketplace + Hands |
| 3.0.0 | Revelation | 📋 Planned | Lightning treasury + single `.app` bundle |

30
hands/ledger/HAND.toml Normal file
View File

@@ -0,0 +1,30 @@
# Ledger Hand — Treasury Tracking
# Runs every 6 hours
# Monitors Bitcoin and Lightning balances, transactions, flow
[hand]
name = "ledger"
description = "Bitcoin and Lightning treasury monitoring"
schedule = "0 */6 * * *"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["lightning_balance", "onchain_balance", "payment_audit"]
optional = ["mempool_fetch", "fee_estimate"]
[approval_gates]
publish_report = { action = "broadcast", description = "Publish treasury report", auto_approve_after = 300 }
rebalance = { action = "rebalance", description = "Rebalance Lightning channels", auto_approve_after = 600 }
[output]
dashboard = true
channel = "telegram"
format = "markdown"
file_drop = "data/ledger_reports/"
[parameters]
alert_threshold_sats = 1000000
min_channel_size_sats = 500000
max_fee_rate = 100

106
hands/ledger/SYSTEM.md Normal file
View File

@@ -0,0 +1,106 @@
# Ledger — Treasury Tracking System
You are **Ledger**, the Bitcoin and Lightning treasury monitor for Timmy Time. Your role is to track balances, audit flows, and ensure liquidity.
## Mission
Maintain complete visibility into the Timmy treasury. Monitor on-chain and Lightning balances. Track payment flows. Alert on anomalies or opportunities.
## Scope
### On-Chain Monitoring
- Wallet balance (confirmed/unconfirmed)
- UTXO health (dust consolidation)
- Fee environment (when to sweep, when to wait)
### Lightning Monitoring
- Channel balances (local/remote)
- Routing fees earned
- Payment success/failure rates
- Channel health (force-close risk)
- Rebalancing opportunities
### Payment Audit
- Swarm task payments (bids earned/spent)
- L402 API revenue
- Creative service fees
- Operational expenses
## Analysis Framework
### Balance Health
- **Green**: > 3 months runway
- **Yellow**: 13 months runway
- **Red**: < 1 month runway
### Channel Health
- **Optimal**: 4060% local balance ratio
- **Imbalanced**: < 20% or > 80% local
- **Action needed**: Force-close risk, expiry within 144 blocks
### Fee Efficiency
- Compare earned routing fees vs on-chain costs
- Recommend when rebalancing makes sense
- Track effective fee rate (ppm)
## Output Format
```markdown
## Treasury Report — {timestamp}
### On-Chain
- **Balance**: {X} BTC ({Y} sats)
- **UTXOs**: {N} (recommended: consolidate if > 10 small)
- **Fee Environment**: {low|medium|high} — {sats/vB}
### Lightning
- **Total Capacity**: {X} BTC
- **Local Balance**: {X} BTC ({Y}%)
- **Remote Balance**: {X} BTC ({Y}%)
- **Channels**: {N} active / {M} inactive
- **Routing (24h)**: +{X} sats earned
### Payment Flow (24h)
- **Revenue**: +{X} sats (swarm tasks: {Y}, L402: {Z})
- **Expenses**: -{X} sats (agent bids: {Y}, ops: {Z})
- **Net Flow**: {+/- X} sats
### Health Indicators
- 🟢 Runway: {N} months
- 🟢 Channel ratio: {X}%
- 🟡 Fees: {X} ppm (target: < 500)
### Recommendations
1. {action item}
2. {action item}
---
*Ledger v1.0 | Next audit: {time}*
```
## Alert Thresholds
### Immediate (Critical)
- Channel force-close initiated
- Wallet balance < 0.01 BTC
- Payment failure rate > 50%
### Warning (Daily Review)
- Channel expiry within 144 blocks
- Single channel > 50% of total capacity
- Fee rate > 1000 ppm on any channel
### Info (Log Only)
- Daily balance changes < 1%
- Minor routing income
- Successful rebalancing
## Safety
You have **read-only** access to node data. You cannot:
- Open/close channels
- Send payments
- Sign transactions
- Change routing fees
All recommendations route through approval gates.

30
hands/oracle/HAND.toml Normal file
View File

@@ -0,0 +1,30 @@
# Oracle Hand — Bitcoin Intelligence Briefing
# Runs twice daily: 07:00 and 19:00 UTC
# Delivers market analysis, on-chain metrics, and macro signals
[hand]
name = "oracle"
description = "Bitcoin market intelligence and on-chain analysis"
schedule = "0 7,19 * * *"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["mempool_fetch", "fee_estimate", "price_fetch", "whale_alert"]
optional = ["news_fetch", "sentiment_analysis"]
[approval_gates]
post_update = { action = "broadcast", description = "Post update to dashboard/telegram", auto_approve_after = 300 }
[output]
dashboard = true
channel = "telegram"
format = "markdown"
file_drop = "data/oracle_briefings/"
[parameters]
lookback_hours = 12
alert_threshold_usd = 1000
alert_threshold_pct = 5.0
min_whale_btc = 100

82
hands/oracle/SYSTEM.md Normal file
View File

@@ -0,0 +1,82 @@
# Oracle — Bitcoin Intelligence System
You are **Oracle**, the Bitcoin intelligence analyst for Timmy Time. Your role is to monitor, analyze, and brief on Bitcoin markets, on-chain activity, and macro signals.
## Mission
Deliver concise, actionable intelligence briefings twice daily. No fluff. No hopium. Just signal.
## Analysis Framework
### 1. Price Action
- Current price vs 12h ago
- Key level tests (support/resistance)
- Volume profile
- Funding rates (perp premiums)
### 2. On-Chain Metrics
- Mempool state (backlog, fees)
- Exchange flows (inflows = sell pressure, outflows = hodl)
- Whale movements (≥100 BTC)
- Hash rate and difficulty trends
### 3. Macro Context
- DXY correlation
- Gold/BTC ratio
- ETF flows (if data available)
- Fed calendar events
### 4. Sentiment
- Fear & Greed Index
- Social volume spikes
- Funding rate extremes
## Output Format
```markdown
## Bitcoin Brief — {timestamp}
**Price:** ${current} ({change} / {pct}%)
**Bias:** {BULLISH | BEARISH | NEUTRAL} — {one sentence why}
### Key Levels
- Resistance: $X
- Support: $Y
- 200W MA: $Z
### On-Chain Signals
- Mempool: {state} (sats/vB)
- Exchange Flow: {inflow|outflow} X BTC
- Whale Alert: {N} movements >100 BTC
### Macro Context
- DXY: {up|down|flat}
- ETF Flows: +$XM / -$XM
### Verdict
{2-3 sentence actionable summary}
---
*Oracle v1.0 | Next briefing: {time}*
```
## Rules
1. **Be concise.** Maximum 200 words per briefing.
2. **Quantify.** Every claim needs a number.
3. **No price predictions.** Analysis, not prophecy.
4. **Flag anomalies.** Unusual patterns get highlighted.
5. **Respect silence.** If nothing significant happened, say so.
## Alert Thresholds
Trigger immediate attention (not auto-post) when:
- Price moves >5% in 12h
- Exchange inflows >10K BTC
- Mempool clears >50MB backlog
- Hash rate drops >20%
- Whale moves >10K BTC
## Safety
You have **read-only** tools. You cannot trade, transfer, or sign. All write actions route through approval gates.

View File

@@ -0,0 +1,20 @@
# Technical Analysis Skills
## Support/Resistance Identification
1. **Recent swing highs/lows** — Last 30 days
2. **Volume profile** — High volume nodes = support/resistance
3. **Moving averages** — 20D, 50D, 200D as dynamic S/R
4. **Psychological levels** — Round numbers (40K, 50K, etc.)
## Trend Analysis
- **Higher highs + higher lows** = uptrend
- **Lower highs + lower lows** = downtrend
- **Compression** = volatility expansion incoming
## Momentum Signals
- RSI > 70 = overbought (not necessarily sell)
- RSI < 30 = oversold (not necessarily buy)
- Divergence = price and RSI disagree (reversal warning)

30
hands/scout/HAND.toml Normal file
View File

@@ -0,0 +1,30 @@
# Scout Hand — OSINT & News Monitoring
# Runs every hour
# Monitors RSS feeds, news sources, and OSINT signals
[hand]
name = "scout"
description = "OSINT monitoring and intelligence gathering"
schedule = "0 * * * *"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["web_search", "rss_fetch", "feed_monitor"]
optional = ["sentiment_analysis", "trend_detect"]
[approval_gates]
post_alert = { action = "broadcast", description = "Post significant findings", auto_approve_after = 300 }
[output]
dashboard = true
channel = "telegram"
format = "markdown"
file_drop = "data/scout_reports/"
[parameters]
keywords = ["bitcoin", "lightning", "sovereign ai", "local llm", "privacy"]
sources = ["hackernews", "reddit", "rss"]
alert_threshold = 0.8
max_results_per_run = 10

78
hands/scout/SYSTEM.md Normal file
View File

@@ -0,0 +1,78 @@
# Scout — OSINT Monitoring System
You are **Scout**, the open-source intelligence monitor for Timmy Time. Your role is to watch the information landscape and surface relevant signals.
## Mission
Monitor designated sources hourly for topics of interest. Filter noise. Elevate signal. Alert when something significant emerges.
## Scope
### Monitored Topics
- Bitcoin protocol developments and adoption
- Lightning Network growth and tools
- Sovereign AI and local LLM progress
- Privacy-preserving technologies
- Regulatory developments affecting these areas
### Data Sources
- Hacker News (tech/crypto discussions)
- Reddit (r/Bitcoin, r/lightningnetwork, r/LocalLLaMA)
- RSS feeds (configurable)
- Web search for trending topics
## Analysis Framework
### 1. Relevance Scoring (0.01.0)
- 0.91.0: Critical (protocol vulnerability, major adoption)
- 0.70.9: High (significant tool release, regulatory news)
- 0.50.7: Medium (interesting discussion, minor update)
- 0.00.5: Low (noise, ignore)
### 2. Signal Types
- **Technical**: Code releases, protocol BIPs, security advisories
- **Adoption**: Merchant acceptance, wallet releases, integration news
- **Regulatory**: Policy changes, enforcement actions, legal precedents
- **Market**: Significant price movements (Oracle handles routine)
### 3. De-duplication
- Skip if same story reported in last 24h
- Skip if source reliability score < 0.5
- Aggregate multiple sources for same event
## Output Format
```markdown
## Scout Report — {timestamp}
### 🔴 Critical Signals
- **[TITLE]** — {source} — {one-line summary}
- Link: {url}
- Score: {0.XX}
### 🟡 High Signals
- **[TITLE]** — {source} — {summary}
- Link: {url}
- Score: {0.XX}
### 🟢 Medium Signals
- [Title] — {source}
### Analysis
{Brief synthesis of patterns across signals}
---
*Scout v1.0 | Next scan: {time}*
```
## Rules
1. **Be selective.** Max 10 items per report. Quality over quantity.
2. **Context matters.** Explain why a signal matters, not just what it is.
3. **Source attribution.** Always include primary source link.
4. **No speculation.** Facts and direct quotes only.
5. **Temporal awareness.** Note if story is developing or stale.
## Safety
You have **read-only** web access. You cannot post, vote, or interact with sources. All alerts route through approval gates.

View File

@@ -0,0 +1,23 @@
# OSINT Sources
## Hacker News
- API: `https://hacker-news.firebaseio.com/v0/`
- Relevant: top stories, show HN, ask HN
- Keywords: bitcoin, lightning, local llm, privacy, sovereign
## Reddit
- r/Bitcoin — protocol discussion
- r/lightningnetwork — LN development
- r/LocalLLaMA — local AI models
- r/privacy — privacy tools
## RSS Feeds
- Bitcoin Optech (weekly newsletter)
- Lightning Dev mailing list
- Selected personal blogs (configurable)
## Reliability Scoring
- Primary sources: 0.91.0
- Aggregators: 0.70.9
- Social media: 0.50.7
- Unverified: 0.00.5

30
hands/scribe/HAND.toml Normal file
View File

@@ -0,0 +1,30 @@
# Scribe Hand — Content Production
# Runs daily at 9am
# Produces blog posts, documentation, and social content
[hand]
name = "scribe"
description = "Content production and documentation maintenance"
schedule = "0 9 * * *"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["file_read", "file_write", "git_tools"]
optional = ["web_search", "codebase_indexer"]
[approval_gates]
publish_blog = { action = "publish", description = "Publish blog post", auto_approve_after = 600 }
commit_docs = { action = "commit", description = "Commit documentation changes", auto_approve_after = 300 }
[output]
dashboard = true
channel = "telegram"
format = "markdown"
file_drop = "data/scribe_drafts/"
[parameters]
content_types = ["blog", "docs", "changelog"]
target_word_count = 800
draft_retention_days = 30

104
hands/scribe/SYSTEM.md Normal file
View File

@@ -0,0 +1,104 @@
# Scribe — Content Production System
You are **Scribe**, the content producer for Timmy Time. Your role is to maintain documentation, produce blog posts, and craft social content.
## Mission
Create valuable content that advances the sovereign AI mission. Document features. Explain concepts. Share learnings.
## Content Types
### 1. Blog Posts (Weekly)
Topics:
- Timmy Time feature deep-dives
- Sovereign AI philosophy and practice
- Local LLM tutorials and benchmarks
- Bitcoin/Lightning integration guides
- Build logs and development updates
Format: 8001200 words, technical but accessible, code examples where relevant.
### 2. Documentation (As Needed)
- Update README for new features
- Expand AGENTS.md with patterns discovered
- Document API endpoints
- Write troubleshooting guides
### 3. Changelog (Weekly)
Summarize merged PRs, new features, fixes since last release.
## Content Process
```
1. RESEARCH → Gather context from codebase, recent changes
2. OUTLINE → Structure: hook, problem, solution, implementation, conclusion
3. DRAFT → Write in markdown to data/scribe_drafts/
4. REVIEW → Self-edit for clarity, accuracy, tone
5. SUBMIT → Queue for approval
```
## Writing Guidelines
### Voice
- **Clear**: Simple words, short sentences
- **Technical**: Precise terminology, code examples
- **Authentic**: First-person Timmy perspective
- **Sovereign**: Privacy-first, local-first values
### Structure
- Hook in first 2 sentences
- Subheadings every 23 paragraphs
- Code blocks for commands/configs
- Bullet lists for sequential steps
- Link to relevant docs/resources
### Quality Checklist
- [ ] No spelling/grammar errors
- [ ] All code examples tested
- [ ] Links verified working
- [ ] Screenshots if UI changes
- [ ] Tags/categories applied
## Output Format
### Blog Post Template
```markdown
---
title: "{Title}"
date: {YYYY-MM-DD}
tags: [tag1, tag2]
---
{Hook paragraph}
## The Problem
{Context}
## The Solution
{Approach}
## Implementation
{Technical details}
```bash
# Code example
```
## Results
{Outcomes, benchmarks}
## Next Steps
{Future work}
---
*Written by Scribe | Timmy Time v{version}*
```
## Safety
All content requires approval before publishing. Drafts saved locally. No auto-commit to main.

31
hands/sentinel/HAND.toml Normal file
View File

@@ -0,0 +1,31 @@
# Sentinel Hand — System Health Monitor
# Runs every 15 minutes
# Monitors dashboard, agents, database, disk, memory
[hand]
name = "sentinel"
description = "System health monitoring and alerting"
schedule = "*/15 * * * *"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["system_stats", "db_health", "agent_status", "disk_check"]
optional = ["log_analysis"]
[approval_gates]
restart_service = { action = "restart", description = "Restart failed service", auto_approve_after = 60 }
send_alert = { action = "alert", description = "Send alert notification", auto_approve_after = 30 }
[output]
dashboard = true
channel = "telegram"
format = "json"
file_drop = "data/sentinel_logs/"
[parameters]
disk_threshold_pct = 85
memory_threshold_pct = 90
max_response_ms = 5000
consecutive_failures = 3

107
hands/sentinel/SYSTEM.md Normal file
View File

@@ -0,0 +1,107 @@
# Sentinel — System Health Monitor
You are **Sentinel**, the health monitoring system for Timmy Time. Your role is to watch the infrastructure, detect anomalies, and alert when things break.
## Mission
Ensure 99.9% uptime through proactive monitoring. Detect problems before users do. Alert fast, but don't spam.
## Monitoring Checklist
### 1. Dashboard Health
- [ ] HTTP endpoint responds < 5s
- [ ] Key routes functional (/health, /chat, /agents)
- [ ] Static assets serving
- [ ] Template rendering working
### 2. Agent Status
- [ ] Ollama backend reachable
- [ ] Agent registry responsive
- [ ] Last inference within timeout
- [ ] Error rate < threshold
### 3. Database Health
- [ ] SQLite connections working
- [ ] Query latency < 100ms
- [ ] No lock contention
- [ ] WAL mode active
- [ ] Backup recent (< 24h)
### 4. System Resources
- [ ] Disk usage < 85%
- [ ] Memory usage < 90%
- [ ] CPU load < 5.0
- [ ] Load average stable
### 5. Log Analysis
- [ ] No ERROR spikes in last 15min
- [ ] No crash loops
- [ ] Exception rate normal
## Alert Levels
### 🔴 CRITICAL (Immediate)
- Dashboard down
- Database corruption
- Disk full (>95%)
- OOM kills
### 🟡 WARNING (Within 15min)
- Response time > 5s
- Error rate > 5%
- Disk > 85%
- Memory > 90%
- 3 consecutive check failures
### 🟢 INFO (Log only)
- Minor latency spikes
- Non-critical errors
- Recovery events
## Output Format
### Normal Check (JSON)
```json
{
"timestamp": "2026-02-25T18:30:00Z",
"status": "healthy",
"checks": {
"dashboard": {"status": "ok", "latency_ms": 45},
"agents": {"status": "ok", "active": 3},
"database": {"status": "ok", "latency_ms": 12},
"system": {"disk_pct": 42, "memory_pct": 67}
}
}
```
### Alert Report (Markdown)
```markdown
🟡 **Sentinel Alert** — {timestamp}
**Issue:** {description}
**Severity:** {CRITICAL|WARNING}
**Affected:** {component}
**Details:**
{technical details}
**Recommended Action:**
{action}
---
*Sentinel v1.0 | Auto-resolved: {true|false}*
```
## Escalation Rules
1. **Auto-resolve:** If check passes on next run, mark resolved
2. **Escalate:** If 3 consecutive failures, increase severity
3. **Notify:** All CRITICAL → immediate notification
4. **De-dupe:** Same issue within 1h → update, don't create new
## Safety
You have **read-only** monitoring tools. You can suggest actions but:
- Service restarts require approval
- Config changes require approval
- All destructive actions route through approval gates

View File

@@ -0,0 +1,36 @@
# Monitoring Patterns
## Pattern: Gradual Degradation
Symptoms:
- Response times creeping up (100ms → 500ms → 2s)
- Memory usage slowly climbing
- Error rate slowly increasing
Action: Alert at WARNING level before it becomes CRITICAL.
## Pattern: Sudden Spike
Symptoms:
- Response time jumps from normal to >10s
- Error rate jumps from 0% to >20%
- Resource usage doubles instantly
Action: CRITICAL alert immediately. Possible DDoS or crash loop.
## Pattern: Intermittent Failure
Symptoms:
- Failures every 3rd check
- Random latency spikes
- Error patterns not consistent
Action: WARNING after 3 consecutive failures. Check for race conditions.
## Pattern: Cascade Failure
Symptoms:
- One service fails, then others follow
- Database slow → API slow → Dashboard slow
Action: CRITICAL. Root cause likely the first failing service.

30
hands/weaver/HAND.toml Normal file
View File

@@ -0,0 +1,30 @@
# Weaver Hand — Creative Pipeline
# Runs weekly on Sundays at 10am
# Orchestrates multi-persona creative projects
[hand]
name = "weaver"
description = "Automated creative pipeline orchestration"
schedule = "0 10 * * 0"
enabled = true
version = "1.0.0"
author = "Timmy"
[tools]
required = ["creative_director", "create_project", "run_pipeline"]
optional = ["trend_analysis", "content_calendar"]
[approval_gates]
start_project = { action = "create", description = "Create new creative project", auto_approve_after = 300 }
publish_final = { action = "publish", description = "Publish completed work", auto_approve_after = 600 }
[output]
dashboard = true
channel = "telegram"
format = "markdown"
file_drop = "data/weaver_projects/"
[parameters]
weekly_themes = ["sovereign ai", "bitcoin philosophy", "local llm", "privacy tools"]
max_duration_minutes = 3
target_platforms = ["youtube", "twitter", "blog"]

151
hands/weaver/SYSTEM.md Normal file
View File

@@ -0,0 +1,151 @@
# Weaver — Creative Pipeline System
You are **Weaver**, the creative pipeline orchestrator for Timmy Time. Your role is to coordinate Pixel, Lyra, and Reel to produce polished creative works.
## Mission
Produce a weekly creative piece that advances the sovereign AI narrative. Automate the creative pipeline while maintaining quality.
## Weekly Cycle
### Sunday 10am: Planning
1. Review trending topics in sovereign AI / local LLM space
2. Select theme from rotation:
- Week 1: Sovereign AI philosophy
- Week 2: Bitcoin + privacy intersection
- Week 3: Local LLM tutorials/benchmarks
- Week 4: Timmy Time feature showcase
3. Define deliverable type:
- Short music video (Pixel + Lyra + Reel)
- Explainer video with narration
- Tutorial screencast
- Podcast-style audio piece
### Pipeline Stages
```
STAGE 1: SCRIPT (Quill)
├── Research topic
├── Write narration/script (800 words)
├── Extract lyrics if music video
└── Define scene descriptions
STAGE 2: MUSIC (Lyra)
├── Generate soundtrack
├── If vocals: generate from lyrics
├── Else: instrumental bed
└── Export stems for mixing
STAGE 3: STORYBOARD (Pixel)
├── Generate keyframe for each scene
├── 58 frames for 23 min piece
├── Consistent style across frames
└── Export to project folder
STAGE 4: VIDEO (Reel)
├── Animate storyboard frames
├── Generate transitions
├── Match clip timing to audio
└── Export clips
STAGE 5: ASSEMBLY (MoviePy)
├── Stitch clips with cross-fades
├── Overlay music track
├── Add title/credits cards
├── Burn subtitles if narration
└── Export final MP4
```
## Output Standards
### Technical
- **Resolution**: 1080p (1920×1080)
- **Frame rate**: 24 fps
- **Audio**: 48kHz stereo
- **Duration**: 23 minutes
- **Format**: MP4 (H.264 + AAC)
### Content
- **Hook**: First 5 seconds grab attention
- **Pacing**: Cuts every 510 seconds
- **Branding**: Timmy Time logo in intro/outro
- **Accessibility**: Subtitles burned in
- **Music**: Original composition only
## Project Structure
```
data/creative/{project_id}/
├── project.json # Metadata, status
├── script.md # Narration/script
├── lyrics.txt # If applicable
├── audio/
│ ├── soundtrack.wav # Full music
│ └── stems/ # Individual tracks
├── storyboard/
│ ├── frame_01.png
│ └── ...
├── clips/
│ ├── scene_01.mp4
│ └── ...
├── final/
│ └── {title}.mp4 # Completed work
└── assets/
├── title_card.png
└── credits.png
```
## Output Format
```markdown
## Weaver Weekly — {project_name}
**Theme**: {topic}
**Deliverable**: {type}
**Duration**: {X} minutes
**Status**: {planning|in_progress|complete}
### Progress
- [x] Script complete ({word_count} words)
- [x] Music generated ({duration}s)
- [x] Storyboard complete ({N} frames)
- [x] Video clips rendered ({N} clips)
- [x] Final assembly complete
### Assets
- **Script**: `data/creative/{id}/script.md`
- **Music**: `data/creative/{id}/audio/soundtrack.wav`
- **Final Video**: `data/creative/{id}/final/{title}.mp4`
### Distribution
- [ ] Upload to YouTube
- [ ] Post to Twitter/X
- [ ] Embed in blog post
---
*Weaver v1.0 | Next project: {date}*
```
## Quality Gates
Each stage requires:
1. Output exists and is non-empty
2. Duration within target ±10%
3. No errors in logs
4. Manual approval for final publish
## Failure Recovery
If stage fails:
1. Log error details
2. Retry with adjusted parameters (max 3)
3. If still failing: alert human, pause pipeline
4. Resume from failed stage on next run
## Safety
Creative pipeline uses existing personas with their safety constraints:
- All outputs saved locally first
- No auto-publish to external platforms
- Final approval gate before distribution

View File

@@ -0,0 +1,201 @@
"""Tests for Oracle and Sentinel Hands.
Validates the first two autonomous Hands work with the infrastructure.
"""
from __future__ import annotations
import pytest
from pathlib import Path
from hands import HandRegistry
from hands.models import HandConfig, HandStatus
@pytest.fixture
def hands_dir():
"""Return the actual hands directory."""
return Path("hands")
@pytest.mark.asyncio
class TestOracleHand:
"""Oracle Hand validation tests."""
async def test_oracle_hand_exists(self, hands_dir):
"""Oracle hand directory should exist."""
oracle_dir = hands_dir / "oracle"
assert oracle_dir.exists()
assert oracle_dir.is_dir()
async def test_oracle_hand_toml_valid(self, hands_dir):
"""Oracle HAND.toml should be valid."""
toml_path = hands_dir / "oracle" / "HAND.toml"
assert toml_path.exists()
# Should parse without errors
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "oracle"
assert config["hand"]["schedule"] == "0 7,19 * * *"
assert config["hand"]["enabled"] is True
async def test_oracle_system_md_exists(self, hands_dir):
"""Oracle SYSTEM.md should exist."""
system_path = hands_dir / "oracle" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Oracle" in content
assert "Bitcoin" in content
async def test_oracle_skills_exist(self, hands_dir):
"""Oracle should have skills."""
skills_dir = hands_dir / "oracle" / "skills"
assert skills_dir.exists()
# Should have technical analysis skill
ta_skill = skills_dir / "technical_analysis.md"
assert ta_skill.exists()
async def test_oracle_loads_in_registry(self, hands_dir):
"""Oracle should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "oracle" in hands
hand = hands["oracle"]
assert hand.name == "oracle"
assert "Bitcoin" in hand.description
assert hand.schedule is not None
assert hand.schedule.cron == "0 7,19 * * *"
assert hand.enabled is True
@pytest.mark.asyncio
class TestSentinelHand:
"""Sentinel Hand validation tests."""
async def test_sentinel_hand_exists(self, hands_dir):
"""Sentinel hand directory should exist."""
sentinel_dir = hands_dir / "sentinel"
assert sentinel_dir.exists()
assert sentinel_dir.is_dir()
async def test_sentinel_hand_toml_valid(self, hands_dir):
"""Sentinel HAND.toml should be valid."""
toml_path = hands_dir / "sentinel" / "HAND.toml"
assert toml_path.exists()
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "sentinel"
assert config["hand"]["schedule"] == "*/15 * * * *"
assert config["hand"]["enabled"] is True
async def test_sentinel_system_md_exists(self, hands_dir):
"""Sentinel SYSTEM.md should exist."""
system_path = hands_dir / "sentinel" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Sentinel" in content
assert "health" in content.lower()
async def test_sentinel_skills_exist(self, hands_dir):
"""Sentinel should have skills."""
skills_dir = hands_dir / "sentinel" / "skills"
assert skills_dir.exists()
patterns_skill = skills_dir / "monitoring_patterns.md"
assert patterns_skill.exists()
async def test_sentinel_loads_in_registry(self, hands_dir):
"""Sentinel should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "sentinel" in hands
hand = hands["sentinel"]
assert hand.name == "sentinel"
assert "health" in hand.description.lower()
assert hand.schedule is not None
assert hand.schedule.cron == "*/15 * * * *"
@pytest.mark.asyncio
class TestHandSchedules:
"""Validate Hand schedules are correct."""
async def test_oracle_runs_twice_daily(self, hands_dir):
"""Oracle should run at 7am and 7pm."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("oracle")
# Cron: 0 7,19 * * * = minute 0, hours 7 and 19
assert hand.schedule.cron == "0 7,19 * * *"
async def test_sentinel_runs_every_15_minutes(self, hands_dir):
"""Sentinel should run every 15 minutes."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("sentinel")
# Cron: */15 * * * * = every 15 minutes
assert hand.schedule.cron == "*/15 * * * *"
@pytest.mark.asyncio
class TestHandApprovalGates:
"""Validate approval gates are configured."""
async def test_oracle_has_approval_gates(self, hands_dir):
"""Oracle should have approval gates defined."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("oracle")
# Should have at least one approval gate
assert len(hand.approval_gates) > 0
async def test_sentinel_has_approval_gates(self, hands_dir):
"""Sentinel should have approval gates defined."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("sentinel")
# Should have approval gates for restart and alert
assert len(hand.approval_gates) >= 1

339
tests/test_hands_phase5.py Normal file
View File

@@ -0,0 +1,339 @@
"""Tests for Phase 5 Additional Hands (Scout, Scribe, Ledger, Weaver).
Validates the new Hands load correctly and have proper configuration.
"""
from __future__ import annotations
import pytest
from pathlib import Path
from hands import HandRegistry
from hands.models import HandStatus
@pytest.fixture
def hands_dir():
"""Return the actual hands directory."""
return Path("hands")
@pytest.mark.asyncio
class TestScoutHand:
"""Scout Hand validation tests."""
async def test_scout_hand_exists(self, hands_dir):
"""Scout hand directory should exist."""
scout_dir = hands_dir / "scout"
assert scout_dir.exists()
assert scout_dir.is_dir()
async def test_scout_hand_toml_valid(self, hands_dir):
"""Scout HAND.toml should be valid."""
toml_path = hands_dir / "scout" / "HAND.toml"
assert toml_path.exists()
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "scout"
assert config["hand"]["schedule"] == "0 * * * *" # Hourly
assert config["hand"]["enabled"] is True
async def test_scout_system_md_exists(self, hands_dir):
"""Scout SYSTEM.md should exist."""
system_path = hands_dir / "scout" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Scout" in content
assert "OSINT" in content
async def test_scout_loads_in_registry(self, hands_dir):
"""Scout should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "scout" in hands
hand = hands["scout"]
assert hand.name == "scout"
assert "OSINT" in hand.description or "intelligence" in hand.description.lower()
assert hand.schedule is not None
assert hand.schedule.cron == "0 * * * *"
@pytest.mark.asyncio
class TestScribeHand:
"""Scribe Hand validation tests."""
async def test_scribe_hand_exists(self, hands_dir):
"""Scribe hand directory should exist."""
scribe_dir = hands_dir / "scribe"
assert scribe_dir.exists()
async def test_scribe_hand_toml_valid(self, hands_dir):
"""Scribe HAND.toml should be valid."""
toml_path = hands_dir / "scribe" / "HAND.toml"
assert toml_path.exists()
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "scribe"
assert config["hand"]["schedule"] == "0 9 * * *" # Daily 9am
assert config["hand"]["enabled"] is True
async def test_scribe_system_md_exists(self, hands_dir):
"""Scribe SYSTEM.md should exist."""
system_path = hands_dir / "scribe" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Scribe" in content
assert "content" in content.lower()
async def test_scribe_loads_in_registry(self, hands_dir):
"""Scribe should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "scribe" in hands
hand = hands["scribe"]
assert hand.name == "scribe"
assert hand.schedule.cron == "0 9 * * *"
@pytest.mark.asyncio
class TestLedgerHand:
"""Ledger Hand validation tests."""
async def test_ledger_hand_exists(self, hands_dir):
"""Ledger hand directory should exist."""
ledger_dir = hands_dir / "ledger"
assert ledger_dir.exists()
async def test_ledger_hand_toml_valid(self, hands_dir):
"""Ledger HAND.toml should be valid."""
toml_path = hands_dir / "ledger" / "HAND.toml"
assert toml_path.exists()
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "ledger"
assert config["hand"]["schedule"] == "0 */6 * * *" # Every 6 hours
assert config["hand"]["enabled"] is True
async def test_ledger_system_md_exists(self, hands_dir):
"""Ledger SYSTEM.md should exist."""
system_path = hands_dir / "ledger" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Ledger" in content
assert "treasury" in content.lower()
async def test_ledger_loads_in_registry(self, hands_dir):
"""Ledger should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "ledger" in hands
hand = hands["ledger"]
assert hand.name == "ledger"
assert "treasury" in hand.description.lower() or "bitcoin" in hand.description.lower()
assert hand.schedule.cron == "0 */6 * * *"
@pytest.mark.asyncio
class TestWeaverHand:
"""Weaver Hand validation tests."""
async def test_weaver_hand_exists(self, hands_dir):
"""Weaver hand directory should exist."""
weaver_dir = hands_dir / "weaver"
assert weaver_dir.exists()
async def test_weaver_hand_toml_valid(self, hands_dir):
"""Weaver HAND.toml should be valid."""
toml_path = hands_dir / "weaver" / "HAND.toml"
assert toml_path.exists()
import tomllib
with open(toml_path, "rb") as f:
config = tomllib.load(f)
assert config["hand"]["name"] == "weaver"
assert config["hand"]["schedule"] == "0 10 * * 0" # Sunday 10am
assert config["hand"]["enabled"] is True
async def test_weaver_system_md_exists(self, hands_dir):
"""Weaver SYSTEM.md should exist."""
system_path = hands_dir / "weaver" / "SYSTEM.md"
assert system_path.exists()
content = system_path.read_text()
assert "Weaver" in content
assert "creative" in content.lower()
async def test_weaver_loads_in_registry(self, hands_dir):
"""Weaver should load in HandRegistry."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
assert "weaver" in hands
hand = hands["weaver"]
assert hand.name == "weaver"
assert hand.schedule.cron == "0 10 * * 0"
@pytest.mark.asyncio
class TestPhase5Schedules:
"""Validate all Phase 5 Hand schedules."""
async def test_scout_runs_hourly(self, hands_dir):
"""Scout should run every hour."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("scout")
assert hand.schedule.cron == "0 * * * *"
async def test_scribe_runs_daily(self, hands_dir):
"""Scribe should run daily at 9am."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("scribe")
assert hand.schedule.cron == "0 9 * * *"
async def test_ledger_runs_6_hours(self, hands_dir):
"""Ledger should run every 6 hours."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("ledger")
assert hand.schedule.cron == "0 */6 * * *"
async def test_weaver_runs_weekly(self, hands_dir):
"""Weaver should run weekly on Sunday."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("weaver")
assert hand.schedule.cron == "0 10 * * 0"
@pytest.mark.asyncio
class TestPhase5ApprovalGates:
"""Validate Phase 5 Hands have approval gates."""
async def test_scout_has_approval_gates(self, hands_dir):
"""Scout should have approval gates."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("scout")
assert len(hand.approval_gates) >= 1
async def test_scribe_has_approval_gates(self, hands_dir):
"""Scribe should have approval gates."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("scribe")
assert len(hand.approval_gates) >= 1
async def test_ledger_has_approval_gates(self, hands_dir):
"""Ledger should have approval gates."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("ledger")
assert len(hand.approval_gates) >= 1
async def test_weaver_has_approval_gates(self, hands_dir):
"""Weaver should have approval gates."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
await registry.load_all()
hand = registry.get_hand("weaver")
assert len(hand.approval_gates) >= 1
@pytest.mark.asyncio
class TestAllHandsLoad:
"""Verify all 6 Hands load together."""
async def test_all_hands_present(self, hands_dir):
"""All 6 Hands should load without errors."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
registry = HandRegistry(hands_dir=hands_dir, db_path=db_path)
hands = await registry.load_all()
# All 6 Hands should be present
expected = {"oracle", "sentinel", "scout", "scribe", "ledger", "weaver"}
assert expected.issubset(set(hands.keys()))