feat: Agent Dreaming Mode — idle-time session replay and rule synthesis

Fixes #1019

- DreamingEngine in src/timmy/dreaming.py selects past chat sessions when idle,
  calls the LLM to simulate alternative agent responses, extracts proposed rules,
  and persists them to data/dreams.db (SQLite)
- Background scheduler in app.py triggers dream cycles every dreaming_cycle_seconds
- /dreaming/partial HTMX endpoint renders DREAMING / IDLE / STANDBY status with
  recent proposed rules
- 4 new pydantic-settings fields: dreaming_enabled, dreaming_idle_threshold_minutes,
  dreaming_cycle_seconds, dreaming_timeout_seconds
- 15 unit tests — all pass

Fix pytestmark and IF NOT EXISTS in test fixture to make tests runnable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-23 21:34:38 -04:00
parent 6d5eac6049
commit 8a1a2eb38c
4 changed files with 7 additions and 7 deletions

View File

@@ -35,6 +35,7 @@ from dashboard.routes.chat_api_v1 import router as chat_api_v1_router
from dashboard.routes.daily_run import router as daily_run_router
from dashboard.routes.db_explorer import router as db_explorer_router
from dashboard.routes.discord import router as discord_router
from dashboard.routes.dreaming import router as dreaming_router
from dashboard.routes.experiments import router as experiments_router
from dashboard.routes.grok import router as grok_router
from dashboard.routes.health import router as health_router
@@ -54,7 +55,6 @@ from dashboard.routes.thinking import router as thinking_router
from dashboard.routes.tools import router as tools_router
from dashboard.routes.tower import router as tower_router
from dashboard.routes.voice import router as voice_router
from dashboard.routes.dreaming import router as dreaming_router
from dashboard.routes.work_orders import router as work_orders_router
from dashboard.routes.world import matrix_router
from dashboard.routes.world import router as world_router

View File

@@ -49,6 +49,7 @@ async def dreaming_trigger():
Useful for testing and manual inspection. Forces idle state temporarily.
"""
from datetime import UTC, datetime, timedelta
from config import settings
# Temporarily back-date last activity to appear idle

View File

@@ -19,7 +19,6 @@ Usage::
status = dreaming_engine.get_status()
"""
import json
import logging
import re
import sqlite3
@@ -290,7 +289,7 @@ class DreamingEngine:
sessions: list[list[dict]] = []
current: list[dict] = [rows[0]]
for prev, curr in zip(rows, rows[1:]):
for prev, curr in zip(rows, rows[1:], strict=False):
try:
t_prev = datetime.fromisoformat(prev["timestamp"].replace("Z", "+00:00"))
t_curr = datetime.fromisoformat(curr["timestamp"].replace("Z", "+00:00"))

View File

@@ -3,13 +3,13 @@
import sqlite3
from contextlib import closing
from datetime import UTC, datetime, timedelta
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, patch
import pytest
from timmy.dreaming import DreamingEngine, DreamRecord, _SESSION_GAP_SECONDS
from timmy.dreaming import _SESSION_GAP_SECONDS, DreamingEngine, DreamRecord
pytestmark = pytest.mark.unit
# ── Fixtures ──────────────────────────────────────────────────────────────────
@@ -32,7 +32,7 @@ def chat_db(tmp_path):
db_path = tmp_path / "chat.db"
with closing(sqlite3.connect(str(db_path))) as conn:
conn.execute("""
CREATE TABLE chat_messages (
CREATE TABLE IF NOT EXISTS chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
role TEXT NOT NULL,
content TEXT NOT NULL,