feat: update kimi-code skill with session persistence, file arch refactor issues #164-#170

This commit is contained in:
Alexander Whitestone
2026-03-15 11:38:17 -04:00
parent d785af7f2f
commit e1ba3fcf6b
5 changed files with 148 additions and 42 deletions

View File

@@ -1,7 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ── Timmy Development Loop v2 ────────────────────────────────────────── # ── Timmy Development Loop v3 ──────────────────────────────────────────
# Runs forever. Each cycle: Timmy triages → Hermes picks work → execute # Runs forever. Each cycle: triage → Hermes executes → retro → repeat.
# → Timmy reviews → merge. State in .loop/state.json. #
# 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 set -uo pipefail
@@ -17,6 +22,12 @@ LOCKFILE="/tmp/timmy-loop.lock"
COOLDOWN=3 COOLDOWN=3
MAX_CYCLE_TIME=1200 # 20 min — enough for complex issues MAX_CYCLE_TIME=1200 # 20 min — enough for complex issues
CLAIM_TTL_SECONDS=3600 # 1 hour — stale claims auto-expire 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 # macOS doesn't have timeout; use perl fallback
if ! command -v timeout &>/dev/null; then if ! command -v timeout &>/dev/null; then
timeout() { timeout() {
@@ -125,11 +136,69 @@ except Exception as e:
print(f'[cleanup] Claim cleanup failed: {e}') print(f'[cleanup] Claim cleanup failed: {e}')
" 2>&1 " 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 <success|failure> [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 ───────────────────────────────────────────────────────── # ── 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 "Timeout: ${MAX_CYCLE_TIME}s | Cooldown: ${COOLDOWN}s | Claim TTL: ${CLAIM_TTL_SECONDS}s"
log "Repo: $REPO" log "Repo: $REPO"
update_state "started_at" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" 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 "cycle" "$CYCLE"
update_state "status" '"working"' update_state "status" '"working"'
CYCLE_START=$(date +%s)
# ── Pre-cycle housekeeping ──────────────────────────────────────── # ── Pre-cycle housekeeping ────────────────────────────────────────
expire_claims 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=$(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. 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" $PROMPT"
log "Spawning hermes for cycle $CYCLE..." log "Spawning hermes for cycle $CYCLE..."
@@ -169,6 +250,8 @@ $PROMPT"
update_state "status" '"idle"' update_state "status" '"idle"'
update_state "last_completed" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" update_state "last_completed" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
# ── Cycle retro (success) ────────────────────────────────────
log_retro success
else else
EXIT_CODE=$? EXIT_CODE=$?
@@ -182,7 +265,10 @@ errs.append({'cycle': $CYCLE, 'code': $EXIT_CODE, 'time': '$(date -u +%Y-%m-%dT%
s['errors'] = errs[-10:] s['errors'] = errs[-10:]
with open('$STATE', 'w') as f: json.dump(s, f, indent=2) 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" cleanup_cycle "$CYCLE"
fi fi

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ── Timmy Loop tmux Session ──────────────────────────────────────────── # ── Timmy Loop tmux Session ────────────────────────────────────────────
# Creates session with 3 panes using standard tmux splits. # Creates session with 4 panes.
# #
# Layout: # Layout (245×62 terminal):
# ┌──────────────────────┬──────────────────────┐ # ┌────────────────────────────────────────────┬──────────────────────┐
# │ LOOP (small) │ │ # │ LOOP (10 rows) │ │
# ├──────────────────────┤ HERMES CHAT # ├──────────────────────┬─────────────────────┤ CHAT (full height)
# │ STATUS DASHBOARD │ (full height) # │ STATUS (81 cols) │ LOOPSTAT (40 cols) │
# │ (live refresh) │ │ # │ (50 rows) │ (50 rows) │ │
# └──────────────────────┴──────────────────────┘ # └──────────────────────┴─────────────────────┴──────────────────────┘
# ─────────────────────────────────────────────────────────────────────── # ───────────────────────────────────────────────────────────────────────
SESSION="timmy-loop" SESSION="timmy-loop"
@@ -21,22 +21,26 @@ sleep 1
# Create session — pane 0 starts as shell # Create session — pane 0 starts as shell
tmux new-session -d -s "$SESSION" -x 245 -y 62 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" tmux split-window -h -p 50 -t "$SESSION:0.0"
# Horizontal split on left pane: Loop (small top) / Status (big bottom) # Horizontal split on left: Loop (small top ~16%) | bottom (~84%)
# Loop gets ~14% height (10 rows out of ~62), Status gets the rest
tmux split-window -v -p 83 -t "$SESSION:0.0" 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: # Pane map after splits:
# 0 = top-left (small) → Loop output # 0 = top-left (full width) → Loop output
# 1 = bottom-left (big) → Status dashboard # 1 = bottom-left (wide) → Status dashboard
# 2 = right (full height) → Hermes chat # 2 = bottom-mid (narrow) → LOOPSTAT (strategy)
# 3 = right (full height) → Hermes chat
# Set titles # Set titles
tmux select-pane -t "$SESSION:0.0" -T "Loop" tmux select-pane -t "$SESSION:0.0" -T "Loop"
tmux select-pane -t "$SESSION:0.1" -T "Status" 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 # Pane border styling
tmux set-option -t "$SESSION" pane-border-status top 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 # 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.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.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 # Focus chat pane
tmux select-pane -t "$SESSION:0.2" tmux select-pane -t "$SESSION:0.3"
echo "" echo ""
echo " ┌──────────────────┬──────────────────┐" echo " ┌──────────────────────────────────┬──────────────────┐"
echo " │ Loop (pane 0) │ │" echo " │ Loop (pane 0) │ │"
echo " ├──────────────────┤ Chat (pane 2) │" echo " ├────────────────────┬─────────────┤ Chat (pane 3) │"
echo " │ Status (pane 1) │ │" echo " │ Status (pane 1) │ LOOPSTAT(2) │ │"
echo " └──────────────────┴──────────────────┘" echo " └────────────────────┴─────────────┴──────────────────┘"
echo "" echo ""
echo " Attach: tmux attach -t timmy-loop" echo " Attach: tmux attach -t timmy-loop"
echo " Stop: touch ~/Timmy-Time-dashboard/.loop/STOP" echo " Stop: touch ~/Timmy-Time-dashboard/.loop/STOP"

View File

@@ -35,12 +35,12 @@
"schedule_display": "every 8m", "schedule_display": "every 8m",
"repeat": { "repeat": {
"times": null, "times": null,
"completed": 20 "completed": 28
}, },
"enabled": true, "enabled": true,
"created_at": "2026-03-14T21:25:31.831983-04:00", "created_at": "2026-03-14T21:25:31.831983-04:00",
"next_run_at": "2026-03-15T10:17:06.096722-04:00", "next_run_at": "2026-03-15T11:35:42.310507-04:00",
"last_run_at": "2026-03-15T10:09:06.096722-04:00", "last_run_at": "2026-03-15T11:27:42.310507-04:00",
"last_status": "ok", "last_status": "ok",
"last_error": null, "last_error": null,
"deliver": "local", "deliver": "local",
@@ -58,12 +58,12 @@
"schedule_display": "every 8m", "schedule_display": "every 8m",
"repeat": { "repeat": {
"times": null, "times": null,
"completed": 18 "completed": 26
}, },
"enabled": true, "enabled": true,
"created_at": "2026-03-14T21:39:34.712372-04:00", "created_at": "2026-03-14T21:39:34.712372-04:00",
"next_run_at": "2026-03-15T10:17:32.633122-04:00", "next_run_at": "2026-03-15T11:35:59.953250-04:00",
"last_run_at": "2026-03-15T10:09:32.633122-04:00", "last_run_at": "2026-03-15T11:27:59.953250-04:00",
"last_status": "ok", "last_status": "ok",
"last_error": null, "last_error": null,
"deliver": "local", "deliver": "local",
@@ -81,17 +81,17 @@
"schedule_display": "every 10m", "schedule_display": "every 10m",
"repeat": { "repeat": {
"times": null, "times": null,
"completed": 3 "completed": 8
}, },
"enabled": true, "enabled": true,
"created_at": "2026-03-15T09:23:09.042883-04:00", "created_at": "2026-03-15T09:23:09.042883-04:00",
"next_run_at": "2026-03-15T10:16:56.086710-04:00", "next_run_at": "2026-03-15T11:32:58.861581-04:00",
"last_run_at": "2026-03-15T10:06:56.086710-04:00", "last_run_at": "2026-03-15T11:22:58.861581-04:00",
"last_status": "ok", "last_status": "ok",
"last_error": null, "last_error": null,
"deliver": "local", "deliver": "local",
"origin": null "origin": null
} }
], ],
"updated_at": "2026-03-15T10:09:32.633259-04:00" "updated_at": "2026-03-15T11:27:59.953394-04:00"
} }

View File

@@ -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). 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. 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.
§ §

View File

@@ -21,7 +21,22 @@ Delegate coding tasks to Kimi Code CLI via the Hermes terminal. Powered by kimi-
- Logged in: `kimi login` - Logged in: `kimi login`
- Config at `~/.kimi/config.toml` - 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) terminal(command="kimi --print -p 'Fix the XSS vulnerability in swarm_live.html by sanitizing innerHTML'", workdir="~/project", timeout=120)