selenium depends on websocket-client which installs a top-level `websocket` package that shadows our src/websocket/ module on CI. Renaming to ws_manager eliminates the conflict entirely — no more sys.path hacks needed in conftest or Selenium tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.6 KiB
CLAUDE.md — AI Assistant Guide for Timmy Time
This file provides context for AI assistants (Claude Code, Copilot, etc.) working in this repository. Read this before making any changes.
For multi-agent development standards and agent-specific conventions, see
AGENTS.md.
Project Summary
Timmy Time is a local-first, sovereign AI agent system with a browser-based Mission Control dashboard. No cloud AI — all inference runs on localhost via Ollama (or AirLLM for large models). Bitcoin Lightning economics are built in for API access gating.
Tech stack: Python 3.11+ · FastAPI · Jinja2 + HTMX · SQLite · Agno (agent framework) · Ollama · pydantic-settings · WebSockets · Docker
Quick Reference Commands
# Setup
make install # Create venv + install dev deps
cp .env.example .env # Configure environment
# Development
make dev # Start dashboard at http://localhost:8000
make test # Run full test suite (no Ollama needed)
make test-cov # Tests + coverage report (terminal + XML)
make lint # Run ruff or flake8
# Docker
make docker-build # Build timmy-time:latest image
make docker-up # Start dashboard container
make docker-agent # Spawn one agent worker
make docker-down # Stop all containers
Project Layout
src/
config.py # Central pydantic-settings (all env vars)
timmy/ # Core agent: agent.py, backends.py, cli.py, prompts.py
dashboard/ # FastAPI app + routes + Jinja2 templates
app.py # App factory, lifespan, router registration
store.py # In-memory MessageLog singleton
routes/ # One file per route group (agents, health, swarm, etc.)
templates/ # base.html + page templates + partials/
swarm/ # Multi-agent coordinator, registry, bidder, tasks, comms
coordinator.py # Central swarm orchestrator (security-sensitive)
docker_runner.py # Spawn agents as Docker containers
timmy_serve/ # L402 Lightning proxy, payment handler, TTS, CLI
spark/ # Intelligence engine — events, predictions, advisory
creative/ # Creative director + video assembler pipeline
tools/ # Git, image, music, video tools for persona agents
lightning/ # Lightning backend abstraction (mock + LND)
agent_core/ # Substrate-agnostic agent interface
voice/ # NLU intent detection (regex-based, local)
ws_manager/ # WebSocket connection manager (ws_manager singleton)
notifications/ # Push notification store (notifier singleton)
shortcuts/ # Siri Shortcuts API endpoints
telegram_bot/ # Telegram bridge
self_tdd/ # Continuous test watchdog
tests/ # One test_*.py per module, all mocked
static/ # style.css + bg.svg (dark arcane theme)
docs/ # GitHub Pages landing site
Architecture Patterns
Config access
All configuration goes through src/config.py using pydantic-settings:
from config import settings
url = settings.ollama_url # never use os.environ.get() directly in app code
Environment variables are read from .env automatically. See .env.example for
all available settings.
Singletons
Core services are module-level singleton instances imported directly:
from dashboard.store import message_log
from notifications.push import notifier
from ws_manager.handler import ws_manager
from timmy_serve.payment_handler import payment_handler
from swarm.coordinator import coordinator
HTMX response pattern
Routes return Jinja2 template partials for HTMX requests:
return templates.TemplateResponse(
"partials/chat_message.html",
{"request": request, "role": "user", "content": message}
)
Graceful degradation
Optional services (Ollama, Redis, AirLLM) degrade gracefully — log the error, return a fallback, never crash:
try:
result = await some_optional_service()
except Exception:
result = fallback_value
Route registration
New routes go in src/dashboard/routes/<name>.py, then register the router in
src/dashboard/app.py:
from dashboard.routes.<name> import router as <name>_router
app.include_router(<name>_router)
Testing
Running tests
make test # Quick run (pytest -q --tb=short)
make test-cov # With coverage (term-missing + XML)
make test-cov-html # With HTML coverage report
No Ollama or external services needed — all heavy dependencies are mocked.
Test conventions
- One test file per module:
tests/test_<module>.py - Stubs in conftest:
agno,airllm,pyttsx3,telegramare stubbed intests/conftest.pyusingsys.modules.setdefault()so tests run without those packages installed - Test mode:
TIMMY_TEST_MODE=1is set automatically in conftest to disable auto-spawning of persona agents during tests - FastAPI testing: Use the
clientfixture (wrapsTestClient) - Database isolation: SQLite files in
data/are cleaned between tests; coordinator state is reset via autouse fixtures - Async:
asyncio_mode = "auto"in pytest config — async test functions are detected automatically - Coverage threshold: CI fails if coverage drops below 60%
(
fail_under = 60inpyproject.toml)
Adding a new test
# tests/test_my_feature.py
from fastapi.testclient import TestClient
def test_my_endpoint(client):
response = client.get("/my-endpoint")
assert response.status_code == 200
CI/CD
GitHub Actions workflow (.github/workflows/tests.yml):
- Runs on every push and pull request to all branches
- Python 3.11, installs
.[dev]dependencies - Runs pytest with coverage + JUnit XML output
- Publishes test results as PR comments and check annotations
- Uploads coverage XML as a downloadable artifact (14-day retention)
Key Conventions
- Tests must stay green. Run
make testbefore committing. - No cloud AI dependencies. All inference runs on localhost.
- No new top-level files without purpose. Keep the root directory clean.
- Follow existing patterns — singletons, graceful degradation, pydantic-settings config.
- Security defaults: Never hard-code secrets. Warn at startup when using default values.
- XSS prevention: Never use
innerHTMLwith untrusted content. - Keep routes thin — business logic lives in the module, not the route.
- Prefer editing existing files over creating new ones.
- Use
from config import settingsfor all env-var access. - Every new module gets a test:
tests/test_<module>.py.
Entry Points
Three CLI commands are installed via pyproject.toml:
| Command | Module | Purpose |
|---|---|---|
timmy |
src/timmy/cli.py |
Chat, think, status commands |
timmy-serve |
src/timmy_serve/cli.py |
L402-gated API server (port 8402) |
self-tdd |
src/self_tdd/watchdog.py |
Continuous test watchdog |
Environment Variables
Key variables (full list in .env.example):
| Variable | Default | Purpose |
|---|---|---|
OLLAMA_URL |
http://localhost:11434 |
Ollama host |
OLLAMA_MODEL |
llama3.2 |
Model served by Ollama |
DEBUG |
false |
Enable /docs and /redoc |
TIMMY_MODEL_BACKEND |
ollama |
ollama / airllm / auto |
AIRLLM_MODEL_SIZE |
70b |
8b / 70b / 405b |
L402_HMAC_SECRET |
(change in prod) | HMAC signing for invoices |
L402_MACAROON_SECRET |
(change in prod) | Macaroon signing |
LIGHTNING_BACKEND |
mock |
mock / lnd |
SPARK_ENABLED |
true |
Enable Spark intelligence engine |
TELEGRAM_TOKEN |
(empty) | Telegram bot token |
Persistence
timmy.db— Agno agent memory (SQLite, project root)data/swarm.db— Swarm registry + tasks (SQLite,data/directory)- All
.dbfiles are gitignored — never commit database files
Docker
Containers share a data/ volume for SQLite. Container agents communicate with
the coordinator over HTTP (not in-memory SwarmComms):
GET /internal/tasks → list tasks open for bidding
POST /internal/bids → submit a bid
COORDINATOR_URL=http://dashboard:8000 is set automatically by docker-compose.
Security-Sensitive Areas
src/swarm/coordinator.py— requires review before changessrc/timmy_serve/l402_proxy.py— Lightning payment gatingsrc/lightning/— payment backend abstraction- Any file handling secrets or authentication tokens