Feature: Agent Dreaming Mode #1047
@@ -277,6 +277,11 @@ class Settings(BaseSettings):
|
|||||||
thinking_memory_check_every: int = 50 # check memory status every Nth thought
|
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
|
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 ─────────────────────────────────────────────
|
# ── Gitea Integration ─────────────────────────────────────────────
|
||||||
# Local Gitea instance for issue tracking and self-improvement.
|
# Local Gitea instance for issue tracking and self-improvement.
|
||||||
# These values are passed as env vars to the gitea-mcp server process.
|
# These values are passed as env vars to the gitea-mcp server process.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from fastapi import APIRouter, Request
|
|||||||
from fastapi.responses import HTMLResponse, JSONResponse
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
|
|
||||||
from dashboard.templating import templates
|
from dashboard.templating import templates
|
||||||
|
from config import settings
|
||||||
from timmy.thinking import thinking_engine
|
from timmy.thinking import thinking_engine
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -61,3 +62,30 @@ async def thought_chain_api(thought_id: str):
|
|||||||
}
|
}
|
||||||
for t in chain
|
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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,25 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container thinking-container py-4">
|
<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 — 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-title">Thought Stream</div>
|
||||||
<div class="thinking-subtitle">
|
<div class="thinking-subtitle">
|
||||||
Inner monologue — always thinking, always pondering.
|
Inner monologue — always thinking, always pondering.
|
||||||
@@ -50,3 +68,13 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch('/thinking/dreaming/status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
document.getElementById('dreamingToggle').checked = data.dreaming_enabled;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -162,6 +162,13 @@ ten abstract sentences about sovereignty.
|
|||||||
|
|
||||||
Your next thought (2-3 sentences, grounded in reality):"""
|
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
|
@dataclass
|
||||||
class Thought:
|
class Thought:
|
||||||
@@ -259,7 +266,7 @@ class ThinkingEngine:
|
|||||||
|
|
||||||
for attempt in range(self._MAX_DEDUP_RETRIES + 1):
|
for attempt in range(self._MAX_DEDUP_RETRIES + 1):
|
||||||
if prompt:
|
if prompt:
|
||||||
seed_type = "prompted"
|
seed_type = "dream" if is_dream else "prompted"
|
||||||
seed_context = f"Journal prompt: {prompt}"
|
seed_context = f"Journal prompt: {prompt}"
|
||||||
else:
|
else:
|
||||||
seed_type, seed_context = self._gather_seed()
|
seed_type, seed_context = self._gather_seed()
|
||||||
@@ -316,7 +323,11 @@ class ThinkingEngine:
|
|||||||
self._write_journal(thought)
|
self._write_journal(thought)
|
||||||
await self._broadcast(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.
|
"""Execute one thinking cycle.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
Reference in New Issue
Block a user