fix: wire up tick engine scheduler + add journal + systemd timer (#163)

This commit is contained in:
Alexander Whitestone
2026-03-11 08:47:57 -04:00
committed by GitHub
parent a927241dbe
commit 07f2c1b41e
5 changed files with 83 additions and 1 deletions

17
deploy/timmy-tick.service Normal file
View 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
View 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

View File

@@ -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:

View File

@@ -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"),

View File

@@ -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: