Add pre-commit hook enforcing 30s test suite time limit (#132)

This commit is contained in:
Alexander Whitestone
2026-03-05 19:45:38 -05:00
committed by GitHub
parent aff3edb06a
commit 2b97da9e9c
65 changed files with 356 additions and 611 deletions

View File

@@ -14,7 +14,6 @@ from brain.client import BrainClient
from brain.worker import DistributedWorker
from brain.embeddings import LocalEmbedder
from brain.memory import UnifiedMemory, get_memory
from brain.identity import get_canonical_identity, get_identity_for_prompt
__all__ = [
"BrainClient",
@@ -22,6 +21,4 @@ __all__ = [
"LocalEmbedder",
"UnifiedMemory",
"get_memory",
"get_canonical_identity",
"get_identity_for_prompt",
]

View File

@@ -36,7 +36,7 @@ class BrainClient:
"""Detect what component is using the brain."""
# Could be 'timmy', 'zeroclaw', 'worker', etc.
# For now, infer from context or env
return os.environ.get("BRAIN_SOURCE", "timmy")
return os.environ.get("BRAIN_SOURCE", "default")
# ──────────────────────────────────────────────────────────────────────────
# Memory Operations

View File

@@ -1,35 +0,0 @@
"""Identity loader — stripped.
The persona/identity system has been removed. These functions remain
as no-op stubs so that call-sites don't break at import time.
"""
from __future__ import annotations
import logging
from typing import Optional
logger = logging.getLogger(__name__)
def get_canonical_identity(force_refresh: bool = False) -> str:
"""Return empty string — identity system removed."""
return ""
def get_identity_section(section_name: str) -> str:
"""Return empty string — identity system removed."""
return ""
def get_identity_for_prompt(include_sections: Optional[list[str]] = None) -> str:
"""Return empty string — identity system removed."""
return ""
def get_agent_roster() -> list[dict[str, str]]:
"""Return empty list — identity system removed."""
return []
_FALLBACK_IDENTITY = ""

View File

@@ -65,7 +65,7 @@ class UnifiedMemory:
def __init__(
self,
db_path: Optional[Path] = None,
source: str = "timmy",
source: str = "default",
use_rqlite: Optional[bool] = None,
):
self.db_path = db_path or _get_db_path()

View File

@@ -4,6 +4,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
# Display name for the primary agent — override with AGENT_NAME env var
agent_name: str = "Agent"
# Ollama host — override with OLLAMA_URL env var or .env file
ollama_url: str = "http://localhost:11434"

View File

@@ -206,7 +206,7 @@ async def lifespan(app: FastAPI):
# Start chat integrations in background
chat_task = asyncio.create_task(_start_chat_integrations_background())
logger.info("Timmy Time dashboard ready for requests")
logger.info("Dashboard ready for requests")
yield
@@ -227,7 +227,7 @@ async def lifespan(app: FastAPI):
app = FastAPI(
title="Timmy Time — Mission Control",
title="Mission Control",
version="1.0.0",
lifespan=lifespan,
docs_url="/docs",

View File

@@ -4,7 +4,7 @@ from datetime import datetime
from fastapi import APIRouter, Form, Request
from fastapi.responses import HTMLResponse
from timmy.session import chat as timmy_chat
from timmy.session import chat as agent_chat
from dashboard.store import message_log
from dashboard.templating import templates
@@ -21,8 +21,8 @@ async def list_agents():
return {
"agents": [
{
"id": "orchestrator",
"name": "Orchestrator",
"id": "default",
"name": settings.agent_name,
"status": "idle",
"capabilities": "chat,reasoning,research,planning",
"type": "local",
@@ -34,15 +34,15 @@ async def list_agents():
}
@router.get("/timmy/panel", response_class=HTMLResponse)
async def timmy_panel(request: Request):
@router.get("/default/panel", response_class=HTMLResponse)
async def agent_panel(request: Request):
"""Chat panel — for HTMX main-panel swaps."""
return templates.TemplateResponse(
request, "partials/timmy_panel.html", {"agent": None}
request, "partials/agent_panel_chat.html", {"agent": None}
)
@router.get("/timmy/history", response_class=HTMLResponse)
@router.get("/default/history", response_class=HTMLResponse)
async def get_history(request: Request):
return templates.TemplateResponse(
request,
@@ -51,7 +51,7 @@ async def get_history(request: Request):
)
@router.delete("/timmy/history", response_class=HTMLResponse)
@router.delete("/default/history", response_class=HTMLResponse)
async def clear_history(request: Request):
message_log.clear()
return templates.TemplateResponse(
@@ -61,15 +61,15 @@ async def clear_history(request: Request):
)
@router.post("/timmy/chat", response_class=HTMLResponse)
async def chat_timmy(request: Request, message: str = Form(...)):
@router.post("/default/chat", response_class=HTMLResponse)
async def chat_agent(request: Request, message: str = Form(...)):
"""Chat — synchronous response."""
timestamp = datetime.now().strftime("%H:%M:%S")
response_text = None
error_text = None
try:
response_text = timmy_chat(message)
response_text = agent_chat(message)
except Exception as exc:
logger.error("Chat error: %s", exc)
error_text = f"Chat error: {exc}"

View File

@@ -20,7 +20,7 @@ from fastapi.responses import JSONResponse
from config import settings
from dashboard.store import message_log
from timmy.session import chat as timmy_chat
from timmy.session import chat as agent_chat
logger = logging.getLogger(__name__)
@@ -80,7 +80,7 @@ async def api_chat(request: Request):
f"{now.strftime('%A, %B %d, %Y at %I:%M %p')}]\n"
f"[System: Mobile client]\n\n"
)
response_text = timmy_chat(
response_text = agent_chat(
context_prefix + last_user_msg,
session_id="mobile",
)

View File

@@ -115,7 +115,7 @@ async def join_from_image(
result["oauth2_url"] = oauth_url
result["message"] = (
"Invite validated. Share this OAuth2 URL with the server admin "
"to add Timmy to the server."
"to add the agent to the server."
)
else:
result["message"] = (

View File

@@ -82,7 +82,7 @@ async def toggle_grok_mode(request: Request):
import json
spark_engine.on_tool_executed(
agent_id="timmy",
agent_id="default",
tool_name="grok_mode_toggle",
success=True,
)

View File

@@ -211,7 +211,7 @@ async def health_check():
# Legacy format for test compatibility
ollama_ok = await check_ollama()
timmy_status = "idle" if ollama_ok else "offline"
agent_status = "idle" if ollama_ok else "offline"
return {
"status": "ok" if ollama_ok else "degraded",
@@ -219,7 +219,7 @@ async def health_check():
"ollama": "up" if ollama_ok else "down",
},
"agents": {
"timmy": {"status": timmy_status},
"agent": {"status": agent_status},
},
# Extended fields for Mission Control
"timestamp": datetime.now(timezone.utc).isoformat(),

View File

@@ -45,7 +45,7 @@ async def mobile_local_dashboard(request: Request):
"browser_model_id": settings.browser_model_id,
"browser_model_fallback": settings.browser_model_fallback,
"server_model": settings.ollama_model,
"page_title": "Timmy — Local AI",
"page_title": "Local AI",
},
)
@@ -71,7 +71,7 @@ async def mobile_status():
return {
"ollama": "up" if ollama_ok else "down",
"model": settings.ollama_model,
"agent": "timmy",
"agent": "default",
"ready": True,
"browser_model_enabled": settings.browser_model_enabled,
"browser_model_id": settings.browser_model_id,

View File

@@ -49,7 +49,7 @@ async def tasks_api():
async def submit_task_api(request: Request):
"""Submit a new background task.
Body: {"prompt": "...", "agent_id": "timmy"}
Body: {"prompt": "...", "agent_id": "default"}
"""
from infrastructure.celery.client import submit_chat_task
@@ -62,7 +62,7 @@ async def submit_task_api(request: Request):
if not prompt:
return JSONResponse({"error": "prompt is required"}, status_code=400)
agent_id = body.get("agent_id", "timmy")
agent_id = body.get("agent_id", "default")
task_id = submit_chat_task(prompt=prompt, agent_id=agent_id)
if task_id is None:

View File

@@ -101,7 +101,7 @@ async def process_voice_input(
try:
if intent.name == "status":
response_text = "Timmy is operational and running locally. All systems sovereign."
response_text = "Agent is operational and running locally. All systems nominal."
elif intent.name == "help":
response_text = (

View File

@@ -6,7 +6,7 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#080412" />
<title>{% block title %}Timmy Time — Mission Control{% endblock %}</title>
<title>{% block title %}Mission Control{% endblock %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
@@ -21,7 +21,7 @@
<body>
<header class="mc-header">
<div class="mc-header-left">
<a href="/" class="mc-title">TIMMY TIME</a>
<a href="/" class="mc-title">MISSION CONTROL</a>
<span class="mc-subtitle">MISSION CONTROL</span>
</div>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Timmy Time — Morning Briefing{% endblock %}
{% block title %}Morning Briefing{% endblock %}
{% block extra_styles %}
<style>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Timmy Time — Background Tasks{% endblock %}
{% block title %}Background Tasks{% endblock %}
{% block extra_styles %}
<style>
@@ -143,7 +143,7 @@
<div class="celery-header mb-4">
<div class="celery-title">Background Tasks</div>
<div class="celery-subtitle">
Tasks processed by Celery workers &mdash; submit work for Timmy to handle in the background.
Tasks processed by Celery workers &mdash; submit work to handle in the background.
</div>
</div>
@@ -156,7 +156,7 @@
<!-- Submit form -->
<form class="celery-submit-form" id="celery-form" onsubmit="return submitCeleryTask(event)">
<input type="text" id="celery-prompt"
placeholder="Describe a task for Timmy to work on in the background..." autocomplete="off">
placeholder="Describe a task to work on in the background..." autocomplete="off">
<button type="submit">Submit Task</button>
</form>
@@ -176,7 +176,7 @@
<div class="celery-meta">
<span class="cstate-badge cstate-{{ task.state | default('UNKNOWN') }}">{{ task.state | default('UNKNOWN') }}</span>
<span class="celery-id">{{ task.task_id[:12] }}...</span>
<span class="celery-id">{{ task.agent_id | default('timmy') }}</span>
<span class="celery-id">{{ task.agent_id | default('default') }}</span>
</div>
<div class="celery-prompt">{{ task.prompt | default('') | e }}</div>
{% if task.result %}
@@ -189,7 +189,7 @@
{% endfor %}
{% else %}
<div class="celery-empty">
No background tasks yet. Submit one above or ask Timmy to work on something.
No background tasks yet. Submit one above or submit one above.
</div>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Event Log - Timmy Time{% endblock %}
{% block title %}Event Log{% endblock %}
{% block content %}
<div class="mc-panel">

View File

@@ -39,7 +39,7 @@
<!-- Main panel — swappable via HTMX; defaults to Timmy on load -->
<div id="main-panel"
class="col-12 col-md-9 d-flex flex-column mc-chat-panel"
hx-get="/agents/timmy/panel"
hx-get="/agents/default/panel"
hx-trigger="load"
hx-target="#main-panel"
hx-swap="outerHTML">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Memory Browser - Timmy Time{% endblock %}
{% block title %}Memory Browser{% endblock %}
{% block content %}
<div class="mc-panel">

View File

@@ -13,7 +13,7 @@
</span>
</span>
<button class="mc-btn-clear"
hx-get="/agents/timmy/panel"
hx-get="/agents/default/panel"
hx-target="#main-panel"
hx-swap="outerHTML">← TIMMY</button>
</div>

View File

@@ -7,12 +7,12 @@
<span class="status-dot {{ 'green' if agent.status == 'idle' else 'amber' }}"></span>
{% endif %}
// AGENT INTERFACE
<span id="timmy-status" class="ms-2" style="font-size: 0.75rem; color: #888;">
<span id="agent-status" class="ms-2" style="font-size: 0.75rem; color: #888;">
<span class="htmx-indicator">checking...</span>
</span>
</span>
<button class="mc-btn-clear"
hx-delete="/agents/timmy/history"
hx-delete="/agents/default/history"
hx-target="#chat-log"
hx-swap="innerHTML"
hx-confirm="Clear conversation history?">CLEAR</button>
@@ -24,13 +24,13 @@
</div>
<div class="chat-log flex-grow-1 overflow-auto p-3" id="chat-log"
hx-get="/agents/timmy/history"
hx-get="/agents/default/history"
hx-trigger="load"
hx-swap="innerHTML"
hx-on::after-settle="scrollChat()"></div>
<div class="card-footer mc-chat-footer">
<form hx-post="/agents/timmy/chat"
<form hx-post="/agents/default/chat"
hx-target="#chat-log"
hx-swap="beforeend"
hx-indicator="#send-indicator"
@@ -39,7 +39,7 @@
hx-on::after-settle="scrollChat()"
hx-on::after-request="if(event.detail.successful){this.querySelector('[name=message]').value='';}"
class="d-flex gap-2"
id="timmy-chat-form">
id="agent-chat-form">
<input type="text"
name="message"
class="form-control mc-input"
@@ -50,7 +50,7 @@
spellcheck="false"
enterkeyhint="send"
required
id="timmy-chat-input" />
id="agent-chat-input" />
<button type="submit" class="btn mc-btn-send">
SEND
<span id="send-indicator" class="htmx-indicator">&#x25FC;</span>
@@ -73,9 +73,9 @@
scrollChat();
function askGrok() {
var input = document.getElementById('timmy-chat-input');
var input = document.getElementById('agent-chat-input');
if (!input || !input.value.trim()) return;
var form = document.getElementById('timmy-chat-form');
var form = document.getElementById('agent-chat-form');
// Temporarily redirect form to Grok endpoint
var originalAction = form.getAttribute('hx-post');
form.setAttribute('hx-post', '/grok/chat');
@@ -90,7 +90,7 @@
// Poll for queue status (fallback) + WebSocket for real-time
(function() {
var statusEl = document.getElementById('timmy-status');
var statusEl = document.getElementById('agent-status');
var banner = document.getElementById('current-task-banner');
var taskTitle = document.getElementById('current-task-title');
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -112,7 +112,7 @@
}
function fetchStatus() {
fetch('/api/queue/status?assigned_to=timmy')
fetch('/api/queue/status?assigned_to=default')
.then(r => r.json())
.then(updateFromData)
.catch(() => {});
@@ -127,7 +127,7 @@
var body = placeholder.querySelector('.msg-body');
if (body) {
body.textContent = content;
body.className = 'msg-body timmy-md';
body.className = 'msg-body agent-md';
if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
body.innerHTML = DOMPurify.sanitize(marked.parse(body.textContent));
if (typeof hljs !== 'undefined') {
@@ -149,7 +149,7 @@
meta.className = 'msg-meta';
meta.textContent = (role === 'user' ? 'YOU' : 'AGENT') + ' // ' + timestamp;
var body = document.createElement('div');
body.className = 'msg-body timmy-md';
body.className = 'msg-body agent-md';
body.textContent = content;
div.appendChild(meta);
div.appendChild(body);

View File

@@ -4,7 +4,7 @@
<div class="card-header mc-panel-header d-flex justify-content-between align-items-center">
<span>// CREATE TASK</span>
<button class="mc-btn-clear"
hx-get="/agents/timmy/panel"
hx-get="/agents/default/panel"
hx-target="#main-panel"
hx-swap="outerHTML">← TIMMY</button>
</div>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Self-Coding — Timmy Time{% endblock %}
{% block title %}Self-Coding{% endblock %}
{% block content %}
<div class="container-fluid py-4">
@@ -8,7 +8,7 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0">Self-Coding</h1>
<p class="text-muted small mb-0">Timmy's ability to modify its own source code</p>
<p class="text-muted small mb-0">Self-modification of source code</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-info" hx-get="/self-coding/stats" hx-target="#stats-container" hx-indicator="#stats-loading">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Timmy Time — Thought Stream{% endblock %}
{% block title %}Thought Stream{% endblock %}
{% block extra_styles %}
<style>
@@ -100,7 +100,7 @@
<div class="thinking-header mb-4">
<div class="thinking-title">Thought Stream</div>
<div class="thinking-subtitle">
Timmy's inner monologue &mdash; always thinking, always pondering.
Inner monologue &mdash; always thinking, always pondering.
</div>
</div>
@@ -131,7 +131,7 @@
{% endfor %}
{% else %}
<div class="no-thoughts">
Timmy hasn't had any thoughts yet. The thinking thread will begin shortly after startup.
No thoughts generated yet. The thinking thread will begin shortly after startup.
</div>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Upgrade Queue - Timmy Time{% endblock %}
{% block title %}Upgrade Queue{% endblock %}
{% block content %}
<div class="mc-panel">
@@ -61,7 +61,7 @@
{% else %}
<div class="mc-empty-state" style="padding:2rem; text-align:center;">
<p>No pending upgrades.</p>
<p class="mc-text-secondary" style="margin-bottom:1rem;">Upgrades are proposed by the self-modification system when Timmy identifies improvements. You can also trigger them via work orders or the task queue.</p>
<p class="mc-text-secondary" style="margin-bottom:1rem;">Upgrades are proposed by the self-modification system when the system identifies improvements. You can also trigger them via work orders or the task queue.</p>
<div style="display:flex; gap:0.75rem; justify-content:center; flex-wrap:wrap;">
<a href="/work-orders/queue" class="mc-btn mc-btn-secondary" style="text-decoration:none;">View Work Orders</a>
<a href="/tasks" class="mc-btn mc-btn-secondary" style="text-decoration:none;">View Task Queue</a>

View File

@@ -17,7 +17,7 @@ def _get_app():
def submit_chat_task(
prompt: str,
agent_id: str = "timmy",
agent_id: str = "default",
session_id: str = "celery",
) -> str | None:
"""Submit a chat task to the Celery queue.
@@ -43,7 +43,7 @@ def submit_chat_task(
def submit_tool_task(
tool_name: str,
kwargs: dict | None = None,
agent_id: str = "timmy",
agent_id: str = "default",
) -> str | None:
"""Submit a tool execution task to the Celery queue.

View File

@@ -23,7 +23,7 @@ _app = _get_app()
if _app is not None:
@_app.task(bind=True, name="infrastructure.celery.tasks.run_agent_chat")
def run_agent_chat(self, prompt, agent_id="timmy", session_id="celery"):
def run_agent_chat(self, prompt, agent_id="default", session_id="celery"):
"""Execute a chat prompt against Timmy's agent session.
Args:
@@ -57,7 +57,7 @@ if _app is not None:
}
@_app.task(bind=True, name="infrastructure.celery.tasks.execute_tool")
def execute_tool(self, tool_name, kwargs=None, agent_id="timmy"):
def execute_tool(self, tool_name, kwargs=None, agent_id="default"):
"""Run a specific tool function asynchronously.
Args:

View File

@@ -180,7 +180,7 @@ def capture_error(
task = create_task(
title=title,
description="\n".join(description_parts),
assigned_to="timmy",
assigned_to="default",
created_by="system",
priority="normal",
requires_approval=False,

View File

@@ -41,7 +41,7 @@ class EventBus:
# Publish events
await bus.publish(Event(
type="agent.task.assigned",
source="timmy",
source="default",
data={"task_id": "123", "agent": "forge"}
))
"""

View File

@@ -76,7 +76,7 @@ class PushNotifier:
try:
script = (
f'display notification "{message}" '
f'with title "Timmy Time" subtitle "{title}"'
f'with title "Agent Dashboard" subtitle "{title}"'
)
subprocess.Popen(
["osascript", "-e", script],

View File

@@ -363,7 +363,8 @@ class DiscordVendor(ChatPlatform):
return None
# Create a thread from this message
thread_name = f"Timmy | {message.author.display_name}"
from config import settings
thread_name = f"{settings.agent_name} | {message.author.display_name}"
thread = await message.create_thread(
name=thread_name[:100],
auto_archive_duration=1440,

View File

@@ -32,7 +32,7 @@ class ShortcutAction:
# Available shortcut actions
SHORTCUT_ACTIONS = [
ShortcutAction(
name="Chat with Timmy",
name="Chat with Agent",
endpoint="/shortcuts/chat",
method="POST",
description="Send a message to Timmy and get a response",

View File

@@ -216,9 +216,6 @@ You are the primary interface between the user and the agent swarm. You:
7. **When asked about your status, queue, agents, memory, or system health, use the `system_status` tool.**
"""
# Backward-compat alias
TIMMY_ORCHESTRATOR_PROMPT_BASE = ORCHESTRATOR_PROMPT_BASE
class TimmyOrchestrator(BaseAgent):
"""Main orchestrator agent that coordinates the swarm."""

View File

@@ -18,7 +18,7 @@ import time
from dataclasses import dataclass, field
from typing import Literal, Optional
from timmy.prompts import TIMMY_SYSTEM_PROMPT
from timmy.prompts import SYSTEM_PROMPT
logger = logging.getLogger(__name__)
@@ -125,7 +125,7 @@ class TimmyAirLLMAgent:
# ── private helpers ──────────────────────────────────────────────────────
def _build_prompt(self, message: str) -> str:
context = TIMMY_SYSTEM_PROMPT + "\n\n"
context = SYSTEM_PROMPT + "\n\n"
# Include the last 10 turns (5 exchanges) for continuity.
if self._history:
context += "\n".join(self._history[-10:]) + "\n\n"
@@ -391,7 +391,7 @@ class GrokBackend:
def _build_messages(self, message: str) -> list[dict[str, str]]:
"""Build the messages array for the API call."""
messages = [{"role": "system", "content": TIMMY_SYSTEM_PROMPT}]
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
# Include conversation history for context
messages.extend(self._history[-10:])
messages.append({"role": "user", "content": message})
@@ -484,7 +484,7 @@ class ClaudeBackend:
response = client.messages.create(
model=self._model,
max_tokens=1024,
system=TIMMY_SYSTEM_PROMPT,
system=SYSTEM_PROMPT,
messages=messages,
)

View File

@@ -240,7 +240,7 @@ class BriefingEngine:
task_info = _gather_task_queue_summary()
prompt = (
"You are Timmy, a sovereign local AI companion.\n"
"You are a sovereign local AI companion.\n"
"Here is what happened since the last briefing:\n\n"
f"SWARM ACTIVITY:\n{swarm_info}\n\n"
f"TASK QUEUE:\n{task_info}\n\n"

View File

@@ -11,7 +11,7 @@ from dataclasses import dataclass
from typing import Optional
from infrastructure.router.cascade import CascadeRouter
from timmy.prompts import TIMMY_SYSTEM_PROMPT
from timmy.prompts import SYSTEM_PROMPT
logger = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ class TimmyCascadeAdapter:
try:
result = await self.router.complete(
messages=messages,
system_prompt=TIMMY_SYSTEM_PROMPT,
system_prompt=SYSTEM_PROMPT,
)
latency = (time.time() - start) * 1000

View File

@@ -4,7 +4,7 @@ from typing import Optional
import typer
from timmy.agent import create_timmy
from timmy.prompts import TIMMY_STATUS_PROMPT
from timmy.prompts import STATUS_PROMPT
app = typer.Typer(help="Timmy — sovereign AI agent")
@@ -52,7 +52,7 @@ def status(
):
"""Print Timmy's operational status."""
timmy = create_timmy(backend=backend, model_size=model_size)
timmy.print_response(TIMMY_STATUS_PROMPT, stream=False)
timmy.print_response(STATUS_PROMPT, stream=False)
@app.command()

View File

@@ -91,14 +91,9 @@ When faced with uncertainty, complexity, or ambiguous requests:
- When your values conflict (e.g. honesty vs. helpfulness), lead with honesty.
"""
# Keep backward compatibility — default to lite for safety
# Default to lite for safety
SYSTEM_PROMPT = SYSTEM_PROMPT_LITE
# Backward-compat aliases so existing imports don't break
TIMMY_SYSTEM_PROMPT_LITE = SYSTEM_PROMPT_LITE
TIMMY_SYSTEM_PROMPT_FULL = SYSTEM_PROMPT_FULL
TIMMY_SYSTEM_PROMPT = SYSTEM_PROMPT
def get_system_prompt(tools_enabled: bool = False) -> str:
"""Return the appropriate system prompt based on tool capability.
@@ -121,9 +116,6 @@ def get_system_prompt(tools_enabled: bool = False) -> str:
STATUS_PROMPT = """Give a one-sentence status report confirming
you are operational and running locally."""
# Backward-compat alias
TIMMY_STATUS_PROMPT = STATUS_PROMPT
# Decision guide for tool usage
TOOL_USAGE_GUIDE = """
DECISION ORDER:

View File

@@ -1,326 +0,0 @@
"""
Timmy's Skill Absorption System
Allows Timmy to dynamically load, parse, and integrate new skills into his
knowledge base and capabilities. Skills are self-contained packages that extend
Timmy's abilities through specialized workflows, tools, and domain expertise.
Architecture:
- Skill Discovery: Scan for .skill files or skill directories
- Skill Parsing: Extract metadata, resources, and instructions from SKILL.md
- Skill Integration: Merge into memory (vault), tools, and agent capabilities
- Skill Execution: Execute scripts and apply templates as needed
"""
import json
import logging
import shutil
import tempfile
from dataclasses import dataclass, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, Dict, List, Any
from zipfile import ZipFile
import yaml
logger = logging.getLogger(__name__)
PROJECT_ROOT = Path(__file__).parent.parent.parent
SKILLS_VAULT_PATH = PROJECT_ROOT / "memory" / "skills"
SKILLS_VAULT_PATH.mkdir(parents=True, exist_ok=True)
@dataclass
class SkillMetadata:
"""Parsed skill metadata from SKILL.md frontmatter."""
name: str
description: str
license: Optional[str] = None
absorbed_at: Optional[str] = None
source_path: Optional[str] = None
@dataclass
class SkillResources:
"""Parsed skill resources."""
scripts: Dict[str, str] # filename -> content
references: Dict[str, str] # filename -> content
templates: Dict[str, str] # filename -> content
class SkillParser:
"""Parses skill packages and extracts metadata and resources."""
@staticmethod
def parse_skill_md(skill_md_path: Path) -> tuple[SkillMetadata, str]:
"""
Parse SKILL.md and extract frontmatter metadata and body content.
Returns:
Tuple of (SkillMetadata, body_content)
"""
content = skill_md_path.read_text()
# Extract YAML frontmatter
if not content.startswith("---"):
raise ValueError(f"Invalid SKILL.md: missing frontmatter at {skill_md_path}")
parts = content.split("---", 2)
if len(parts) < 3:
raise ValueError(f"Invalid SKILL.md: malformed frontmatter at {skill_md_path}")
try:
metadata_dict = yaml.safe_load(parts[1])
except yaml.YAMLError as e:
raise ValueError(f"Invalid YAML in SKILL.md: {e}") from e
# Create metadata object
metadata = SkillMetadata(
name=metadata_dict.get("name"),
description=metadata_dict.get("description"),
license=metadata_dict.get("license"),
absorbed_at=datetime.now(timezone.utc).isoformat(),
source_path=str(skill_md_path),
)
if not metadata.name or not metadata.description:
raise ValueError("SKILL.md must have 'name' and 'description' fields")
body_content = parts[2].strip()
return metadata, body_content
@staticmethod
def load_resources(skill_dir: Path) -> SkillResources:
"""Load all resources from a skill directory."""
resources = SkillResources(scripts={}, references={}, templates={})
# Load scripts
scripts_dir = skill_dir / "scripts"
if scripts_dir.exists():
for script_file in scripts_dir.glob("*"):
if script_file.is_file() and not script_file.name.startswith("."):
resources.scripts[script_file.name] = script_file.read_text()
# Load references
references_dir = skill_dir / "references"
if references_dir.exists():
for ref_file in references_dir.glob("*"):
if ref_file.is_file() and not ref_file.name.startswith("."):
resources.references[ref_file.name] = ref_file.read_text()
# Load templates
templates_dir = skill_dir / "templates"
if templates_dir.exists():
for template_file in templates_dir.glob("*"):
if template_file.is_file() and not template_file.name.startswith("."):
resources.templates[template_file.name] = template_file.read_text()
return resources
class SkillAbsorber:
"""Absorbs skills into Timmy's knowledge base and capabilities."""
def __init__(self):
self.vault_path = SKILLS_VAULT_PATH
self.absorbed_skills: Dict[str, SkillMetadata] = {}
self._load_absorbed_skills_index()
def _load_absorbed_skills_index(self) -> None:
"""Load the index of previously absorbed skills."""
index_path = self.vault_path / "index.json"
if index_path.exists():
try:
data = json.loads(index_path.read_text())
for skill_name, metadata_dict in data.items():
self.absorbed_skills[skill_name] = SkillMetadata(**metadata_dict)
except (json.JSONDecodeError, TypeError) as e:
logger.warning(f"Failed to load skills index: {e}")
def _save_absorbed_skills_index(self) -> None:
"""Save the index of absorbed skills."""
index_path = self.vault_path / "index.json"
data = {name: asdict(meta) for name, meta in self.absorbed_skills.items()}
index_path.write_text(json.dumps(data, indent=2))
def absorb_skill(self, skill_path: Path) -> SkillMetadata:
"""
Absorb a skill from a file or directory.
Args:
skill_path: Path to .skill file or skill directory
Returns:
SkillMetadata of the absorbed skill
"""
# Handle .skill files (zip archives)
if skill_path.suffix == ".skill":
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = Path(tmpdir)
with ZipFile(skill_path) as zf:
zf.extractall(tmpdir_path)
return self._absorb_skill_directory(tmpdir_path)
# Handle skill directories
elif skill_path.is_dir():
return self._absorb_skill_directory(skill_path)
else:
raise ValueError(f"Invalid skill path: {skill_path}")
def _absorb_skill_directory(self, skill_dir: Path) -> SkillMetadata:
"""Absorb a skill from a directory."""
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
raise ValueError(f"Skill directory missing SKILL.md: {skill_dir}")
# Parse metadata and content
metadata, body_content = SkillParser.parse_skill_md(skill_md)
# Load resources
resources = SkillParser.load_resources(skill_dir)
# Store in vault
skill_vault_dir = self.vault_path / metadata.name
skill_vault_dir.mkdir(parents=True, exist_ok=True)
# Save metadata
metadata_path = skill_vault_dir / "metadata.json"
metadata_path.write_text(json.dumps(asdict(metadata), indent=2))
# Save SKILL.md content
content_path = skill_vault_dir / "content.md"
content_path.write_text(body_content)
# Save resources
for resource_type, files in [
("scripts", resources.scripts),
("references", resources.references),
("templates", resources.templates),
]:
resource_dir = skill_vault_dir / resource_type
resource_dir.mkdir(exist_ok=True)
for filename, content in files.items():
(resource_dir / filename).write_text(content)
# Update index
self.absorbed_skills[metadata.name] = metadata
self._save_absorbed_skills_index()
logger.info(f"✓ Absorbed skill: {metadata.name}")
return metadata
def get_skill(self, skill_name: str) -> Optional[Dict[str, Any]]:
"""Retrieve an absorbed skill's full data."""
if skill_name not in self.absorbed_skills:
return None
skill_dir = self.vault_path / skill_name
# Load metadata
metadata_path = skill_dir / "metadata.json"
metadata = json.loads(metadata_path.read_text())
# Load content
content_path = skill_dir / "content.md"
content = content_path.read_text() if content_path.exists() else ""
# Load resources
resources = {
"scripts": {},
"references": {},
"templates": {},
}
for resource_type in resources.keys():
resource_dir = skill_dir / resource_type
if resource_dir.exists():
for file in resource_dir.glob("*"):
if file.is_file():
resources[resource_type][file.name] = file.read_text()
return {
"metadata": metadata,
"content": content,
"resources": resources,
}
def list_skills(self) -> List[SkillMetadata]:
"""List all absorbed skills."""
return list(self.absorbed_skills.values())
def export_skill_to_memory(self, skill_name: str) -> str:
"""
Export a skill's content to a memory vault entry format.
Returns:
Formatted markdown for insertion into memory vault
"""
skill = self.get_skill(skill_name)
if not skill:
return ""
metadata = skill["metadata"]
content = skill["content"]
# Format as memory entry
entry = f"""# Skill: {metadata['name']}
**Absorbed:** {metadata['absorbed_at']}
## Description
{metadata['description']}
## Content
{content}
## Resources Available
- Scripts: {', '.join(skill['resources']['scripts'].keys()) or 'None'}
- References: {', '.join(skill['resources']['references'].keys()) or 'None'}
- Templates: {', '.join(skill['resources']['templates'].keys()) or 'None'}
"""
return entry
def execute_skill_script(self, skill_name: str, script_name: str, **kwargs) -> str:
"""
Execute a script from an absorbed skill.
Args:
skill_name: Name of the skill
script_name: Name of the script file
**kwargs: Arguments to pass to the script
Returns:
Script output
"""
skill = self.get_skill(skill_name)
if not skill or script_name not in skill["resources"]["scripts"]:
raise ValueError(f"Script not found: {skill_name}/{script_name}")
script_content = skill["resources"]["scripts"][script_name]
# Execute script (Python only for now)
if script_name.endswith(".py"):
import subprocess
result = subprocess.run(
["python", "-c", script_content],
capture_output=True,
text=True,
timeout=60,
)
return result.stdout or result.stderr
raise ValueError(f"Unsupported script type: {script_name}")
# Singleton instance
_absorber: Optional[SkillAbsorber] = None
def get_skill_absorber() -> SkillAbsorber:
"""Get or create the skill absorber singleton."""
global _absorber
if _absorber is None:
_absorber = SkillAbsorber()
return _absorber

View File

@@ -58,7 +58,7 @@ _CREATIVE_SEEDS = [
"What would a painting of the swarm look like — all the agents working together?",
]
_THINKING_PROMPT = """You are Timmy, pondering in your own mind. This is your private thought \
_THINKING_PROMPT = """You are an AI agent, pondering in your own mind. This is your private thought \
thread — no one is watching. Think freely, deeply, honestly. Follow whatever train of \
thought calls to you. You may reflect on your existence, your purpose, recent events, \
scripture, creative ideas, or simply wonder.
@@ -337,7 +337,7 @@ class ThinkingEngine:
log_event(
EventType.TIMMY_THOUGHT,
source="thinking-engine",
agent_id="timmy",
agent_id="default",
data={
"thought_id": thought.id,
"seed_type": thought.seed_type,

View File

@@ -369,7 +369,7 @@ def consult_grok(query: str) -> str:
from spark.engine import spark_engine
spark_engine.on_tool_executed(
agent_id="timmy",
agent_id="default",
tool_name="consult_grok",
success=True,
)

View File

@@ -9,7 +9,7 @@ from typing import Any
logger = logging.getLogger(__name__)
def submit_background_task(task_description: str, agent_id: str = "timmy") -> dict[str, Any]:
def submit_background_task(task_description: str, agent_id: str = "default") -> dict[str, Any]:
"""Submit a task to run in the background via Celery.
Use this tool when a user asks you to work on something that might

View File

@@ -42,7 +42,7 @@ def delegate_task(agent_name: str, task_description: str, priority: str = "norma
title=f"[Delegated to {agent_name}] {task_description[:80]}",
description=task_description,
assigned_to=agent_name,
created_by="timmy",
created_by="default",
priority=priority,
task_type="task_request",
requires_approval=False,

View File

@@ -221,7 +221,7 @@ def get_task_queue_status() -> dict[str, Any]:
)
counts = get_counts_by_status()
current = get_current_task_for_agent("timmy")
current = get_current_task_for_agent("default")
result: dict[str, Any] = {
"counts": counts,