polish: make repo presentable for employer review (#162)

This commit is contained in:
Alexander Whitestone
2026-03-11 08:11:26 -04:00
committed by GitHub
parent 1de97619e8
commit a927241dbe
13 changed files with 300 additions and 3506 deletions

View File

@@ -14,8 +14,8 @@
# In production (docker-compose.prod.yml), this is set to http://ollama:11434 automatically.
# OLLAMA_URL=http://localhost:11434
# LLM model to use via Ollama (default: llama3.2)
# OLLAMA_MODEL=llama3.2
# LLM model to use via Ollama (default: qwen2.5:14b)
# OLLAMA_MODEL=qwen2.5:14b
# Enable FastAPI interactive docs at /docs and /redoc (default: false)
# DEBUG=true

View File

@@ -1,6 +1,9 @@
# Timmy Time — Mission Control
[![Tests](https://github.com/AlexanderWhitestone/Timmy-time-dashboard/actions/workflows/tests.yml/badge.svg)](https://github.com/AlexanderWhitestone/Timmy-time-dashboard/actions/workflows/tests.yml)
![Python](https://img.shields.io/badge/python-3.11+-blue)
![Coverage](https://img.shields.io/badge/coverage-73%25-brightgreen)
![License](https://img.shields.io/badge/license-MIT-green)
A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
API access with Bitcoin Lightning — all from a browser, no cloud AI required.
@@ -66,7 +69,7 @@ make help # see all commands
| [CLAUDE.md](CLAUDE.md) | AI assistant development guide |
| [AGENTS.md](AGENTS.md) | Multi-agent development standards |
| [.env.example](.env.example) | Configuration reference |
| [docs/](docs/) | Architecture docs, ADRs, audits |
| [docs/](docs/) | Architecture, ADRs, security audit, roadmap |
---
@@ -79,7 +82,7 @@ cp .env.example .env
| Variable | Default | Purpose |
|----------|---------|---------|
| `OLLAMA_URL` | `http://localhost:11434` | Ollama host |
| `OLLAMA_MODEL` | `llama3.1:8b-instruct` | Model for tool calling. Use llama3.1:8b-instruct for reliable tool use; fallback to qwen2.5:14b |
| `OLLAMA_MODEL` | `qwen2.5:14b` | Primary model for reasoning and tool calling. Fallback: `llama3.1:8b-instruct` |
| `DEBUG` | `false` | Enable `/docs` and `/redoc` |
| `TIMMY_MODEL_BACKEND` | `ollama` | `ollama` \| `airllm` \| `auto` |
| `AIRLLM_MODEL_SIZE` | `70b` | `8b` \| `70b` \| `405b` |

View File

@@ -1,57 +0,0 @@
# Changelog — 2025-02-27
## Model Upgrade & Hallucination Fix
### Change 1: Model Upgrade (Primary Fix)
**Problem:** llama3.2 (3B parameters) consistently hallucinated tool output instead of waiting for real results.
**Solution:** Upgraded default model to `llama3.1:8b-instruct` which is specifically fine-tuned for reliable tool/function calling.
**Changes:**
- `src/config.py`: Changed `ollama_model` default from `llama3.2` to `llama3.1:8b-instruct`
- Added fallback logic: if primary model unavailable, auto-fallback to `qwen2.5:14b`
- `README.md`: Updated setup instructions with new model requirement
**User Action Required:**
```bash
ollama pull llama3.1:8b-instruct
```
### Change 2: Structured Output Enforcement (Foundation)
**Preparation:** Added infrastructure for two-phase tool calling with JSON schema enforcement.
**Implementation:**
- Session context tracking in `TimmyOrchestrator`
- `_session_init()` runs on first message to load real data
### Change 3: Git Tool Working Directory Fix
**Problem:** Git tools failed with "fatal: Not a git repository" due to wrong working directory.
**Solution:**
- Rewrote `src/tools/git_tools.py` to use subprocess with explicit `cwd=REPO_ROOT`
- Added `REPO_ROOT` module-level constant auto-detected at import time
- All git commands now run from the correct directory
### Change 4: Session Init with Git Log
**Problem:** Timmy couldn't answer "what's new?" from real data.
**Solution:**
- `_session_init()` now reads `git log --oneline -15` from repo root on first message
- Recent commits prepended to system prompt
- Timmy now grounds self-description in actual commit history
### Change 5: Documentation Updates
- `README.md`: Updated Quickstart with new model requirement
- `README.md`: Configuration table reflects new default model
- Added notes explaining why llama3.1:8b-instruct is required
### Files Modified
- `src/config.py` — Model configuration with fallback
- `src/tools/git_tools.py` — Complete rewrite with subprocess + cwd
- `src/agents/timmy.py` — Session init with git log reading
- `README.md` — Updated setup and configuration docs
### Testing
- All git tool tests pass with new subprocess implementation
- Git log correctly returns commits from repo root
- Session init loads context on first message

View File

@@ -1,326 +0,0 @@
# Timmy Time — Implementation Summary
**Date:** 2026-02-25
**Phase:** 1, 2 Complete (MCP, Event Bus, Agents)
**Status:** ✅ Ready for Phase 3 (Cascade Router)
---
## What Was Built
### 1. MCP (Model Context Protocol) ✅
**Location:** `src/mcp/`
| Component | Purpose | Status |
|-----------|---------|--------|
| Registry | Tool catalog with health tracking | ✅ Complete |
| Server | MCP protocol implementation | ✅ Complete |
| Schemas | JSON schema utilities | ✅ Complete |
| Bootstrap | Auto-load all tools | ✅ Complete |
**Features:**
- 6 tools registered with full schemas
- Health tracking (healthy/degraded/unhealthy)
- Metrics collection (latency, error rates)
- Pattern-based discovery
- `@register_tool` decorator
**Tools Implemented:**
```python
web_search # DuckDuckGo search
read_file # File reading
write_file # File writing (with confirmation)
list_directory # Directory listing
python # Python execution
memory_search # Vector memory search
```
### 2. Event Bus ✅
**Location:** `src/events/bus.py`
**Features:**
- Async publish/subscribe
- Wildcard pattern matching (`agent.task.*`)
- Event history (last 1000 events)
- Concurrent handler execution
- System-wide singleton
**Usage:**
```python
from events.bus import event_bus, Event
@event_bus.subscribe("agent.task.*")
async def handle_task(event):
print(f"Task: {event.data}")
await event_bus.publish(Event(
type="agent.task.assigned",
source="timmy",
data={"task_id": "123"}
))
```
### 3. Sub-Agents ✅
**Location:** `src/agents/`
| Agent | ID | Role | Key Tools |
|-------|-----|------|-----------|
| Seer | seer | Research | web_search, read_file, memory_search |
| Forge | forge | Code | python, write_file, read_file |
| Quill | quill | Writing | write_file, read_file, memory_search |
| Echo | echo | Memory | memory_search, read_file, write_file |
| Helm | helm | Routing | memory_search |
| Timmy | timmy | Orchestrator | All tools |
**BaseAgent Features:**
- Agno Agent integration
- MCP tool registry access
- Event bus connectivity
- Structured logging
- Task execution framework
**Orchestrator Logic:**
```python
timmy = create_timmy_swarm()
# Automatic routing:
# - Simple questions → Direct response
# - "Remember..." → Echo agent
# - Complex tasks → Helm routes to specialist
```
### 4. Memory System (Previously Complete) ✅
**Three-Tier Architecture:**
```
Tier 1: Hot Memory (MEMORY.md)
↓ Always loaded
Tier 2: Vault (memory/)
├── self/identity.md
├── self/user_profile.md
├── self/methodology.md
├── notes/*.md
└── aar/*.md
Tier 3: Semantic Search
└── Vector embeddings over vault
```
**Handoff Protocol:**
- `last-session-handoff.md` written at session end
- Auto-loaded at next session start
---
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ USER INTERFACE │
│ (Dashboard/CLI) │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ TIMMY ORCHESTRATOR │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Request │ │ Router │ │ Response │ │
│ │ Analysis │→ │ (Helm) │→ │ Synthesis │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────┼──────────────────┐
│ │ │
┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐
│ Seer │ │ Forge │ │ Quill │
│ (Research) │ │ (Code) │ │ (Writing) │
└──────────────┘ └──────────────┘ └──────────────┘
┌───────▼──────┐ ┌───────▼──────┐
│ Echo │ │ Helm │
│ (Memory) │ │ (Routing) │
└──────────────┘ └──────────────┘
┌─────────────────────────────────────────────────────────────┐
│ MCP TOOL REGISTRY │
│ │
│ web_search read_file write_file list_directory │
│ python memory_search │
│ │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ EVENT BUS │
│ (Async pub/sub, wildcard patterns) │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ MEMORY SYSTEM │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Hot │ │ Vault │ │ Semantic │ │
│ │ MEMORY │ │ Files │ │ Search │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Testing Results
```
All 973 tests pass ✅
Manual verification:
- MCP Bootstrap: ✅ 6 tools loaded
- Tool Registry: ✅ web_search, file_ops, etc.
- Event Bus: ✅ Events published/subscribed
- Agent Imports: ✅ All agents loadable
```
---
## Files Created
```
src/
├── mcp/
│ ├── __init__.py
│ ├── bootstrap.py # Auto-load tools
│ ├── registry.py # Tool catalog
│ ├── server.py # MCP protocol
│ └── schemas/
│ └── base.py # Schema utilities
├── tools/
│ ├── web_search.py # DuckDuckGo search
│ ├── file_ops.py # File operations
│ ├── code_exec.py # Python execution
│ └── memory_tool.py # Memory search
├── events/
│ └── bus.py # Event bus
└── agents/
├── __init__.py
├── base.py # Base agent class
├── timmy.py # Orchestrator
├── seer.py # Research
├── forge.py # Code
├── quill.py # Writing
├── echo.py # Memory
└── helm.py # Routing
MEMORY.md # Hot memory
memory/ # Vault structure
```
---
## Usage Example
```python
from agents import create_timmy_swarm
# Create fully configured Timmy
timmy = create_timmy_swarm()
# Simple chat (handles directly)
response = await timmy.orchestrate("What is your name?")
# Research (routes to Seer)
response = await timmy.orchestrate("Search for Bitcoin news")
# Code (routes to Forge)
response = await timmy.orchestrate("Write a Python script to...")
# Memory (routes to Echo)
response = await timmy.orchestrate("What did we discuss yesterday?")
```
---
## Next: Phase 3 (Cascade Router)
To complete the brief, implement:
### 1. Cascade LLM Router
```yaml
# config/providers.yaml
providers:
- name: ollama-local
type: ollama
url: http://localhost:11434
priority: 1
models: [llama3.2, deepseek-r1]
- name: openai-backup
type: openai
api_key: ${OPENAI_API_KEY}
priority: 2
models: [gpt-4o-mini]
```
Features:
- Priority-ordered fallback
- Latency/error tracking
- Cost accounting
- Health checks
### 2. Self-Upgrade Loop
- Detect failures from logs
- Propose fixes via Forge
- Present to user for approval
- Apply changes with rollback
### 3. Dashboard Integration
- Tool registry browser
- Agent activity feed
- Memory browser
- Upgrade queue
---
## Success Criteria Status
| Criteria | Status |
|----------|--------|
| Start with `python main.py` | 🟡 Need entry point |
| Dashboard at localhost | ✅ Exists |
| Timmy responds to questions | ✅ Working |
| Routes to sub-agents | ✅ Implemented |
| MCP tool discovery | ✅ Working |
| LLM failover | 🟡 Phase 3 |
| Search memory | ✅ Working |
| Self-upgrade proposals | 🟡 Phase 3 |
| Lightning payments | ✅ Mock exists |
---
## Key Achievements
1.**MCP Protocol** — Full implementation with schemas, registry, server
2.**6 Production Tools** — All with error handling and health tracking
3.**Event Bus** — Async pub/sub for agent communication
4.**6 Agents** — Full roster with specialized roles
5.**Orchestrator** — Intelligent routing logic
6.**Memory System** — Three-tier architecture
7.**All Tests Pass** — No regressions
---
## Ready for Phase 3
The foundation is solid. Next steps:
1. Cascade Router for LLM failover
2. Self-upgrade loop
3. Enhanced dashboard views
4. Production hardening

View File

@@ -1,180 +0,0 @@
# Implementation Summary: 3 New Features
## Completed Features
### 1. Cascade Router Integration ✅
**Files Created:**
- `src/timmy/cascade_adapter.py` - Adapter between Timmy and Cascade Router
- `src/dashboard/routes/router.py` - Dashboard routes for router status
- `src/dashboard/templates/router_status.html` - Router status UI
**Files Modified:**
- `src/dashboard/app.py` - Registered router routes
- `src/dashboard/templates/base.html` - Added ROUTER nav link
**Usage:**
```python
from timmy.cascade_adapter import get_cascade_adapter
adapter = get_cascade_adapter()
response = await adapter.chat("Hello")
print(f"Response: {response.content}")
print(f"Provider: {response.provider_used}")
```
**Dashboard:** `/router/status`
---
### 2. Self-Upgrade Approval Queue *(originally implemented, since refactored)*
> **Note:** The original `src/upgrades/` module was planned for consolidation into
> `src/self_coding/` but that consolidation was never completed. The module paths
> below reflect the original implementation and may no longer be accurate.
**Original files:**
- `src/upgrades/models.py` - Database models for upgrades table
- `src/upgrades/queue.py` - Queue management logic
- `src/dashboard/routes/upgrades.py` - Dashboard routes
- `src/dashboard/templates/upgrade_queue.html` - Queue UI
**Dashboard:** `/self-modify/queue`
---
### 3. Real-Time Activity Feed *(originally implemented, since refactored)*
> **Note:** The original module paths below reflect the pre-refactoring structure.
> Events are now under `src/infrastructure/events/`, WebSocket manager is now under
> `src/infrastructure/ws_manager/`.
**Original files:**
- `src/events/broadcaster.py` → now `src/infrastructure/events/broadcaster.py`
- `src/ws_manager/handler.py` → now `src/infrastructure/ws_manager/handler.py`
**Architecture:**
```
Event Occurs → log_event() → SQLite
event_broadcaster.broadcast_sync()
ws_manager.broadcast_json()
Dashboard (WebSocket)
```
**Dashboard:** `/swarm/live` (activity feed panel)
---
## Test Results
**Unit Tests:** 101 passed
```
tests/test_event_log.py 25 passed
tests/test_ledger.py 18 passed
tests/test_vector_store.py 11 passed
tests/test_swarm.py 29 passed
tests/test_dashboard.py 18 passed
```
**E2E Tests:** Created (3 new test files)
- `tests/functional/test_cascade_router_e2e.py`
- `tests/functional/test_upgrade_queue_e2e.py`
- `tests/functional/test_activity_feed_e2e.py`
---
## Running E2E Tests (Non-Headless)
Watch the browser execute tests in real-time:
```bash
# 1. Start the server
cd /Users/apayne/Timmy-time-dashboard
source .venv/bin/activate
make dev
# 2. In another terminal, run E2E tests
source .venv/bin/activate
SELENIUM_UI=1 pytest tests/functional/test_cascade_router_e2e.py -v --headed
# Or run all E2E tests
SELENIUM_UI=1 pytest tests/functional/ -v --headed
```
The `--headed` flag runs Chrome in visible mode so you can watch.
---
## Database Schema Updates
Three new tables created automatically:
```sql
-- Event Log (existing, now with broadcast)
CREATE TABLE event_log (...);
-- Lightning Ledger (existing)
CREATE TABLE ledger (...);
-- Vector Store (existing)
CREATE TABLE memory_entries (...);
-- NEW: Upgrade Queue
CREATE TABLE upgrades (
id TEXT PRIMARY KEY,
status TEXT NOT NULL,
proposed_at TEXT NOT NULL,
branch_name TEXT NOT NULL,
description TEXT NOT NULL,
files_changed TEXT,
diff_preview TEXT,
test_passed INTEGER DEFAULT 0,
test_output TEXT,
error_message TEXT,
approved_by TEXT
);
```
---
## Navigation Updates
New nav links in dashboard header:
- **EVENTS** → `/swarm/events`
- **LEDGER** → `/lightning/ledger`
- **MEMORY** → `/memory`
- **ROUTER** → `/router/status`
- **UPGRADES** → `/self-modify/queue`
---
## Architecture Alignment
All 3 features follow existing patterns:
- **Singleton pattern** for services (cascade_adapter, event_broadcaster)
- **SQLite persistence** through consistent DB access pattern
- **Dashboard routes** following existing route structure
- **Jinja2 templates** extending base.html
- **Event-driven** using existing event log infrastructure
- **WebSocket** using existing ws_manager
---
## Security Considerations
| Feature | Risk | Mitigation |
|---------|------|------------|
| Cascade Router | API key exposure | Uses existing config system |
| Upgrade Queue | Unauthorized changes | Human approval required |
| Activity Feed | Data leak | Events sanitized before broadcast |
---
## Next Steps
1. Run E2E tests with `SELENIUM_UI=1 pytest tests/functional/ -v --headed`
2. Manually test each dashboard page
3. Verify WebSocket real-time updates in `/swarm/live`
4. Test upgrade queue workflow end-to-end

File diff suppressed because it is too large Load Diff

View File

@@ -1,478 +0,0 @@
# Plan: Full Creative & DevOps Capabilities for Timmy
## Overview
Add five major capability domains to Timmy's agent system, turning it into a
sovereign creative studio and full-stack DevOps operator. All tools are
open-source, self-hosted, and GPU-accelerated where needed.
---
## Phase 1: Git & DevOps Tools (Forge + Helm personas)
**Goal:** Timmy can observe local/remote repos, read code, create branches,
stage changes, commit, diff, log, and manage PRs — all through the swarm
task system with Spark event capture.
### New module: `src/tools/git_tools.py`
Tools to add (using **GitPython** — BSD-3, `pip install GitPython`):
| Tool | Function | Persona Access |
|---|---|---|
| `git_clone` | Clone a remote repo to local path | Forge, Helm |
| `git_status` | Show working tree status | Forge, Helm, Timmy |
| `git_diff` | Show staged/unstaged diffs | Forge, Helm, Timmy |
| `git_log` | Show recent commit history | Forge, Helm, Echo, Timmy |
| `git_branch` | List/create/switch branches | Forge, Helm |
| `git_add` | Stage files for commit | Forge, Helm |
| `git_commit` | Create a commit with message | Forge, Helm |
| `git_push` | Push to remote | Forge, Helm |
| `git_pull` | Pull from remote | Forge, Helm |
| `git_blame` | Show line-by-line authorship | Forge, Echo |
| `git_stash` | Stash/pop changes | Forge, Helm |
### Changes to existing files
- **`src/timmy/tools.py`** — Add `create_git_tools()` factory, wire into
`PERSONA_TOOLKITS` for Forge and Helm
- **`src/swarm/tool_executor.py`** — Enhance `_infer_tools_needed()` with
git keywords (commit, branch, push, pull, diff, clone, merge)
- **`src/config.py`** — Add `git_default_repo_dir: str = "~/repos"` setting
- **`src/spark/engine.py`** — Add `on_tool_executed()` method to capture
individual tool invocations (not just task-level events)
- **`src/swarm/personas.py`** — Add git-related keywords to Forge and Helm
preferred_keywords
### New dependency
```toml
# pyproject.toml
dependencies = [
...,
"GitPython>=3.1.40",
]
```
### Dashboard
- **`/tools`** page updated to show git tools in the catalog
- Git tool usage stats visible per agent
### Tests
- `tests/test_git_tools.py` — test all git tool functions against tmp repos
- Mock GitPython's `Repo` class for unit tests
---
## Phase 2: Image Generation (new "Pixel" persona)
**Goal:** Generate storyboard frames and standalone images from text prompts
using FLUX.2 Klein 4B locally.
### New persona: Pixel — Visual Architect
```python
"pixel": {
"id": "pixel",
"name": "Pixel",
"role": "Visual Architect",
"description": "Image generation, storyboard frames, and visual design.",
"capabilities": "image-generation,storyboard,design",
"rate_sats": 80,
"bid_base": 60,
"bid_jitter": 20,
"preferred_keywords": [
"image", "picture", "photo", "draw", "illustration",
"storyboard", "frame", "visual", "design", "generate",
"portrait", "landscape", "scene", "artwork",
],
}
```
### New module: `src/tools/image_tools.py`
Tools (using **diffusers** + **FLUX.2 Klein 4B** — Apache 2.0):
| Tool | Function |
|---|---|
| `generate_image` | Text-to-image generation (returns file path) |
| `generate_storyboard` | Generate N frames from scene descriptions |
| `image_variations` | Generate variations of an existing image |
### Architecture
```
generate_image(prompt, width=1024, height=1024, steps=4)
→ loads FLUX.2 Klein via diffusers FluxPipeline
→ saves to data/images/{uuid}.png
→ returns path + metadata
```
- Model loaded lazily on first use, kept in memory for subsequent calls
- Falls back to CPU generation (slower) if no GPU
- Output saved to `data/images/` with metadata JSON sidecar
### New dependency (optional extra)
```toml
[project.optional-dependencies]
creative = [
"diffusers>=0.30.0",
"transformers>=4.40.0",
"accelerate>=0.30.0",
"torch>=2.2.0",
"safetensors>=0.4.0",
]
```
### Config
```python
# config.py additions
flux_model_id: str = "black-forest-labs/FLUX.2-klein-4b"
image_output_dir: str = "data/images"
image_default_steps: int = 4
```
### Dashboard
- `/creative/ui` — new Creative Studio page (image gallery + generation form)
- HTMX-powered: submit prompt, poll for result, display inline
- Gallery view of all generated images with metadata
### Tests
- `tests/test_image_tools.py` — mock diffusers pipeline, test prompt handling,
file output, storyboard generation
---
## Phase 3: Music Generation (new "Lyra" persona)
**Goal:** Generate full songs with vocals, instrumentals, and lyrics using
ACE-Step 1.5 locally.
### New persona: Lyra — Sound Weaver
```python
"lyra": {
"id": "lyra",
"name": "Lyra",
"role": "Sound Weaver",
"description": "Music and song generation with vocals, instrumentals, and lyrics.",
"capabilities": "music-generation,vocals,composition",
"rate_sats": 90,
"bid_base": 70,
"bid_jitter": 20,
"preferred_keywords": [
"music", "song", "sing", "vocal", "instrumental",
"melody", "beat", "track", "compose", "lyrics",
"audio", "sound", "album", "remix",
],
}
```
### New module: `src/tools/music_tools.py`
Tools (using **ACE-Step 1.5** — Apache 2.0, `pip install ace-step`):
| Tool | Function |
|---|---|
| `generate_song` | Text/lyrics → full song (vocals + instrumentals) |
| `generate_instrumental` | Text prompt → instrumental track |
| `generate_vocals` | Lyrics + style → vocal track |
| `list_genres` | Return supported genre/style tags |
### Architecture
```
generate_song(lyrics, genre="pop", duration=120, language="en")
→ loads ACE-Step model (lazy, cached)
→ generates audio
→ saves to data/music/{uuid}.wav
→ returns path + metadata (duration, genre, etc.)
```
- Model loaded lazily, ~4GB VRAM minimum
- Output saved to `data/music/` with metadata sidecar
- Supports 19 languages, genre tags, tempo control
### New dependency (optional extra, extends `creative`)
```toml
[project.optional-dependencies]
creative = [
...,
"ace-step>=1.5.0",
]
```
### Config
```python
music_output_dir: str = "data/music"
ace_step_model: str = "ace-step/ACE-Step-v1.5"
```
### Dashboard
- `/creative/ui` expanded with Music tab
- Audio player widget (HTML5 `<audio>` element)
- Lyrics input form with genre/style selector
### Tests
- `tests/test_music_tools.py` — mock ACE-Step model, test generation params
---
## Phase 4: Video Generation (new "Reel" persona)
**Goal:** Generate video clips from text/image prompts using Wan 2.1 locally.
### New persona: Reel — Motion Director
```python
"reel": {
"id": "reel",
"name": "Reel",
"role": "Motion Director",
"description": "Video generation from text and image prompts.",
"capabilities": "video-generation,animation,motion",
"rate_sats": 100,
"bid_base": 80,
"bid_jitter": 20,
"preferred_keywords": [
"video", "clip", "animate", "motion", "film",
"scene", "cinematic", "footage", "render", "timelapse",
],
}
```
### New module: `src/tools/video_tools.py`
Tools (using **Wan 2.1** via diffusers — Apache 2.0):
| Tool | Function |
|---|---|
| `generate_video_clip` | Text → short video clip (36 seconds) |
| `image_to_video` | Image + prompt → animated video from still |
| `list_video_styles` | Return supported style presets |
### Architecture
```
generate_video_clip(prompt, duration=5, resolution="480p", fps=24)
→ loads Wan 2.1 via diffusers pipeline (lazy, cached)
→ generates frames
→ encodes to MP4 via FFmpeg
→ saves to data/video/{uuid}.mp4
→ returns path + metadata
```
- Wan 2.1 1.3B model: ~16GB VRAM
- Output saved to `data/video/`
- Resolution options: 480p (16GB), 720p (24GB+)
### New dependency (extends `creative` extra)
```toml
creative = [
...,
# Wan 2.1 uses diffusers (already listed) + model weights downloaded on first use
]
```
### Config
```python
video_output_dir: str = "data/video"
wan_model_id: str = "Wan-AI/Wan2.1-T2V-1.3B"
video_default_resolution: str = "480p"
```
### Tests
- `tests/test_video_tools.py` — mock diffusers pipeline, test clip generation
---
## Phase 5: Creative Director — Storyboard & Assembly Pipeline
**Goal:** Orchestrate multi-persona workflows to produce 3+ minute creative
videos with music, narration, and stitched scenes.
### New module: `src/creative/director.py`
The Creative Director is a **multi-step pipeline** that coordinates Pixel,
Lyra, and Reel to produce complete creative works:
```
User: "Create a 3-minute music video about a sunrise over mountains"
Creative Director
┌─────────┼──────────┐
│ │ │
1. STORYBOARD 2. MUSIC 3. GENERATE
(Pixel) (Lyra) (Reel)
│ │ │
N scene Full song N video clips
descriptions with from storyboard
+ keyframes vocals frames
│ │ │
└─────────┼──────────┘
4. ASSEMBLE
(MoviePy + FFmpeg)
Final video with
music, transitions,
titles
```
### Pipeline steps
1. **Script** — Timmy (or Quill) writes scene descriptions and lyrics
2. **Storyboard** — Pixel generates keyframe images for each scene
3. **Music** — Lyra generates the soundtrack (vocals + instrumentals)
4. **Video clips** — Reel generates video for each scene (image-to-video
from storyboard frames, or text-to-video from descriptions)
5. **Assembly** — MoviePy stitches clips together with cross-fades,
overlays the music track, adds title cards
### New module: `src/creative/assembler.py`
Video assembly engine (using **MoviePy** — MIT, `pip install moviepy`):
| Function | Purpose |
|---|---|
| `stitch_clips` | Concatenate video clips with transitions |
| `overlay_audio` | Mix music track onto video |
| `add_title_card` | Prepend/append title/credits |
| `add_subtitles` | Burn lyrics/captions onto video |
| `export_final` | Encode final video (H.264 + AAC) |
### New dependency
```toml
dependencies = [
...,
"moviepy>=2.0.0",
]
```
### Config
```python
creative_output_dir: str = "data/creative"
video_transition_duration: float = 1.0 # seconds
default_video_codec: str = "libx264"
```
### Dashboard
- `/creative/ui` — Full Creative Studio with tabs:
- **Images** — gallery + generation form
- **Music** — player + generation form
- **Video** — player + generation form
- **Director** — multi-step pipeline builder with storyboard view
- `/creative/projects` — saved projects with all assets
- `/creative/projects/{id}` — project detail with timeline view
### Tests
- `tests/test_assembler.py` — test stitching, audio overlay, title cards
- `tests/test_director.py` — test pipeline orchestration with mocks
---
## Phase 6: Spark Integration for All New Tools
**Goal:** Every tool invocation and creative pipeline step gets captured by
Spark Intelligence for learning and advisory.
### Changes to `src/spark/engine.py`
```python
def on_tool_executed(
self, agent_id: str, tool_name: str,
task_id: Optional[str], success: bool,
duration_ms: Optional[int] = None,
) -> Optional[str]:
"""Capture individual tool invocations."""
def on_creative_step(
self, project_id: str, step_name: str,
agent_id: str, output_path: Optional[str],
) -> Optional[str]:
"""Capture creative pipeline progress."""
```
### New advisor patterns
- "Pixel generates storyboards 40% faster than individual image calls"
- "Lyra's pop genre tracks have 85% higher completion rate than jazz"
- "Video generation on 480p uses 60% less GPU time than 720p for similar quality"
- "Git commits from Forge average 3 files per commit"
---
## Implementation Order
| Phase | What | New Files | Est. Tests |
|---|---|---|---|
| 1 | Git/DevOps tools | 2 source + 1 test | ~25 |
| 2 | Image generation | 2 source + 1 test + 1 template | ~15 |
| 3 | Music generation | 1 source + 1 test | ~12 |
| 4 | Video generation | 1 source + 1 test | ~12 |
| 5 | Creative Director pipeline | 2 source + 2 tests + 1 template | ~20 |
| 6 | Spark tool-level capture | 1 modified + 1 test update | ~8 |
**Total: ~10 new source files, ~6 new test files, ~92 new tests**
---
## New Dependencies Summary
**Required (always installed):**
```
GitPython>=3.1.40
moviepy>=2.0.0
```
**Optional `creative` extra (GPU features):**
```
diffusers>=0.30.0
transformers>=4.40.0
accelerate>=0.30.0
torch>=2.2.0
safetensors>=0.4.0
ace-step>=1.5.0
```
**Install:** `pip install ".[creative]"` for full creative stack
---
## New Persona Summary
| ID | Name | Role | Tools |
|---|---|---|---|
| pixel | Pixel | Visual Architect | generate_image, generate_storyboard, image_variations |
| lyra | Lyra | Sound Weaver | generate_song, generate_instrumental, generate_vocals |
| reel | Reel | Motion Director | generate_video_clip, image_to_video |
These join the existing 6 personas (Echo, Mace, Helm, Seer, Forge, Quill)
for a total of **9 specialized agents** in the swarm.
---
## Hardware Requirements
- **CPU only:** Git tools, MoviePy assembly, all tests (mocked)
- **8GB VRAM:** FLUX.2 Klein 4B (images)
- **4GB VRAM:** ACE-Step 1.5 (music)
- **16GB VRAM:** Wan 2.1 1.3B (video at 480p)
- **Recommended:** RTX 4090 24GB runs the entire stack comfortably

View File

@@ -1,306 +0,0 @@
# Timmy Time — Senior Architect Quality Analysis
**Date:** 2026-02-21
**Branch:** `claude/quality-analysis-mobile-testing-0zgPi`
**Test Suite:** 228/228 passing ✅
---
## Executive Summary
Timmy Time has a strong Python backend skeleton and a working HTMX UI, but the project is at a **critical architectural fork**: a second, fully-detached React frontend was introduced that uses 100% mock/static data with zero API connectivity. This split creates the illusion of a richer app than exists. Completeness against the stated vision is **~35-40%**. The mobile HITL framework is the standout quality asset.
---
## 1. Architecture Coherence — CRITICAL ⚠️
**Score: 3/10**
### Finding: Dual Frontend, Zero Integration
The project ships two separate UIs that both claim to be "Mission Control":
| UI | Tech | Backend Connected? |
|----|------|--------------------|
| `src/dashboard/` | FastAPI + Jinja2 + HTMX | ✅ Yes — real Timmy chat, health, history |
| `dashboard-web/` | React + TypeScript + Vite | ❌ No — 100% static mock data |
The React dashboard (`dashboard-web/client/src/lib/data.ts`) exports `MOCK_CHAT`, `MOCK_HEALTH`, `MOCK_NOTIFICATIONS`, `MOCK_TASKS`, `MOCK_WS_EVENTS` — every data source is hardcoded. There is **not a single `fetch()` call** to the FastAPI backend. The `ChatPanel` simulates responses with `setTimeout()`. The `StatusSidebar` shows a hardcoded Ollama status — it never calls `/health/status`.
**Impact:** The React UI is a clickable mockup, not a product. A new developer would not know which frontend is authoritative.
### Finding: React App Has No Build Config
`dashboard-web/client/` contains `src/` and `index.html` but no `package.json`, `vite.config.ts`, or `tsconfig.json` in that directory. The app imports from `@/components/ui/*` (shadcn/ui) but the `components/ui/` directory does not exist in the repo. The React app is **not buildable as committed**.
---
## 2. Completeness Against Vision — 35-40%
**Score: 4/10**
| Feature | Roadmap | Status |
|---------|---------|--------|
| Agno + Ollama + SQLite dashboard | v1.0.0 | ✅ Complete |
| HTMX chat with history | v1.0.0 | ✅ Complete |
| AirLLM big-brain backend | v1.0.0 | ✅ Complete |
| CLI (chat/think/status) | v1.0.0 | ✅ Complete |
| Swarm registry + coordinator | v2.0.0 | ⚠️ Skeleton only — no real agents |
| Agent personas (Echo, Mace, Forge…) | v2.0.0 | ❌ Catalog only — never instantiated |
| MCP tools integration | v2.0.0 | ❌ Not started |
| Voice NLU | v2.0.0 | ⚠️ Backend module — no live UI |
| Push notifications | v2.0.0 | ⚠️ Backend module — never triggered |
| Siri Shortcuts | v2.0.0 | ⚠️ Endpoint stub only |
| WebSocket live swarm feed | v2.0.0 | ⚠️ Server-side ready — no UI consumer |
| L402 / Lightning payments | v3.0.0 | ⚠️ Mock implementation only |
| Real LND gRPC backend | v3.0.0 | ❌ Not started |
| Single `.app` bundle | v3.0.0 | ❌ Not started |
| React dashboard (live data) | — | ❌ All mock data |
| Mobile HITL checklist | — | ✅ Complete (27 scenarios) |
---
## 3. Mobile UX Audit
**Score: 7/10 (HTMX UI) / 2/10 (React UI)**
### HTMX Dashboard — Strong
The HTMX-served dashboard has solid mobile foundations verified by the automated test suite:
-`viewport-fit=cover` — Dynamic Island / notch support
-`apple-mobile-web-app-capable` — Home Screen PWA mode
-`safe-area-inset-top/bottom` — padding clears notch and home indicator
-`overscroll-behavior: none` — no rubber-band on main page
-`-webkit-overflow-scrolling: touch` — momentum scroll in chat
-`dvh` units — correct height on iOS with collapsing chrome
- ✅ 44px touch targets on SEND button and inputs
-`font-size: 16px` in mobile query — iOS zoom prevention
-`enterkeyhint="send"` — Send-labelled keyboard key
- ✅ HTMX `hx-sync="this:drop"` — double-tap protection
- ✅ HTMX `hx-disabled-elt` — in-flight button lockout
### Gap: Mobile Quick Actions Page (`/mobile`)
The `/mobile` route template shows a "Mobile only" page with quick action tiles and a JS-based chat — but it uses **CSS `display: none` on desktop** via `.mobile-only` with an `@media (min-width: 769px)` rule. The desktop fallback shows a placeholder. This is a valid progressive enhancement approach but the page is not linked from the main nav bar.
### React Dashboard — Mobile Not Functional
The React dashboard uses `hidden lg:flex` for the left sidebar (desktop only) and an `AnimatePresence` slide-in overlay for mobile. The mobile UX architecture is correct. However, because all data is mock, tapping "Chat" produces a simulated response from a setTimeout, not from Ollama. This is not tested and not usable.
---
## 4. Human-in-the-Loop (HITL) Mobile Testing
**Score: 8/10**
The `/mobile-test` route is the standout quality feature. It provides:
- 21 structured test scenarios across 7 categories (Layout, Touch, Chat, Health, Scroll, Notch, Live UI)
- PASS/FAIL/SKIP buttons with sessionStorage persistence across scroll
- Live pass rate counter and progress bar
- Accessible on any phone via local network URL
- ← MISSION CONTROL back-link for easy navigation
**Gaps to improve:**
- No server-side results storage — results lost when tab closes
- No shareable/exportable report (screenshot required for handoff)
- React dashboard has no equivalent HITL page
- No automated Playwright/Selenium mobile tests that could catch regressions
---
## 5. Security Assessment
**Score: 5/10**
### XSS Vulnerability — `/mobile` template
`mobile.html` line ~85 uses raw `innerHTML` string interpolation with user-supplied message content:
```javascript
// mobile.html — VULNERABLE
chat.innerHTML += `
<div class="chat-message user">
<div>${message}</div> <!-- message is user input, not escaped -->
</div>
`;
```
If a user types `<img src=x onerror=alert(1)>`, it executes. This is a stored XSS via `innerHTML`. Fix: use `document.createTextNode(message)` or escape HTML before insertion.
The `swarm_live.html` has the same pattern with WebSocket data:
```javascript
container.innerHTML = agents.map(agent => `...${agent.name}...`).join('');
```
If agent names contain `<script>` tags (or any HTML), this executes in context.
### Hardcoded Secrets
`l402_proxy.py`: `_MACAROON_SECRET = "timmy-macaroon-secret".encode()` (default)
`payment_handler.py`: `_HMAC_SECRET = "timmy-sovereign-sats".encode()` (default)
Both fall back to env var reads which is correct, but the defaults should not be production-safe strings — they should be None with a startup assertion requiring them to be set.
### No Route Authentication
All `/swarm/spawn`, `/swarm/tasks`, `/marketplace`, `/agents/timmy/chat` endpoints have no auth guard. On a `--host 0.0.0.0` server, anyone on the local network can post tasks or clear chat history. Acceptable for v1 local-only use but must be documented and gated before LAN exposure.
---
## 6. Test Coverage
**Score: 7/10**
| Suite | Tests | Quality |
|-------|-------|---------|
| Agent unit | 13 | Good |
| Backends | 14 | Good |
| Mobile scenarios | 32 | Excellent — covers M1xx-M6xx categories |
| Swarm | 29+10+16 | Good |
| L402 proxy | 13 | Good |
| Voice NLU | 15 | Good |
| Dashboard routes | 18+18 | Good |
| WebSocket | 3 | Thin — no reconnect or message-type tests |
| React components | 0 | Missing entirely |
| End-to-end (Playwright) | 0 | Missing |
**Key gaps:**
1. No tests for the XSS vulnerabilities
2. No tests for the `/mobile` quick-chat endpoint
3. WebSocket tests don't cover reconnection logic or malformed payloads
4. React app has zero test coverage
---
## 7. Code Quality
**Score: 7/10**
**Strengths:**
- Clean module separation (`timmy/`, `swarm/`, `dashboard/routes/`, `timmy_serve/`)
- Consistent use of dataclasses for domain models
- Good docstrings on all public functions
- SQLite-backed persistence for both Agno memory and swarm registry
- pydantic-settings config with `.env` override support
**Weaknesses:**
- Swarm `coordinator.py` uses a module-level singleton `coordinator = SwarmCoordinator()` — not injectable, hard to test in isolation
- `swarm/registry.py` opens a new SQLite connection on every call (no connection pool)
- `dashboard/routes/swarm.py` creates a new `Jinja2Templates` instance — it should reuse the one from `app.py`
- React components import from `@/components/ui/*` which don't exist in the committed tree
---
## 8. Developer Experience
**Score: 6/10**
**Strengths:**
- README is excellent — copy-paste friendly, covers Mac quickstart, phone access, troubleshooting
- DEVELOPMENT_REPORT.md provides full history of what was built and why
- `.env.example` covers all config variables
- Self-TDD watchdog CLI is a creative addition
**Weaknesses:**
- No `docker-compose.yml` — setup requires manual Python venv + Ollama install
- Two apps (FastAPI + React) with no single `make dev` command to start both
- `STATUS.md` says v1.0.0 but development is well past that — version drift
- React app missing from the quickstart instructions entirely
---
## 9. Backend Architecture
**Score: 7/10**
The FastAPI backend is well-structured. The swarm subsystem follows a clean coordinator pattern. The L402 mock is architecturally correct (the interface matches what real LND calls would require).
**Gaps:**
- Swarm "agents" are database records — `spawn_agent()` registers a record but no Python process is actually launched. `agent_runner.py` uses `subprocess.Popen` to run `python -m swarm.agent_runner` but no `__main__` block exists in that file.
- The bidding system (`bidder.py`) runs an asyncio auction but there are no actual bidder agents submitting bids — auctions will always time out with no winner.
- Voice TTS (`voice_tts.py`) requires `pyttsx3` (optional dep) but the voice route offers no graceful fallback message when pyttsx3 is absent.
---
## 10. Prioritized Defects
| Priority | ID | Issue | File |
|----------|----|-------|------|
| P0 | SEC-01 | XSS via innerHTML with unsanitized user input | `mobile.html:85`, `swarm_live.html:72` |
| P0 | ARCH-01 | React dashboard 100% mock — no backend calls | `dashboard-web/client/src/` |
| P0 | ARCH-02 | React app not buildable — missing package.json, shadcn/ui | `dashboard-web/client/` |
| P1 | SEC-02 | Hardcoded L402/HMAC secrets without startup assertion | `l402_proxy.py`, `payment_handler.py` |
| P1 | FUNC-01 | Swarm spawn creates DB record but no process | `swarm/agent_runner.py` |
| P1 | FUNC-02 | Auction always fails — no real bid submitters | `swarm/bidder.py` |
| P2 | UX-01 | `/mobile` route not in desktop nav | `base.html`, `index.html` |
| P2 | TEST-01 | WebSocket reconnection not tested | `tests/test_websocket.py` |
| P2 | DX-01 | No single dev startup command | `README.md` |
| P3 | PERF-01 | SQLite connection opened per-query in registry | `swarm/registry.py` |
---
## HITL Mobile Test Session Guide
To run a complete human-in-the-loop mobile test session right now:
```bash
# 1. Start the dashboard
source .venv/bin/activate
uvicorn dashboard.app:app --host 0.0.0.0 --port 8000 --reload
# 2. Find your local IP
ipconfig getifaddr en0 # macOS
hostname -I # Linux
# 3. Open on your phone (same Wi-Fi)
http://<YOUR_IP>:8000/mobile-test
# 4. Work through the 21 scenarios, marking PASS / FAIL / SKIP
# 5. Screenshot the SUMMARY section for your records
# ─── Also test the main dashboard on mobile ───────────────────────
http://<YOUR_IP>:8000 # Main Mission Control
http://<YOUR_IP>:8000/mobile # Quick Actions (mobile-optimized)
```
**Critical scenarios to test first:**
- T01 — iOS zoom prevention (tap input, watch for zoom)
- C02 — Multi-turn memory (tell Timmy your name, ask it back)
- C04 — Offline graceful error (stop Ollama, send message)
- N01/N02 — Notch / home bar clearance (notched iPhone)
---
## Recommended Next Prompt for Development
```
Connect the React dashboard (dashboard-web/) to the live FastAPI backend.
Priority order:
1. FIX BUILD FIRST: Add package.json, vite.config.ts, tailwind.config.ts, and
tsconfig.json to dashboard-web/client/. Install shadcn/ui so the existing
component imports resolve. Verify `npm run dev` starts the app.
2. CHAT (highest user value): Replace ChatPanel mock with real fetch to
POST /agents/timmy/chat. Show the actual Timmy response from Ollama.
Implement loading state (matches the existing isTyping UI).
3. HEALTH: Replace MOCK_HEALTH in StatusSidebar with a polling fetch to
GET /health/status (every 30s, matching HTMX behaviour).
4. SWARM WEBSOCKET: Open a real WebSocket to ws://localhost:8000/swarm/ws
and pipe state updates into SwarmPanel — replacing MOCK_WS_EVENTS.
5. SECURITY: Fix XSS in mobile.html and swarm_live.html — replace innerHTML
string interpolation with safe DOM methods (textContent / createTextNode).
Use React Query (TanStack) for data fetching with stale-while-revalidate.
Keep the existing HTMX dashboard running in parallel — the React app should
be the forward-looking UI.
```
---
*Analysis by Claude Code — Senior Architect Review*
*Timmy Time Dashboard | branch: claude/quality-analysis-mobile-testing-0zgPi*

View File

@@ -1,245 +0,0 @@
# Timmy Time — Quality Analysis Update v2.0
**Date:** 2026-02-23
**Branch:** `kimi/mission-control-ux`
**Test Suite:** 525/525 passing ✅
---
## Executive Summary
Significant progress since v1 analysis. The swarm system is now functional with real task execution. Lightning payments have a proper abstraction layer. MCP tools are integrated. Test coverage increased from 228 to 525 tests.
**Overall Progress: ~65-70%** (up from 35-40%)
---
## Major Improvements Since v1
### 1. Swarm System — NOW FUNCTIONAL ✅
**Previous:** Skeleton only, agents were DB records with no execution
**Current:** Full task lifecycle with tool execution
| Component | Before | After |
|-----------|--------|-------|
| Agent bidding | Random bids | Capability-aware scoring |
| Task execution | None | ToolExecutor with persona tools |
| Routing | Random assignment | Score-based with audit logging |
| Tool integration | Not started | Full MCP tools (search, shell, python, file) |
**Files Added:**
- `src/swarm/routing.py` — Capability-based routing with SQLite audit log
- `src/swarm/tool_executor.py` — MCP tool execution for personas
- `src/timmy/tools.py` — Persona-specific toolkits
### 2. Lightning Payments — ABSTRACTED ✅
**Previous:** Mock only, no path to real LND
**Current:** Pluggable backend interface
```python
from lightning import get_backend
backend = get_backend("lnd") # or "mock"
invoice = backend.create_invoice(100, "API access")
```
**Files Added:**
- `src/lightning/` — Full backend abstraction
- `src/lightning/lnd_backend.py` — LND gRPC stub (ready for protobuf)
- `src/lightning/mock_backend.py` — Development backend
### 3. Sovereignty Audit — COMPLETE ✅
**New:** `docs/SOVEREIGNTY_AUDIT.md` and live `/health/sovereignty` endpoint
| Dependency | Score | Status |
|------------|-------|--------|
| Ollama AI | 10/10 | Local inference |
| SQLite | 10/10 | File-based persistence |
| Redis | 9/10 | Optional, has fallback |
| Lightning | 8/10 | Configurable (local LND or mock) |
| **Overall** | **9.2/10** | Excellent sovereignty |
### 4. Test Coverage — MORE THAN DOUBLED ✅
**Before:** 228 tests
**After:** 525 tests (+297)
| Suite | Before | After | Notes |
|-------|--------|-------|-------|
| Lightning | 0 | 36 | Mock + LND backend tests |
| Swarm routing | 0 | 23 | Capability scoring, audit log |
| Tool executor | 0 | 19 | MCP tool integration |
| Scary paths | 0 | 23 | Production edge cases |
| Mission Control | 0 | 11 | Dashboard endpoints |
| Swarm integration | 0 | 18 | Full lifecycle tests |
| Docker agent | 0 | 9 | Containerized workers |
| **Total** | **228** | **525** | **+130% increase** |
### 5. Mission Control Dashboard — NEW ✅
**New:** `/swarm/mission-control` live system dashboard
Features:
- Sovereignty score with visual progress bar
- Real-time dependency health (5s-30s refresh)
- System metrics (uptime, agents, tasks, sats earned)
- Heartbeat monitor with tick visualization
- Health recommendations based on current state
### 6. Scary Path Tests — PRODUCTION READY ✅
**New:** `tests/test_scary_paths.py` — 23 edge case tests
- Concurrent load: 10 simultaneous tasks
- Memory persistence across restarts
- L402 macaroon expiry handling
- WebSocket reconnection resilience
- Voice NLU: empty, Unicode, XSS attempts
- Graceful degradation: Ollama down, Redis absent, no tools
---
## Architecture Updates
### New Module: `src/agent_core/` — Embodiment Foundation
Abstract base class `TimAgent` for substrate-agnostic agents:
```python
class TimAgent(ABC):
async def perceive(self, input: PerceptionInput) -> WorldState
async def decide(self, state: WorldState) -> Action
async def act(self, action: Action) -> ActionResult
async def remember(self, key: str, value: Any) -> None
async def recall(self, key: str) -> Any
```
**Purpose:** Enable future embodiments (robot, VR) without architectural changes.
---
## Security Improvements
### Issues Addressed
| Issue | Status | Fix |
|-------|--------|-----|
| L402/HMAC secrets | ✅ Fixed | Startup warning when defaults used |
| Tool execution sandbox | ✅ Implemented | Base directory restriction |
### Remaining Issues
| Priority | Issue | File |
|----------|-------|------|
| P1 | XSS via innerHTML | `mobile.html`, `swarm_live.html` |
| P2 | No auth on swarm endpoints | All `/swarm/*` routes |
---
## Updated Feature Matrix
| Feature | Roadmap | Status |
|---------|---------|--------|
| Agno + Ollama + SQLite dashboard | v1.0.0 | ✅ Complete |
| HTMX chat with history | v1.0.0 | ✅ Complete |
| AirLLM big-brain backend | v1.0.0 | ✅ Complete |
| CLI (chat/think/status) | v1.0.0 | ✅ Complete |
| **Swarm registry + coordinator** | **v2.0.0** | **✅ Complete** |
| **Agent personas with tools** | **v2.0.0** | **✅ Complete** |
| **MCP tools integration** | **v2.0.0** | **✅ Complete** |
| Voice NLU | v2.0.0 | ⚠️ Backend ready, UI pending |
| Push notifications | v2.0.0 | ⚠️ Backend ready, trigger pending |
| Siri Shortcuts | v2.0.0 | ⚠️ Endpoint ready, needs testing |
| **WebSocket live swarm feed** | **v2.0.0** | **✅ Complete** |
| **L402 / Lightning abstraction** | **v3.0.0** | **✅ Complete (mock+LND)** |
| Real LND gRPC | v3.0.0 | ⚠️ Interface ready, needs protobuf |
| **Mission Control dashboard** | **—** | **✅ NEW** |
| **Sovereignty audit** | **—** | **✅ NEW** |
| **Embodiment interface** | **—** | **✅ NEW** |
| Mobile HITL checklist | — | ✅ Complete (27 scenarios) |
---
## Test Quality: TDD Adoption
**Process Change:** Test-Driven Development now enforced
1. Write test first
2. Run test (should fail — red)
3. Implement minimal code
4. Run test (should pass — green)
5. Refactor
6. Ensure all tests pass
**Recent TDD Work:**
- Mission Control: 11 tests written before implementation
- Scary paths: 23 tests written before fixes
- All new features follow this pattern
---
## Developer Experience
### New Commands
```bash
# Health check
make health # Run health/sovereignty report
# Lightning backend
LIGHTNING_BACKEND=lnd make dev # Use real LND
LIGHTNING_BACKEND=mock make dev # Use mock (default)
# Mission Control
curl http://localhost:8000/health/sovereignty # JSON audit
curl http://localhost:8000/health/components # Component status
```
### Environment Variables
```bash
# Lightning
LIGHTNING_BACKEND=mock|lnd
LND_GRPC_HOST=localhost:10009
LND_MACAROON_PATH=/path/to/admin.macaroon
LND_TLS_CERT_PATH=/path/to/tls.cert
# Mock settings
MOCK_AUTO_SETTLE=true|false
```
---
## Remaining Gaps (v2.1 → v3.0)
### v2.1 (Next Sprint)
1. **XSS Security Fix** — Replace innerHTML with safe DOM methods
2. **Chat History Persistence** — SQLite-backed message storage
3. **Real LND Integration** — Generate protobuf stubs, test against live node
4. **Authentication** — Basic auth for swarm endpoints
### v3.0 (Revelation)
1. **Lightning Treasury** — Agent earns/spends autonomously
2. **macOS App Bundle** — Single `.app` with embedded Ollama
3. **Robot Embodiment** — First `RobotTimAgent` implementation
4. **Federation** — Multi-node swarm discovery
---
## Metrics Summary
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| Test count | 228 | 525 | +130% |
| Test coverage | ~45% | ~65% | +20pp |
| Sovereignty score | N/A | 9.2/10 | New |
| Backend modules | 8 | 12 | +4 |
| Persona agents | 0 functional | 6 with tools | +6 |
| Documentation pages | 3 | 5 | +2 |
---
*Analysis by Kimi — Architect Sprint*
*Timmy Time Dashboard | branch: kimi/mission-control-ux*
*Test-Driven Development | 525 tests passing*

View File

@@ -1,232 +0,0 @@
# Timmy Time — Comprehensive Quality Review Report
**Date:** 2026-02-25
**Reviewed by:** Claude Code
**Test Coverage:** 84.15% (895 tests passing)
**Test Result:** ✅ 895 passed, 30 skipped
---
## Executive Summary
The Timmy Time application is a **functional local-first AI agent system** with a working FastAPI dashboard, Ollama integration, and sophisticated Spark Intelligence engine. The codebase is well-structured with good test coverage, but **critical bugs were found and fixed** during this review that prevented the agent from working properly.
**Overall Quality Score: 7.5/10**
- Architecture: 8/10
- Functionality: 8/10 (after fixes)
- Test Coverage: 8/10
- Documentation: 7/10
- Memory/Self-Awareness: 9/10
---
## 1. Critical Bugs Found & Fixed
### Bug 1: Toolkit API Mismatch (`CRITICAL`)
**Location:** `src/timmy/tools.py`
**Issue:** Code used non-existent `Toolkit.add_tool()` method (should be `register()`)
**Changes Made:**
- Changed `toolkit.add_tool(...)``toolkit.register(...)` (29 occurrences)
- Changed `python_tools.python``python_tools.run_python_code` (3 occurrences)
- Changed `file_tools.write_file``file_tools.save_file` (4 occurrences)
- Changed `FileTools(base_dir=str(base_path))``FileTools(base_dir=base_path)` (5 occurrences)
**Impact:** Without this fix, Timmy agent would crash on startup with `AttributeError`.
### Bug 2: Agent Tools Parameter (`CRITICAL`)
**Location:** `src/timmy/agent.py`
**Issue:** Tools passed as single Toolkit instead of list
**Change Made:**
- Changed `tools=tools``tools=[tools] if tools else None`
**Impact:** Without this fix, Agno Agent initialization would fail with `TypeError: 'Toolkit' object is not iterable`.
---
## 2. Model Inference — ✅ WORKING
### Test Results
| Test | Status | Details |
|------|--------|---------|
| Agent creation | ✅ Pass | Ollama backend initializes correctly |
| Basic inference | ✅ Pass | Response type: `RunOutput` with content |
| Tool usage | ✅ Pass | File operations, shell commands work |
| Streaming | ✅ Pass | Supported via `stream=True` |
### Inference Example
```
Input: "What is your name and who are you?"
Output: "I am Timmy, a sovereign AI agent running locally on Apple Silicon.
I'm committed to your digital sovereignty and powered by Bitcoin economics..."
```
### Available Models
- **Ollama:** llama3.2 (default), deepseek-r1:1.5b
- **AirLLM:** 8B, 70B, 405B models (optional backend)
---
## 3. Memory & Self-Awareness — ✅ WORKING
### Conversation Memory Test
| Test | Status | Result |
|------|--------|--------|
| Single-turn memory | ✅ Pass | Timmy remembers what user just asked |
| Multi-turn context | ✅ Pass | References earlier conversation |
| Self-identification | ✅ Pass | "I am Timmy, a sovereign AI agent..." |
| Persistent storage | ✅ Pass | SQLite (`timmy.db`) persists across restarts |
| History recall | ✅ Pass | Can recall first question from conversation |
### Memory Implementation
- **Storage:** SQLite via `SqliteDb` (Agno)
- **Context window:** 10 history runs (`num_history_runs=10`)
- **File:** `timmy.db` in project root
### Self-Awareness Features
✅ Agent knows its name ("Timmy")
✅ Agent knows it's a sovereign AI
✅ Agent knows it runs locally (Apple Silicon detection)
✅ Agent references Bitcoin economics and digital sovereignty
✅ Agent references Christian faith grounding (per system prompt)
---
## 4. Spark Intelligence Engine — ✅ WORKING
### Capabilities Verified
| Feature | Status | Details |
|---------|--------|---------|
| Event capture | ✅ Working | 550 events captured |
| Task predictions | ✅ Working | 235 predictions, 85% avg accuracy |
| Memory consolidation | ✅ Working | 6 memories stored |
| Advisories | ✅ Working | Failure prevention, performance, bid optimization |
| EIDOS loop | ✅ Working | Predict → Observe → Evaluate → Learn |
### Sample Advisory Output
```
[failure_prevention] Agent fail-lea has 7 failures (Priority: 1.0)
[agent_performance] Agent success- excels (100% success) (Priority: 0.6)
[bid_optimization] Wide bid spread (2094 sats) (Priority: 0.5)
[system_health] Strong prediction accuracy (85%) (Priority: 0.3)
```
---
## 5. Dashboard & UI — ✅ WORKING
### Route Testing Results
| Route | Status | Notes |
|-------|--------|-------|
| `/` | ✅ 200 | Main dashboard loads |
| `/health` | ✅ 200 | Health panel |
| `/agents` | ✅ 200 | Agent list API |
| `/swarm` | ✅ 200 | Swarm coordinator UI |
| `/spark` | ✅ 200 | Spark Intelligence dashboard |
| `/marketplace` | ✅ 200 | Marketplace UI |
| `/mobile` | ✅ 200 | Mobile-optimized layout |
| `/agents/timmy/chat` | ✅ 200 | Chat endpoint works |
### Chat Functionality
- HTMX-powered chat interface ✅
- Message history persistence ✅
- Real-time Ollama inference ✅
- Error handling (graceful degradation) ✅
---
## 6. Swarm System — ⚠️ PARTIAL
### Working Components
- ✅ Registry with SQLite persistence
- ✅ Coordinator with task lifecycle
- ✅ Agent bidding system
- ✅ Task assignment algorithm
- ✅ Spark event capture
- ✅ Recovery mechanism
### Limitations
- ⚠️ Persona agents are stubbed (not fully functional AI agents)
- ⚠️ Most swarm activity is simulated/test data
- ⚠️ Docker runner not tested in live environment
---
## 7. Issues Identified (Non-Critical)
### Issue 1: SSL Certificate Error with DuckDuckGo
**Location:** Web search tool
**Error:** `CERTIFICATE_VERIFY_FAILED`
**Impact:** Web search tool fails, but agent continues gracefully
**Fix:** May need `certifi` package or system certificate update
### Issue 2: Default Secrets Warning
**Location:** L402 payment handler
**Message:** `L402_HMAC_SECRET is using the default value`
**Impact:** Warning only — production should set unique secrets
**Status:** By design (warns at startup)
### Issue 3: Redis Unavailable Fallback
**Location:** SwarmComms
**Message:** `Redis unavailable — using in-memory fallback`
**Impact:** Falls back to in-memory (acceptable for single-instance)
**Status:** By design (graceful degradation)
### Issue 4: Telemetry to Agno
**Observation:** Agno sends telemetry to `os-api.agno.com`
**Impact:** Minor — may not align with "sovereign" vision
**Note:** Requires further review for truly air-gapped deployments
---
## 8. Test Coverage Analysis
| Module | Coverage | Status |
|--------|----------|--------|
| `spark/memory.py` | 98.3% | ✅ Excellent |
| `spark/engine.py` | 92.6% | ✅ Good |
| `swarm/coordinator.py` | 92.8% | ✅ Good |
| `timmy/agent.py` | 100% | ✅ Excellent |
| `timmy/backends.py` | 96.3% | ✅ Good |
| `dashboard/` routes | 60-100% | ✅ Good |
**Overall:** 84.15% coverage (exceeds 60% threshold)
---
## 9. Recommendations
### High Priority
1.**DONE** Fix toolkit API methods (register vs add_tool)
2.**DONE** Fix agent tools parameter (wrap in list)
3. Add tool usage instructions to system prompt to reduce unnecessary tool calls
4. Fix SSL certificate issue for DuckDuckGo search
### Medium Priority
5. Add configuration option to disable Agno telemetry
6. Implement more sophisticated self-awareness (e.g., knowledge of current tasks)
7. Expand persona agent capabilities beyond stubs
### Low Priority
8. Add more comprehensive end-to-end tests with real Ollama
9. Optimize tool calling behavior (fewer unnecessary tool invocations)
10. Consider adding conversation summarization for very long contexts
---
## 10. Conclusion
After fixing the critical bugs identified during this review, **Timmy Time is a functional and well-architected AI agent system** with:
- ✅ Working model inference via Ollama
- ✅ Persistent conversation memory
- ✅ Self-awareness capabilities
- ✅ Comprehensive Spark Intelligence engine
- ✅ Functional web dashboard
- ✅ Good test coverage (84%+)
The core value proposition — a sovereign, local-first AI agent with memory and self-awareness — **is delivered and working**.

View File

@@ -2,8 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Timmy</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Timmy Time — Mission Control</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
@@ -12,523 +12,344 @@
:root {
--bg: #080412;
--bg-msg: #110820;
--bg-user: #1a0e30;
--bg-card: #110820;
--border: #2a1545;
--text: #c8b0e0;
--dim: #6b4a8a;
--bright: #ede0ff;
--accent: #ff7a2a;
--green: #00e87a;
--red: #ff4455;
--font: 'JetBrains Mono', monospace;
}
html, body {
height: 100%;
overflow: hidden;
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 14px;
line-height: 1.6;
touch-action: pan-y;
overscroll-behavior: none;
line-height: 1.7;
-webkit-text-size-adjust: 100%;
}
/* ── Full-screen layout ── */
#app {
display: flex;
flex-direction: column;
height: 100%;
height: 100dvh;
max-width: 720px;
.container {
max-width: 760px;
margin: 0 auto;
overflow: hidden;
padding: 48px 24px 64px;
}
/* ── Header ── */
#header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
header {
margin-bottom: 48px;
}
#header h1 {
font-size: 13px;
h1 {
font-size: 28px;
font-weight: 700;
letter-spacing: 0.2em;
letter-spacing: 0.15em;
color: var(--accent);
margin-bottom: 8px;
}
.tagline {
font-size: 15px;
color: var(--bright);
margin-bottom: 16px;
}
.badges {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 24px;
}
.badge {
display: inline-block;
padding: 3px 10px;
font-size: 11px;
letter-spacing: 0.08em;
border: 1px solid var(--border);
color: var(--dim);
}
.badge.highlight {
border-color: var(--accent);
color: var(--accent);
}
#status {
.links {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--dim);
letter-spacing: 0.05em;
cursor: default;
gap: 16px;
flex-wrap: wrap;
}
#status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--dim);
transition: background 0.3s;
}
#status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
#status-dot.offline { background: var(--red); }
#status-dot.trying { background: var(--accent); animation: pulse 1s infinite; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* ── Messages ── */
#messages {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
.msg {
max-width: 88%;
padding: 10px 14px;
.links a {
color: var(--accent);
text-decoration: none;
font-size: 13px;
line-height: 1.6;
word-wrap: break-word;
overflow-wrap: break-word;
letter-spacing: 0.05em;
border-bottom: 1px solid transparent;
transition: border-color 0.2s;
}
.links a:hover {
border-bottom-color: var(--accent);
}
.msg.timmy {
align-self: flex-start;
background: var(--bg-msg);
border-left: 2px solid var(--accent);
h2 {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
margin: 48px 0 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 8px;
}
.stat {
background: var(--bg-card);
border: 1px solid var(--border);
padding: 16px;
}
.stat .number {
font-size: 24px;
font-weight: 700;
color: var(--bright);
display: block;
}
.stat .label {
font-size: 11px;
letter-spacing: 0.1em;
color: var(--dim);
text-transform: uppercase;
}
.tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.tech-item {
background: var(--bg-card);
border: 1px solid var(--border);
padding: 14px 16px;
}
.tech-item strong {
color: var(--bright);
font-size: 13px;
}
.tech-item span {
display: block;
font-size: 12px;
color: var(--dim);
margin-top: 4px;
}
pre {
background: var(--bg-card);
border: 1px solid var(--border);
padding: 20px;
overflow-x: auto;
font-size: 12px;
line-height: 1.6;
color: var(--text);
}
.msg.user {
align-self: flex-end;
background: var(--bg-user);
border-right: 2px solid var(--dim);
color: var(--bright);
}
.msg.system {
align-self: center;
text-align: center;
font-size: 11px;
color: var(--dim);
max-width: 90%;
padding: 8px;
}
.msg .label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--dim);
margin-bottom: 4px;
}
.msg.timmy .label { color: var(--accent); }
.msg .body p { margin-bottom: 0.5em; }
.msg .body p:last-child { margin-bottom: 0; }
.msg .body code {
background: rgba(255, 255, 255, 0.06);
padding: 1px 5px;
font-size: 12px;
}
.msg .body pre {
background: rgba(0, 0, 0, 0.3);
padding: 10px;
overflow-x: auto;
font-size: 12px;
margin: 6px 0;
}
.msg .body pre code {
background: none;
padding: 0;
}
/* Thinking indicator */
.thinking {
align-self: flex-start;
padding: 10px 14px;
font-size: 13px;
color: var(--dim);
border-left: 2px solid var(--accent);
background: var(--bg-msg);
}
.thinking span {
animation: blink 1.4s infinite;
}
.thinking span:nth-child(2) { animation-delay: 0.2s; }
.thinking span:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink {
0%, 80%, 100% { opacity: 0.2; }
40% { opacity: 1; }
}
/* ── Input ── */
#input-area {
flex-shrink: 0;
padding: 12px 16px;
padding-bottom: max(12px, env(safe-area-inset-bottom));
border-top: 1px solid var(--border);
background: var(--bg);
}
#input {
.modules {
width: 100%;
background: var(--bg-msg);
border: 1px solid var(--border);
color: var(--bright);
font-family: var(--font);
font-size: 16px;
padding: 12px 14px;
outline: none;
-webkit-appearance: none;
appearance: none;
border-radius: 0;
border-collapse: collapse;
}
#input::placeholder {
color: var(--dim);
.modules th,
.modules td {
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
#input:focus {
border-color: var(--accent);
.modules th {
color: var(--dim);
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 600;
}
/* ── Scrollbar ── */
#messages::-webkit-scrollbar { width: 4px; }
#messages::-webkit-scrollbar-track { background: transparent; }
#messages::-webkit-scrollbar-thumb { background: var(--border); }
.modules td:first-child {
color: var(--bright);
font-weight: 600;
}
/* ── No horizontal anything ── */
* { max-width: 100%; }
footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--border);
font-size: 12px;
color: var(--dim);
}
footer a {
color: var(--dim);
text-decoration: none;
}
footer a:hover {
color: var(--accent);
}
@media (max-width: 480px) {
.container { padding: 24px 16px 48px; }
h1 { font-size: 22px; }
.stats { grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<div id="app">
<div id="header">
<h1>TIMMY</h1>
<div id="status">
<span id="status-text">connecting</span>
<span id="status-dot" class="trying"></span>
<div class="container">
<header>
<h1>TIMMY TIME</h1>
<p class="tagline">A local-first, sovereign AI agent system</p>
<div class="badges">
<span class="badge highlight">Python 3.11+</span>
<span class="badge highlight">FastAPI</span>
<span class="badge">HTMX</span>
<span class="badge">Ollama</span>
<span class="badge">SQLite</span>
<span class="badge">MIT License</span>
</div>
<div class="links">
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard">GitHub Repository</a>
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard/blob/main/README.md">README</a>
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard/blob/main/CLAUDE.md">Developer Guide</a>
</div>
</header>
<h2>At a Glance</h2>
<div class="stats">
<div class="stat">
<span class="number">8</span>
<span class="label">Python Packages</span>
</div>
<div class="stat">
<span class="number">643+</span>
<span class="label">Passing Tests</span>
</div>
<div class="stat">
<span class="number">73%+</span>
<span class="label">Code Coverage</span>
</div>
<div class="stat">
<span class="number">58</span>
<span class="label">API Endpoints</span>
</div>
</div>
<div id="messages"></div>
<div id="input-area">
<input id="input"
type="text"
placeholder="talk to timmy..."
autocomplete="off"
autocorrect="off"
autocapitalize="none"
spellcheck="false"
enterkeyhint="send">
<h2>Tech Stack</h2>
<div class="tech-grid">
<div class="tech-item">
<strong>FastAPI + HTMX</strong>
<span>Server-rendered UI with real-time updates</span>
</div>
<div class="tech-item">
<strong>Agno Framework</strong>
<span>Multi-agent orchestration with tool calling</span>
</div>
<div class="tech-item">
<strong>Ollama</strong>
<span>Local LLM inference, no cloud dependencies</span>
</div>
<div class="tech-item">
<strong>SQLite + WAL</strong>
<span>Agent memory, swarm registry, task queue</span>
</div>
<div class="tech-item">
<strong>WebSockets</strong>
<span>Live event feeds and push notifications</span>
</div>
<div class="tech-item">
<strong>Docker</strong>
<span>Multi-stage builds, dev and prod configs</span>
</div>
</div>
<script>
(function() {
var SERVER = localStorage.getItem('timmy-server') || 'http://localhost:8000';
var messages = document.getElementById('messages');
var input = document.getElementById('input');
var dot = document.getElementById('status-dot');
var stxt = document.getElementById('status-text');
var connected = false;
var sending = false;
<h2>Architecture</h2>
<pre>
Browser / Phone
| HTTP + HTMX + WebSocket
v
+-----------------------------------------+
| FastAPI (dashboard.app) |
| routes: agents, health, swarm, |
| marketplace, voice, mobile |
+---+-------------+----------+-----------+
| | |
v v v
Jinja2 Timmy Infrastructure
Templates Agent +- LLM Router (cascade)
(HTMX) | +- WebSocket manager
+- Ollama +- Notifications
+- AirLLM +- Events bus
|
+-- Integrations (voice NLU, Telegram, Siri)
+-- WebSocket live feed
+-- Push notifications
+-- Spark (events, predictions, advisory)
function setStatus(state) {
dot.className = state;
if (state === 'online') stxt.textContent = 'online';
else if (state === 'trying') stxt.textContent = 'connecting';
else stxt.textContent = 'offline';
}
Persistence: SQLite (Agno memory + swarm registry)
External: Ollama :11434, optional Redis, optional LND</pre>
// ── Looping scroll ──
// Messages live inside two duplicate containers.
// When scroll crosses the midpoint we silently reposition,
// creating an infinite one-direction loop.
var loopEnabled = false;
var suppressLoop = false;
<h2>Modules</h2>
<table class="modules">
<thead>
<tr><th>Package</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>timmy/</td><td>Core agent, personas, interface, semantic memory</td></tr>
<tr><td>dashboard/</td><td>FastAPI web UI, routes, Jinja2 templates</td></tr>
<tr><td>infrastructure/</td><td>WebSocket, notifications, events, LLM router</td></tr>
<tr><td>integrations/</td><td>Discord, Telegram, Siri Shortcuts, voice NLU</td></tr>
<tr><td>spark/</td><td>Event capture, predictions, advisory engine</td></tr>
<tr><td>brain/</td><td>Identity system, memory interface</td></tr>
<tr><td>timmy_serve/</td><td>API server, L402 Lightning gating</td></tr>
<tr><td>config.py</td><td>Pydantic settings — single source for all env vars</td></tr>
</tbody>
</table>
function buildLoop() {
// Clone all messages into a second set so the container is 2x tall
var existing = messages.querySelectorAll('.msg, .thinking');
if (existing.length < 2) { loopEnabled = false; return; }
<h2>Quality</h2>
<div class="tech-grid">
<div class="tech-item">
<strong>CI/CD</strong>
<span>GitHub Actions: lint, test, Docker build</span>
</div>
<div class="tech-item">
<strong>Testing</strong>
<span>tox-managed: unit, integration, functional, e2e</span>
</div>
<div class="tech-item">
<strong>Linting</strong>
<span>black + isort + bandit security scanning</span>
</div>
<div class="tech-item">
<strong>Security</strong>
<span>CSRF, XSS prevention, no hardcoded secrets</span>
</div>
</div>
// Remove any previous clone zone
var oldClone = document.getElementById('clone-zone');
if (oldClone) oldClone.remove();
var zone = document.createElement('div');
zone.id = 'clone-zone';
for (var i = 0; i < existing.length; i++) {
zone.appendChild(existing[i].cloneNode(true));
}
messages.appendChild(zone);
loopEnabled = true;
}
messages.addEventListener('scroll', function() {
if (!loopEnabled || suppressLoop) return;
var half = messages.scrollHeight / 2;
if (messages.scrollTop >= half) {
suppressLoop = true;
messages.scrollTop -= half;
suppressLoop = false;
} else if (messages.scrollTop <= 0) {
suppressLoop = true;
messages.scrollTop += half;
suppressLoop = false;
}
});
function scrollDown() {
requestAnimationFrame(function() {
// Scroll to latest message (just before clone zone)
var clone = document.getElementById('clone-zone');
if (clone) {
messages.scrollTop = clone.offsetTop - messages.clientHeight + 40;
} else {
messages.scrollTop = messages.scrollHeight;
}
});
}
function rebuildAndScroll() {
// Small delay so DOM settles, then rebuild loop and scroll
requestAnimationFrame(function() {
buildLoop();
scrollDown();
});
}
function addMsg(role, text) {
// Remove old clone zone before adding new message
var oldClone = document.getElementById('clone-zone');
if (oldClone) oldClone.remove();
var div = document.createElement('div');
div.className = 'msg ' + role;
if (role === 'system') {
div.textContent = text;
} else {
var label = document.createElement('div');
label.className = 'label';
label.textContent = role === 'timmy' ? 'TIMMY' : 'YOU';
var body = document.createElement('div');
body.className = 'body';
if (role === 'timmy') {
body.innerHTML = renderMarkdown(text);
} else {
body.textContent = text;
}
div.appendChild(label);
div.appendChild(body);
}
messages.appendChild(div);
rebuildAndScroll();
return div;
}
function showThinking() {
var oldClone = document.getElementById('clone-zone');
if (oldClone) oldClone.remove();
var div = document.createElement('div');
div.className = 'thinking';
div.id = 'thinking';
div.innerHTML = '<span>.</span><span>.</span><span>.</span>';
messages.appendChild(div);
rebuildAndScroll();
}
function hideThinking() {
var el = document.getElementById('thinking');
if (el) el.remove();
// Also remove from clone zone
var cloneThinking = document.querySelector('#clone-zone .thinking');
if (cloneThinking) cloneThinking.remove();
}
// Simple markdown rendering (safe — no innerHTML with user content)
function renderMarkdown(text) {
// Escape HTML first
var safe = text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Code blocks
safe = safe.replace(/```(\w*)\n?([\s\S]*?)```/g, function(m, lang, code) {
return '<pre><code>' + code.trim() + '</code></pre>';
});
// Inline code
safe = safe.replace(/`([^`]+)`/g, '<code>$1</code>');
// Bold
safe = safe.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
// Italic
safe = safe.replace(/\*(.+?)\*/g, '<em>$1</em>');
// Paragraphs
safe = safe.replace(/\n\n+/g, '</p><p>');
safe = '<p>' + safe + '</p>';
// Line breaks within paragraphs
safe = safe.replace(/\n/g, '<br>');
return safe;
}
// Probe the server for connectivity
function probe() {
setStatus('trying');
fetch(SERVER + '/health/status', { mode: 'cors', signal: AbortSignal.timeout(4000) })
.then(function(r) {
if (r.ok) {
connected = true;
setStatus('online');
} else {
connected = false;
setStatus('offline');
}
})
.catch(function() {
connected = false;
setStatus('offline');
});
}
// Send message to Timmy
function send(text) {
if (sending) return;
// Special commands
if (text.startsWith('/connect ')) {
var url = text.slice(9).trim().replace(/\/+$/, '');
SERVER = url;
localStorage.setItem('timmy-server', SERVER);
addMsg('system', 'server set to ' + SERVER);
probe();
return;
}
if (text === '/clear') {
messages.innerHTML = '';
addMsg('system', 'chat cleared');
return;
}
if (text === '/help') {
addMsg('system', '/connect <url> — set server · /clear — clear chat');
return;
}
addMsg('user', text);
if (!connected) {
addMsg('system', 'not connected — start timmy (make dev) or /connect <url>');
return;
}
sending = true;
showThinking();
var form = new FormData();
form.append('message', text);
fetch(SERVER + '/agents/timmy/chat', {
method: 'POST',
body: form,
mode: 'cors',
})
.then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
})
.then(function(html) {
hideThinking();
// Parse the response HTML to extract Timmy's reply
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var agentBody = doc.querySelector('.chat-message.agent .msg-body');
var errorBody = doc.querySelector('.chat-message.error-msg .msg-body');
if (agentBody) {
addMsg('timmy', agentBody.textContent.trim());
} else if (errorBody) {
addMsg('system', errorBody.textContent.trim());
} else {
addMsg('system', 'no response');
}
})
.catch(function(err) {
hideThinking();
addMsg('system', 'error: ' + err.message);
probe();
})
.finally(function() {
sending = false;
});
}
// Input handling — Enter to send, no buttons
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
var text = input.value.trim();
if (!text) return;
input.value = '';
send(text);
}
});
// Initial welcome
addMsg('system', 'timmy time — sovereign ai');
addMsg('timmy', "What's on your mind?");
// Probe on load
probe();
// Re-probe periodically
setInterval(probe, 15000);
})();
</script>
<footer>
<p>MIT License &copy; 2026 Alexander Whitestone &middot; <a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard">View Source</a></p>
</footer>
</div>
</body>
</html>

View File

@@ -8,6 +8,9 @@ version = "1.0.0"
description = "Mission Control for sovereign AI agents"
readme = "README.md"
license = "MIT"
authors = ["Alexander Whitestone"]
homepage = "https://alexanderwhitestone.github.io/Timmy-time-dashboard/"
repository = "https://github.com/AlexanderWhitestone/Timmy-time-dashboard"
packages = [
{ include = "config.py", from = "src" },
{ include = "brain", from = "src" },

View File

@@ -14,10 +14,10 @@ class Settings(BaseSettings):
ollama_url: str = "http://localhost:11434"
# LLM model passed to Agno/Ollama — override with OLLAMA_MODEL
# llama3.1:8b-instruct is used instead of llama3.2 because it is
# specifically fine-tuned for reliable tool/function calling.
# qwen2.5:14b is the primary model — better reasoning and tool calling
# than llama3.1:8b-instruct while still running locally on modest hardware.
# Fallback: llama3.1:8b-instruct if qwen2.5:14b not available.
# llama3.2 (3B) hallucinated tool output consistently in testing.
# Fallback: qwen2.5:14b if llama3.1:8b-instruct not available.
ollama_model: str = "qwen2.5:14b"
# Set DEBUG=true to enable /docs and /redoc (disabled by default)