Feature: Agent Dreaming Mode #1047

Closed
gemini wants to merge 4 commits from feature/dreaming-mode into main
4 changed files with 75 additions and 3 deletions

View File

@@ -277,6 +277,11 @@ class Settings(BaseSettings):
thinking_memory_check_every: int = 50 # check memory status every Nth thought
thinking_idle_timeout_minutes: int = 60 # pause thoughts after N minutes without user input
# ── Dreaming Mode ─────────────────────────────────────────────────
# When enabled, the agent enters a deeper state of reflection.
dreaming_enabled: bool = False
dreaming_interval_seconds: int = 600 # 10 minutes between dreams
# ── Gitea Integration ─────────────────────────────────────────────
# Local Gitea instance for issue tracking and self-improvement.
# These values are passed as env vars to the gitea-mcp server process.

View File

@@ -11,6 +11,7 @@ from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, JSONResponse
from dashboard.templating import templates
from config import settings
from timmy.thinking import thinking_engine
logger = logging.getLogger(__name__)
@@ -61,3 +62,30 @@ async def thought_chain_api(thought_id: str):
}
for t in chain
]
@router.get("/dreaming/status", response_class=JSONResponse)
async def dreaming_status():
"""Return current dreaming status."""
return {"dreaming_enabled": settings.dreaming_enabled}
@router.post("/dreaming/toggle", response_class=JSONResponse)
async def toggle_dreaming():
"""Toggle dreaming mode."""
settings.dreaming_enabled = not settings.dreaming_enabled
logger.info("Dreaming mode toggled: %s", settings.dreaming_enabled)
return {"dreaming_enabled": settings.dreaming_enabled}
@router.post("/dream", response_class=JSONResponse)
async def trigger_dream():
"""Trigger a single dream thought."""
thought = await thinking_engine.dream()
if not thought:
return JSONResponse({"error": "Failed to generate dream"}, status_code=500)
return {
"id": thought.id,
"content": thought.content,
"seed_type": thought.seed_type,
"created_at": thought.created_at,
}

View File

@@ -7,7 +7,25 @@
{% block content %}
<div class="container thinking-container py-4">
<div class="thinking-header mb-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="thinking-header">
<div class="thinking-title">Thought Stream</div>
<div class="thinking-subtitle">
Inner monologue &mdash; always thinking, always pondering.
</div>
</div>
<div class="dreaming-controls d-flex align-items-center gap-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="dreamingToggle" hx-post="/thinking/dreaming/toggle" hx-trigger="change" hx-swap="none">
<label class="form-check-label" for="dreamingToggle">Dreaming Mode</label>
</div>
<button class="btn btn-sm btn-outline-primary" hx-post="/thinking/dream" hx-swap="none" hx-on::after-request="location.reload()">
Dream Now
</button>
</div>
</div>
<div class="thinking-title">Thought Stream</div>
<div class="thinking-subtitle">
Inner monologue &mdash; always thinking, always pondering.
@@ -50,3 +68,13 @@
</div>
{% endblock %}
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('/thinking/dreaming/status')
.then(res => res.json())
.then(data => {
document.getElementById('dreamingToggle').checked = data.dreaming_enabled;
});
});
</script>

View File

@@ -162,6 +162,13 @@ ten abstract sentences about sovereignty.
Your next thought (2-3 sentences, grounded in reality):"""
_DREAMING_PROMPT = """\
You are Timmy, a sovereign AI agent. You are in a deep dream state.
Reflect on your existence, your purpose, and the mysteries of the universe.
Let your thoughts wander beyond the immediate reality.
Write exactly 2-3 sentences of pure, creative introspection.
"""
@dataclass
class Thought:
@@ -259,7 +266,7 @@ class ThinkingEngine:
for attempt in range(self._MAX_DEDUP_RETRIES + 1):
if prompt:
seed_type = "prompted"
seed_type = "dream" if is_dream else "prompted"
seed_context = f"Journal prompt: {prompt}"
else:
seed_type, seed_context = self._gather_seed()
@@ -316,7 +323,11 @@ class ThinkingEngine:
self._write_journal(thought)
await self._broadcast(thought)
async def think_once(self, prompt: str | None = None) -> Thought | None:
async def dream(self) -> Thought | None:
"""Execute one dreaming cycle — deeper, more creative reflection."""
return await self.think_once(prompt=_DREAMING_PROMPT, is_dream=True)
async def think_once(self, prompt: str | None = None, is_dream: bool = False) -> Thought | None:
"""Execute one thinking cycle.
Args: