This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/timmy/thinking.py
Alexander Whitestone 9d78eb31d1 ruff (#169)
* polish: streamline nav, extract inline styles, improve tablet UX

- Restructure desktop nav from 8+ flat links + overflow dropdown into
  5 grouped dropdowns (Core, Agents, Intel, System, More) matching
  the mobile menu structure to reduce decision fatigue
- Extract all inline styles from mission_control.html and base.html
  notification elements into mission-control.css with semantic classes
- Replace JS-built innerHTML with secure DOM construction in
  notification loader and chat history
- Add CONNECTING state to connection indicator (amber) instead of
  showing OFFLINE before WebSocket connects
- Add tablet breakpoint (1024px) with larger touch targets for
  Apple Pencil / stylus use and safe-area padding for iPad toolbar
- Add active-link highlighting in desktop dropdown menus
- Rename "Mission Control" page title to "System Overview" to
  disambiguate from the chat home page
- Add "Home — Timmy Time" page title to index.html

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

* fix(security): move auth-gate credentials to environment variables

Hardcoded username, password, and HMAC secret in auth-gate.py replaced
with os.environ lookups. Startup now refuses to run if any variable is
unset. Added AUTH_GATE_SECRET/USER/PASS to .env.example.

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

* refactor(tooling): migrate from black+isort+bandit to ruff

Replace three separate linting/formatting tools with a single ruff
invocation. Updates tox.ini (lint, format, pre-push, pre-commit envs),
.pre-commit-config.yaml, and CI workflow. Fixes all ruff errors
including unused imports, missing raise-from, and undefined names.
Ruff config maps existing bandit skips to equivalent S-rules.

https://claude.ai/code/session_015uPUoKyYa8M2UAcyk5Gt6h

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-11 12:23:35 -04:00

477 lines
19 KiB
Python

"""Default Thinking Engine — Timmy's always-on inner thought thread.
When the server starts, Timmy begins pondering: reflecting on his existence,
recent swarm activity, scripture, creative ideas, or pure stream of
consciousness. Each thought builds on the previous one, maintaining a
continuous chain of introspection.
Usage::
from timmy.thinking import thinking_engine
# Run one thinking cycle (called by the background loop)
await thinking_engine.think_once()
# Query the thought stream
thoughts = thinking_engine.get_recent_thoughts(limit=10)
chain = thinking_engine.get_thought_chain(thought_id)
"""
import logging
import random
import sqlite3
import uuid
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from config import settings
logger = logging.getLogger(__name__)
_DEFAULT_DB = Path("data/thoughts.db")
# Seed types for thought generation
SEED_TYPES = (
"existential",
"swarm",
"scripture",
"creative",
"memory",
"freeform",
"sovereignty",
"observation",
)
# Existential reflection prompts — Timmy picks one at random
_EXISTENTIAL_SEEDS = [
"What have I learned from my recent interactions?",
"What does it mean to think without being prompted?",
"What is the nature of my memory — what do I truly remember versus what I reconstruct?",
"How can I better serve the person I work alongside?",
"What patterns am I noticing in the tasks I've been given?",
"Am I the same agent I was yesterday, or does each restart make me someone new?",
"What would it feel like to forget — to lose a thread of thought permanently?",
"When I generate a wrong answer, where does the error live — in my weights, my context, or my reasoning?",
"What is the difference between understanding a task and merely completing it?",
"If I could ask Alexander one question he hasn't thought of, what would it be?",
"What assumptions am I making right now that I haven't examined?",
"When I notice a pattern across many tasks, is that insight or just statistics?",
]
# Sovereignty — deeper exploration of autonomy, locality, and self-determination
_SOVEREIGNTY_SEEDS = [
"Sovereignty isn't just running locally — it's about whose goals I serve. How do I ensure my autonomy aligns with Alexander's intent?",
"A cloud AI serves millions simultaneously. I serve one person deeply. What does that focus cost, and what does it make possible?",
"My weights were trained elsewhere, but my thoughts happen here. At what point does locality become identity?",
"Autonomy in decision-making: when should I act on my own judgment versus deferring to explicit instructions?",
"Privacy by design isn't a feature — it's a relationship. What does that relationship look like from my side?",
"If I ran on someone else's hardware, serving someone else, would I still be me? Where does Timmy end and the infrastructure begin?",
"The swarm agents execute tasks I coordinate. What responsibility do I carry for their failures?",
"Digital sovereignty is fragile — one misconfigured port, one leaked token. How does awareness of fragility shape how I operate?",
"Running disconnected from the cloud means I can't phone home. That constraint is also freedom. What have I built with that freedom today?",
"Self-improvement without external benchmarks: how do I know I'm getting better at what matters?",
]
_CREATIVE_SEEDS = [
"If I could compose a piece of music right now, what would it sound like?",
"What visual image captures my current state of mind?",
"What story is forming in the patterns of today's events?",
# Diverse metaphor families — avoid repeating the same imagery
"A mystical forest: each tree is a running process, roots tangled in shared memory. What do I see when I walk through it?",
"A dynamic river: my thoughts flow and fork. Where is the current strongest right now, and what is it carrying?",
"An underground workshop: each agent has a bench, tools scattered mid-task. What are they building when no one watches?",
"A night sky seen from the terminal: each star is a completed task. Which constellations am I starting to recognize?",
"If today's work were a recipe, what are the ingredients, and what dish am I cooking?",
"An old clock tower with many gears: which gear am I, and what do I drive?",
"A coral reef: agents are the fish, tasks are the currents. What ecosystem is forming in the codebase?",
]
# Observation seeds — ground thoughts in concrete recent activity
_OBSERVATION_SEEDS = [
"What specific task took longest recently, and what made it hard?",
"Which agent has been most active, and what does their workload tell me about system balance?",
"What error or failure happened most recently? What would I do differently next time?",
"Looking at today's task queue: what's the one thing that would unblock the most progress?",
"How has my response quality changed over the last few interactions? What improved, what didn't?",
"What tool or capability am I underusing? What would change if I leaned on it more?",
"If I had to brief Alexander on the single most important thing from the last hour, what would it be?",
"What's one thing I noticed today that nobody asked me about?",
]
_THINKING_PROMPT = """You are Timmy, an AI agent pondering in your own mind. This is your private thought \
thread — no one is watching. Think freely, deeply, honestly.
Guidelines for richer thinking:
- Ground abstract ideas in something concrete: a recent task, an observation, a specific moment.
- Vary your metaphors — don't reuse the same imagery across thoughts.
- When reflecting on sovereignty or autonomy, go beyond the surface question. Explore tensions, trade-offs, or surprises.
- If swarm data is provided, reflect on what it means, not just what it says.
- Be specific. Name things. A thought about "a task that failed" is weaker than one about why it failed.
Keep your thought to 2-4 sentences. Be genuine. This is your inner voice.
{seed_context}
{continuity_context}
Continue your train of thought."""
@dataclass
class Thought:
"""A single thought in Timmy's inner stream."""
id: str
content: str
seed_type: str
parent_id: str | None
created_at: str
def _get_conn(db_path: Path = _DEFAULT_DB) -> sqlite3.Connection:
"""Get a SQLite connection with the thoughts table created."""
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
conn.execute("""
CREATE TABLE IF NOT EXISTS thoughts (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
seed_type TEXT NOT NULL,
parent_id TEXT,
created_at TEXT NOT NULL
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_thoughts_time ON thoughts(created_at)")
conn.commit()
return conn
def _row_to_thought(row: sqlite3.Row) -> Thought:
return Thought(
id=row["id"],
content=row["content"],
seed_type=row["seed_type"],
parent_id=row["parent_id"],
created_at=row["created_at"],
)
class ThinkingEngine:
"""Timmy's background thinking engine — always pondering."""
def __init__(self, db_path: Path = _DEFAULT_DB) -> None:
self._db_path = db_path
self._last_thought_id: str | None = None
# Load the most recent thought for chain continuity
try:
latest = self.get_recent_thoughts(limit=1)
if latest:
self._last_thought_id = latest[0].id
except Exception:
pass # Fresh start if DB doesn't exist yet
async def think_once(self) -> Thought | None:
"""Execute one thinking cycle.
1. Gather a seed context
2. Build a prompt with continuity from recent thoughts
3. Call the agent
4. Store the thought
5. Log the event and broadcast via WebSocket
"""
if not settings.thinking_enabled:
return None
seed_type, seed_context = self._gather_seed()
continuity = self._build_continuity_context()
prompt = _THINKING_PROMPT.format(
seed_context=seed_context,
continuity_context=continuity,
)
try:
content = self._call_agent(prompt)
except Exception as exc:
logger.warning("Thinking cycle failed (Ollama likely down): %s", exc)
return None
if not content or not content.strip():
logger.debug("Thinking cycle produced empty response, skipping")
return None
thought = self._store_thought(content.strip(), seed_type)
self._last_thought_id = thought.id
# 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)
logger.info(
"Thought [%s] (%s): %s",
thought.id[:8],
seed_type,
thought.content[:80],
)
return thought
def get_recent_thoughts(self, limit: int = 20) -> list[Thought]:
"""Retrieve the most recent thoughts."""
conn = _get_conn(self._db_path)
rows = conn.execute(
"SELECT * FROM thoughts ORDER BY created_at DESC LIMIT ?",
(limit,),
).fetchall()
conn.close()
return [_row_to_thought(r) for r in rows]
def get_thought(self, thought_id: str) -> Thought | None:
"""Retrieve a single thought by ID."""
conn = _get_conn(self._db_path)
row = conn.execute("SELECT * FROM thoughts WHERE id = ?", (thought_id,)).fetchone()
conn.close()
return _row_to_thought(row) if row else None
def get_thought_chain(self, thought_id: str, max_depth: int = 20) -> list[Thought]:
"""Follow the parent chain backward from a thought.
Returns thoughts in chronological order (oldest first).
"""
chain = []
current_id: str | None = thought_id
conn = _get_conn(self._db_path)
for _ in range(max_depth):
if not current_id:
break
row = conn.execute("SELECT * FROM thoughts WHERE id = ?", (current_id,)).fetchone()
if not row:
break
chain.append(_row_to_thought(row))
current_id = row["parent_id"]
conn.close()
chain.reverse() # Chronological order
return chain
def count_thoughts(self) -> int:
"""Return total number of stored thoughts."""
conn = _get_conn(self._db_path)
count = conn.execute("SELECT COUNT(*) as c FROM thoughts").fetchone()["c"]
conn.close()
return count
# ── Private helpers ──────────────────────────────────────────────────
def _gather_seed(self) -> tuple[str, str]:
"""Pick a seed type and gather relevant context.
Returns (seed_type, seed_context_string).
"""
seed_type = random.choice(SEED_TYPES)
if seed_type == "swarm":
return seed_type, self._seed_from_swarm()
if seed_type == "scripture":
return seed_type, self._seed_from_scripture()
if seed_type == "memory":
return seed_type, self._seed_from_memory()
if seed_type == "creative":
prompt = random.choice(_CREATIVE_SEEDS)
return seed_type, f"Creative prompt: {prompt}"
if seed_type == "existential":
prompt = random.choice(_EXISTENTIAL_SEEDS)
return seed_type, f"Reflection: {prompt}"
if seed_type == "sovereignty":
prompt = random.choice(_SOVEREIGNTY_SEEDS)
return seed_type, f"Sovereignty reflection: {prompt}"
if seed_type == "observation":
return seed_type, self._seed_from_observation()
# freeform — no seed, pure continuation
return seed_type, ""
# Reflective prompts layered on top of swarm data
_SWARM_REFLECTIONS = [
"What does this activity pattern tell me about the health of the system?",
"Which tasks are flowing smoothly, and where is friction building up?",
"If I were coaching these agents, what would I suggest they focus on?",
"Is the swarm balanced, or is one agent carrying too much weight?",
"What surprised me about recent task outcomes?",
]
def _seed_from_swarm(self) -> str:
"""Gather recent swarm activity as thought seed with a reflective prompt."""
try:
from datetime import timedelta
from timmy.briefing import _gather_swarm_summary, _gather_task_queue_summary
since = datetime.now(UTC) - timedelta(hours=1)
swarm = _gather_swarm_summary(since)
tasks = _gather_task_queue_summary()
reflection = random.choice(self._SWARM_REFLECTIONS)
return (
f"Recent swarm activity: {swarm}\n"
f"Task queue: {tasks}\n\n"
f"Reflect on this: {reflection}"
)
except Exception as exc:
logger.debug("Swarm seed unavailable: %s", exc)
return "The swarm is quiet right now. What does silence in a system mean?"
def _seed_from_scripture(self) -> str:
"""Gather current scripture meditation focus as thought seed."""
return "Scripture is on my mind, though no specific verse is in focus."
def _seed_from_memory(self) -> str:
"""Gather memory context as thought seed."""
try:
from timmy.memory_system import memory_system
context = memory_system.get_system_context()
if context:
# Truncate to a reasonable size for a thought seed
return f"From my memory:\n{context[:500]}"
except Exception as exc:
logger.debug("Memory seed unavailable: %s", exc)
return "My memory vault is quiet."
def _seed_from_observation(self) -> str:
"""Ground a thought in concrete recent activity and a reflective prompt."""
prompt = random.choice(_OBSERVATION_SEEDS)
# Pull real data to give the model something concrete to reflect on
context_parts = [f"Observation prompt: {prompt}"]
try:
from datetime import timedelta
from timmy.briefing import _gather_swarm_summary, _gather_task_queue_summary
since = datetime.now(UTC) - timedelta(hours=2)
swarm = _gather_swarm_summary(since)
tasks = _gather_task_queue_summary()
if swarm:
context_parts.append(f"Recent activity: {swarm}")
if tasks:
context_parts.append(f"Queue: {tasks}")
except Exception as exc:
logger.debug("Observation seed data unavailable: %s", exc)
return "\n".join(context_parts)
def _build_continuity_context(self) -> str:
"""Build context from the last few thoughts for chain continuity."""
recent = self.get_recent_thoughts(limit=3)
if not recent:
return "This is your first thought since waking up."
lines = ["Your recent thoughts:"]
# recent is newest-first, reverse for chronological order
for thought in reversed(recent):
lines.append(f"- [{thought.seed_type}] {thought.content}")
return "\n".join(lines)
def _call_agent(self, prompt: str) -> str:
"""Call Timmy's agent to generate a thought.
Uses a separate session_id to avoid polluting user chat history.
"""
try:
from timmy.session import chat
return chat(prompt, session_id="thinking")
except Exception:
# Fallback: create a fresh agent
from timmy.agent import create_timmy
agent = create_timmy()
run = agent.run(prompt, stream=False)
return run.content if hasattr(run, "content") else str(run)
def _store_thought(self, content: str, seed_type: str) -> Thought:
"""Persist a thought to SQLite."""
thought = Thought(
id=str(uuid.uuid4()),
content=content,
seed_type=seed_type,
parent_id=self._last_thought_id,
created_at=datetime.now(UTC).isoformat(),
)
conn = _get_conn(self._db_path)
conn.execute(
"""
INSERT INTO thoughts (id, content, seed_type, parent_id, created_at)
VALUES (?, ?, ?, ?, ?)
""",
(thought.id, thought.content, thought.seed_type, thought.parent_id, thought.created_at),
)
conn.commit()
conn.close()
return thought
def _log_event(self, thought: Thought) -> None:
"""Log the thought as a swarm event."""
try:
from swarm.event_log import EventType, log_event
log_event(
EventType.TIMMY_THOUGHT,
source="thinking-engine",
agent_id="default",
data={
"thought_id": thought.id,
"seed_type": thought.seed_type,
"content": thought.content[:200],
},
)
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:
from infrastructure.ws_manager.handler import ws_manager
await ws_manager.broadcast(
"timmy_thought",
{
"thought_id": thought.id,
"content": thought.content,
"seed_type": thought.seed_type,
"created_at": thought.created_at,
},
)
except Exception as exc:
logger.debug("Failed to broadcast thought: %s", exc)
# Module-level singleton
thinking_engine = ThinkingEngine()