Compare commits
1 Commits
step35/443
...
step35/330
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cd6ecd7bd |
13
config.yaml
13
config.yaml
@@ -191,6 +191,19 @@ system_prompt_suffix: "You are Timmy. Your soul is defined in SOUL.md \u2014 rea
|
||||
skills:
|
||||
creation_nudge_interval: 15
|
||||
|
||||
# ── Creativity Engine ───────────────────────────────────────────────────
|
||||
# Generate innovation during idle cycles. When enabled, the cron job will:
|
||||
# - detect idle agents (no active work for 30+ minutes)
|
||||
# - ask them to generate creative output (code improvements, issue drafts, skill updates)
|
||||
# - log results to ~/.local/timmy/creativity/
|
||||
creativity:
|
||||
enabled: true
|
||||
check_interval_minutes: 15
|
||||
idle_threshold_minutes: 30
|
||||
model: hermes4:14b
|
||||
provider: ollama
|
||||
base_url: http://localhost:11434/v1
|
||||
|
||||
# ── Fallback Model ────────────────────────────────────────────────────
|
||||
# Automatic provider failover when primary is unavailable.
|
||||
# Uncomment and configure to enable. Triggers on rate limits (429),
|
||||
|
||||
@@ -226,7 +226,38 @@
|
||||
"tmux-supervisor"
|
||||
],
|
||||
"skill": "tmux-supervisor"
|
||||
},
|
||||
{
|
||||
"id": "creativity-eng-8617",
|
||||
"name": "Creativity Engine",
|
||||
"prompt": "Run the Creativity Engine (fleet/creativity_engine.py) to generate innovation during idle cycles.\n\nSteps:\n1. Execute: python3 ~/.timmy/timmy-config/fleet/creativity_engine.py\n2. Capture stdout/stderr\n3. If creative output was generated, note what was produced\n4. If no idle agents found or creativity disabled, that's fine \u2014 report 'no action'\n5. Always report: did the script run successfully? Was output generated?\n",
|
||||
"schedule": {
|
||||
"kind": "interval",
|
||||
"minutes": 15,
|
||||
"display": "every 15m"
|
||||
},
|
||||
"schedule_display": "every 15m",
|
||||
"repeat": {
|
||||
"times": null,
|
||||
"completed": 0
|
||||
},
|
||||
"enabled": true,
|
||||
"created_at": "2026-04-26T15:50:17.000000Z",
|
||||
"next_run_at": null,
|
||||
"last_run_at": null,
|
||||
"last_status": null,
|
||||
"last_error": null,
|
||||
"deliver": "local",
|
||||
"origin": null,
|
||||
"state": "scheduled",
|
||||
"paused_at": null,
|
||||
"paused_reason": null,
|
||||
"skills": [],
|
||||
"skill": null,
|
||||
"model": "hermes4:14b",
|
||||
"provider": "ollama",
|
||||
"base_url": "http://localhost:11434/v1"
|
||||
}
|
||||
],
|
||||
"updated_at": "2026-04-13T02:00:00+00:00"
|
||||
"updated_at": "2026-04-26T15:50:17.000000Z"
|
||||
}
|
||||
156
fleet/creativity_engine.py
Executable file
156
fleet/creativity_engine.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Creativity Engine — generate innovation during idle cycles.
|
||||
PAPERCLIPS-2: "Use idle operations to generate new problems and solutions."
|
||||
|
||||
When agents are idle (no active work), this daemon asks them to generate:
|
||||
- Code improvement proposals
|
||||
- New issue drafts
|
||||
- Skill updates
|
||||
|
||||
Run via cron every 15 minutes. Config controlled via creativity: section in config.yaml.
|
||||
|
||||
Usage:
|
||||
python3 fleet/creativity_engine.py --dry-run # Show what would run, no generation
|
||||
python3 fleet/creativity_engine.py # Run full creativity cycle
|
||||
"""
|
||||
|
||||
import os, sys, json, time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# ─── Paths ────────────────────────────────────────────────────────────────────
|
||||
CONFIG_PATH = Path.home() / ".hermes" / "config.yaml"
|
||||
AGENTS_DB = Path.home() / ".local" / "timmy" / "fleet-agents" / "agents.json"
|
||||
LOG_DIR = Path.home() / ".local" / "timmy" / "creativity"
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ─── Config loader ────────────────────────────────────────────────────────────
|
||||
def load_config():
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("[ERROR] PyYAML not installed. Install: pip install pyyaml", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if CONFIG_PATH.exists():
|
||||
with open(CONFIG_PATH) as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
return {}
|
||||
|
||||
# ─── Agent state ──────────────────────────────────────────────────────────────
|
||||
def load_agents():
|
||||
if AGENTS_DB.exists():
|
||||
with open(AGENTS_DB) as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def is_agent_idle(agent, idle_threshold_minutes=30):
|
||||
"""An agent is idle if state==idle OR no heartbeat for threshold_minutes."""
|
||||
state = agent.get("state", "")
|
||||
if state == "idle":
|
||||
return True
|
||||
hb = agent.get("last_heartbeat")
|
||||
if hb:
|
||||
try:
|
||||
hb_t = datetime.fromisoformat(hb)
|
||||
age = (datetime.now(timezone.utc) - hb_t).total_seconds() / 60
|
||||
if age > idle_threshold_minutes:
|
||||
return True
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return False
|
||||
|
||||
# ─── Ollama client ────────────────────────────────────────────────────────────
|
||||
def call_ollama(prompt: str, model: str, base_url: str) -> str:
|
||||
"""Call local Ollama via OpenAI-compatible /chat/completions endpoint."""
|
||||
import urllib.request
|
||||
import json
|
||||
url = base_url.rstrip('/') + '/chat/completions'
|
||||
body = json.dumps({
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0.8,
|
||||
"stream": False,
|
||||
}).encode()
|
||||
try:
|
||||
req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}, method="POST")
|
||||
resp = urllib.request.urlopen(req, timeout=120)
|
||||
data = json.loads(resp.read())
|
||||
return data["choices"][0]["message"]["content"]
|
||||
except Exception as e:
|
||||
return f"[CREATIVITY ERROR: {e}]"
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
def main():
|
||||
cfg = load_config()
|
||||
creativity_cfg = cfg.get("creativity", {})
|
||||
|
||||
# 1. Check if enabled
|
||||
if not creativity_cfg.get("enabled", False):
|
||||
print("Creativity engine disabled in config.")
|
||||
sys.exit(0)
|
||||
|
||||
# 2. Load agents
|
||||
agents = load_agents()
|
||||
if not agents:
|
||||
print("No agents found in agents DB.")
|
||||
sys.exit(0)
|
||||
|
||||
# 3. Find idle agents
|
||||
idle_threshold = creativity_cfg.get("idle_threshold_minutes", 30)
|
||||
idle_agents = [
|
||||
(name, a) for name, a in agents.items()
|
||||
if a.get("state") == "deployed" and is_agent_idle(a, idle_threshold)
|
||||
]
|
||||
|
||||
if not idle_agents:
|
||||
print(f"No idle agents found (threshold={idle_threshold}m).")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"Idle agents: {[n for n,_ in idle_agents]}")
|
||||
|
||||
# 4. Generation settings
|
||||
model = creativity_cfg.get("model", "hermes4:14b")
|
||||
provider = creativity_cfg.get("provider", "ollama")
|
||||
base_url = creativity_cfg.get("base_url", "http://localhost:11434/v1")
|
||||
|
||||
# 5. For each idle agent, generate creative output
|
||||
session_ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
||||
log_file = LOG_DIR / f"creativity-{session_ts}.md"
|
||||
log_lines = [
|
||||
f"# Creativity Engine — {session_ts} UTC\n",
|
||||
f"**Idle agents:** {len(idle_agents)}\n",
|
||||
f"**Model:** {model}\n",
|
||||
"---\n\n",
|
||||
]
|
||||
|
||||
for agent_name, agent_data in idle_agents:
|
||||
vps = agent_data.get("vps", "unknown")
|
||||
log_lines.append(f"## Agent: {agent_name} (VPS: {vps})\n\n")
|
||||
|
||||
prompt = (
|
||||
"You are an AI agent with idle capacity. Generate innovative ideas.\n"
|
||||
"Produce THREE distinct creative outputs:\n"
|
||||
"1. **Code Improvement** — 1-2 concrete suggestions for existing codebases\n"
|
||||
"2. **New Issue Draft** — a well-formed issue idea for a Timmy_Foundation repo\n"
|
||||
"3. **Skill Update** — a new skill or skill improvement for Hermes agents\n\n"
|
||||
"Be specific, actionable, and concise. Format as:\n"
|
||||
"### Code Improvement\n<description>\n\n"
|
||||
"### New Issue\n**Title:** <title>\n**Body:** <description>\n\n"
|
||||
"### Skill Update\n**Skill Name:** <name>\n**Summary:** <what and why>\n"
|
||||
)
|
||||
|
||||
output = call_ollama(prompt, model, base_url)
|
||||
log_lines.append(output)
|
||||
log_lines.append("\n\n---\n\n")
|
||||
|
||||
# 6. Write log
|
||||
log_file.write_text("".join(log_lines))
|
||||
print(f"Creativity log written: {log_file}")
|
||||
|
||||
# 7. Also print a brief summary
|
||||
for agent_name, _ in idle_agents:
|
||||
print(f" ✓ Generated creative output for {agent_name}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user