forked from Rockachopa/Timmy-time-dashboard
fix: wire up tick engine scheduler + add journal + systemd timer (#163)
This commit is contained in:
committed by
GitHub
parent
a927241dbe
commit
07f2c1b41e
17
deploy/timmy-tick.service
Normal file
17
deploy/timmy-tick.service
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Timmy Tick — one autonomous thinking cycle
|
||||
Documentation=https://github.com/AlexanderWhitestone/Timmy-time-dashboard
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/opt/timmy
|
||||
EnvironmentFile=-/opt/timmy/.env
|
||||
ExecStart=/opt/timmy/.venv/bin/timmy tick
|
||||
TimeoutStartSec=120
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/opt/timmy/data
|
||||
11
deploy/timmy-tick.timer
Normal file
11
deploy/timmy-tick.timer
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Timmy thinking timer — every 5 minutes
|
||||
Documentation=https://github.com/AlexanderWhitestone/Timmy-time-dashboard
|
||||
|
||||
[Timer]
|
||||
OnBootSec=30
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=30s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -120,6 +120,22 @@ async def _briefing_scheduler() -> None:
|
||||
await asyncio.sleep(_BRIEFING_INTERVAL_HOURS * 3600)
|
||||
|
||||
|
||||
async def _thinking_scheduler() -> None:
|
||||
"""Background task: execute Timmy's thinking cycle every N seconds."""
|
||||
from timmy.thinking import thinking_engine
|
||||
|
||||
await asyncio.sleep(5) # Stagger after briefing scheduler
|
||||
|
||||
while True:
|
||||
try:
|
||||
if settings.thinking_enabled:
|
||||
await thinking_engine.think_once()
|
||||
except Exception as exc:
|
||||
logger.error("Thinking scheduler error: %s", exc)
|
||||
|
||||
await asyncio.sleep(settings.thinking_interval_seconds)
|
||||
|
||||
|
||||
async def _start_chat_integrations_background() -> None:
|
||||
"""Background task: start chat integrations without blocking startup."""
|
||||
from integrations.chat_bridge.registry import platform_registry
|
||||
@@ -211,6 +227,7 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# Create all background tasks without waiting for them
|
||||
briefing_task = asyncio.create_task(_briefing_scheduler())
|
||||
thinking_task = asyncio.create_task(_thinking_scheduler())
|
||||
|
||||
# Initialize Spark Intelligence engine
|
||||
from spark.engine import get_spark_engine
|
||||
@@ -266,7 +283,7 @@ async def lifespan(app: FastAPI):
|
||||
await discord_bot.stop()
|
||||
await telegram_bot.stop()
|
||||
|
||||
for task in [briefing_task, chat_task]:
|
||||
for task in [briefing_task, thinking_task, chat_task]:
|
||||
if task:
|
||||
task.cancel()
|
||||
try:
|
||||
|
||||
@@ -23,6 +23,20 @@ _MODEL_SIZE_OPTION = typer.Option(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def tick():
|
||||
"""Run one autonomous thinking cycle (used by systemd timer)."""
|
||||
import asyncio
|
||||
|
||||
from timmy.thinking import thinking_engine
|
||||
|
||||
thought = asyncio.run(thinking_engine.think_once())
|
||||
if thought:
|
||||
typer.echo(f"[{thought.seed_type}] {thought.content}")
|
||||
else:
|
||||
typer.echo("No thought produced (thinking disabled or Ollama down).")
|
||||
|
||||
|
||||
@app.command()
|
||||
def think(
|
||||
topic: str = typer.Argument(..., help="Topic to reason about"),
|
||||
|
||||
@@ -163,6 +163,9 @@ class ThinkingEngine:
|
||||
# Log to swarm event system
|
||||
self._log_event(thought)
|
||||
|
||||
# Append to daily journal file
|
||||
self._write_journal(thought)
|
||||
|
||||
# Broadcast to WebSocket clients
|
||||
await self._broadcast(thought)
|
||||
|
||||
@@ -345,6 +348,26 @@ class ThinkingEngine:
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to log thought event: %s", exc)
|
||||
|
||||
def _write_journal(self, thought: Thought) -> None:
|
||||
"""Append the thought to a daily markdown journal file.
|
||||
|
||||
Writes to data/journal/YYYY-MM-DD.md — one file per day, append-only.
|
||||
"""
|
||||
try:
|
||||
ts = datetime.fromisoformat(thought.created_at)
|
||||
journal_dir = self._db_path.parent / "journal"
|
||||
journal_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
journal_file = journal_dir / f"{ts.strftime('%Y-%m-%d')}.md"
|
||||
time_str = ts.strftime("%I:%M %p").lstrip("0")
|
||||
|
||||
entry = f"## {time_str} — {thought.seed_type}\n\n{thought.content}\n\n---\n\n"
|
||||
|
||||
with open(journal_file, "a", encoding="utf-8") as f:
|
||||
f.write(entry)
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to write journal entry: %s", exc)
|
||||
|
||||
async def _broadcast(self, thought: Thought) -> None:
|
||||
"""Broadcast the thought to WebSocket clients."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user