Compare commits

3 Commits

Author SHA1 Message Date
173a154433 fix: require push pr and proof for kimi loop (#3) 2026-04-05 19:33:36 +00:00
a9b9fc4b1a fix: require push pr and proof for kimi loop (#2) 2026-04-05 18:24:39 +00:00
Alexander Whitestone
28ecfaa7a6 wip: preserve timmy loop script changes 2026-03-27 22:00:16 -04:00
5 changed files with 224 additions and 52 deletions

View File

@@ -11,7 +11,7 @@ set -euo pipefail
NUM_WORKERS="${1:-5}" NUM_WORKERS="${1:-5}"
MAX_WORKERS=12 MAX_WORKERS=12
WORKTREE_BASE="$HOME/worktrees" WORKTREE_BASE="$HOME/worktrees"
GITEA_URL="http://143.198.27.163:3000" GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}"
GITEA_TOKEN=$(cat "$HOME/.hermes/gemini_token") GITEA_TOKEN=$(cat "$HOME/.hermes/gemini_token")
GEMINI_TIMEOUT=600 # 10 min per issue GEMINI_TIMEOUT=600 # 10 min per issue
COOLDOWN=2 # seconds between issues — max speed COOLDOWN=2 # seconds between issues — max speed

View File

@@ -15,7 +15,7 @@ set -euo pipefail
# === CONFIG === # === CONFIG ===
REPO_DIR="$HOME/worktrees/kimi-repo" REPO_DIR="$HOME/worktrees/kimi-repo"
WORKTREE_BASE="$HOME/worktrees" WORKTREE_BASE="$HOME/worktrees"
GITEA_URL="http://143.198.27.163:3000" GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}"
GITEA_TOKEN=$(cat "$HOME/.hermes/kimi_token") GITEA_TOKEN=$(cat "$HOME/.hermes/kimi_token")
REPO_OWNER="rockachopa" REPO_OWNER="rockachopa"
REPO_NAME="Timmy-time-dashboard" REPO_NAME="Timmy-time-dashboard"
@@ -82,7 +82,108 @@ log() {
echo "$msg" >> "$LOG_DIR/kimi-loop.log" echo "$msg" >> "$LOG_DIR/kimi-loop.log"
} }
post_issue_comment() {
local issue_num="$1"
local body="$2"
local payload
payload=$(python3 - "$body" <<'PY'
import json, sys
print(json.dumps({"body": sys.argv[1]}))
PY
)
curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$payload" >/dev/null 2>&1 || true
}
remote_branch_exists() {
local branch="$1"
git ls-remote --heads origin "$branch" 2>/dev/null | grep -q .
}
get_pr_num() {
local branch="$1"
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls?state=all&head=${REPO_OWNER}:${branch}&limit=1" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
import sys,json
prs = json.load(sys.stdin)
if prs: print(prs[0]['number'])
else: print('')
" 2>/dev/null
}
get_pr_file_count() {
local pr_num="$1"
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/files" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
import sys, json
try:
files = json.load(sys.stdin)
print(len(files) if isinstance(files, list) else 0)
except:
print(0)
" 2>/dev/null
}
get_pr_state() {
local pr_num="$1"
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
import sys, json
try:
pr = json.load(sys.stdin)
if pr.get('merged'):
print('merged')
else:
print(pr.get('state', 'unknown'))
except:
print('unknown')
" 2>/dev/null
}
get_issue_state() {
local issue_num="$1"
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
import sys, json
try:
issue = json.load(sys.stdin)
print(issue.get('state', 'unknown'))
except:
print('unknown')
" 2>/dev/null
}
proof_comment_status() {
local issue_num="$1"
local branch="$2"
curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" -H "Authorization: token ${GITEA_TOKEN}" | BRANCH="$branch" python3 -c "
import os, sys, json
branch = os.environ.get('BRANCH', '').lower()
try:
comments = json.load(sys.stdin)
except Exception:
print('missing|')
raise SystemExit(0)
for c in reversed(comments):
user = ((c.get('user') or {}).get('login') or '').lower()
body = c.get('body') or ''
body_l = body.lower()
if user != 'kimi':
continue
if 'proof:' not in body_l and 'verification:' not in body_l:
continue
has_branch = branch in body_l
has_pr = ('pr:' in body_l) or ('pull request:' in body_l) or ('/pulls/' in body_l)
has_push = ('push:' in body_l) or ('pushed' in body_l)
has_verify = ('tox -e unit' in body_l) or ('pytest' in body_l) or ('verification:' in body_l)
status = 'ok' if (has_branch and has_pr and has_push and has_verify) else 'incomplete'
print(status + '|' + (c.get('html_url') or ''))
raise SystemExit(0)
print('missing|')
" 2>/dev/null
}
cleanup_worktree() { cleanup_worktree() {
local wt="$1" local wt="$1"
local branch="$2" local branch="$2"
if [ -d "$wt" ]; then if [ -d "$wt" ]; then
@@ -164,19 +265,36 @@ You can do ANYTHING a developer can do. You are not limited to the narrow task.
- tox -e lint (must be clean) - tox -e lint (must be clean)
3. COMMIT with conventional commits: fix: / feat: / refactor: / test: / chore: 3. COMMIT with conventional commits: fix: / feat: / refactor: / test: / chore:
ALWAYS stage files first — NEVER commit without git add:
git add .
git diff --cached --stat # verify non-empty — abort if nothing staged
git commit -m "fix: ... Fixes #${issue_num}"
Include "Fixes #${issue_num}" or "Refs #${issue_num}" in the message. Include "Fixes #${issue_num}" or "Refs #${issue_num}" in the message.
If git diff --cached --stat shows nothing, DO NOT commit or create a PR —
comment on the issue explaining what you found instead.
4. PUSH to your branch (kimi/issue-${issue_num}) and CREATE A PR: 4. PUSH to your branch (kimi/issue-${issue_num}) and CREATE A PR.
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls" \ Capture the PR URL in your output and in the issue comment.
-H "Authorization: token ${GITEA_TOKEN}" \ curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"title": "[kimi] <description> (#${issue_num})", "body": "Fixes #${issue_num}
-H "Content-Type: application/json" \
-d '{"title": "[kimi] <description> (#${issue_num})", "body": "Fixes #${issue_num}\n\n<describe what you did>", "head": "kimi/issue-${issue_num}", "base": "main"}'
5. COMMENT on the issue when done: <describe what you did>
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \ Verification:
-H "Content-Type: application/json" \ - tox -e format
-d '{"body": "PR created. <summary of changes>"}' - tox -e unit
- tox -e lint", "head": "kimi/issue-${issue_num}", "base": "main"}'
5. COMMENT on the issue with a PROOF BLOCK before you exit. Use this exact shape:
Proof:
- branch: kimi/issue-${issue_num}
- commit: <full sha>
- push: ok
- pr: <full PR url>
- verification:
- tox -e format: <pass/fail>
- tox -e unit: <pass/fail>
- tox -e lint: <pass/fail>
- summary: <what changed>
6. FILE NEW ISSUES if you find bugs, missing tests, or improvements while working: 6. FILE NEW ISSUES if you find bugs, missing tests, or improvements while working:
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues" \ curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues" \
@@ -190,6 +308,16 @@ You can do ANYTHING a developer can do. You are not limited to the narrow task.
- Never use --no-verify on git commands. - Never use --no-verify on git commands.
- If tests fail after 2 attempts, STOP and comment on the issue explaining why. - If tests fail after 2 attempts, STOP and comment on the issue explaining why.
- Be thorough. If you see something broken nearby, file an issue for it. - Be thorough. If you see something broken nearby, file an issue for it.
- ALWAYS run `git add .` before `git commit`. NEVER create an empty commit.
- ALWAYS check `git diff --cached --stat` before committing — if empty, do NOT commit or create a PR.
- DO NOT claim success unless the branch exists on Gitea, the PR exists, and the Proof block is posted on the issue.
== CRITICAL: FINISH = PUSHED + PR'D + PROVED ==
- NEVER exit without committing your work. Even partial progress MUST be committed.
- Before you finish, ALWAYS: git add -A && git commit && git push origin kimi/issue-${issue_num}
- ALWAYS create a PR before exiting. No exceptions.
- ALWAYS post the Proof block before exiting. No proof comment = not done.
- Your work is WASTED if it's not pushed. Push early, push often.
PROMPT PROMPT
} }
@@ -271,37 +399,76 @@ while true; do
set -e set -e
if [ "$exit_code" -eq 0 ]; then if [ "$exit_code" -eq 0 ]; then
log "SUCCESS: #${issue_num} completed — attempting auto-merge..." log "SUCCESS: #${issue_num} exited 0 verifying push + PR + proof..."
# Find and merge the PR kimi created cd "$worktree"
pr_num=$(curl -sf "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls?state=open&head=${REPO_OWNER}:${branch}&limit=1" \ if ! remote_branch_exists "$branch"; then
-H "Authorization: token ${GITEA_TOKEN}" | python3 -c " log " BLOCKED: remote branch ${branch} not found — work was not pushed"
import sys,json post_issue_comment "$issue_num" "Loop gate blocked completion: remote branch \`${branch}\` was not found on origin after Kimi exited. Issue remains open for retry."
prs = json.load(sys.stdin) mark_skip "$issue_num" "missing_remote_branch" 1
if prs: print(prs[0]['number']) failure_count=$((failure_count + 1))
else: print('') cd "$REPO_DIR"
" 2>/dev/null)
if [ -n "$pr_num" ]; then
merge_result=$(curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/merge" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"Do": "squash"}' 2>&1) || true
log " PR #${pr_num} merge attempted"
# Close the issue (Gitea auto-close via "Fixes #N" is unreliable)
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"state": "closed"}' >/dev/null 2>&1 || true
log " Issue #${issue_num} closed"
else else
log " WARN: No open PR found for branch ${branch}" cd "$REPO_DIR"
pr_num=$(get_pr_num "$branch")
if [ -z "$pr_num" ]; then
log " BLOCKED: no PR found for branch ${branch}"
post_issue_comment "$issue_num" "Loop gate blocked completion: branch \`${branch}\` exists remotely, but no pull request was found. Issue remains open for retry."
mark_skip "$issue_num" "missing_pr" 1
failure_count=$((failure_count + 1))
else
pr_files=$(get_pr_file_count "$pr_num")
if [ "${pr_files:-0}" -eq 0 ]; then
log " BLOCKED: PR #${pr_num} has 0 changed files — empty commit detected, closing PR without merge"
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"state": "closed"}' >/dev/null 2>&1 || true
post_issue_comment "$issue_num" "PR #${pr_num} was closed automatically: it had 0 changed files (empty commit). Kimi must stage files with \`git add\` before committing. Issue remains open for retry."
mark_skip "$issue_num" "empty_commit" 2
failure_count=$((failure_count + 1))
else
proof_status=$(proof_comment_status "$issue_num" "$branch")
proof_state="${proof_status%%|*}"
proof_url="${proof_status#*|}"
if [ "$proof_state" != "ok" ]; then
log " BLOCKED: proof comment missing or incomplete (${proof_state})"
post_issue_comment "$issue_num" "Loop gate blocked completion: PR #${pr_num} exists and has ${pr_files} changed file(s), but the required Proof block from Kimi is missing or incomplete. Issue remains open for retry."
mark_skip "$issue_num" "missing_proof" 1
failure_count=$((failure_count + 1))
else
log " PROOF: verified issue comment ${proof_url}"
pr_state=$(get_pr_state "$pr_num")
if [ "$pr_state" = "open" ]; then
curl -sf -X POST "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${pr_num}/merge" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"Do": "squash"}' >/dev/null 2>&1 || true
pr_state=$(get_pr_state "$pr_num")
fi
if [ "$pr_state" = "merged" ]; then
curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issue_num}" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"state": "closed"}' >/dev/null 2>&1 || true
issue_state=$(get_issue_state "$issue_num")
if [ "$issue_state" = "closed" ]; then
log " VERIFIED: branch pushed, PR #${pr_num} merged, proof present, issue closed"
failure_count=0
issues_completed=$((issues_completed + 1))
log "Stats: ${issues_completed} issues completed this session"
else
log " BLOCKED: PR #${pr_num} merged but issue #${issue_num} did not close"
post_issue_comment "$issue_num" "Loop gate published and merged the work in PR #${pr_num}, but could not verify the issue closed cleanly. Please inspect state manually."
mark_skip "$issue_num" "issue_close_unverified" 1
failure_count=$((failure_count + 1))
fi
else
log " BLOCKED: PR #${pr_num} state is ${pr_state} — merge not verified"
post_issue_comment "$issue_num" "Loop gate published the work and verified proof, but could not verify merge for PR #${pr_num}. Issue remains open for retry/review."
mark_skip "$issue_num" "merge_unverified" 1
failure_count=$((failure_count + 1))
fi
fi
fi
fi
fi fi
failure_count=0
issues_completed=$((issues_completed + 1))
log "Stats: ${issues_completed} issues completed this session"
elif [ "$exit_code" -eq 124 ]; then elif [ "$exit_code" -eq 124 ]; then
log "TIMEOUT: #${issue_num} exceeded ${KIMI_TIMEOUT}s" log "TIMEOUT: #${issue_num} exceeded ${KIMI_TIMEOUT}s"
mark_skip "$issue_num" "timeout" 1 mark_skip "$issue_num" "timeout" 1

