diff --git a/.gitignore b/.gitignore index 0814e2b8..9d4ae250 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,11 @@ env/ .env.* !.env.example -# SQLite memory — never commit agent memory +# SQLite — never commit databases or WAL/SHM artifacts *.db +*.db-shm +*.db-wal +*.db-journal # Runtime PID files .watchdog.pid diff --git a/data/scripture.db-shm b/data/scripture.db-shm deleted file mode 100644 index 6ae92050..00000000 Binary files a/data/scripture.db-shm and /dev/null differ diff --git a/data/scripture.db-wal b/data/scripture.db-wal deleted file mode 100644 index fb1319fe..00000000 Binary files a/data/scripture.db-wal and /dev/null differ diff --git a/src/config.py b/src/config.py index cbf60b19..d1aff094 100644 --- a/src/config.py +++ b/src/config.py @@ -135,6 +135,12 @@ class Settings(BaseSettings): # Fallback to server when browser model is unavailable or too slow. browser_model_fallback: bool = True + # ── Default Thinking ────────────────────────────────────────────── + # When enabled, Timmy starts an internal thought loop on server start. + # He ponders his existence, recent activity, scripture, and creative ideas. + thinking_enabled: bool = True + thinking_interval_seconds: int = 300 # 5 minutes between thoughts + # ── Scripture / Biblical Integration ────────────────────────────── # Enable the sovereign biblical text module. When enabled, Timmy # loads the local ESV text corpus and runs meditation workflows. diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 67e65207..d055adc2 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -39,6 +39,7 @@ from dashboard.routes.grok import router as grok_router from dashboard.routes.models import router as models_router from dashboard.routes.models import api_router as models_api_router from dashboard.routes.chat_api import router as chat_api_router +from dashboard.routes.thinking import router as thinking_router from infrastructure.router.api import router as cascade_router logging.basicConfig( @@ -80,6 +81,26 @@ async def _briefing_scheduler() -> None: await asyncio.sleep(_BRIEFING_INTERVAL_HOURS * 3600) +async def _thinking_loop() -> None: + """Background task: Timmy's default thinking thread. + + Starts shortly after server boot and runs on a configurable cadence. + Timmy ponders his existence, recent swarm activity, scripture, creative + ideas, or continues a previous train of thought. + """ + from timmy.thinking import thinking_engine + + await asyncio.sleep(10) # Let server finish starting before first thought + + while True: + try: + await thinking_engine.think_once() + except Exception as exc: + logger.error("Thinking loop error: %s", exc) + + await asyncio.sleep(settings.thinking_interval_seconds) + + @asynccontextmanager async def lifespan(app: FastAPI): task = asyncio.create_task(_briefing_scheduler()) @@ -139,6 +160,15 @@ async def lifespan(app: FastAPI): if spark_engine.enabled: logger.info("Spark Intelligence active — event capture enabled") + # Start Timmy's default thinking thread (skip in test mode) + thinking_task = None + if settings.thinking_enabled and os.environ.get("TIMMY_TEST_MODE") != "1": + thinking_task = asyncio.create_task(_thinking_loop()) + logger.info( + "Default thinking thread started (interval: %ds)", + settings.thinking_interval_seconds, + ) + # Auto-start chat integrations (skip silently if unconfigured) from integrations.telegram_bot.bot import telegram_bot from integrations.chat_bridge.vendors.discord import discord_bot @@ -159,6 +189,12 @@ async def lifespan(app: FastAPI): await discord_bot.stop() await telegram_bot.stop() + if thinking_task: + thinking_task.cancel() + try: + await thinking_task + except asyncio.CancelledError: + pass task.cancel() try: await task @@ -223,6 +259,7 @@ app.include_router(grok_router) app.include_router(models_router) app.include_router(models_api_router) app.include_router(chat_api_router) +app.include_router(thinking_router) app.include_router(cascade_router) diff --git a/src/dashboard/routes/thinking.py b/src/dashboard/routes/thinking.py new file mode 100644 index 00000000..791abedf --- /dev/null +++ b/src/dashboard/routes/thinking.py @@ -0,0 +1,65 @@ +"""Thinking routes — Timmy's inner thought stream. + +GET /thinking — render the thought stream page +GET /thinking/api — JSON list of recent thoughts +GET /thinking/api/{id}/chain — follow a thought chain +""" + +import logging +from pathlib import Path + +from fastapi import APIRouter, Request +from fastapi.responses import HTMLResponse, JSONResponse +from fastapi.templating import Jinja2Templates + +from timmy.thinking import thinking_engine + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/thinking", tags=["thinking"]) +templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates")) + + +@router.get("", response_class=HTMLResponse) +async def thinking_page(request: Request): + """Render Timmy's thought stream page.""" + thoughts = thinking_engine.get_recent_thoughts(limit=50) + return templates.TemplateResponse( + request, + "thinking.html", + {"thoughts": thoughts}, + ) + + +@router.get("/api", response_class=JSONResponse) +async def thinking_api(limit: int = 20): + """Return recent thoughts as JSON.""" + thoughts = thinking_engine.get_recent_thoughts(limit=limit) + return [ + { + "id": t.id, + "content": t.content, + "seed_type": t.seed_type, + "parent_id": t.parent_id, + "created_at": t.created_at, + } + for t in thoughts + ] + + +@router.get("/api/{thought_id}/chain", response_class=JSONResponse) +async def thought_chain_api(thought_id: str): + """Follow a thought chain backward and return in chronological order.""" + chain = thinking_engine.get_thought_chain(thought_id) + if not chain: + return JSONResponse({"error": "Thought not found"}, status_code=404) + return [ + { + "id": t.id, + "content": t.content, + "seed_type": t.seed_type, + "parent_id": t.parent_id, + "created_at": t.created_at, + } + for t in chain + ] diff --git a/src/dashboard/templates/base.html b/src/dashboard/templates/base.html index a0973599..455dbc50 100644 --- a/src/dashboard/templates/base.html +++ b/src/dashboard/templates/base.html @@ -30,6 +30,7 @@