From 8a1a2eb38c36fff5d8309d274c59a53b8bbe155c Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Mon, 23 Mar 2026 21:34:38 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Agent=20Dreaming=20Mode=20=E2=80=94=20i?= =?UTF-8?q?dle-time=20session=20replay=20and=20rule=20synthesis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/dashboard/app.py | 2 +- src/dashboard/routes/dreaming.py | 1 + src/timmy/dreaming.py | 3 +-- tests/unit/test_dreaming.py | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 41f0dce..93f4add 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -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 diff --git a/src/dashboard/routes/dreaming.py b/src/dashboard/routes/dreaming.py index 77b49ea..a344ade 100644 --- a/src/dashboard/routes/dreaming.py +++ b/src/dashboard/routes/dreaming.py @@ -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 diff --git a/src/timmy/dreaming.py b/src/timmy/dreaming.py index 524a571..ff037d2 100644 --- a/src/timmy/dreaming.py +++ b/src/timmy/dreaming.py @@ -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")) diff --git a/tests/unit/test_dreaming.py b/tests/unit/test_dreaming.py index c47ec42..8b85865 100644 --- a/tests/unit/test_dreaming.py +++ b/tests/unit/test_dreaming.py @@ -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,