From e1ba3fcf6b03dfb3b8135f47b3b21b5025562f68 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sun, 15 Mar 2026 11:38:17 -0400 Subject: [PATCH] feat: update kimi-code skill with session persistence, file arch refactor issues #164-#170 --- bin/timmy-loop.sh | 100 ++++++++++++++++-- bin/timmy-tmux.sh | 49 +++++---- cron/jobs.json | 20 ++-- memories/MEMORY.md | 4 +- .../autonomous-ai-agents/kimi-code/SKILL.md | 17 ++- 5 files changed, 148 insertions(+), 42 deletions(-) diff --git a/bin/timmy-loop.sh b/bin/timmy-loop.sh index b8660b3..191fe92 100755 --- a/bin/timmy-loop.sh +++ b/bin/timmy-loop.sh @@ -1,7 +1,12 @@ #!/usr/bin/env bash -# ── Timmy Development Loop v2 ────────────────────────────────────────── -# Runs forever. Each cycle: Timmy triages → Hermes picks work → execute -# → Timmy reviews → merge. State in .loop/state.json. +# ── Timmy Development Loop v3 ────────────────────────────────────────── +# Runs forever. Each cycle: triage → Hermes executes → retro → repeat. +# +# TRIAGE (two tiers): +# Fast (every 5 cycles): mechanical scoring → .loop/queue.json +# Deep (every 20 cycles): Hermes reads code + consults Timmy → refined queue +# +# RETRO (every cycle): structured log → .loop/retro/cycles.jsonl # ─────────────────────────────────────────────────────────────────────── set -uo pipefail @@ -17,6 +22,12 @@ LOCKFILE="/tmp/timmy-loop.lock" COOLDOWN=3 MAX_CYCLE_TIME=1200 # 20 min — enough for complex issues CLAIM_TTL_SECONDS=3600 # 1 hour — stale claims auto-expire +FAST_TRIAGE_INTERVAL=5 # mechanical scoring every N cycles +DEEP_TRIAGE_INTERVAL=20 # Hermes+Timmy deep triage every N cycles +TRIAGE_SCRIPT="$REPO/scripts/triage_score.py" +RETRO_SCRIPT="$REPO/scripts/cycle_retro.py" +DEEP_TRIAGE_SCRIPT="$REPO/scripts/deep_triage.sh" +QUEUE_FILE="$REPO/.loop/queue.json" # macOS doesn't have timeout; use perl fallback if ! command -v timeout &>/dev/null; then timeout() { @@ -125,11 +136,69 @@ except Exception as e: print(f'[cleanup] Claim cleanup failed: {e}') " 2>&1 } +# ── Fast triage (mechanical scoring) ────────────────────────────────── +run_fast_triage() { + if [ -f "$TRIAGE_SCRIPT" ]; then + log "Running fast triage..." + python3 "$TRIAGE_SCRIPT" 2>&1 + else + log "WARNING: triage script not found at $TRIAGE_SCRIPT" + fi +} +# ── Deep triage (Hermes + Timmy) ───────────────────────────────────── +run_deep_triage() { + if [ -f "$DEEP_TRIAGE_SCRIPT" ]; then + log "Running deep triage (Hermes + Timmy)..." + timeout 600 bash "$DEEP_TRIAGE_SCRIPT" 2>&1 + else + log "WARNING: deep triage script not found at $DEEP_TRIAGE_SCRIPT" + fi +} +# ── Cycle retrospective ───────────────────────────────────────────── +log_retro() { + # Usage: log_retro [extra args for cycle_retro.py] + local outcome="$1"; shift + if [ -f "$RETRO_SCRIPT" ]; then + local duration=$(( $(date +%s) - CYCLE_START )) + if [ "$outcome" = "success" ]; then + python3 "$RETRO_SCRIPT" --cycle "$CYCLE" --success \ + --duration "$duration" "$@" 2>&1 + else + python3 "$RETRO_SCRIPT" --cycle "$CYCLE" --failure \ + --duration "$duration" "$@" 2>&1 + fi + fi +} + +# ── Inject queue context into prompt ───────────────────────────────── +get_queue_context() { + if [ -f "$QUEUE_FILE" ]; then + python3 -c " +import json +with open('$QUEUE_FILE') as f: q = json.load(f) +if not q: + print('Queue is empty. Check open issues on Gitea and pick the highest-priority one.') +else: + print(f'PRIORITIZED QUEUE ({len(q)} ready issues):') + for i, item in enumerate(q[:8]): + flag = 'BUG' if item.get('type') == 'bug' else item.get('type', '?').upper() + print(f' {i+1}. #{item[\"issue\"]} [{flag}] score={item[\"score\"]} — {item.get(\"title\",\"?\")[:60]}') + if item.get('files'): + print(f' files: {\", \".join(item[\"files\"][:3])}') + if len(q) > 8: + print(f' ... +{len(q)-8} more') + print() + print(f'Pick from the TOP of this queue. Issue #{q[0][\"issue\"]} is highest priority.') +" 2>/dev/null + else + echo "No queue file. Check open issues on Gitea and pick the highest-priority one." + fi +} # ── Main Loop ───────────────────────────────────────────────────────── -log "Timmy development loop v2 starting. PID $$" +log "Timmy development loop v3 starting. PID $$" log "Timeout: ${MAX_CYCLE_TIME}s | Cooldown: ${COOLDOWN}s | Claim TTL: ${CLAIM_TTL_SECONDS}s" log "Repo: $REPO" update_state "started_at" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" @@ -152,13 +221,25 @@ while true; do update_state "cycle" "$CYCLE" update_state "status" '"working"' + CYCLE_START=$(date +%s) + # ── Pre-cycle housekeeping ──────────────────────────────────────── expire_claims - - # ── Build the prompt with time budget ────────────────────────────── + + # ── Triage (fast every 5, deep every 20) ───────────────────────── + if (( CYCLE % DEEP_TRIAGE_INTERVAL == 0 )); then + run_deep_triage + elif (( CYCLE % FAST_TRIAGE_INTERVAL == 0 )); then + run_fast_triage + fi + + # ── Build the prompt with time budget + queue ──────────────────── + QUEUE_CONTEXT=$(get_queue_context) PROMPT=$(cat "$PROMPT_FILE") PROMPT="TIME BUDGET: You have $((MAX_CYCLE_TIME / 60)) minutes for this cycle. Plan accordingly — do not start work you cannot finish. +$QUEUE_CONTEXT + $PROMPT" log "Spawning hermes for cycle $CYCLE..." @@ -169,6 +250,8 @@ $PROMPT" update_state "status" '"idle"' update_state "last_completed" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" + # ── Cycle retro (success) ──────────────────────────────────── + log_retro success else EXIT_CODE=$? @@ -182,7 +265,10 @@ errs.append({'cycle': $CYCLE, 'code': $EXIT_CODE, 'time': '$(date -u +%Y-%m-%dT% s['errors'] = errs[-10:] with open('$STATE', 'w') as f: json.dump(s, f, indent=2) " - # ── Cleanup on failure ──────────────────────────────────────── + # ── Cycle retro (failure) ──────────────────────────────────── + log_retro failure --reason "exit code $EXIT_CODE" + + # ── Cleanup on failure ─────────────────────────────────────── cleanup_cycle "$CYCLE" fi diff --git a/bin/timmy-tmux.sh b/bin/timmy-tmux.sh index 5919ca4..add371e 100755 --- a/bin/timmy-tmux.sh +++ b/bin/timmy-tmux.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash # ── Timmy Loop tmux Session ──────────────────────────────────────────── -# Creates session with 3 panes using standard tmux splits. +# Creates session with 4 panes. # -# Layout: -# ┌──────────────────────┬──────────────────────┐ -# │ LOOP (small) │ │ -# ├──────────────────────┤ HERMES CHAT │ -# │ STATUS DASHBOARD │ (full height) │ -# │ (live refresh) │ │ -# └──────────────────────┴──────────────────────┘ +# Layout (245×62 terminal): +# ┌────────────────────────────────────────────┬──────────────────────┐ +# │ LOOP (10 rows) │ │ +# ├──────────────────────┬─────────────────────┤ CHAT (full height) │ +# │ STATUS (81 cols) │ LOOPSTAT (40 cols) │ │ +# │ (50 rows) │ (50 rows) │ │ +# └──────────────────────┴─────────────────────┴──────────────────────┘ # ─────────────────────────────────────────────────────────────────────── SESSION="timmy-loop" @@ -21,22 +21,26 @@ sleep 1 # Create session — pane 0 starts as shell tmux new-session -d -s "$SESSION" -x 245 -y 62 -# Vertical split: left (50%) | right (50%) +# Vertical split: left (~50%) | right Chat (~50%) tmux split-window -h -p 50 -t "$SESSION:0.0" -# Horizontal split on left pane: Loop (small top) / Status (big bottom) -# Loop gets ~14% height (10 rows out of ~62), Status gets the rest +# Horizontal split on left: Loop (small top ~16%) | bottom (~84%) tmux split-window -v -p 83 -t "$SESSION:0.0" +# Vertical split on bottom-left: Status (wide ~67%) | LOOPSTAT (~33%) +tmux split-window -h -p 33 -t "$SESSION:0.1" + # Pane map after splits: -# 0 = top-left (small) → Loop output -# 1 = bottom-left (big) → Status dashboard -# 2 = right (full height) → Hermes chat +# 0 = top-left (full width) → Loop output +# 1 = bottom-left (wide) → Status dashboard +# 2 = bottom-mid (narrow) → LOOPSTAT (strategy) +# 3 = right (full height) → Hermes chat # Set titles tmux select-pane -t "$SESSION:0.0" -T "Loop" tmux select-pane -t "$SESSION:0.1" -T "Status" -tmux select-pane -t "$SESSION:0.2" -T "Chat" +tmux select-pane -t "$SESSION:0.2" -T "LOOPSTAT" +tmux select-pane -t "$SESSION:0.3" -T "Chat" # Pane border styling tmux set-option -t "$SESSION" pane-border-status top @@ -47,17 +51,18 @@ tmux set-option -t "$SESSION" pane-active-border-style "fg=cyan" # Start processes tmux send-keys -t "$SESSION:0.0" "export PATH=\"$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:\$PATH\" && $HOME/.hermes/bin/timmy-loop.sh" Enter tmux send-keys -t "$SESSION:0.1" "$HOME/.hermes/bin/timmy-status.sh" Enter -tmux send-keys -t "$SESSION:0.2" "cd ~/Timmy-Time-dashboard && hermes" Enter +tmux send-keys -t "$SESSION:0.2" "$HOME/.hermes/bin/timmy-loopstat.sh" Enter +tmux send-keys -t "$SESSION:0.3" "cd ~/Timmy-Time-dashboard && hermes" Enter # Focus chat pane -tmux select-pane -t "$SESSION:0.2" +tmux select-pane -t "$SESSION:0.3" echo "" -echo " ┌──────────────────┬──────────────────┐" -echo " │ Loop (pane 0) │ │" -echo " ├──────────────────┤ Chat (pane 2) │" -echo " │ Status (pane 1) │ │" -echo " └──────────────────┴──────────────────┘" +echo " ┌──────────────────────────────────┬──────────────────┐" +echo " │ Loop (pane 0) │ │" +echo " ├────────────────────┬─────────────┤ Chat (pane 3) │" +echo " │ Status (pane 1) │ LOOPSTAT(2) │ │" +echo " └────────────────────┴─────────────┴──────────────────┘" echo "" echo " Attach: tmux attach -t timmy-loop" echo " Stop: touch ~/Timmy-Time-dashboard/.loop/STOP" diff --git a/cron/jobs.json b/cron/jobs.json index 38e082b..2bbd4df 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -35,12 +35,12 @@ "schedule_display": "every 8m", "repeat": { "times": null, - "completed": 20 + "completed": 28 }, "enabled": true, "created_at": "2026-03-14T21:25:31.831983-04:00", - "next_run_at": "2026-03-15T10:17:06.096722-04:00", - "last_run_at": "2026-03-15T10:09:06.096722-04:00", + "next_run_at": "2026-03-15T11:35:42.310507-04:00", + "last_run_at": "2026-03-15T11:27:42.310507-04:00", "last_status": "ok", "last_error": null, "deliver": "local", @@ -58,12 +58,12 @@ "schedule_display": "every 8m", "repeat": { "times": null, - "completed": 18 + "completed": 26 }, "enabled": true, "created_at": "2026-03-14T21:39:34.712372-04:00", - "next_run_at": "2026-03-15T10:17:32.633122-04:00", - "last_run_at": "2026-03-15T10:09:32.633122-04:00", + "next_run_at": "2026-03-15T11:35:59.953250-04:00", + "last_run_at": "2026-03-15T11:27:59.953250-04:00", "last_status": "ok", "last_error": null, "deliver": "local", @@ -81,17 +81,17 @@ "schedule_display": "every 10m", "repeat": { "times": null, - "completed": 3 + "completed": 8 }, "enabled": true, "created_at": "2026-03-15T09:23:09.042883-04:00", - "next_run_at": "2026-03-15T10:16:56.086710-04:00", - "last_run_at": "2026-03-15T10:06:56.086710-04:00", + "next_run_at": "2026-03-15T11:32:58.861581-04:00", + "last_run_at": "2026-03-15T11:22:58.861581-04:00", "last_status": "ok", "last_error": null, "deliver": "local", "origin": null } ], - "updated_at": "2026-03-15T10:09:32.633259-04:00" + "updated_at": "2026-03-15T11:27:59.953394-04:00" } \ No newline at end of file diff --git a/memories/MEMORY.md b/memories/MEMORY.md index 1f195a7..1955563 100644 --- a/memories/MEMORY.md +++ b/memories/MEMORY.md @@ -1,8 +1,8 @@ -Gitea (localhost:3000): Users: rockachopa(admin), hermes(id=4), manus(id=3), kimi(id=5). Repos: rockachopa/Timmy-time-dashboard, rockachopa/hermes-agent, rockachopa/hermes-config. Hermes token: ~/.hermes/gitea_token. DO NOT use Alex's token — ever. Future work: agentd user isolation. +Gitea (localhost:3000): Users: rockachopa(admin), hermes(id=4), manus(id=3), kimi(id=5). Repos: rockachopa/Timmy-time-dashboard, hermes/hermes-config (private, rockachopa=admin collab). Hermes token: ~/.hermes/gitea_token. DO NOT use Alex's token — ever. Sync tool: ~/.hermes/bin/hermes-config-sync — run after modifying config/scripts/memories/skills. Future work: agentd user isolation. § Timmy architecture plan: Replace hardcoded _PERSONAS and TimmyOrchestrator with YAML-driven agent config (agents.yaml). One seed class, all differentiation via config. User wants to update YAML files, not Python, to add capabilities. Key files to refactor: agents/timmy.py, agents/base.py, tools_delegation/__init__.py, tools_intro/__init__.py. SubAgent class stays mostly as-is, just reads from YAML. Routing should be config-based pattern matching, not LLM calls. Per-agent model assignment (big model for orchestrator/code/research, small for simple tasks). qwen3:30b pulling as primary local model (~18GB, MoE 3B active). § -Hermes-Timmy workspace: ~/Timmy-Time-dashboard/workspace/. Flat file correspondence, append-only. +Kimi delegation: 1-3 files max, pre-extract context (scans full codebase otherwise), ~/worktrees/ not /tmp/, verify commits, two-attempt rule. Worktrees: ~/worktrees/kimi-*. § 2026-03-14: Issues #36-#40,#52 fixed. Voice loop, fallback chain, self-prompt queue built. Timmy on qwen3:30b (4096 ctx). Loop v2 with 20min timeout. Filed eval issues #77-#87. Status: ~/.hermes/bin/timmy-status.sh. § diff --git a/skills/autonomous-ai-agents/kimi-code/SKILL.md b/skills/autonomous-ai-agents/kimi-code/SKILL.md index 45d110e..1f221db 100644 --- a/skills/autonomous-ai-agents/kimi-code/SKILL.md +++ b/skills/autonomous-ai-agents/kimi-code/SKILL.md @@ -21,7 +21,22 @@ Delegate coding tasks to Kimi Code CLI via the Hermes terminal. Powered by kimi- - Logged in: `kimi login` - Config at `~/.kimi/config.toml` -## One-Shot Tasks (Print Mode) +## Session Persistence (ALWAYS USE) + +Kimi scans the entire codebase on first run. Reuse sessions to keep context warm: + +``` +# First task in a worktree — creates session, caches codebase +terminal(command="kimi --print --session worktree-148 -p 'Fix the XSS vulnerability'", workdir="~/worktrees/kimi-148", timeout=300) + +# Subsequent tasks — context is cached, much faster +terminal(command="kimi --print --continue --session worktree-148 -p 'Now fix the CSRF issue'", workdir="~/worktrees/kimi-148", timeout=180) +``` + +**NEVER run Kimi without --session on large codebases.** First run takes 2-5 min. +Subsequent runs in same session take 30-90s. + +## One-Shot Tasks (only for tiny repos or quick questions) ``` terminal(command="kimi --print -p 'Fix the XSS vulnerability in swarm_live.html by sanitizing innerHTML'", workdir="~/project", timeout=120)