2026-02-28 11:07:19 -05:00
|
|
|
"""Optimized dashboard app with improved async handling and non-blocking startup.
|
|
|
|
|
|
|
|
|
|
Key improvements:
|
|
|
|
|
1. Background tasks use asyncio.create_task() to avoid blocking startup
|
2026-03-02 13:17:38 -05:00
|
|
|
2. Chat integrations start in background
|
|
|
|
|
3. All startup operations complete quickly
|
2026-03-04 07:58:39 -05:00
|
|
|
4. Security and logging handled by dedicated middleware
|
2026-02-28 11:07:19 -05:00
|
|
|
"""
|
|
|
|
|
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
import asyncio
|
2026-03-18 22:22:02 -04:00
|
|
|
import json
|
2026-02-19 19:31:48 +00:00
|
|
|
import logging
|
2026-03-21 14:56:43 +00:00
|
|
|
import re
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
from contextlib import asynccontextmanager
|
2026-02-19 19:05:01 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-02-28 12:18:18 -05:00
|
|
|
from fastapi import FastAPI, Request, WebSocket
|
feat: replace GitHub page with embedded Timmy chat interface
Replaces the marketing landing page with a minimal, full-screen chat
interface that connects to a running Timmy instance. Mobile-first design
with single vertical scroll direction, looping scroll, no zoom, no
buttons — just type and press Enter to talk to Timmy.
- docs/index.html: full rewrite as a clean chat UI with dark terminal
theme, looping infinite scroll, markdown rendering, connection status,
and /connect, /clear, /help slash commands
- src/dashboard/app.py: add CORS middleware so the GitHub Pages site can
reach a local Timmy server cross-origin
- src/config.py: add cors_origins setting (defaults to ["*"])
https://claude.ai/code/session_01AWLxg6KDWsfCATiuvsRMGr
2026-02-27 00:35:33 +00:00
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
2026-03-01 11:50:34 -05:00
|
|
|
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
2026-02-19 19:05:01 +00:00
|
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
|
from fastapi.staticfiles import StaticFiles
|
2026-03-08 12:50:44 -04:00
|
|
|
|
2026-03-02 13:17:38 -05:00
|
|
|
from config import settings
|
2026-03-08 12:50:44 -04:00
|
|
|
|
|
|
|
|
# Import dedicated middleware
|
|
|
|
|
from dashboard.middleware.csrf import CSRFMiddleware
|
2026-03-21 16:23:16 +00:00
|
|
|
from dashboard.middleware.rate_limit import RateLimitMiddleware
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.middleware.request_logging import RequestLoggingMiddleware
|
|
|
|
|
from dashboard.middleware.security_headers import SecurityHeadersMiddleware
|
2026-03-02 13:17:38 -05:00
|
|
|
from dashboard.routes.agents import router as agents_router
|
|
|
|
|
from dashboard.routes.briefing import router as briefing_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.calm import router as calm_router
|
|
|
|
|
from dashboard.routes.chat_api import router as chat_api_router
|
2026-03-18 18:20:14 -04:00
|
|
|
from dashboard.routes.chat_api_v1 import router as chat_api_v1_router
|
2026-03-21 19:58:25 +00:00
|
|
|
from dashboard.routes.daily_run import router as daily_run_router
|
2026-03-12 10:41:13 -04:00
|
|
|
from dashboard.routes.db_explorer import router as db_explorer_router
|
2026-03-02 13:17:38 -05:00
|
|
|
from dashboard.routes.discord import router as discord_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.experiments import router as experiments_router
|
2026-03-02 13:17:38 -05:00
|
|
|
from dashboard.routes.grok import router as grok_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.health import router as health_router
|
feat: add Loop QA self-testing framework
Structured self-test framework that probes 6 capabilities (tool use,
multistep planning, memory read/write, self-coding, lightning econ) in
round-robin. Reuses existing infra: event_log for persistence,
create_task() for upgrade proposals, capture_error() for crash handling,
and in-memory circuit breaker for failure tracking.
- src/timmy/loop_qa.py: Capability enum, 6 async probes, orchestrator
- src/dashboard/routes/loop_qa.py: JSON + HTMX health endpoints
- HTMX partial polls every 30s on the health panel
- Background scheduler in app.py lifespan
- 25 tests covering probes, orchestrator, health snapshot, routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:33:16 -04:00
|
|
|
from dashboard.routes.loop_qa import router as loop_qa_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.memory import router as memory_router
|
|
|
|
|
from dashboard.routes.mobile import router as mobile_router
|
2026-03-02 13:17:38 -05:00
|
|
|
from dashboard.routes.models import api_router as models_api_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.models import router as models_router
|
2026-03-21 20:45:35 +00:00
|
|
|
from dashboard.routes.quests import router as quests_router
|
2026-03-22 01:41:52 +00:00
|
|
|
from dashboard.routes.scorecards import router as scorecards_router
|
2026-03-23 14:09:03 +00:00
|
|
|
from dashboard.routes.sovereignty_metrics import router as sovereignty_metrics_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.spark import router as spark_router
|
|
|
|
|
from dashboard.routes.system import router as system_router
|
2026-03-07 23:21:30 -05:00
|
|
|
from dashboard.routes.tasks import router as tasks_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.telegram import router as telegram_router
|
|
|
|
|
from dashboard.routes.thinking import router as thinking_router
|
|
|
|
|
from dashboard.routes.tools import router as tools_router
|
2026-03-20 16:10:42 -04:00
|
|
|
from dashboard.routes.tower import router as tower_router
|
2026-03-08 12:50:44 -04:00
|
|
|
from dashboard.routes.voice import router as voice_router
|
2026-03-07 23:21:30 -05:00
|
|
|
from dashboard.routes.work_orders import router as work_orders_router
|
2026-03-21 14:18:46 +00:00
|
|
|
from dashboard.routes.world import matrix_router
|
2026-03-18 22:13:49 -04:00
|
|
|
from dashboard.routes.world import router as world_router
|
2026-03-18 22:33:06 -04:00
|
|
|
from timmy.workshop_state import PRESENCE_FILE
|
2026-02-19 19:05:01 +00:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
|
2026-03-11 10:37:20 -04:00
|
|
|
class _ColorFormatter(logging.Formatter):
|
|
|
|
|
"""ANSI color formatter — red is reserved for ERROR/CRITICAL only."""
|
|
|
|
|
|
|
|
|
|
RESET = "\033[0m"
|
|
|
|
|
COLORS = {
|
|
|
|
|
logging.DEBUG: "\033[37m", # white/gray
|
|
|
|
|
logging.INFO: "\033[32m", # green
|
|
|
|
|
logging.WARNING: "\033[33m", # yellow
|
|
|
|
|
logging.ERROR: "\033[31m", # red
|
|
|
|
|
logging.CRITICAL: "\033[1;31m", # bold red
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
|
|
|
color = self.COLORS.get(record.levelno, self.RESET)
|
|
|
|
|
formatted = super().format(record)
|
|
|
|
|
return f"{color}{formatted}{self.RESET}"
|
|
|
|
|
|
|
|
|
|
|
2026-02-27 19:51:37 -05:00
|
|
|
def _configure_logging() -> None:
|
|
|
|
|
"""Configure logging with console and optional rotating file handler."""
|
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
|
root_logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
|
|
console = logging.StreamHandler()
|
|
|
|
|
console.setLevel(logging.INFO)
|
|
|
|
|
console.setFormatter(
|
2026-03-11 10:37:20 -04:00
|
|
|
_ColorFormatter(
|
2026-02-27 19:51:37 -05:00
|
|
|
"%(asctime)s %(levelname)-8s %(name)s — %(message)s",
|
|
|
|
|
datefmt="%H:%M:%S",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
root_logger.addHandler(console)
|
|
|
|
|
|
2026-03-11 10:37:20 -04:00
|
|
|
# Override uvicorn's default colored formatter so all console output
|
|
|
|
|
# uses our color scheme (red = ERROR/CRITICAL only).
|
|
|
|
|
for name in ("uvicorn", "uvicorn.error", "uvicorn.access"):
|
|
|
|
|
uv_logger = logging.getLogger(name)
|
|
|
|
|
uv_logger.handlers.clear()
|
|
|
|
|
uv_logger.propagate = True
|
|
|
|
|
|
2026-02-27 19:51:37 -05:00
|
|
|
if settings.error_log_enabled:
|
|
|
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
|
|
|
|
|
|
log_dir = Path(settings.repo_root) / settings.error_log_dir
|
|
|
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
error_file = log_dir / "errors.log"
|
|
|
|
|
|
|
|
|
|
file_handler = RotatingFileHandler(
|
|
|
|
|
error_file,
|
|
|
|
|
maxBytes=settings.error_log_max_bytes,
|
|
|
|
|
backupCount=settings.error_log_backup_count,
|
|
|
|
|
)
|
|
|
|
|
file_handler.setLevel(logging.ERROR)
|
|
|
|
|
file_handler.setFormatter(
|
|
|
|
|
logging.Formatter(
|
|
|
|
|
"%(asctime)s %(levelname)-8s %(name)s — %(message)s\n"
|
|
|
|
|
" File: %(pathname)s:%(lineno)d\n"
|
|
|
|
|
" Function: %(funcName)s",
|
|
|
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_configure_logging()
|
2026-02-19 19:31:48 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-02-19 19:05:01 +00:00
|
|
|
BASE_DIR = Path(__file__).parent
|
|
|
|
|
PROJECT_ROOT = BASE_DIR.parent.parent
|
|
|
|
|
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
_BRIEFING_INTERVAL_HOURS = 6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _briefing_scheduler() -> None:
|
2026-02-28 11:07:19 -05:00
|
|
|
"""Background task: regenerate Timmy's briefing every 6 hours."""
|
2026-02-26 22:07:41 +00:00
|
|
|
from infrastructure.notifications.push import notify_briefing_ready
|
2026-03-08 12:50:44 -04:00
|
|
|
from timmy.briefing import engine as briefing_engine
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
await asyncio.sleep(2)
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
if briefing_engine.needs_refresh():
|
|
|
|
|
logger.info("Generating morning briefing…")
|
|
|
|
|
briefing = briefing_engine.generate()
|
|
|
|
|
await notify_briefing_ready(briefing)
|
|
|
|
|
else:
|
|
|
|
|
logger.info("Briefing is fresh; skipping generation.")
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error("Briefing scheduler error: %s", exc)
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(_BRIEFING_INTERVAL_HOURS * 3600)
|
|
|
|
|
|
|
|
|
|
|
2026-03-11 08:47:57 -04:00
|
|
|
async def _thinking_scheduler() -> None:
|
|
|
|
|
"""Background task: execute Timmy's thinking cycle every N seconds."""
|
|
|
|
|
from timmy.thinking import thinking_engine
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(5) # Stagger after briefing scheduler
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
if settings.thinking_enabled:
|
2026-03-19 20:18:31 -04:00
|
|
|
await asyncio.wait_for(
|
|
|
|
|
thinking_engine.think_once(),
|
|
|
|
|
timeout=settings.thinking_timeout_seconds,
|
|
|
|
|
)
|
|
|
|
|
except TimeoutError:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"Thinking cycle timed out after %ds — Ollama may be unresponsive",
|
|
|
|
|
settings.thinking_timeout_seconds,
|
|
|
|
|
)
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
raise
|
2026-03-11 08:47:57 -04:00
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error("Thinking scheduler error: %s", exc)
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(settings.thinking_interval_seconds)
|
|
|
|
|
|
|
|
|
|
|
feat: add Loop QA self-testing framework
Structured self-test framework that probes 6 capabilities (tool use,
multistep planning, memory read/write, self-coding, lightning econ) in
round-robin. Reuses existing infra: event_log for persistence,
create_task() for upgrade proposals, capture_error() for crash handling,
and in-memory circuit breaker for failure tracking.
- src/timmy/loop_qa.py: Capability enum, 6 async probes, orchestrator
- src/dashboard/routes/loop_qa.py: JSON + HTMX health endpoints
- HTMX partial polls every 30s on the health panel
- Background scheduler in app.py lifespan
- 25 tests covering probes, orchestrator, health snapshot, routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:33:16 -04:00
|
|
|
async def _loop_qa_scheduler() -> None:
|
|
|
|
|
"""Background task: run capability self-tests on a separate timer.
|
|
|
|
|
|
|
|
|
|
Independent of the thinking loop — runs every N thinking ticks
|
|
|
|
|
to probe subsystems and detect degradation.
|
|
|
|
|
"""
|
|
|
|
|
from timmy.loop_qa import loop_qa_orchestrator
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(10) # Stagger after thinking scheduler
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
if settings.loop_qa_enabled:
|
2026-03-19 20:18:31 -04:00
|
|
|
result = await asyncio.wait_for(
|
|
|
|
|
loop_qa_orchestrator.run_next_test(),
|
|
|
|
|
timeout=settings.thinking_timeout_seconds,
|
|
|
|
|
)
|
feat: add Loop QA self-testing framework
Structured self-test framework that probes 6 capabilities (tool use,
multistep planning, memory read/write, self-coding, lightning econ) in
round-robin. Reuses existing infra: event_log for persistence,
create_task() for upgrade proposals, capture_error() for crash handling,
and in-memory circuit breaker for failure tracking.
- src/timmy/loop_qa.py: Capability enum, 6 async probes, orchestrator
- src/dashboard/routes/loop_qa.py: JSON + HTMX health endpoints
- HTMX partial polls every 30s on the health panel
- Background scheduler in app.py lifespan
- 25 tests covering probes, orchestrator, health snapshot, routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:33:16 -04:00
|
|
|
if result:
|
|
|
|
|
status = "PASS" if result["success"] else "FAIL"
|
|
|
|
|
logger.info(
|
|
|
|
|
"Loop QA [%s]: %s — %s",
|
|
|
|
|
result["capability"],
|
|
|
|
|
status,
|
|
|
|
|
result.get("details", "")[:80],
|
|
|
|
|
)
|
2026-03-19 20:18:31 -04:00
|
|
|
except TimeoutError:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"Loop QA test timed out after %ds",
|
|
|
|
|
settings.thinking_timeout_seconds,
|
|
|
|
|
)
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
raise
|
feat: add Loop QA self-testing framework
Structured self-test framework that probes 6 capabilities (tool use,
multistep planning, memory read/write, self-coding, lightning econ) in
round-robin. Reuses existing infra: event_log for persistence,
create_task() for upgrade proposals, capture_error() for crash handling,
and in-memory circuit breaker for failure tracking.
- src/timmy/loop_qa.py: Capability enum, 6 async probes, orchestrator
- src/dashboard/routes/loop_qa.py: JSON + HTMX health endpoints
- HTMX partial polls every 30s on the health panel
- Background scheduler in app.py lifespan
- 25 tests covering probes, orchestrator, health snapshot, routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:33:16 -04:00
|
|
|
except Exception as exc:
|
|
|
|
|
logger.error("Loop QA scheduler error: %s", exc)
|
|
|
|
|
|
|
|
|
|
interval = settings.thinking_interval_seconds * settings.loop_qa_interval_ticks
|
|
|
|
|
await asyncio.sleep(interval)
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 22:22:02 -04:00
|
|
|
_PRESENCE_POLL_SECONDS = 30
|
|
|
|
|
_PRESENCE_INITIAL_DELAY = 3
|
|
|
|
|
|
|
|
|
|
_SYNTHESIZED_STATE: dict = {
|
|
|
|
|
"version": 1,
|
|
|
|
|
"liveness": None,
|
|
|
|
|
"current_focus": "",
|
|
|
|
|
"mood": "idle",
|
|
|
|
|
"active_threads": [],
|
|
|
|
|
"recent_events": [],
|
|
|
|
|
"concerns": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _presence_watcher() -> None:
|
|
|
|
|
"""Background task: watch ~/.timmy/presence.json and broadcast changes via WS.
|
|
|
|
|
|
|
|
|
|
Polls the file every 30 seconds (matching Timmy's write cadence).
|
|
|
|
|
If the file doesn't exist, broadcasts a synthesised idle state.
|
|
|
|
|
"""
|
|
|
|
|
from infrastructure.ws_manager.handler import ws_manager as ws_mgr
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(_PRESENCE_INITIAL_DELAY) # Stagger after other schedulers
|
|
|
|
|
|
|
|
|
|
last_mtime: float = 0.0
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
2026-03-18 22:33:06 -04:00
|
|
|
if PRESENCE_FILE.exists():
|
|
|
|
|
mtime = PRESENCE_FILE.stat().st_mtime
|
2026-03-18 22:22:02 -04:00
|
|
|
if mtime != last_mtime:
|
|
|
|
|
last_mtime = mtime
|
2026-03-18 22:33:06 -04:00
|
|
|
raw = await asyncio.to_thread(PRESENCE_FILE.read_text)
|
2026-03-18 22:22:02 -04:00
|
|
|
state = json.loads(raw)
|
|
|
|
|
await ws_mgr.broadcast("timmy_state", state)
|
|
|
|
|
else:
|
|
|
|
|
# File absent — broadcast synthesised state once per cycle
|
|
|
|
|
if last_mtime != -1.0:
|
|
|
|
|
last_mtime = -1.0
|
|
|
|
|
await ws_mgr.broadcast("timmy_state", _SYNTHESIZED_STATE)
|
|
|
|
|
except json.JSONDecodeError as exc:
|
|
|
|
|
logger.warning("presence.json parse error: %s", exc)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning("Presence watcher error: %s", exc)
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(_PRESENCE_POLL_SECONDS)
|
|
|
|
|
|
|
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
async def _start_chat_integrations_background() -> None:
|
|
|
|
|
"""Background task: start chat integrations without blocking startup."""
|
2026-02-28 15:01:48 -05:00
|
|
|
from integrations.chat_bridge.registry import platform_registry
|
2026-03-08 12:50:44 -04:00
|
|
|
from integrations.chat_bridge.vendors.discord import discord_bot
|
|
|
|
|
from integrations.telegram_bot.bot import telegram_bot
|
2026-02-28 15:01:48 -05:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
await asyncio.sleep(0.5)
|
2026-02-28 15:01:48 -05:00
|
|
|
|
|
|
|
|
# Register Discord in the platform registry
|
|
|
|
|
platform_registry.register(discord_bot)
|
2026-03-02 13:17:38 -05:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
if settings.telegram_token:
|
2026-02-27 19:51:37 -05:00
|
|
|
try:
|
2026-02-28 11:07:19 -05:00
|
|
|
await telegram_bot.start()
|
|
|
|
|
logger.info("Telegram bot started")
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning("Failed to start Telegram bot: %s", exc)
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("Telegram: no token configured, skipping")
|
2026-02-27 01:52:42 -05:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
if settings.discord_token or discord_bot.load_token():
|
|
|
|
|
try:
|
|
|
|
|
await discord_bot.start()
|
|
|
|
|
logger.info("Discord bot started")
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning("Failed to start Discord bot: %s", exc)
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("Discord: no token configured, skipping")
|
2026-02-27 01:52:42 -05:00
|
|
|
|
feat: Phase 1 autonomy upgrades — introspection, heartbeat, source tagging, Discord auto-detect (#101)
UC-01: Live System Introspection Tool
- Add get_task_queue_status(), get_agent_roster(), get_live_system_status()
to timmy/tools_intro with graceful degradation
- Enhanced get_memory_status() with line counts, section headers, vault
directory listing, semantic memory row count, self-coding journal stats
- Register system_status MCP tool (creative/tools/system_status.py)
- Add system_status to Timmy's tool list + Hard Rule #7
UC-02: Fix Offline Status Bug
- Add registry.heartbeat() calls in task_processor run_loop() and
process_single_task() so health endpoint reflects actual agent status
- health.py now consults swarm registry instead of Ollama connectivity
UC-03: Message Source Tagging
- Add source field to Message dataclass (default "browser")
- Tag all message_log.append() calls: browser, api, system
- Include source in /api/chat/history response
UC-04: Discord Token Auto-Detection & Docker Fix
- Add _discord_token_watcher() background coroutine that polls every 30s
for DISCORD_TOKEN in env vars, .env file, or state file
- Add --extras discord to all three Dockerfiles (main, dashboard, test)
All 26 Phase 1 tests pass in Docker (make test-docker).
Full suite: 1889 passed, 77 skipped, 0 failed.
Co-authored-by: Alexander Payne <apayne@MM.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:24 -05:00
|
|
|
# If Discord isn't connected yet, start a watcher that polls for the
|
|
|
|
|
# token to appear in the environment or .env file.
|
|
|
|
|
if discord_bot.state.name != "CONNECTED":
|
|
|
|
|
asyncio.create_task(_discord_token_watcher())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _discord_token_watcher() -> None:
|
|
|
|
|
"""Poll for DISCORD_TOKEN appearing in env or .env and auto-start Discord bot."""
|
|
|
|
|
from integrations.chat_bridge.vendors.discord import discord_bot
|
|
|
|
|
|
2026-03-11 10:37:20 -04:00
|
|
|
# Don't poll if discord.py isn't even installed
|
|
|
|
|
try:
|
|
|
|
|
import discord as _discord_check # noqa: F401
|
|
|
|
|
except ImportError:
|
|
|
|
|
logger.debug("discord.py not installed — token watcher exiting")
|
|
|
|
|
return
|
|
|
|
|
|
feat: Phase 1 autonomy upgrades — introspection, heartbeat, source tagging, Discord auto-detect (#101)
UC-01: Live System Introspection Tool
- Add get_task_queue_status(), get_agent_roster(), get_live_system_status()
to timmy/tools_intro with graceful degradation
- Enhanced get_memory_status() with line counts, section headers, vault
directory listing, semantic memory row count, self-coding journal stats
- Register system_status MCP tool (creative/tools/system_status.py)
- Add system_status to Timmy's tool list + Hard Rule #7
UC-02: Fix Offline Status Bug
- Add registry.heartbeat() calls in task_processor run_loop() and
process_single_task() so health endpoint reflects actual agent status
- health.py now consults swarm registry instead of Ollama connectivity
UC-03: Message Source Tagging
- Add source field to Message dataclass (default "browser")
- Tag all message_log.append() calls: browser, api, system
- Include source in /api/chat/history response
UC-04: Discord Token Auto-Detection & Docker Fix
- Add _discord_token_watcher() background coroutine that polls every 30s
for DISCORD_TOKEN in env vars, .env file, or state file
- Add --extras discord to all three Dockerfiles (main, dashboard, test)
All 26 Phase 1 tests pass in Docker (make test-docker).
Full suite: 1889 passed, 77 skipped, 0 failed.
Co-authored-by: Alexander Payne <apayne@MM.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:24 -05:00
|
|
|
while True:
|
|
|
|
|
await asyncio.sleep(30)
|
|
|
|
|
|
|
|
|
|
if discord_bot.state.name == "CONNECTED":
|
|
|
|
|
return # Already running — stop watching
|
|
|
|
|
|
2026-03-08 12:50:44 -04:00
|
|
|
# 1. Check settings (pydantic-settings reads env on instantiation;
|
|
|
|
|
# hot-reload is handled by re-reading .env below)
|
|
|
|
|
token = settings.discord_token
|
feat: Phase 1 autonomy upgrades — introspection, heartbeat, source tagging, Discord auto-detect (#101)
UC-01: Live System Introspection Tool
- Add get_task_queue_status(), get_agent_roster(), get_live_system_status()
to timmy/tools_intro with graceful degradation
- Enhanced get_memory_status() with line counts, section headers, vault
directory listing, semantic memory row count, self-coding journal stats
- Register system_status MCP tool (creative/tools/system_status.py)
- Add system_status to Timmy's tool list + Hard Rule #7
UC-02: Fix Offline Status Bug
- Add registry.heartbeat() calls in task_processor run_loop() and
process_single_task() so health endpoint reflects actual agent status
- health.py now consults swarm registry instead of Ollama connectivity
UC-03: Message Source Tagging
- Add source field to Message dataclass (default "browser")
- Tag all message_log.append() calls: browser, api, system
- Include source in /api/chat/history response
UC-04: Discord Token Auto-Detection & Docker Fix
- Add _discord_token_watcher() background coroutine that polls every 30s
for DISCORD_TOKEN in env vars, .env file, or state file
- Add --extras discord to all three Dockerfiles (main, dashboard, test)
All 26 Phase 1 tests pass in Docker (make test-docker).
Full suite: 1889 passed, 77 skipped, 0 failed.
Co-authored-by: Alexander Payne <apayne@MM.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:24 -05:00
|
|
|
|
|
|
|
|
# 2. Re-read .env file for hot-reload
|
|
|
|
|
if not token:
|
|
|
|
|
try:
|
|
|
|
|
from dotenv import dotenv_values
|
|
|
|
|
|
|
|
|
|
env_path = Path(settings.repo_root) / ".env"
|
|
|
|
|
if env_path.exists():
|
|
|
|
|
vals = dotenv_values(env_path)
|
|
|
|
|
token = vals.get("DISCORD_TOKEN", "")
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass # python-dotenv not installed
|
|
|
|
|
|
|
|
|
|
# 3. Check state file (written by /discord/setup)
|
|
|
|
|
if not token:
|
|
|
|
|
token = discord_bot.load_token() or ""
|
|
|
|
|
|
|
|
|
|
if token:
|
|
|
|
|
try:
|
2026-03-11 20:33:59 -04:00
|
|
|
logger.info(
|
|
|
|
|
"Discord watcher: token found, attempting start (state=%s)",
|
|
|
|
|
discord_bot.state.name,
|
|
|
|
|
)
|
feat: Phase 1 autonomy upgrades — introspection, heartbeat, source tagging, Discord auto-detect (#101)
UC-01: Live System Introspection Tool
- Add get_task_queue_status(), get_agent_roster(), get_live_system_status()
to timmy/tools_intro with graceful degradation
- Enhanced get_memory_status() with line counts, section headers, vault
directory listing, semantic memory row count, self-coding journal stats
- Register system_status MCP tool (creative/tools/system_status.py)
- Add system_status to Timmy's tool list + Hard Rule #7
UC-02: Fix Offline Status Bug
- Add registry.heartbeat() calls in task_processor run_loop() and
process_single_task() so health endpoint reflects actual agent status
- health.py now consults swarm registry instead of Ollama connectivity
UC-03: Message Source Tagging
- Add source field to Message dataclass (default "browser")
- Tag all message_log.append() calls: browser, api, system
- Include source in /api/chat/history response
UC-04: Discord Token Auto-Detection & Docker Fix
- Add _discord_token_watcher() background coroutine that polls every 30s
for DISCORD_TOKEN in env vars, .env file, or state file
- Add --extras discord to all three Dockerfiles (main, dashboard, test)
All 26 Phase 1 tests pass in Docker (make test-docker).
Full suite: 1889 passed, 77 skipped, 0 failed.
Co-authored-by: Alexander Payne <apayne@MM.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:24 -05:00
|
|
|
success = await discord_bot.start(token=token)
|
|
|
|
|
if success:
|
|
|
|
|
logger.info("Discord bot auto-started (token detected)")
|
|
|
|
|
return # Done — stop watching
|
2026-03-11 20:33:59 -04:00
|
|
|
logger.warning(
|
|
|
|
|
"Discord watcher: start() returned False (state=%s)",
|
|
|
|
|
discord_bot.state.name,
|
|
|
|
|
)
|
feat: Phase 1 autonomy upgrades — introspection, heartbeat, source tagging, Discord auto-detect (#101)
UC-01: Live System Introspection Tool
- Add get_task_queue_status(), get_agent_roster(), get_live_system_status()
to timmy/tools_intro with graceful degradation
- Enhanced get_memory_status() with line counts, section headers, vault
directory listing, semantic memory row count, self-coding journal stats
- Register system_status MCP tool (creative/tools/system_status.py)
- Add system_status to Timmy's tool list + Hard Rule #7
UC-02: Fix Offline Status Bug
- Add registry.heartbeat() calls in task_processor run_loop() and
process_single_task() so health endpoint reflects actual agent status
- health.py now consults swarm registry instead of Ollama connectivity
UC-03: Message Source Tagging
- Add source field to Message dataclass (default "browser")
- Tag all message_log.append() calls: browser, api, system
- Include source in /api/chat/history response
UC-04: Discord Token Auto-Detection & Docker Fix
- Add _discord_token_watcher() background coroutine that polls every 30s
for DISCORD_TOKEN in env vars, .env file, or state file
- Add --extras discord to all three Dockerfiles (main, dashboard, test)
All 26 Phase 1 tests pass in Docker (make test-docker).
Full suite: 1889 passed, 77 skipped, 0 failed.
Co-authored-by: Alexander Payne <apayne@MM.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:24 -05:00
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning("Discord auto-start failed: %s", exc)
|
|
|
|
|
|
2026-02-27 01:52:42 -05:00
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
def _startup_init() -> None:
|
|
|
|
|
"""Validate config and enable event persistence."""
|
2026-03-08 16:07:02 -04:00
|
|
|
from config import validate_startup
|
|
|
|
|
|
|
|
|
|
validate_startup()
|
|
|
|
|
|
|
|
|
|
from infrastructure.events.bus import init_event_bus_persistence
|
|
|
|
|
|
|
|
|
|
init_event_bus_persistence()
|
|
|
|
|
|
|
|
|
|
from spark.engine import get_spark_engine
|
2026-03-08 12:50:44 -04:00
|
|
|
|
2026-03-08 16:07:02 -04:00
|
|
|
if get_spark_engine().enabled:
|
feat: integrate Spark Intelligence into Timmy swarm system
Adds a self-evolving cognitive layer inspired by vibeship-spark-intelligence,
adapted for Timmy's agent architecture. Spark captures swarm events, runs
EIDOS prediction-evaluation loops, consolidates memories, and generates
advisory recommendations — all backed by SQLite consistent with existing
patterns.
New modules:
- spark/memory.py — event capture with importance scoring + memory consolidation
- spark/eidos.py — EIDOS cognitive loop (predict → observe → evaluate → learn)
- spark/advisor.py — ranked advisory generation from accumulated intelligence
- spark/engine.py — top-level API wiring all subsystems together
Dashboard:
- /spark/ui — full Spark Intelligence dashboard (3-column: status/advisories,
predictions/memories, event timeline) with HTMX auto-refresh
- /spark — JSON API for programmatic access
- SPARK link added to navigation header
Integration:
- Coordinator hooks emit Spark events on task post, bid, assign, complete, fail
- EIDOS predictions generated when tasks are posted, evaluated on completion
- Memory consolidation triggers when agents accumulate enough outcomes
- SPARK_ENABLED config toggle (default: true)
Tests: 47 new tests covering all Spark subsystems + dashboard routes.
Full suite: 538 tests passing.
https://claude.ai/code/session_01KJm6jQkNi3aA3yoQJn636c
2026-02-24 15:51:15 +00:00
|
|
|
logger.info("Spark Intelligence active — event capture enabled")
|
|
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
|
|
|
|
|
def _startup_background_tasks() -> list[asyncio.Task]:
|
|
|
|
|
"""Spawn all recurring background tasks (non-blocking)."""
|
2026-03-23 18:09:29 +00:00
|
|
|
bg_tasks = [
|
2026-03-19 19:30:32 -04:00
|
|
|
asyncio.create_task(_briefing_scheduler()),
|
|
|
|
|
asyncio.create_task(_thinking_scheduler()),
|
|
|
|
|
asyncio.create_task(_loop_qa_scheduler()),
|
|
|
|
|
asyncio.create_task(_presence_watcher()),
|
|
|
|
|
asyncio.create_task(_start_chat_integrations_background()),
|
|
|
|
|
]
|
2026-03-23 18:09:29 +00:00
|
|
|
try:
|
|
|
|
|
from timmy.paperclip import start_paperclip_poller
|
|
|
|
|
bg_tasks.append(asyncio.create_task(start_paperclip_poller()))
|
|
|
|
|
logger.info("Paperclip poller started")
|
|
|
|
|
except ImportError:
|
|
|
|
|
logger.debug("Paperclip module not found, skipping poller")
|
|
|
|
|
|
|
|
|
|
return bg_tasks
|
2026-03-19 19:30:32 -04:00
|
|
|
|
|
|
|
|
|
2026-03-20 17:44:32 -04:00
|
|
|
def _try_prune(label: str, prune_fn, days: int) -> None:
|
|
|
|
|
"""Run a prune function, log results, swallow errors."""
|
|
|
|
|
try:
|
|
|
|
|
pruned = prune_fn()
|
|
|
|
|
if pruned:
|
|
|
|
|
logger.info(
|
|
|
|
|
"%s auto-prune: removed %d entries older than %d days",
|
|
|
|
|
label,
|
|
|
|
|
pruned,
|
|
|
|
|
days,
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("%s auto-prune skipped: %s", label, exc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_vault_size() -> None:
|
|
|
|
|
"""Warn if the memory vault exceeds the configured size limit."""
|
|
|
|
|
try:
|
|
|
|
|
vault_path = Path(settings.repo_root) / "memory" / "notes"
|
|
|
|
|
if vault_path.exists():
|
|
|
|
|
total_bytes = sum(f.stat().st_size for f in vault_path.rglob("*") if f.is_file())
|
|
|
|
|
total_mb = total_bytes / (1024 * 1024)
|
|
|
|
|
if total_mb > settings.memory_vault_max_mb:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"Memory vault (%.1f MB) exceeds limit (%d MB) — consider archiving old notes",
|
|
|
|
|
total_mb,
|
|
|
|
|
settings.memory_vault_max_mb,
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("Vault size check skipped: %s", exc)
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
def _startup_pruning() -> None:
|
|
|
|
|
"""Auto-prune old memories, thoughts, and events on startup."""
|
2026-03-07 22:34:30 -05:00
|
|
|
if settings.memory_prune_days > 0:
|
2026-03-20 17:44:32 -04:00
|
|
|
from timmy.memory_system import prune_memories
|
2026-03-08 12:50:44 -04:00
|
|
|
|
2026-03-20 17:44:32 -04:00
|
|
|
_try_prune(
|
|
|
|
|
"Memory",
|
|
|
|
|
lambda: prune_memories(
|
2026-03-07 22:34:30 -05:00
|
|
|
older_than_days=settings.memory_prune_days,
|
|
|
|
|
keep_facts=settings.memory_prune_keep_facts,
|
2026-03-20 17:44:32 -04:00
|
|
|
),
|
|
|
|
|
settings.memory_prune_days,
|
|
|
|
|
)
|
2026-03-07 22:34:30 -05:00
|
|
|
|
2026-03-12 11:23:18 -04:00
|
|
|
if settings.thoughts_prune_days > 0:
|
2026-03-20 17:44:32 -04:00
|
|
|
from timmy.thinking import thinking_engine
|
2026-03-12 11:23:18 -04:00
|
|
|
|
2026-03-20 17:44:32 -04:00
|
|
|
_try_prune(
|
|
|
|
|
"Thought",
|
|
|
|
|
lambda: thinking_engine.prune_old_thoughts(
|
2026-03-12 11:23:18 -04:00
|
|
|
keep_days=settings.thoughts_prune_days,
|
|
|
|
|
keep_min=settings.thoughts_prune_keep_min,
|
2026-03-20 17:44:32 -04:00
|
|
|
),
|
|
|
|
|
settings.thoughts_prune_days,
|
|
|
|
|
)
|
2026-03-12 11:23:18 -04:00
|
|
|
|
|
|
|
|
if settings.events_prune_days > 0:
|
2026-03-20 17:44:32 -04:00
|
|
|
from swarm.event_log import prune_old_events
|
2026-03-12 11:23:18 -04:00
|
|
|
|
2026-03-20 17:44:32 -04:00
|
|
|
_try_prune(
|
|
|
|
|
"Event",
|
|
|
|
|
lambda: prune_old_events(
|
2026-03-12 11:23:18 -04:00
|
|
|
keep_days=settings.events_prune_days,
|
|
|
|
|
keep_min=settings.events_prune_keep_min,
|
2026-03-20 17:44:32 -04:00
|
|
|
),
|
|
|
|
|
settings.events_prune_days,
|
|
|
|
|
)
|
2026-03-12 11:23:18 -04:00
|
|
|
|
2026-03-07 22:34:30 -05:00
|
|
|
if settings.memory_vault_max_mb > 0:
|
2026-03-20 17:44:32 -04:00
|
|
|
_check_vault_size()
|
2026-03-07 22:34:30 -05:00
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
|
|
|
|
|
async def _shutdown_cleanup(
|
|
|
|
|
bg_tasks: list[asyncio.Task],
|
|
|
|
|
workshop_heartbeat,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Stop chat bots, MCP sessions, heartbeat, and cancel background tasks."""
|
|
|
|
|
from integrations.chat_bridge.vendors.discord import discord_bot
|
|
|
|
|
from integrations.telegram_bot.bot import telegram_bot
|
|
|
|
|
|
|
|
|
|
await discord_bot.stop()
|
|
|
|
|
await telegram_bot.stop()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from timmy.mcp_tools import close_mcp_sessions
|
|
|
|
|
|
|
|
|
|
await close_mcp_sessions()
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("MCP shutdown: %s", exc)
|
|
|
|
|
|
|
|
|
|
await workshop_heartbeat.stop()
|
|
|
|
|
|
|
|
|
|
for task in bg_tasks:
|
|
|
|
|
task.cancel()
|
|
|
|
|
try:
|
|
|
|
|
await task
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
"""Application lifespan manager with non-blocking startup."""
|
|
|
|
|
_startup_init()
|
|
|
|
|
bg_tasks = _startup_background_tasks()
|
|
|
|
|
_startup_pruning()
|
|
|
|
|
|
2026-03-19 00:25:11 -04:00
|
|
|
# Start Workshop presence heartbeat with WS relay
|
|
|
|
|
from dashboard.routes.world import broadcast_world_state
|
2026-03-18 22:07:32 -04:00
|
|
|
from timmy.workshop_state import WorkshopHeartbeat
|
|
|
|
|
|
2026-03-19 00:25:11 -04:00
|
|
|
workshop_heartbeat = WorkshopHeartbeat(on_change=broadcast_world_state)
|
2026-03-18 22:07:32 -04:00
|
|
|
await workshop_heartbeat.start()
|
|
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
# Register session logger with error capture
|
2026-03-15 12:52:18 -04:00
|
|
|
try:
|
|
|
|
|
from infrastructure.error_capture import register_error_recorder
|
|
|
|
|
from timmy.session_logger import get_session_logger
|
|
|
|
|
|
|
|
|
|
register_error_recorder(get_session_logger().record_error)
|
|
|
|
|
except Exception:
|
2026-03-19 19:05:02 -04:00
|
|
|
logger.debug("Failed to register error recorder")
|
2026-03-15 12:52:18 -04:00
|
|
|
|
2026-03-05 19:45:38 -05:00
|
|
|
logger.info("✓ Dashboard ready for requests")
|
2026-02-25 01:11:14 +00:00
|
|
|
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
yield
|
2026-02-22 17:16:12 +00:00
|
|
|
|
2026-03-19 19:30:32 -04:00
|
|
|
await _shutdown_cleanup(bg_tasks, workshop_heartbeat)
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
|
|
|
|
|
|
2026-02-19 19:31:48 +00:00
|
|
|
app = FastAPI(
|
2026-03-05 19:45:38 -05:00
|
|
|
title="Mission Control",
|
2026-02-19 19:31:48 +00:00
|
|
|
version="1.0.0",
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
lifespan=lifespan,
|
2026-02-28 11:07:19 -05:00
|
|
|
docs_url="/docs",
|
|
|
|
|
openapi_url="/openapi.json",
|
2026-02-19 19:31:48 +00:00
|
|
|
)
|
2026-02-19 19:05:01 +00:00
|
|
|
|
2026-03-01 11:50:34 -05:00
|
|
|
|
|
|
|
|
def _get_cors_origins() -> list[str]:
|
2026-03-21 14:56:43 +00:00
|
|
|
"""Get CORS origins from settings, rejecting wildcards in production.
|
|
|
|
|
|
|
|
|
|
Adds matrix_frontend_url when configured. Always allows Tailscale IPs
|
|
|
|
|
(100.x.x.x range) for development convenience.
|
|
|
|
|
"""
|
|
|
|
|
origins = list(settings.cors_origins)
|
|
|
|
|
|
|
|
|
|
# Strip wildcards in production (security)
|
2026-03-19 14:57:36 -04:00
|
|
|
if "*" in origins and not settings.debug:
|
|
|
|
|
logger.warning(
|
2026-03-19 15:05:27 -04:00
|
|
|
"Wildcard '*' in CORS_ORIGINS stripped in production — "
|
|
|
|
|
"set explicit origins via CORS_ORIGINS env var"
|
2026-03-19 14:57:36 -04:00
|
|
|
)
|
2026-03-19 15:05:27 -04:00
|
|
|
origins = [o for o in origins if o != "*"]
|
2026-03-21 14:56:43 +00:00
|
|
|
|
|
|
|
|
# Add Matrix frontend URL if configured
|
|
|
|
|
if settings.matrix_frontend_url:
|
|
|
|
|
url = settings.matrix_frontend_url.strip()
|
|
|
|
|
if url and url not in origins:
|
|
|
|
|
origins.append(url)
|
|
|
|
|
logger.debug("Added Matrix frontend to CORS: %s", url)
|
|
|
|
|
|
2026-03-01 11:50:34 -05:00
|
|
|
return origins
|
|
|
|
|
|
|
|
|
|
|
2026-03-21 14:56:43 +00:00
|
|
|
# Pattern to match Tailscale IPs (100.x.x.x) for CORS origin regex
|
|
|
|
|
_TAILSCALE_IP_PATTERN = re.compile(r"^https?://100\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?$")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_tailscale_origin(origin: str) -> bool:
|
|
|
|
|
"""Check if origin is a Tailscale IP (100.x.x.x range)."""
|
|
|
|
|
return bool(_TAILSCALE_IP_PATTERN.match(origin))
|
|
|
|
|
|
|
|
|
|
|
2026-03-04 07:58:39 -05:00
|
|
|
# Add dedicated middleware in correct order
|
|
|
|
|
# 1. Logging (outermost to capture everything)
|
|
|
|
|
app.add_middleware(RequestLoggingMiddleware, skip_paths=["/health"])
|
2026-03-01 11:50:34 -05:00
|
|
|
|
2026-03-21 16:23:16 +00:00
|
|
|
# 2. Rate Limiting (before security to prevent abuse early)
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
RateLimitMiddleware,
|
|
|
|
|
path_prefixes=["/api/matrix/"],
|
|
|
|
|
requests_per_minute=30,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 3. Security Headers
|
2026-03-08 12:50:44 -04:00
|
|
|
app.add_middleware(SecurityHeadersMiddleware, production=not settings.debug)
|
2026-03-01 11:50:34 -05:00
|
|
|
|
2026-03-21 16:23:16 +00:00
|
|
|
# 4. CSRF Protection
|
2026-03-04 07:58:39 -05:00
|
|
|
app.add_middleware(CSRFMiddleware)
|
2026-03-01 11:50:34 -05:00
|
|
|
|
2026-03-04 07:58:39 -05:00
|
|
|
# 4. Standard FastAPI middleware
|
2026-03-11 10:37:20 -04:00
|
|
|
# In development, allow all hosts (Tailscale IPs, MagicDNS, etc.)
|
|
|
|
|
_trusted = settings.trusted_hosts if settings.timmy_env == "production" else ["*"]
|
2026-03-01 11:50:34 -05:00
|
|
|
app.add_middleware(
|
|
|
|
|
TrustedHostMiddleware,
|
2026-03-11 10:37:20 -04:00
|
|
|
allowed_hosts=_trusted,
|
2026-03-01 11:50:34 -05:00
|
|
|
)
|
|
|
|
|
|
feat: replace GitHub page with embedded Timmy chat interface
Replaces the marketing landing page with a minimal, full-screen chat
interface that connects to a running Timmy instance. Mobile-first design
with single vertical scroll direction, looping scroll, no zoom, no
buttons — just type and press Enter to talk to Timmy.
- docs/index.html: full rewrite as a clean chat UI with dark terminal
theme, looping infinite scroll, markdown rendering, connection status,
and /connect, /clear, /help slash commands
- src/dashboard/app.py: add CORS middleware so the GitHub Pages site can
reach a local Timmy server cross-origin
- src/config.py: add cors_origins setting (defaults to ["*"])
https://claude.ai/code/session_01AWLxg6KDWsfCATiuvsRMGr
2026-02-27 00:35:33 +00:00
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
2026-03-01 11:50:34 -05:00
|
|
|
allow_origins=_get_cors_origins(),
|
2026-03-21 14:56:43 +00:00
|
|
|
allow_origin_regex=r"https?://100\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?",
|
feat: replace GitHub page with embedded Timmy chat interface
Replaces the marketing landing page with a minimal, full-screen chat
interface that connects to a running Timmy instance. Mobile-first design
with single vertical scroll direction, looping scroll, no zoom, no
buttons — just type and press Enter to talk to Timmy.
- docs/index.html: full rewrite as a clean chat UI with dark terminal
theme, looping infinite scroll, markdown rendering, connection status,
and /connect, /clear, /help slash commands
- src/dashboard/app.py: add CORS middleware so the GitHub Pages site can
reach a local Timmy server cross-origin
- src/config.py: add cors_origins setting (defaults to ["*"])
https://claude.ai/code/session_01AWLxg6KDWsfCATiuvsRMGr
2026-02-27 00:35:33 +00:00
|
|
|
allow_credentials=True,
|
2026-03-01 11:50:34 -05:00
|
|
|
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
|
|
|
allow_headers=["Content-Type", "Authorization"],
|
feat: replace GitHub page with embedded Timmy chat interface
Replaces the marketing landing page with a minimal, full-screen chat
interface that connects to a running Timmy instance. Mobile-first design
with single vertical scroll direction, looping scroll, no zoom, no
buttons — just type and press Enter to talk to Timmy.
- docs/index.html: full rewrite as a clean chat UI with dark terminal
theme, looping infinite scroll, markdown rendering, connection status,
and /connect, /clear, /help slash commands
- src/dashboard/app.py: add CORS middleware so the GitHub Pages site can
reach a local Timmy server cross-origin
- src/config.py: add cors_origins setting (defaults to ["*"])
https://claude.ai/code/session_01AWLxg6KDWsfCATiuvsRMGr
2026-02-27 00:35:33 +00:00
|
|
|
)
|
|
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
# Mount static files
|
|
|
|
|
static_dir = PROJECT_ROOT / "static"
|
|
|
|
|
if static_dir.exists():
|
|
|
|
|
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
2026-02-26 23:58:53 -05:00
|
|
|
|
2026-03-05 18:56:52 -05:00
|
|
|
# Shared templates instance
|
|
|
|
|
from dashboard.templating import templates # noqa: E402
|
CI/CD Optimization: Guard Rails, Pre-commit Checks, and Test Fixes (#90)
* CI/CD Optimization: Guard Rails, Black Linting, and Pre-commit Hooks
- Fixed all test collection errors (Selenium imports, fixture paths, syntax)
- Implemented pre-commit hooks with Black formatting and isort
- Created comprehensive Makefile with test targets (unit, integration, functional, e2e)
- Added pytest.ini with marker definitions for test categorization
- Established guard rails to prevent future collection errors
- Wrapped optional dependencies (Selenium, MoviePy) in try-except blocks
- Added conftest_markers for automatic test categorization
This ensures a smooth development stream with:
- Fast feedback loops (pre-commit checks before push)
- Consistent code formatting (Black)
- Reliable CI/CD (no collection errors, proper test isolation)
- Clear test organization (unit, integration, functional, E2E)
* Fix CI/CD test failures:
- Export templates from dashboard.app
- Fix model name assertion in test_agent.py
- Fix platform-agnostic path resolution in test_path_resolution.py
- Skip Docker tests in test_docker_deployment.py if docker not available
- Fix test_model_fallback_chain logic in test_ollama_integration.py
* Add preventative pre-commit checks and Docker test skipif decorators:
- Create pre_commit_checks.py script for common CI failures
- Add skipif decorators to Docker tests
- Improve test robustness for CI environments
2026-02-28 11:36:50 -05:00
|
|
|
|
2026-02-28 11:07:19 -05:00
|
|
|
# Include routers
|
2026-02-19 19:05:01 +00:00
|
|
|
app.include_router(health_router)
|
|
|
|
|
app.include_router(agents_router)
|
feat: Mission Control v2 — swarm, L402, voice, marketplace, React dashboard
Major expansion of the Timmy Time Dashboard:
Backend modules:
- Swarm subsystem: registry, manager, bidder, coordinator, agent_runner, swarm_node, tasks, comms
- L402/Lightning: payment_handler, l402_proxy with HMAC macaroons
- Voice NLU: regex-based intent detection (chat, status, swarm, task, help, voice)
- Notifications: push notifier for swarm events
- Shortcuts: Siri Shortcuts iOS integration endpoints
- WebSocket: live dashboard event manager
- Inter-agent: agent-to-agent messaging layer
Dashboard routes:
- /swarm/* — swarm management and agent registry
- /marketplace — agent catalog with sat pricing
- /voice/* — voice command processing
- /mobile — mobile status endpoint
- /swarm/live — WebSocket live feed
React web dashboard (dashboard-web/):
- Sovereign Terminal design — dark theme with Bitcoin orange accents
- Three-column layout: status sidebar, workspace tabs, context panel
- Chat, Swarm, Tasks, Marketplace tab views
- JetBrains Mono typography, terminal aesthetic
- Framer Motion animations throughout
Tests: 228 passing (expanded from 93)
Includes Kimi's additional templates and QA work.
2026-02-21 12:57:38 -05:00
|
|
|
app.include_router(voice_router)
|
|
|
|
|
app.include_router(mobile_router)
|
feat(briefing): morning briefing + approval queue
Implements the Morning Briefing and Approval Queue feature — the first step
from tool to companion. Timmy now shows up before the owner asks.
New modules
-----------
• src/timmy/approvals.py — ApprovalItem dataclass, GOLDEN_TIMMY governance
constant, full SQLite CRUD (create / list / approve / reject / expire).
Items auto-expire after 7 days if not actioned.
• src/timmy/briefing.py — BriefingEngine that queries swarm activity and
chat history, calls Timmy's Agno agent for a prose summary, and caches
the result in SQLite (~/.timmy/briefings.db). get_or_generate() skips
regeneration if a fresh briefing (< 30 min) already exists.
New routes (src/dashboard/routes/briefing.py)
----------------------------------------------
GET /briefing — full briefing page
GET /briefing/approvals — HTMX partial: pending approval cards
POST /briefing/approvals/{id}/approve — approve via HTMX (no page reload)
POST /briefing/approvals/{id}/reject — reject via HTMX (no page reload)
New templates
-------------
• briefing.html — clean, mobile-first prose layout (max 680px)
• partials/approval_cards.html — list of approval cards
• partials/approval_card_single.html — single approval card with
Approve/Reject HTMX buttons
App wiring (src/dashboard/app.py)
----------------------------------
• Added asynccontextmanager lifespan with _briefing_scheduler background task.
Generates a briefing at startup and every 6 hours; skips if fresh.
Push notification hook (src/notifications/push.py)
---------------------------------------------------
• notify_briefing_ready(briefing) — logs + triggers local notifier.
Placeholder for APNs/Pushover wiring later.
Navigation
----------
• Added BRIEFING link to the header nav in base.html.
Tests
-----
• tests/test_approvals.py — 17 tests: GOLDEN_TIMMY, CRUD, expiry, ordering
• tests/test_briefing.py — 22 tests: dataclass, freshness, cache round-trip,
generate/get_or_generate, push notification hook
354 tests, 354 passing.
https://claude.ai/code/session_01D7p5w91KX3grBeioGiiGy8
2026-02-22 14:04:20 +00:00
|
|
|
app.include_router(briefing_router)
|
2026-02-22 17:16:12 +00:00
|
|
|
app.include_router(telegram_router)
|
feat: swarm E2E, MCP tools, timmy-serve L402, tests, notifications
Major Features:
- Auto-spawn persona agents (Echo, Forge, Seer) on app startup
- WebSocket broadcasts for real-time swarm UI updates
- MCP tool integration: web search, file I/O, shell, Python execution
- New /tools dashboard page showing agent capabilities
- Real timmy-serve start with L402 payment gating middleware
- Browser push notifications for briefings and task events
Tests:
- test_docker_agent.py: 9 tests for Docker agent runner
- test_swarm_integration_full.py: 18 E2E lifecycle tests
- Fixed all pytest warnings (436 tests, 0 warnings)
Improvements:
- Fixed coroutine warnings in coordinator broadcasts
- Fixed ResourceWarning for unclosed process pipes
- Added pytest-asyncio config to pyproject.toml
- Test isolation with proper event loop cleanup
2026-02-22 19:01:04 -05:00
|
|
|
app.include_router(tools_router)
|
feat: integrate Spark Intelligence into Timmy swarm system
Adds a self-evolving cognitive layer inspired by vibeship-spark-intelligence,
adapted for Timmy's agent architecture. Spark captures swarm events, runs
EIDOS prediction-evaluation loops, consolidates memories, and generates
advisory recommendations — all backed by SQLite consistent with existing
patterns.
New modules:
- spark/memory.py — event capture with importance scoring + memory consolidation
- spark/eidos.py — EIDOS cognitive loop (predict → observe → evaluate → learn)
- spark/advisor.py — ranked advisory generation from accumulated intelligence
- spark/engine.py — top-level API wiring all subsystems together
Dashboard:
- /spark/ui — full Spark Intelligence dashboard (3-column: status/advisories,
predictions/memories, event timeline) with HTMX auto-refresh
- /spark — JSON API for programmatic access
- SPARK link added to navigation header
Integration:
- Coordinator hooks emit Spark events on task post, bid, assign, complete, fail
- EIDOS predictions generated when tasks are posted, evaluated on completion
- Memory consolidation triggers when agents accumulate enough outcomes
- SPARK_ENABLED config toggle (default: true)
Tests: 47 new tests covering all Spark subsystems + dashboard routes.
Full suite: 538 tests passing.
https://claude.ai/code/session_01KJm6jQkNi3aA3yoQJn636c
2026-02-24 15:51:15 +00:00
|
|
|
app.include_router(spark_router)
|
2026-02-25 01:11:14 +00:00
|
|
|
app.include_router(discord_router)
|
feat: complete Event Log, Ledger, Memory, Cascade Router, Upgrade Queue, Activity Feed
This commit implements six major features:
1. Event Log System (src/swarm/event_log.py)
- SQLite-based audit trail for all swarm events
- Task lifecycle tracking (created, assigned, completed, failed)
- Agent lifecycle tracking (joined, left, status changes)
- Integrated with coordinator for automatic logging
- Dashboard page at /swarm/events
2. Lightning Ledger (src/lightning/ledger.py)
- Transaction tracking for Lightning Network payments
- Balance calculations (incoming, outgoing, net, available)
- Integrated with payment_handler for automatic logging
- Dashboard page at /lightning/ledger
3. Semantic Memory / Vector Store (src/memory/vector_store.py)
- Embedding-based similarity search for Echo agent
- Fallback to keyword matching if sentence-transformers unavailable
- Personal facts storage and retrieval
- Dashboard page at /memory
4. Cascade Router Integration (src/timmy/cascade_adapter.py)
- Automatic LLM failover between providers (Ollama → AirLLM → API)
- Circuit breaker pattern for failing providers
- Metrics tracking per provider (latency, error rates)
- Dashboard status page at /router/status
5. Self-Upgrade Approval Queue (src/upgrades/)
- State machine for self-modifications: proposed → approved/rejected → applied/failed
- Human approval required before applying changes
- Git integration for branch management
- Dashboard queue at /self-modify/queue
6. Real-Time Activity Feed (src/events/broadcaster.py)
- WebSocket-based live activity streaming
- Bridges event_log to dashboard clients
- Activity panel on /swarm/live
Tests:
- 101 unit tests passing
- 4 new E2E test files for Selenium testing
- Run with: SELENIUM_UI=1 pytest tests/functional/ -v --headed
Documentation:
- 6 ADRs (017-022) documenting architecture decisions
- Implementation summary in docs/IMPLEMENTATION_SUMMARY.md
- Architecture diagram in docs/architecture-v2.md
2026-02-26 08:01:01 -05:00
|
|
|
app.include_router(memory_router)
|
feat: add Grok (xAI) as opt-in premium backend with monetization
- Add GrokBackend class in src/timmy/backends.py with full sync/async
support, health checks, usage stats, and cost estimation in sats
- Add consult_grok tool to Timmy's toolkit for proactive Grok queries
- Extend cascade router with Grok provider type for failover chain
- Add Grok Mode toggle card to Mission Control dashboard (HTMX live)
- Add "Ask Grok" button on chat input for direct Grok queries
- Add /grok/* routes: status, toggle, chat, stats endpoints
- Integrate Lightning invoice generation for Grok usage monetization
- Add GROK_ENABLED, XAI_API_KEY, GROK_DEFAULT_MODEL, GROK_MAX_SATS_PER_QUERY,
GROK_FREE config settings via pydantic-settings
- Update .env.example and docker-compose.yml with Grok env vars
- Add 21 tests covering backend, tools, and route endpoints (all green)
Local-first ethos preserved: Grok is premium augmentation only,
disabled by default, and Lightning-payable when enabled.
https://claude.ai/code/session_01FygwN8wS8J6WGZ8FPb7XGV
2026-02-27 01:12:51 +00:00
|
|
|
app.include_router(grok_router)
|
feat: add custom weights, model registry, per-agent models, and reward scoring
Inspired by OpenClaw-RL's multi-model orchestration, this adds four
features for custom model management:
1. Custom model registry (infrastructure/models/registry.py) — SQLite-backed
registry for GGUF, safetensors, HF checkpoint, and Ollama models with
role-based lookups (general, reward, teacher, judge).
2. Per-agent model assignment — each swarm persona can use a different model
instead of sharing the global default. Resolved via registry assignment >
persona default > global default.
3. Runtime model management API (/api/v1/models) — REST endpoints to register,
list, assign, enable/disable, and remove custom models without restart.
Includes a dashboard page at /models.
4. Reward model scoring (PRM-style) — majority-vote quality evaluation of
agent outputs using a configurable reward model. Scores persist in SQLite
and feed into the swarm learner.
New config settings: custom_weights_dir, reward_model_enabled,
reward_model_name, reward_model_votes.
54 new tests covering registry CRUD, API endpoints, agent assignments,
role lookups, and reward scoring.
https://claude.ai/code/session_01V4iTozMwcE2gjfnCJdCugC
2026-02-27 01:08:03 +00:00
|
|
|
app.include_router(models_router)
|
|
|
|
|
app.include_router(models_api_router)
|
2026-02-26 23:58:53 -05:00
|
|
|
app.include_router(chat_api_router)
|
2026-03-18 18:20:14 -04:00
|
|
|
app.include_router(chat_api_v1_router)
|
2026-02-27 01:00:11 -05:00
|
|
|
app.include_router(thinking_router)
|
2026-03-02 11:46:40 -05:00
|
|
|
app.include_router(calm_router)
|
2026-03-07 23:21:30 -05:00
|
|
|
app.include_router(tasks_router)
|
|
|
|
|
app.include_router(work_orders_router)
|
feat: add Loop QA self-testing framework
Structured self-test framework that probes 6 capabilities (tool use,
multistep planning, memory read/write, self-coding, lightning econ) in
round-robin. Reuses existing infra: event_log for persistence,
create_task() for upgrade proposals, capture_error() for crash handling,
and in-memory circuit breaker for failure tracking.
- src/timmy/loop_qa.py: Capability enum, 6 async probes, orchestrator
- src/dashboard/routes/loop_qa.py: JSON + HTMX health endpoints
- HTMX partial polls every 30s on the health panel
- Background scheduler in app.py lifespan
- 25 tests covering probes, orchestrator, health snapshot, routes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:33:16 -04:00
|
|
|
app.include_router(loop_qa_router)
|
2026-03-04 17:15:46 -05:00
|
|
|
app.include_router(system_router)
|
2026-03-08 12:50:44 -04:00
|
|
|
app.include_router(experiments_router)
|
2026-03-12 10:41:13 -04:00
|
|
|
app.include_router(db_explorer_router)
|
2026-03-18 22:13:49 -04:00
|
|
|
app.include_router(world_router)
|
2026-03-21 14:18:46 +00:00
|
|
|
app.include_router(matrix_router)
|
2026-03-20 16:10:42 -04:00
|
|
|
app.include_router(tower_router)
|
2026-03-21 19:58:25 +00:00
|
|
|
app.include_router(daily_run_router)
|
2026-03-21 20:45:35 +00:00
|
|
|
app.include_router(quests_router)
|
2026-03-22 01:41:52 +00:00
|
|
|
app.include_router(scorecards_router)
|
2026-03-23 14:09:03 +00:00
|
|
|
app.include_router(sovereignty_metrics_router)
|
2026-02-19 19:05:01 +00:00
|
|
|
|
|
|
|
|
|
2026-02-28 12:18:18 -05:00
|
|
|
@app.websocket("/ws")
|
|
|
|
|
async def ws_redirect(websocket: WebSocket):
|
2026-02-28 20:03:15 -05:00
|
|
|
"""Catch stale /ws connections and close cleanly.
|
|
|
|
|
|
|
|
|
|
websockets 16.0 dropped the legacy ``transfer_data_task`` attribute,
|
|
|
|
|
so calling ``websocket.close()`` after accept triggers an
|
|
|
|
|
AttributeError. Use the raw ASGI send instead.
|
|
|
|
|
"""
|
2026-02-28 12:18:18 -05:00
|
|
|
await websocket.accept()
|
2026-02-28 20:09:03 -05:00
|
|
|
try:
|
2026-03-02 13:17:38 -05:00
|
|
|
await websocket.close(code=1008, reason="Deprecated endpoint")
|
2026-02-28 20:09:03 -05:00
|
|
|
except AttributeError:
|
2026-02-28 20:03:15 -05:00
|
|
|
# websockets >= 16.0 — close via raw ASGI message
|
2026-02-28 20:09:03 -05:00
|
|
|
await websocket.send({"type": "websocket.close", "code": 1008})
|
2026-02-28 12:18:18 -05:00
|
|
|
|
|
|
|
|
|
2026-03-14 16:29:35 -04:00
|
|
|
@app.websocket("/swarm/live")
|
|
|
|
|
async def swarm_live(websocket: WebSocket):
|
|
|
|
|
"""Swarm live event stream via WebSocket."""
|
|
|
|
|
from infrastructure.ws_manager.handler import ws_manager as ws_mgr
|
|
|
|
|
|
|
|
|
|
await ws_mgr.connect(websocket)
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
# Keep connection alive; events are pushed via ws_mgr.broadcast()
|
|
|
|
|
await websocket.receive_text()
|
2026-03-14 19:07:14 -04:00
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("WebSocket disconnect error: %s", exc)
|
2026-03-14 16:29:35 -04:00
|
|
|
ws_mgr.disconnect(websocket)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/swarm/agents/sidebar", response_class=HTMLResponse)
|
|
|
|
|
async def swarm_agents_sidebar():
|
|
|
|
|
"""HTMX partial: list active swarm agents for the dashboard sidebar."""
|
|
|
|
|
try:
|
|
|
|
|
from config import settings
|
|
|
|
|
|
|
|
|
|
agents_yaml = settings.agents_config
|
|
|
|
|
agents = agents_yaml.get("agents", {})
|
|
|
|
|
lines = []
|
|
|
|
|
for name, cfg in agents.items():
|
|
|
|
|
model = cfg.get("model", "default")
|
|
|
|
|
lines.append(
|
|
|
|
|
f'<div class="mc-agent-row">'
|
|
|
|
|
f'<span class="mc-agent-name">{name}</span>'
|
|
|
|
|
f'<span class="mc-agent-model">{model}</span>'
|
|
|
|
|
f"</div>"
|
|
|
|
|
)
|
|
|
|
|
return "\n".join(lines) if lines else '<div class="mc-muted">No agents configured</div>'
|
2026-03-14 19:07:14 -04:00
|
|
|
except Exception as exc:
|
|
|
|
|
logger.debug("Agents sidebar error: %s", exc)
|
2026-03-14 16:29:35 -04:00
|
|
|
return '<div class="mc-muted">Agents unavailable</div>'
|
|
|
|
|
|
|
|
|
|
|
2026-02-19 19:05:01 +00:00
|
|
|
@app.get("/", response_class=HTMLResponse)
|
2026-02-28 11:07:19 -05:00
|
|
|
async def root(request: Request):
|
|
|
|
|
"""Serve the main dashboard page."""
|
2026-03-04 17:15:46 -05:00
|
|
|
return templates.TemplateResponse(request, "index.html", {})
|
CI/CD Optimization: Guard Rails, Pre-commit Checks, and Test Fixes (#90)
* CI/CD Optimization: Guard Rails, Black Linting, and Pre-commit Hooks
- Fixed all test collection errors (Selenium imports, fixture paths, syntax)
- Implemented pre-commit hooks with Black formatting and isort
- Created comprehensive Makefile with test targets (unit, integration, functional, e2e)
- Added pytest.ini with marker definitions for test categorization
- Established guard rails to prevent future collection errors
- Wrapped optional dependencies (Selenium, MoviePy) in try-except blocks
- Added conftest_markers for automatic test categorization
This ensures a smooth development stream with:
- Fast feedback loops (pre-commit checks before push)
- Consistent code formatting (Black)
- Reliable CI/CD (no collection errors, proper test isolation)
- Clear test organization (unit, integration, functional, E2E)
* Fix CI/CD test failures:
- Export templates from dashboard.app
- Fix model name assertion in test_agent.py
- Fix platform-agnostic path resolution in test_path_resolution.py
- Skip Docker tests in test_docker_deployment.py if docker not available
- Fix test_model_fallback_chain logic in test_ollama_integration.py
* Add preventative pre-commit checks and Docker test skipif decorators:
- Create pre_commit_checks.py script for common CI failures
- Add skipif decorators to Docker tests
- Improve test robustness for CI environments
2026-02-28 11:36:50 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/shortcuts/setup")
|
|
|
|
|
async def shortcuts_setup():
|
|
|
|
|
"""Siri Shortcuts setup guide."""
|
|
|
|
|
from integrations.shortcuts.siri import get_setup_guide
|
|
|
|
|
|
|
|
|
|
return get_setup_guide()
|