View File

@@ -13,10 +13,10 @@ set -uo pipefail
export PATH="$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH" export PATH="$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH"
REPO="$HOME/Timmy-Time-dashboard" REPO="$HOME/.hermes"
STATE="$REPO/.loop/state.json" STATE="$REPO/loop/state.json"
LOG_DIR="$REPO/.loop/logs" LOG_DIR="$REPO/loop/logs"
CLAIMS="$REPO/.loop/claims.json" CLAIMS="$REPO/loop/claims.json"
PROMPT_FILE="$HOME/.hermes/bin/timmy-loop-prompt.md" PROMPT_FILE="$HOME/.hermes/bin/timmy-loop-prompt.md"
LOCKFILE="/tmp/timmy-loop.lock" LOCKFILE="/tmp/timmy-loop.lock"
COOLDOWN=3 COOLDOWN=3
@@ -30,7 +30,7 @@ DEEP_TRIAGE_INTERVAL=20 # Hermes+Timmy deep triage every N cycles
TRIAGE_SCRIPT="$REPO/scripts/triage_score.py" TRIAGE_SCRIPT="$REPO/scripts/triage_score.py"
RETRO_SCRIPT="$REPO/scripts/cycle_retro.py" RETRO_SCRIPT="$REPO/scripts/cycle_retro.py"
DEEP_TRIAGE_SCRIPT="$REPO/scripts/deep_triage.sh" DEEP_TRIAGE_SCRIPT="$REPO/scripts/deep_triage.sh"
QUEUE_FILE="$REPO/.loop/queue.json" 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() {
@@ -211,7 +211,7 @@ CYCLE=$(python3 -c "import json; print(json.load(open('$STATE'))['cycle'])")
while true; do while true; do
# Check for stop file # Check for stop file
if [ -f "$REPO/.loop/STOP" ]; then if [ -f "$REPO/loop/STOP" ]; then
log "STOP file found. Halting loop." log "STOP file found. Halting loop."
update_state "status" '"stopped"' update_state "status" '"stopped"'
break break

View File

@@ -8,10 +8,10 @@ set -uo pipefail
LOG_DIR="$HOME/.hermes/logs" LOG_DIR="$HOME/.hermes/logs"
LOG="$LOG_DIR/timmy-orchestrator.log" LOG="$LOG_DIR/timmy-orchestrator.log"
PIDFILE="$LOG_DIR/timmy-orchestrator.pid" PIDFILE="$LOG_DIR/timmy-orchestrator.pid"
GITEA_URL="http://143.198.27.163:3000" GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}"
GITEA_TOKEN=$(cat "$HOME/.config/gitea/token" 2>/dev/null) GITEA_TOKEN=$(cat "$HOME/.config/gitea/token" 2>/dev/null)
CYCLE_INTERVAL=300 CYCLE_INTERVAL=300
HERMES_TIMEOUT=180 HERMES_TIMEOUT=300
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
@@ -30,7 +30,7 @@ log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] TIMMY: $*" >> "$LOG" echo "[$(date '+%Y-%m-%d %H:%M:%S')] TIMMY: $*" >> "$LOG"
} }
REPOS="rockachopa/Timmy-time-dashboard rockachopa/alexanderwhitestone.com rockachopa/hermes-agent replit/timmy-tower replit/token-gated-economy" REPOS="Timmy_Foundation/hermes-agent Timmy_Foundation/the-nexus hermes/hermes-config Rockachopa/alexanderwhitestone.com"
gather_state() { gather_state() {
local state_dir="/tmp/timmy-state-$$" local state_dir="/tmp/timmy-state-$$"
@@ -158,7 +158,14 @@ HEADER
fi fi
echo "" >> "$prompt_file" echo "" >> "$prompt_file"
echo "Review each PR above. Execute curl commands for your decisions. Be brief." >> "$prompt_file" cat >> "$prompt_file" <<'FOOTER'
INSTRUCTIONS: For EACH PR above, do ONE of the following RIGHT NOW using your terminal tool:
- Run the merge curl command if the diff looks good
- Run the close curl command if it is a duplicate or garbage
- Run the comment curl command only if there is a clear bug
IMPORTANT: Actually run the curl commands in your terminal. Do not just describe what you would do. Most PRs from agents are fine — merge aggressively. Output only the commands you ran and their results.
FOOTER
local prompt_text local prompt_text
prompt_text=$(cat "$prompt_file") prompt_text=$(cat "$prompt_file")

View File

@@ -1,6 +1,4 @@
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. Gitea (143.198.27.163:3000): Organization: Timmy_Foundation (rockachopa, Timmy, perplexity = Owners). Active repos: Timmy_Foundation/hermes-agent (sovereign agent runtime, primary), Timmy_Foundation/the-nexus (3D environment, Timmy's home), hermes/hermes-config (operational config, scripts, skills, memories), Rockachopa/alexanderwhitestone.com (public site). Frozen/retired: rockachopa/Timmy-time-dashboard (ARCHIVED, do not use), Timmy/the-matrix (empty). Agent accounts: claude, kimi, hermes, perplexity, google, replit, manus. 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.
§
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).
§ §
Kimi delegation: 1-3 files max, pre-extract context (scans full codebase otherwise), ~/worktrees/ not /tmp/, verify commits, two-attempt rule. Worktrees: ~/worktrees/kimi-*. Kimi delegation: 1-3 files max, pre-extract context (scans full codebase otherwise), ~/worktrees/ not /tmp/, verify commits, two-attempt rule. Worktrees: ~/worktrees/kimi-*.
§ §