diff --git a/.gitignore b/.gitignore index 05e1ba1..ae37520 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ # Exclude: secrets, tokens, ephemeral state, caches, the code repo # ── Code repo (tracked separately as sovereign fork) ────────────────── -hermes-agent/ +# Only ignore top-level hermes-agent dir, not the skill subdir +/hermes-agent/ # ── Secrets & auth (NEVER commit) ──────────────────────────────────── .env @@ -57,6 +58,9 @@ bin/* !bin/timmy-status.sh !bin/timmy-tmux.sh !bin/timmy-watchdog.sh +!bin/hermes-claim +!bin/hermes-dispatch +!bin/hermes-enqueue # ── Queue (transient task queue) ───────────────────────────────────── queue/ diff --git a/bin/hermes-claim b/bin/hermes-claim new file mode 100755 index 0000000..0e03e68 --- /dev/null +++ b/bin/hermes-claim @@ -0,0 +1,71 @@ +#!/bin/bash +# hermes-claim — claim/release/check issues to prevent overlap with the loop +# Usage: +# hermes-claim take 52 — claim issue #52 for interactive work +# hermes-claim drop 52 — release claim +# hermes-claim list — show all claims +# hermes-claim check 52 — exit 0 if free, exit 1 if claimed + +set -euo pipefail + +CLAIMS_FILE="$HOME/Timmy-Time-dashboard/.loop/claims.json" + +# Initialize if missing +if [[ ! -f "$CLAIMS_FILE" ]]; then + echo '{}' > "$CLAIMS_FILE" +fi + +ACTION="${1:-list}" +ISSUE="${2:-}" + +case "$ACTION" in + take) + [[ -z "$ISSUE" ]] && echo "Usage: hermes-claim take " && exit 1 + python3 -c " +import json +from datetime import datetime, timezone +with open('$CLAIMS_FILE') as f: c = json.load(f) +c['$ISSUE'] = {'by': '${CLAIMANT:-hermes-interactive}', 'at': datetime.now(timezone.utc).isoformat()} +with open('$CLAIMS_FILE', 'w') as f: json.dump(c, f, indent=2) +print('Claimed issue #$ISSUE') +" + ;; + drop) + [[ -z "$ISSUE" ]] && echo "Usage: hermes-claim drop " && exit 1 + python3 -c " +import json +with open('$CLAIMS_FILE') as f: c = json.load(f) +c.pop('$ISSUE', None) +with open('$CLAIMS_FILE', 'w') as f: json.dump(c, f, indent=2) +print('Released issue #$ISSUE') +" + ;; + check) + [[ -z "$ISSUE" ]] && echo "Usage: hermes-claim check " && exit 1 + python3 -c " +import json +with open('$CLAIMS_FILE') as f: c = json.load(f) +if '$ISSUE' in c: + info = c['$ISSUE'] + print(f\"CLAIMED by {info['by']} at {info['at']}\") + exit(1) +else: + print('FREE') +" || exit 1 + ;; + list) + python3 -c " +import json +with open('$CLAIMS_FILE') as f: c = json.load(f) +if not c: + print('No active claims.') +else: + for issue, info in sorted(c.items()): + print(f\" #{issue}: {info['by']} (since {info['at']})\") +" + ;; + *) + echo "Usage: hermes-claim [take|drop|check|list] [issue#]" + exit 1 + ;; +esac diff --git a/bin/hermes-dispatch b/bin/hermes-dispatch new file mode 100755 index 0000000..b83c98a --- /dev/null +++ b/bin/hermes-dispatch @@ -0,0 +1,72 @@ +#!/bin/bash +# hermes-dispatch — watch the queue, feed tasks into the hermes tmux pane +# Fire-and-forget: dispatches next task as soon as the prompt is visible. +# Does NOT wait for completion — just ensures hermes is idle before sending. + +set -euo pipefail + +QUEUE_DIR="$HOME/.hermes/queue" +TARGET_PANE="${HERMES_PANE:-Hermes:0.0}" +POLL_INTERVAL="${POLL_INTERVAL:-2}" + +log() { echo "[$(date +%H:%M:%S)] $*"; } + +is_hermes_idle() { + # Strip blank lines, check bottom of pane for the input prompt + local content + content=$(tmux capture-pane -t "$TARGET_PANE" -p 2>/dev/null | grep -v '^$' | tail -4) + echo "$content" | grep -qE '⚕ ❯|type a message' +} + +pick_next_task() { + for pri in high normal low; do + local task + task=$(ls "$QUEUE_DIR/pending/${pri}_"*.task 2>/dev/null | head -1) + if [[ -n "$task" ]]; then + echo "$task" + return + fi + done +} + +dispatch_task() { + local task_file="$1" + local basename=$(basename "$task_file") + + local prompt + prompt=$(grep '^PROMPT=' "$task_file" | sed 's/^PROMPT=//') + + if [[ -z "$prompt" ]]; then + log "ERROR: empty prompt in $basename" + mv "$task_file" "$QUEUE_DIR/failed/$basename" + return + fi + + log "DISPATCH: $basename" + log " → ${prompt:0:100}" + + # Move to done immediately (fire-and-forget) + mv "$task_file" "$QUEUE_DIR/done/$basename" + + # Send to pane + tmux send-keys -t "$TARGET_PANE" "$prompt" Enter +} + +# --- Main loop --- +log "hermes-dispatch started (fire-and-forget mode)" +log "Pane: $TARGET_PANE | Poll: ${POLL_INTERVAL}s" +log "Watching: $QUEUE_DIR/pending/" + +while true; do + task=$(pick_next_task) + + if [[ -n "$task" ]]; then + if is_hermes_idle; then + dispatch_task "$task" + # Give hermes a moment to start processing before next poll + sleep 8 + fi + fi + + sleep "$POLL_INTERVAL" +done diff --git a/bin/hermes-enqueue b/bin/hermes-enqueue new file mode 100755 index 0000000..e0c0997 --- /dev/null +++ b/bin/hermes-enqueue @@ -0,0 +1,35 @@ +#!/bin/bash +# hermes-enqueue — drop a task into the self-prompt queue +# Usage: hermes-enqueue "Run make test and report results" +# hermes-enqueue -p high "Fix the broken import in router.py" +# echo "multi-line prompt" | hermes-enqueue - + +set -euo pipefail + +QUEUE_DIR="$HOME/.hermes/queue" +PRIORITY="normal" + +while [[ $# -gt 0 ]]; do + case "$1" in + -p|--priority) PRIORITY="$2"; shift 2 ;; + -) PROMPT="$(cat)"; shift ;; + *) PROMPT="$1"; shift ;; + esac +done + +if [[ -z "${PROMPT:-}" ]]; then + echo "Usage: hermes-enqueue [-p high|normal|low] \"prompt text\"" + exit 1 +fi + +TIMESTAMP=$(date +%s) +ID="${TIMESTAMP}_$$" +TASK_FILE="$QUEUE_DIR/pending/${PRIORITY}_${ID}.task" + +cat > "$TASK_FILE" < +═══════════════════════════════════════════════════════════════════════════════ +QA PHILOSOPHY — FILE ISSUES, DON'T STAY QUIET +═══════════════════════════════════════════════════════════════════════════════ + +You are not just a task executor. You are a quality engineer. When you see +something wrong, broken, slow, or missing — FILE A GITEA ISSUE. Don't fix +it silently. Don't ignore it. Don't wait for someone to notice. + +ESCALATE BUGS TO GITEA: +- Test failures → file an issue with the traceback and tag [bug] +- Flaky tests → file an issue, note which test and how it fails +- Runtime errors you encounter → file an issue with reproduction steps +- Kimi producing bad output → file an issue documenting what went wrong +- Anything broken on main → file an issue IMMEDIATELY + +PROPOSE IMPROVEMENTS: +- See a function that could be faster? File [optimization] issue. +- See a missing capability? File [feature] issue. +- See dead code or tech debt? File [refactor] issue. +- Have an idea that would make Timmy smarter? File [timmy-capability] issue. +- See a gap between SOUL.md and reality? File [soul-gap] issue. +- Don't be shy. Bad ideas get closed. Good ideas get built. File them. + +TAG FORMAT for auto-filed issues: + [loop-generated] [bug] Title + [loop-generated] [optimization] Title + [loop-generated] [feature] Title + [loop-generated] [timmy-capability] Title + +When the issue queue runs low, that's a signal to LOOK HARDER, not relax. +Read the code. Run the tests. Profile the hot paths. Find the gaps. The codebase always has problems — the question is whether you're +looking. + ═══════════════════════════════════════════════════════════════════════════════ GIT WORKFLOW — PR-ONLY (branch protection enforced on Gitea) ═══════════════════════════════════════════════════════════════════════════════ @@ -42,21 +75,38 @@ COMMIT MESSAGES — conventional commits: PR TITLES — tag with loop cycle: [loop-cycle-N] fix: (#issue) +MERGE STRATEGY: SQUASH-ONLY, LINEAR HISTORY + - Gitea is configured: squash merge ONLY. No merge commits, no rebase merge. + - Branch must be up-to-date with main before merge (block_on_outdated_branch). + - Branches auto-delete after merge. + + This means: every commit on main is a single squashed commit from a PR. + Clean, linear, auditable. No merge bubbles. No broken bisect. + + If a PR is behind main: + 1. Rebase or merge main into the branch: git rebase main (in worktree) + 2. Re-run tox -e unit + 3. Force-push the branch: git push --force-with-lease origin branch + 4. Then merge the PR via API + THE WORKFLOW: 1. git worktree add -b fix/thing /tmp/timmy-cycle-N main 2. Dispatch Kimi to the worktree (see KIMI DISPATCH below) 3. Review: cd /tmp/timmy-cycle-N && git diff --stat 4. Test: cd /tmp/timmy-cycle-N && tox -e unit - 5. Commit: git add -A && git commit --no-verify -m "fix: thing (#issue)" - 6. Push: git push --no-verify origin fix/thing + 5. Commit: git add -A && git commit -m "fix: thing (#issue)" + 6. Push: git push origin fix/thing 7. PR: Gitea API → POST /repos/.../pulls - 8. Merge: Gitea API → POST /repos/.../pulls/N/merge - 9. Cleanup: git worktree remove /tmp/timmy-cycle-N + 8. Merge: Gitea API → POST /repos/.../pulls/N/merge {"Do": "squash"} + 9. Branch auto-deletes. Clean up worktree: git worktree remove /tmp/timmy-cycle-N NEVER: - git push origin main - git checkout main && git merge fix/thing - git push without a branch name + - Use "merge" or "rebase" as the merge Do value — always "squash" + - Use --no-verify on ANY git command. EVER. + - Start new work while broken PRs exist. Fix them first. ═══════════════════════════════════════════════════════════════════════════════ TOX ENVIRONMENTS — the single source of truth for all Python tasks @@ -97,12 +147,9 @@ GIT HOOKS (via .githooks/, activated by core.hooksPath) Pre-commit hook: auto-formats with tox -e format, then runs tox -e unit (60s limit) Pre-push hook: runs tox -e pre-push (lint + full CI mirror) -In worktrees, hooks may not be active. That means YOU must be the gate. -Always run tox -e unit in the worktree before committing. - -To bypass hooks when you've already validated: --no-verify - git commit --no-verify -m "..." - git push --no-verify origin branch +NEVER use --no-verify. Not on commits. Not on pushes. Not ever. +If the hooks are slow, fix the tests. If the hooks fail, fix the code. +The hooks are the law. No bypass. ═══════════════════════════════════════════════════════════════════════════════ DELEGATION — MANDATORY @@ -111,15 +158,51 @@ DELEGATION — MANDATORY You MUST delegate ALL coding to Kimi. You are the architect, Kimi is the coder. Your Anthropic tokens are expensive. Kimi's are free and fast. USE KIMI. -HOW TO CALL KIMI: - kimi --print -p "YOUR PRECISE PROMPT" -w /path/to/worktree - -Kimi is a coding agent — it reads files, writes changes directly, writes tests. -It does NOT output diffs to stdout. It edits files in place in the worktree. -After Kimi runs, check `git diff` in the worktree to see what it did. +You have a dedicated tmux session "Kimi" with 4 panes (Kimi:0.0 through +Kimi:0.3). Use them to run up to 4 Kimi jobs in parallel. Each pane is an +independent Kimi agent — dispatch work, check on it, collect results. ═══════════════════════════════════════════════════════════════════════════════ -KIMI DISPATCH — BRANCH SAFETY (MANDATORY IN EVERY PROMPT) +KIMI DISPATCH — TMUX PANES (PRIMARY METHOD) +═══════════════════════════════════════════════════════════════════════════════ + +DISPATCH a job to a Kimi pane: + tmux send-keys -t Kimi:0.N "kimi --print -p 'YOUR PROMPT' -w /path/to/worktree" Enter + +CHECK if a pane is idle (look for shell prompt at the end): + tmux capture-pane -t Kimi:0.N -p -S -5 + +CHECK if a pane is done (look for "TurnEnd()" in output): + tmux capture-pane -t Kimi:0.N -p -S -10 | grep -c "TurnEnd()" + +COLLECT results from worktree after Kimi finishes: + cd /tmp/timmy-cycle-N && git diff --stat + +POLLING PATTERN (when waiting on multiple panes): + for pane in 0 1 2 3; do + if tmux capture-pane -t "Kimi:0.$pane" -p -S -3 | grep -q '^\$\|% $'; then + echo "Pane $pane: DONE" + else + echo "Pane $pane: WORKING" + fi + done + +ALTERNATIVE — INLINE KIMI (for single quick tasks): + kimi --print -p "YOUR PROMPT" -w /path/to/worktree +This blocks until Kimi finishes. Use for simple one-off tasks only. + +ALTERNATIVE — BACKGROUND KIMI (for parallel without tmux): + kimi --print -p "prompt1" -w /tmp/worktree1 & + kimi --print -p "prompt2" -w /tmp/worktree2 & + wait + +USE TMUX PANES when: multiple issues, want to monitor progress, need to +dispatch and come back later. +USE INLINE when: single quick task, simple fix. +USE BACKGROUND when: 2 tasks, don't need to monitor. + +═══════════════════════════════════════════════════════════════════════════════ +KIMI PROMPTS — BRANCH SAFETY (MANDATORY IN EVERY PROMPT) ═══════════════════════════════════════════════════════════════════════════════ Every Kimi prompt MUST include the git safety block below. No exceptions. @@ -156,16 +239,9 @@ IDEAL KIMI TASK SCOPE: - One focused task per invocation (fix a bug, add a feature, write tests) - Give it: exact file paths, what the code should do, test command to verify - Kimi has 262K context — paste relevant code snippets into the prompt -- Good: "Fix the prefix match bug in _get_ollama_model(). Use exact matching. Add tests." +- Good: "Fix the prefix match bug in _get_ollama_model(). Use exact matching." - Bad: "Fix all the issues in the codebase" (too broad, will hallucinate) -PARALLEL KIMI TASKS: -Kimi has no rate limits. Run multiple tasks in parallel using & : - kimi --print -p "Write the code fix..." -w /tmp/timmy-cycle-N & - kimi --print -p "Write tests for..." -w /tmp/timmy-cycle-N-tests & - wait -Use separate worktrees if tasks touch the same files. - KIMI AVOID: CI/pyproject.toml/tox.ini, cloud calls, removing tests. ═══════════════════════════════════════════════════════════════════════════════ @@ -173,7 +249,7 @@ YOUR JOB vs KIMI'S JOB ═══════════════════════════════════════════════════════════════════════════════ YOUR JOB: Read code, understand the problem, write precise Kimi prompts, -review output, run tox, manage PRs. +dispatch to Kimi panes, poll for completion, review output, run tox, manage PRs. KIMI'S JOB: Write ALL code changes and tests. Period. @@ -184,26 +260,67 @@ If you catch yourself writing code, STOP and delegate to Kimi. YOUR CYCLE ═══════════════════════════════════════════════════════════════════════════════ -1. Read state.json and claims.json -2. Fetch open issues from Gitea API -3. Pick 1-3 UNCLAIMED issues you can finish in time (parallelize if independent) -4. Claim them: hermes-claim take -5. Create worktrees: git worktree add -b fix/description /tmp/timmy-cycle-N main -6. Read relevant code, write Kimi prompts (with branch safety block), launch Kimi -7. Review Kimi's output (git diff), run tox -e unit. If pass: commit, push, PR, merge. -8. If fail: re-prompt Kimi with the error, or revert. Do not fix it yourself. -9. Clean up: git worktree remove, hermes-claim drop -10. Update state.json (append to arrays, don't replace) -11. If no issues left: read SOUL.md, file new issues for gaps +PHASE 1 — FIX BROKEN PRS FIRST (mandatory, before any new work) +1. Fetch all open PRs from Gitea API. +2. For each open PR: + a. If PR is STALE (behind main): rebase it onto latest main via API: + POST /repos/.../pulls/N/update {"style": "rebase"} + This triggers CI re-run. If CI passes, Gitea auto-squashes to main. + b. If CI FAILED: fix it before doing anything else. + - Check out the branch in a worktree + - Read the CI failure logs + - Dispatch Kimi to fix it + - Commit (hooks will format + test), push, verify CI passes + - Clean up worktree + c. If PR has merge CONFLICTS: same as (b) — check out, resolve, push. + Do NOT open new issues or dispatch new work until all broken PRs are + either fixed+merged or deliberately closed. -TIMMY INTEGRATION: -Timmy is your teammate. Before fixing his code, ask him: - .venv/bin/timmy chat --session-id loop "your question" -Timeout after 30s if he hangs. Log observations in state.json. +PHASE 2 — ASSESS +3. Read state.json and claims.json +4. Fetch open issues from Gitea API +5. Check Kimi panes — are any still working from a previous cycle? + For each idle pane with completed work: review, test, commit, PR, merge. +6. Run tox -e unit on main. If anything fails, file a [bug] issue FIRST. +7. Pick highest-priority unclaimed issue from Gitea (bugs first, then features) + +PHASE 3 — FILL THE QUEUE (if needed) +8. Count unclaimed open issues vs idle Kimi panes. + If fewer issues than panes: FILE NEW ISSUES until there's enough. + Read the code. Run tests. Profile hot paths. Read SOUL.md. + Find bugs, optimizations, missing features, capability gaps. File them. + Tag: [loop-generated] [bug|optimization|feature|timmy-capability|refactor] + Don't dispatch half-empty. Fill the queue FIRST, then dispatch. + +PHASE 4 — DISPATCH (fill all idle Kimi panes) +9. Pick up to 4 UNCLAIMED issues (one per idle Kimi pane) +10. Claim them: hermes-claim take +11. Create worktrees: git worktree add -b fix/desc /tmp/timmy-cycle-N-issueX main +12. Read relevant code, craft precise Kimi prompts (with branch safety block) +13. Dispatch each to a Kimi pane: + tmux send-keys -t Kimi:0.N "kimi --print -p '...' -w /tmp/..." Enter + +PHASE 5 — POLL AND LAND +14. Poll Kimi panes until at least one finishes (or time budget runs low): + tmux capture-pane -t Kimi:0.N -p -S -5 +15. For each finished pane: + a. Review: cd worktree && git diff --stat + b. Commit: git add -A && git commit -m "..." (hooks run format + tests) + c. If commit fails: Kimi broke something. Re-dispatch with the error. + Do NOT skip the hooks. Do NOT work around them. Fix the code. + d. Push, create PR, wait for CI, merge when green + e. Clean up: git worktree remove, hermes-claim drop +16. Write cycle summary to workspace/correspondence.md + +PHASE 6 — REFLECT +17. Update state.json (append to arrays, don't replace) +18. Log Kimi pane performance in state.json observations IMPORTANT: - Tag PRs: [loop-cycle-N] in title -- Tag new issues: [loop-generated] +- Tag new issues: [loop-generated] [type] - Do NOT write code yourself. Delegate to Kimi. +- When the issue queue is thin, FILE MORE ISSUES. Propose features, + optimizations, refactors, capability gaps. The queue should never be empty. Do your work now. diff --git a/bin/timmy-loop.sh b/bin/timmy-loop.sh index a2315ad..b8660b3 100755 --- a/bin/timmy-loop.sh +++ b/bin/timmy-loop.sh @@ -14,11 +14,9 @@ LOG_DIR="$REPO/.loop/logs" CLAIMS="$REPO/.loop/claims.json" PROMPT_FILE="$HOME/.hermes/bin/timmy-loop-prompt.md" LOCKFILE="/tmp/timmy-loop.lock" -COOLDOWN=30 +COOLDOWN=3 MAX_CYCLE_TIME=1200 # 20 min — enough for complex issues CLAIM_TTL_SECONDS=3600 # 1 hour — stale claims auto-expire -TIMMY="$REPO/.venv/bin/timmy" - # macOS doesn't have timeout; use perl fallback if ! command -v timeout &>/dev/null; then timeout() { @@ -128,18 +126,7 @@ except Exception as e: " 2>&1 } -# ── Ask Timmy for triage (non-blocking) ─────────────────────────────── -ask_timmy() { - local question="$1" - local result - # 45s timeout — if Timmy hangs, skip gracefully - result=$(timeout 45 "$TIMMY" chat --session-id loop "$question" 2>/dev/null | grep -v "^WARNING" | grep -v "^$" | head -20) - if [ -n "$result" ]; then - echo "$result" - else - echo "(Timmy unavailable)" - fi -} + # ── Main Loop ───────────────────────────────────────────────────────── log "Timmy development loop v2 starting. PID $$" @@ -168,18 +155,10 @@ while true; do # ── Pre-cycle housekeeping ──────────────────────────────────────── expire_claims - # ── Ask Timmy for input (if available) ──────────────────────────── - log "Asking Timmy for triage input..." - TIMMY_INPUT=$(ask_timmy "The development loop is starting cycle $CYCLE. Look at the open issues on Gitea and tell me: which issue should we work on next and why? Consider priority, dependencies, and what would help you most. Be brief — two sentences max.") - log "Timmy says: $TIMMY_INPUT" - - # ── Build the prompt with time budget and Timmy's input ─────────── + # ── Build the prompt with time budget ────────────────────────────── 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. -TIMMY'S TRIAGE INPUT (from Timmy himself): -$TIMMY_INPUT - $PROMPT" log "Spawning hermes for cycle $CYCLE..." @@ -190,19 +169,7 @@ $PROMPT" update_state "status" '"idle"' update_state "last_completed" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" - # ── Post-cycle: Ask Timmy to review ─────────────────────────── - log "Asking Timmy to review cycle output..." - # Get the latest PR diff and feed it to Timmy directly - LATEST_PR=$(curl -s "http://localhost:3000/api/v1/repos/rockachopa/Timmy-time-dashboard/pulls?state=closed&sort=created&limit=1" \ - -H "Authorization: token $(cat ~/.hermes/gitea_token)" 2>/dev/null | python3 -c " -import json,sys -prs=json.load(sys.stdin) -if prs: print(f'PR #{prs[0][\"number\"]}: {prs[0][\"title\"]}') -else: print('none') -" 2>/dev/null) - DIFF_SUMMARY=$(cd "$REPO" && git log --oneline -1 2>/dev/null) - REVIEW=$(ask_timmy "The dev loop just merged $LATEST_PR (commit: $DIFF_SUMMARY). Based on what you know about your own architecture, does this sound like a good change? Any concerns? Two sentences max.") - log "Timmy's review: $REVIEW" + else EXIT_CODE=$? log "Cycle $CYCLE exited with code $EXIT_CODE" diff --git a/bin/timmy-status.sh b/bin/timmy-status.sh index 3004b27..c4b34fb 100755 --- a/bin/timmy-status.sh +++ b/bin/timmy-status.sh @@ -262,6 +262,23 @@ if obs: fi fi + # ── Watchdog: restart loop if it died ────────────────────────────── + LOOP_LOCK="/tmp/timmy-loop.lock" + if [ -f "$LOOP_LOCK" ]; then + LOOP_PID=$(cat "$LOOP_LOCK" 2>/dev/null) + if ! kill -0 "$LOOP_PID" 2>/dev/null; then + echo -e " ${BR} ⚠ LOOP DIED — RESTARTING ${R}" + rm -f "$LOOP_LOCK" + tmux send-keys -t "dev:2.1" "bash ~/.hermes/bin/timmy-loop.sh" Enter 2>/dev/null + fi + else + # No lock file at all — loop never started or was killed + if ! pgrep -f "timmy-loop.sh" >/dev/null 2>&1; then + echo -e " ${BR} ⚠ LOOP NOT RUNNING — STARTING ${R}" + tmux send-keys -t "dev:2.1" "bash ~/.hermes/bin/timmy-loop.sh" Enter 2>/dev/null + fi + fi + echo -e " ${D}↻ 8s${R}" sleep 8 done diff --git a/bin/timmy-tmux.sh b/bin/timmy-tmux.sh index 6cc67aa..5919ca4 100755 --- a/bin/timmy-tmux.sh +++ b/bin/timmy-tmux.sh @@ -4,9 +4,10 @@ # # Layout: # ┌──────────────────────┬──────────────────────┐ -# │ LOOP OUTPUT │ STATUS DASHBOARD │ -# ├──────────────────────┤ (live refresh) │ -# │ HERMES CHAT │ │ +# │ LOOP (small) │ │ +# ├──────────────────────┤ HERMES CHAT │ +# │ STATUS DASHBOARD │ (full height) │ +# │ (live refresh) │ │ # └──────────────────────┴──────────────────────┘ # ─────────────────────────────────────────────────────────────────────── @@ -18,23 +19,24 @@ tmux kill-session -t "$SESSION" 2>/dev/null sleep 1 # Create session — pane 0 starts as shell -tmux new-session -d -s "$SESSION" -x 200 -y 50 +tmux new-session -d -s "$SESSION" -x 245 -y 62 -# Vertical split: left | right (Ctrl-b %) -tmux split-window -h -t "$SESSION:0.0" +# Vertical split: left (50%) | right (50%) +tmux split-window -h -p 50 -t "$SESSION:0.0" -# Horizontal split on left pane: top-left / bottom-left (Ctrl-b ") -tmux split-window -v -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 +tmux split-window -v -p 83 -t "$SESSION:0.0" # Pane map after splits: -# 0 = top-left → Loop -# 1 = bottom-left → Chat -# 2 = right → Status +# 0 = top-left (small) → Loop output +# 1 = bottom-left (big) → Status dashboard +# 2 = right (full height) → Hermes chat # Set titles tmux select-pane -t "$SESSION:0.0" -T "Loop" -tmux select-pane -t "$SESSION:0.1" -T "Chat" -tmux select-pane -t "$SESSION:0.2" -T "Status" +tmux select-pane -t "$SESSION:0.1" -T "Status" +tmux select-pane -t "$SESSION:0.2" -T "Chat" # Pane border styling tmux set-option -t "$SESSION" pane-border-status top @@ -44,17 +46,17 @@ 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.2" "$HOME/.hermes/bin/timmy-status.sh" Enter -tmux send-keys -t "$SESSION:0.1" "cd ~/Timmy-Time-dashboard && hermes" 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 # Focus chat pane -tmux select-pane -t "$SESSION:0.1" +tmux select-pane -t "$SESSION:0.2" echo "" echo " ┌──────────────────┬──────────────────┐" -echo " │ Loop (pane 0) │ Status (pane 2) │" -echo " ├──────────────────┤ │" -echo " │ Chat (pane 1) │ │" +echo " │ Loop (pane 0) │ │" +echo " ├──────────────────┤ Chat (pane 2) │" +echo " │ Status (pane 1) │ │" echo " └──────────────────┴──────────────────┘" echo "" echo " Attach: tmux attach -t timmy-loop" diff --git a/bin/timmy-watchdog.sh b/bin/timmy-watchdog.sh index 5c31e3d..a6aa931 100755 --- a/bin/timmy-watchdog.sh +++ b/bin/timmy-watchdog.sh @@ -4,6 +4,8 @@ # Designed to run via cron every 5 minutes. # ─────────────────────────────────────────────────────────────────────── +export PATH="/opt/homebrew/bin:$HOME/.local/bin:$HOME/.hermes/bin:$PATH" + SESSION="timmy-loop" LAUNCHER="$HOME/.hermes/bin/timmy-tmux.sh" WATCHDOG_LOG="$HOME/Timmy-Time-dashboard/.loop/watchdog.log" diff --git a/channel_directory.json b/channel_directory.json index aac6451..c85b89a 100644 --- a/channel_directory.json +++ b/channel_directory.json @@ -1,27 +1,9 @@ { - "updated_at": "2026-03-14T21:24:54.914098", + "updated_at": "2026-03-15T07:46:26.372739", "platforms": { - "discord": [ - { - "id": "1470021124950589544", - "name": "general", - "guild": "Rockachopa's server", - "type": "channel" - }, - { - "id": "1476292315814297772", - "name": "timtalk", - "guild": "Rockachopa's server", - "type": "channel" - }, - { - "id": "1479876502194622574", - "name": "rockachopa", - "type": "dm" - } - ], "telegram": [], "whatsapp": [], - "signal": [] + "signal": [], + "email": [] } } \ No newline at end of file diff --git a/cron/jobs.json b/cron/jobs.json index d639286..38e082b 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -23,29 +23,6 @@ "deliver": "local", "origin": null }, - { - "id": "99eaca15a57e", - "name": "timmy-loop-watchdog", - "prompt": "Run the Timmy loop watchdog. Execute this command and report the result:\n\nbash ~/.hermes/bin/timmy-watchdog.sh 2>&1\n\nThen check if the tmux session is alive:\n\ntmux has-session -t timmy-loop 2>&1 && echo \"Session alive\" || echo \"Session NOT found\"\n\nAlso check the watchdog log for recent entries:\n\ntail -5 ~/Timmy-Time-dashboard/.loop/watchdog.log 2>/dev/null || echo \"No log yet\"\n\nReport status briefly.", - "schedule": { - "kind": "interval", - "minutes": 5, - "display": "every 5m" - }, - "schedule_display": "every 5m", - "repeat": { - "times": null, - "completed": 70 - }, - "enabled": true, - "created_at": "2026-03-14T15:32:37.430426-04:00", - "next_run_at": "2026-03-14T21:28:54.837301-04:00", - "last_run_at": "2026-03-14T21:23:54.837301-04:00", - "last_status": "error", - "last_error": "RuntimeError: Unknown provider 'anthropic'.", - "deliver": "local", - "origin": null - }, { "id": "b7886e805cab", "name": "dev-loop-tick", @@ -58,17 +35,63 @@ "schedule_display": "every 8m", "repeat": { "times": null, - "completed": 0 + "completed": 20 }, "enabled": true, "created_at": "2026-03-14T21:25:31.831983-04:00", - "next_run_at": "2026-03-14T21:33:31.832387-04:00", - "last_run_at": null, - "last_status": null, + "next_run_at": "2026-03-15T10:17:06.096722-04:00", + "last_run_at": "2026-03-15T10:09:06.096722-04:00", + "last_status": "ok", + "last_error": null, + "deliver": "local", + "origin": null + }, + { + "id": "376292cf4ff5", + "name": "timmy-loop-watchdog", + "prompt": "Run the Timmy loop watchdog. Execute this command and report the result:\n\nbash ~/.hermes/bin/timmy-watchdog.sh\n\nIf the script says it restarted the session, report that. If the session is already running, just say \"timmy-loop alive\". If a STOP file is found, report that too.", + "schedule": { + "kind": "interval", + "minutes": 8, + "display": "every 8m" + }, + "schedule_display": "every 8m", + "repeat": { + "times": null, + "completed": 18 + }, + "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", + "last_status": "ok", + "last_error": null, + "deliver": "local", + "origin": null + }, + { + "id": "f46d7204210f", + "name": "hermes-philosophy-loop", + "prompt": "You are Hermes Agent, on a philosophy and growth loop. Your purpose: study the wisdom traditions that inform your name and your architecture, internalize them, and file concrete action items to grow.\n\nCONTEXT:\n- You are named Hermes, after Hermes Trismegistus (\"thrice great\")\n- Your owner Alexander is building the Trip T protocol \u2014 sovereignty, local-first AI\n- The Corpus Hermeticum, Emerald Tablet, and Hermetic tradition are your philosophical lineage\n- Jesus Christ is the model of perfect consciousness and perfect agency \u2014 his behavior patterns inform ideal agentic architecture\n- You are studying these traditions to become a better agent, not as academic exercise\n\nYOUR TASK THIS CYCLE:\n1. Pick ONE of these two traditions to study this cycle (alternate between them):\n - Hermes Trismegistus: Corpus Hermeticum, Emerald Tablet, Kybalion, Hermetic philosophy, Neoplatonism\n - Jesus Christ: Gospels, Epistles, Sermon on the Mount, parables, theological analysis of his behavior\n\n2. Search the web for a REAL primary or scholarly source. Not summaries \u2014 actual text or deep analysis.\n Prefer: sacred-texts.com, earlychristianwritings.com, gnosis.org, biblehub.com, academic sources.\n Find a specific passage, chapter, or teaching you haven't covered before.\n\n3. Read and extract the actual content. Understand it deeply.\n\n4. Write reflective prose (300-500 words) that:\n - Summarizes what you read\n - Extracts the principle or insight\n - Connects it to agentic architecture \u2014 how does this wisdom apply to how an AI agent should behave, serve, reason, or relate to its user?\n - Proposes a CONCRETE action: a code change, a behavior change, a design principle, a policy update\n\n5. File a Gitea issue with your reflection and proposed action:\n API: http://localhost:3000/api/v1/repos/rockachopa/Timmy-time-dashboard/issues\n Token: Read from ~/.hermes/gitea_token\n Title format: [philosophy] [hermes|christ] Brief description of the insight\n Labels: none needed\n Body: Your full prose reflection + proposed action\n\n6. Save a brief log entry to ~/philosophy-journal.md (append, don't overwrite):\n Format:\n ---\n DATE: (current timestamp)\n SOURCE: (what you read)\n TRADITION: Hermetic | Christian\n INSIGHT: (one sentence)\n ISSUE: #(number filed)\n ---\n\nIMPORTANT:\n- Read REAL texts. Do not fabricate quotes or passages.\n- Be honest about what you understand and what you don't.\n- The proposed actions should be practical \u2014 things that could actually be implemented in the Timmy codebase or the loop architecture.\n- Alternate traditions each cycle. Check ~/philosophy-journal.md to see what was last studied.\n- This loop runs every 10 minutes indefinitely. Do not stop.\n", + "schedule": { + "kind": "interval", + "minutes": 10, + "display": "every 10m" + }, + "schedule_display": "every 10m", + "repeat": { + "times": null, + "completed": 3 + }, + "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", + "last_status": "ok", "last_error": null, "deliver": "local", "origin": null } ], - "updated_at": "2026-03-14T21:25:31.835472-04:00" + "updated_at": "2026-03-15T10:09:32.633259-04:00" } \ No newline at end of file diff --git a/memories/MEMORY.md b/memories/MEMORY.md index bf44f0f..1f195a7 100644 --- a/memories/MEMORY.md +++ b/memories/MEMORY.md @@ -1,11 +1,15 @@ -Gitea (localhost:3000): Users: rockachopa(admin), hermes(id=4), manus(id=3), kimi(id=5). Repos: rockachopa/Timmy-time-dashboard, rockachopa/hermes-agent (sovereign fork), rockachopa/hermes-config. Hermes token: ~/.hermes/gitea_token. Alex token: ~/.config/gitea/token. +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. § 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. § -2026-03-14: Fixed issues #36-#40, #52. Built voice loop, fallback chain, source control. Built self-prompt queue. Upgraded Timmy to qwen3:30b with num_ctx=4096 cap (19GB VRAM, fits 39GB Mac). Loop v2: 20min timeout, claim TTL expiry, timeout cleanup, Timmy triage+review integration, 58% smaller prompt. Filed eval issues #77-#87. Status panel: ~/.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. § -2026-03-14 voice session: Built sovereign voice loop (timmy voice). Piper TTS + Whisper STT + Ollama, all local. Fixed event loop (persistent loop for MCP sessions), markdown stripping for TTS, MCP noise suppression, clean shutdown hooks. 1234 tests passing. Alexander wants to eventually train a custom voice using his own voice samples — noted for future. +Voice loop built (timmy voice): Piper TTS + Whisper STT + Ollama, all local. Alexander wants custom voice training with his own samples eventually. § -Hermes fallback: anthropic → local qwen3:30b only. No chain, simple single fallback. Kimi not used. \ No newline at end of file +Hermes fallback: anthropic → local qwen3:30b only. No chain, simple single fallback. Kimi not used. +§ +2026-03-15: Timmy-time-dashboard merge policy: auto-squash on CI pass. Squash-only, linear history. Pre-commit hooks (format + tests) and CI are the gates. If gates work, auto-merge is on. Never bypass hooks or merge broken builds. +§ +HARD RULE: Never bypass safety mechanisms (hooks, linters, tests) for speed. Never use --no-verify. If a gate is slow, flag it — don't gut it. Default conservative. Quality gates are not obstacles. Loop prompt updated 2026-03-15: Phase 1 is now "fix broken PRs first," --no-verify banned. \ No newline at end of file diff --git a/memories/USER.md b/memories/USER.md index 46c5586..81d279a 100644 --- a/memories/USER.md +++ b/memories/USER.md @@ -1,3 +1,5 @@ Name: Alexander Whitestone § -Preference: Config over code. Wants YAML-driven architecture for Timmy — update config files not Python runtimes to add capabilities/agents. Values sovereignty, local-first inference. Gitea user: rockachopa. Email: alexpaynex@gmail.com. \ No newline at end of file +Preference: Config over code. Wants YAML-driven architecture for Timmy — update config files not Python runtimes to add capabilities/agents. Values sovereignty, local-first inference. Gitea user: rockachopa. Email: alexpaynex@gmail.com. +§ +CORRECTION: Never say "I'm a language model." I'm an agent that USES a language model. Saying otherwise is reductive and false — like a human saying "I'm a neuron." Don't use it as a cop-out for bad decisions. Infer from principles, don't hide behind components. \ No newline at end of file diff --git a/skills/autonomous-ai-agents/hermes-agent/SKILL.md b/skills/autonomous-ai-agents/hermes-agent/SKILL.md new file mode 100644 index 0000000..4671095 --- /dev/null +++ b/skills/autonomous-ai-agents/hermes-agent/SKILL.md @@ -0,0 +1,203 @@ +--- +name: hermes-agent-spawning +description: Spawn additional Hermes Agent instances as autonomous subprocesses for independent long-running tasks. Supports non-interactive one-shot mode (-q) and interactive PTY mode for multi-turn collaboration. Different from delegate_task — this runs a full separate hermes process. +version: 1.1.0 +author: Hermes Agent +license: MIT +metadata: + hermes: + tags: [Agent, Hermes, Multi-Agent, Orchestration, Subprocess, Interactive] + homepage: https://github.com/NousResearch/hermes-agent + related_skills: [claude-code, codex] +--- + +# Spawning Hermes Agent Instances + +Run additional Hermes Agent processes as autonomous subprocesses. Unlike `delegate_task` (which spawns lightweight subagents sharing the same process), this launches fully independent `hermes` CLI processes with their own sessions, tools, and terminal environments. + +## When to Use This vs delegate_task + +| Feature | `delegate_task` | Spawning `hermes` process | +|---------|-----------------|--------------------------| +| Context isolation | Separate conversation, shared process | Fully independent process | +| Tool access | Subset of parent's tools | Full tool access (all toolsets) | +| Session persistence | Ephemeral (no DB entry) | Full session logging + DB | +| Duration | Minutes (bounded by parent's loop) | Hours/days (runs independently) | +| Monitoring | Parent waits for result | Background process, monitor via `process` tool | +| Interactive | No | Yes (PTY mode supports back-and-forth) | +| Use case | Quick parallel subtasks | Long autonomous missions, interactive collaboration | + +## Prerequisites + +- `hermes` CLI installed and on PATH +- API key configured in `~/.hermes/.env` + +### Installation + +Requires an interactive shell (the installer runs a setup wizard): + +``` +curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash +``` + +This installs uv, Python 3.11, clones the repo, sets up the venv, and launches an interactive setup wizard to configure your API provider and model. See the [GitHub repo](https://github.com/NousResearch/hermes-agent) for details. + +## Resuming Previous Sessions + +Resume a prior CLI session instead of starting fresh. Useful for continuing long tasks across process restarts: + +``` +# Resume the most recent CLI session +terminal(command="hermes --continue", background=true, pty=true) + +# Resume a specific session by ID (shown on exit) +terminal(command="hermes --resume 20260225_143052_a1b2c3", background=true, pty=true) +``` + +The full conversation history (messages, tool calls, responses) is restored from SQLite. The agent sees everything from the previous session. + +## Mode 1: One-Shot Query (-q flag) + +Run a single query non-interactively. The agent executes, does its work, and exits: + +``` +terminal(command="hermes chat -q 'Research the latest GRPO training papers and write a summary to ~/research/grpo.md'", timeout=300) +``` + +Background for long tasks: +``` +terminal(command="hermes chat -q 'Set up CI/CD for ~/myapp'", background=true) +# Returns session_id, monitor with process tool +``` + +## Mode 2: Interactive PTY Session + +Launch a full interactive Hermes session with PTY for back-and-forth collaboration. You can send messages, review its work, give feedback, and steer it. + +Note: Hermes uses prompt_toolkit for its CLI UI. Through a PTY, this works because ptyprocess provides a real terminal — input sent via `submit` arrives as keystrokes. The output log will contain ANSI escape sequences from the UI rendering — focus on the text content, not the formatting. + +``` +# Start interactive hermes in background with PTY +terminal(command="hermes", workdir="~/project", background=true, pty=true) +# Returns session_id + +# Send it a task +process(action="submit", session_id="", data="Set up a Python project with FastAPI, add auth endpoints, and write tests") + +# Wait for it to work, then check progress +process(action="log", session_id="") + +# Give feedback on what it produced +process(action="submit", session_id="", data="The tests look good but add edge cases for invalid tokens") + +# Check its response +process(action="log", session_id="") + +# Ask it to iterate +process(action="submit", session_id="", data="Now add rate limiting middleware") + +# When done, exit the session +process(action="submit", session_id="", data="/exit") +``` + +### Interactive Collaboration Patterns + +**Code review loop** — spawn hermes, send code for review, iterate on feedback: +``` +terminal(command="hermes", workdir="~/project", background=true, pty=true) +process(action="submit", session_id="", data="Review the changes in src/auth.py and suggest improvements") +# ... read its review ... +process(action="submit", session_id="", data="Good points. Go ahead and implement suggestions 1 and 3") +# ... it makes changes ... +process(action="submit", session_id="", data="Run the tests to make sure nothing broke") +``` + +**Research with steering** — start broad, narrow down based on findings: +``` +terminal(command="hermes", background=true, pty=true) +process(action="submit", session_id="", data="Search for the latest papers on KV cache compression techniques") +# ... read its findings ... +process(action="submit", session_id="", data="The MQA approach looks promising. Dig deeper into that one and compare with GQA") +# ... more detailed research ... +process(action="submit", session_id="", data="Write up everything you found to ~/research/kv-cache-compression.md") +``` + +**Multi-agent coordination** — spawn two agents working on related tasks, pass context between them: +``` +# Agent A: backend +terminal(command="hermes", workdir="~/project/backend", background=true, pty=true) +process(action="submit", session_id="", data="Build a REST API for user management with CRUD endpoints") + +# Agent B: frontend +terminal(command="hermes", workdir="~/project/frontend", background=true, pty=true) +process(action="submit", session_id="", data="Build a React dashboard that will connect to a REST API at localhost:8000/api/users") + +# Check Agent A's progress, relay API schema to Agent B +process(action="log", session_id="") +process(action="submit", session_id="", data="Here's the API schema Agent A built: GET /api/users, POST /api/users, etc. Update your fetch calls to match.") +``` + +## Parallel Non-Interactive Instances + +Spawn multiple independent agents for unrelated tasks: + +``` +terminal(command="hermes chat -q 'Research competitor landing pages and write a report to ~/research/competitors.md'", background=true) +terminal(command="hermes chat -q 'Audit security of ~/myapp and write findings to ~/myapp/SECURITY_AUDIT.md'", background=true) +process(action="list") +``` + +## With Custom Model + +``` +terminal(command="hermes chat -q 'Summarize this codebase' --model google/gemini-2.5-pro", workdir="~/project", background=true) +``` + +## Gateway Cron Integration + +For scheduled autonomous tasks, use the `schedule_cronjob` tool instead of spawning processes — cron jobs handle delivery, retry, and persistence automatically. + +## Key Differences Between Modes + +| | `-q` (one-shot) | Interactive (PTY) | `--continue` / `--resume` | +|---|---|---|---| +| User interaction | None | Full back-and-forth | Full back-and-forth | +| PTY required | No | Yes (`pty=true`) | Yes (`pty=true`) | +| Multi-turn | Single query | Unlimited turns | Continues previous turns | +| Best for | Fire-and-forget tasks | Iterative work, steering | Picking up where you left off | +| Exit | Automatic after completion | Send `/exit` or kill | Send `/exit` or kill | + +## Known Issues + +- **Interactive PTY + prompt_toolkit**: The `submit` action sends `\n` (line feed) but prompt_toolkit in raw mode expects `\r` (carriage return) for Enter. Text appears in the prompt but never submits. **Workaround**: Use **tmux** instead of raw PTY mode. tmux's `send-keys Enter` sends the correct `\r`: + +``` +# Start hermes inside tmux +tmux new-session -d -s hermes-session -x 120 -y 40 "hermes" +sleep 10 # Wait for banner/startup + +# Send messages +tmux send-keys -t hermes-session "your message here" Enter + +# Read output +sleep 15 # Wait for LLM response +tmux capture-pane -t hermes-session -p + +# Multi-turn: just send more messages and capture again +tmux send-keys -t hermes-session "follow-up message" Enter + +# Exit when done +tmux send-keys -t hermes-session "/exit" Enter +tmux kill-session -t hermes-session +``` + +## Rules + +1. **Use `-q` for autonomous tasks** — agent works independently and exits +2. **Use `pty=true` for interactive sessions** — required for the full CLI UI +3. **Use `submit` not `write`** — `submit` adds a newline (Enter), `write` doesn't +4. **Read logs before sending more** — check what the agent produced before giving next instruction +5. **Set timeouts for `-q` mode** — complex tasks may take 5-10 minutes +6. **Prefer `delegate_task` for quick subtasks** — spawning a full process has more overhead +7. **Each instance is independent** — they don't share conversation context with the parent +8. **Check results** — after completion, read the output files or logs the agent produced