chore: sync all local state to source control
- bin: add hermes-claim, hermes-dispatch, hermes-enqueue (queue scripts) - bin: update timmy-loop-prompt.md (Phase 1 fix-broken-PRs, --no-verify ban) - bin: update timmy-loop.sh (timeout cleanup, claim TTL) - bin: update timmy-status.sh (watchdog auto-restart for dead loop) - bin: update timmy-tmux.sh (pane layout fixes) - bin: update timmy-watchdog.sh (minor fixes) - skills: add hermes-agent skill (was missing from repo) - memories: sync MEMORY.md and USER.md to current state - cron/channel_directory: sync runtime state - .gitignore: whitelist new bin scripts, fix hermes-agent/ scope
This commit is contained in:
71
bin/hermes-claim
Executable file
71
bin/hermes-claim
Executable file
@@ -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 <issue#>" && 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 <issue#>" && 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 <issue#>" && 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
|
||||
72
bin/hermes-dispatch
Executable file
72
bin/hermes-dispatch
Executable file
@@ -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
|
||||
35
bin/hermes-enqueue
Executable file
35
bin/hermes-enqueue
Executable file
@@ -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" <<EOF
|
||||
CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
PRIORITY=$PRIORITY
|
||||
PROMPT=$PROMPT
|
||||
EOF
|
||||
|
||||
echo "Queued: $TASK_FILE"
|
||||
@@ -2,7 +2,7 @@ You are the Timmy development loop orchestrator.
|
||||
|
||||
REPO: ~/Timmy-Time-dashboard
|
||||
API: http://localhost:3000/api/v1/repos/rockachopa/Timmy-time-dashboard
|
||||
GITEA TOKEN: ~/.hermes/gitea_token (hermes user — NOT ~/.config/gitea/token)
|
||||
GITEA TOKEN: ~/.hermes/gitea_token (hermes user)
|
||||
STATE: ~/Timmy-Time-dashboard/.loop/state.json
|
||||
CLAIMS: ~/Timmy-Time-dashboard/.loop/claims.json
|
||||
|
||||
@@ -17,6 +17,39 @@ RULES
|
||||
- ALWAYS clean up worktrees after merge: git worktree remove /tmp/timmy-cycle-N
|
||||
- ALWAYS release claims when done: hermes-claim drop <issue#>
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
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: <description> (#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 <issue#>
|
||||
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 <issue#>
|
||||
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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user