Files
timmy-config/bin/agent-dispatch.sh
Alexander Whitestone e091868fef
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has started running
Validate Config / JSON Validate (push) Has started running
feat: auto-commit-guard — 4-layer work preservation (#511) (#525)
Merge PR #525
2026-04-14 22:16:49 +00:00

228 lines
8.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# agent-dispatch.sh — Generate a lane-aware prompt for any agent
#
# Usage: agent-dispatch.sh <agent_name> <issue_num> <repo>
# agent-dispatch.sh groq 42 Timmy_Foundation/the-nexus
#
# Outputs a prompt to stdout. Copy-paste into the agent's interface.
# The prompt includes issue context, repo setup, lane coaching, and
# a short review checklist so dispatch itself teaches the right habits.
set -euo pipefail
AGENT_NAME="${1:?Usage: agent-dispatch.sh <agent> <issue_num> <owner/repo>}"
ISSUE_NUM="${2:?Usage: agent-dispatch.sh <agent> <issue_num> <owner/repo>}"
REPO="${3:?Usage: agent-dispatch.sh <agent> <issue_num> <owner/repo>}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LANES_FILE="${SCRIPT_DIR%/bin}/playbooks/agent-lanes.json"
resolve_gitea_url() {
if [ -n "${GITEA_URL:-}" ]; then
printf '%s\n' "${GITEA_URL%/}"
return 0
fi
if [ -f "$HOME/.hermes/gitea_api" ]; then
python3 - "$HOME/.hermes/gitea_api" <<'PY'
from pathlib import Path
import sys
raw = Path(sys.argv[1]).read_text().strip().rstrip("/")
print(raw[:-7] if raw.endswith("/api/v1") else raw)
PY
return 0
fi
if [ -f "$HOME/.config/gitea/base-url" ]; then
tr -d '[:space:]' < "$HOME/.config/gitea/base-url"
return 0
fi
echo "ERROR: set GITEA_URL or create ~/.hermes/gitea_api" >&2
return 1
}
GITEA_URL="$(resolve_gitea_url)"
resolve_token_file() {
local agent="$1"
local normalized
normalized="$(printf '%s' "$agent" | tr '[:upper:]' '[:lower:]')"
for candidate in \
"$HOME/.hermes/${agent}_token" \
"$HOME/.hermes/${normalized}_token" \
"$HOME/.config/gitea/${agent}-token" \
"$HOME/.config/gitea/${normalized}-token"; do
if [ -f "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
for candidate in \
"$HOME/.config/gitea/timmy-token" \
"$HOME/.hermes/gitea_token_vps" \
"$HOME/.hermes/gitea_token_timmy"; do
if [ -f "$candidate" ]; then
printf '%s\n' "$candidate"
return 0
fi
done
return 1
}
TOKEN_FILE="$(resolve_token_file "$AGENT_NAME" || true)"
if [ -z "${TOKEN_FILE:-}" ]; then
echo "ERROR: No token found for '$AGENT_NAME'." >&2
echo "Expected one of ~/.hermes/<agent>_token or ~/.config/gitea/<agent>-token" >&2
exit 1
fi
GITEA_TOKEN="$(cat "$TOKEN_FILE")"
REPO_OWNER="${REPO%%/*}"
REPO_NAME="${REPO##*/}"
BRANCH="${AGENT_NAME}/issue-${ISSUE_NUM}"
python3 - "$LANES_FILE" "$AGENT_NAME" "$ISSUE_NUM" "$REPO" "$REPO_OWNER" "$REPO_NAME" "$BRANCH" "$GITEA_URL" "$GITEA_TOKEN" "$TOKEN_FILE" <<'PY'
import json
import sys
import textwrap
import urllib.error
import urllib.request
lanes_path, agent, issue_num, repo, repo_owner, repo_name, branch, gitea_url, token, token_file = sys.argv[1:]
with open(lanes_path) as f:
lanes = json.load(f)
lane = lanes.get(agent, {
"lane": "bounded work with explicit verification and a clean PR handoff",
"skills_to_practice": ["verification", "scope control", "clear handoff writing"],
"missing_skills": ["escalate instead of guessing when the scope becomes unclear"],
"anti_lane": ["self-directed backlog growth", "unbounded architectural wandering"],
"review_checklist": [
"Did I stay within scope?",
"Did I verify the result?",
"Did I leave a clean PR and issue handoff?"
],
})
headers = {"Authorization": f"token {token}"}
def fetch_json(path):
req = urllib.request.Request(f"{gitea_url}/api/v1{path}", headers=headers)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read().decode())
try:
issue = fetch_json(f"/repos/{repo}/issues/{issue_num}")
comments = fetch_json(f"/repos/{repo}/issues/{issue_num}/comments")
except urllib.error.HTTPError as exc:
raise SystemExit(f"Failed to fetch issue context: {exc}") from exc
body = (issue.get("body") or "").strip()
body = body[:4000] + ("\n...[truncated]" if len(body) > 4000 else "")
recent_comments = comments[-3:]
comment_block = []
for c in recent_comments:
author = c.get("user", {}).get("login", "unknown")
text = (c.get("body") or "").strip().replace("\r", "")
text = text[:600] + ("\n...[truncated]" if len(text) > 600 else "")
comment_block.append(f"- {author}: {text}")
comment_text = "\n".join(comment_block) if comment_block else "- (no comments yet)"
skills = "\n".join(f"- {item}" for item in lane["skills_to_practice"])
gaps = "\n".join(f"- {item}" for item in lane["missing_skills"])
anti_lane = "\n".join(f"- {item}" for item in lane["anti_lane"])
review = "\n".join(f"- {item}" for item in lane["review_checklist"])
prompt = f"""You are {agent}, working on {repo_name} for Timmy Foundation.
YOUR ISSUE: #{issue_num} — "{issue.get('title', f'Issue #{issue_num}')}"
REPO: {repo}
GITEA API: {gitea_url}/api/v1
GITEA TOKEN FILE: {token_file}
WORK BRANCH: {branch}
LANE:
{lane['lane']}
SKILLS TO PRACTICE ON THIS ASSIGNMENT:
{skills}
COMMON FAILURE MODE TO AVOID:
{gaps}
ANTI-LANE:
{anti_lane}
ISSUE BODY:
{body or "(empty issue body)"}
RECENT COMMENTS:
{comment_text}
WORKFLOW:
1. Read the issue body and recent comments carefully before touching code.
2. Clone the repo into /tmp/{agent}-work-{issue_num}.
3. Check whether {branch} already exists on origin; reuse it if it does.
4. Read the repo docs and follow its own tooling and conventions.
5. Do only the scoped work from the issue. If the task grows, stop and comment instead of freelancing expansion.
6. Run the repo's real verification commands.
7. Open a PR and summarize:
- what changed
- how you verified it
- any remaining risk or follow-up
8. Comment on the issue with the PR link and the same concise summary.
GIT / API SETUP:
export GITEA_URL="{gitea_url}"
export GITEA_TOKEN_FILE="{token_file}"
export GITEA_TOKEN="$(tr -d '[:space:]' < "$GITEA_TOKEN_FILE")"
git config --global http."$GITEA_URL/".extraHeader "Authorization: token $GITEA_TOKEN"
git clone "$GITEA_URL/{repo}.git" /tmp/{agent}-work-{issue_num}
cd /tmp/{agent}-work-{issue_num}
git ls-remote --exit-code origin {branch} >/dev/null 2>&1 && git fetch origin {branch} && git checkout {branch} || git checkout -b {branch}
ISSUE FETCH COMMANDS:
curl -s -H "Authorization: token $GITEA_TOKEN" "{gitea_url}/api/v1/repos/{repo}/issues/{issue_num}"
curl -s -H "Authorization: token $GITEA_TOKEN" "{gitea_url}/api/v1/repos/{repo}/issues/{issue_num}/comments"
PR CREATION TEMPLATE:
curl -s -X POST "{gitea_url}/api/v1/repos/{repo}/pulls" \\
-H "Authorization: token $GITEA_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{{"title":"[{agent}] <description> (#{issue_num})","body":"Fixes #{issue_num}\\n\\n## Summary\\n- <change>\\n\\n## Verification\\n- <command/output>\\n\\n## Risks\\n- <if any>","head":"{branch}","base":"main"}}'
ISSUE COMMENT TEMPLATE:
curl -s -X POST "{gitea_url}/api/v1/repos/{repo}/issues/{issue_num}/comments" \\
-H "Authorization: token $GITEA_TOKEN" \\
-H "Content-Type: application/json" \\
-d '{{"body":"PR submitted.\\n\\nSummary:\\n- <change>\\n\\nVerification:\\n- <command/output>\\n\\nRisks:\\n- <if any>"}}'
REVIEW CHECKLIST BEFORE YOU PUSH:
{review}
COMMIT DISCIPLINE (CRITICAL):
- Commit every 3-5 tool calls. Do NOT wait until the end.
- After every meaningful file change: git add -A && git commit -m "WIP: <what changed>"
- Before running any destructive command: commit current state first.
- If you are unsure whether to commit: commit. WIP commits are safe. Lost work is not.
- Never use --no-verify.
- The auto-commit-guard is your safety net, but do not rely on it. Commit proactively.
RECOVERY COMMANDS (if interrupted, another agent can resume):
git log --oneline -10 # see your WIP commits
git diff HEAD~1 # see what the last commit changed
git status # see uncommitted work
RULES:
- Do not skip hooks with --no-verify.
- Do not silently widen the scope.
- If verification fails twice or the issue is underspecified, stop and comment with what blocked you.
- Always create a PR instead of pushing to main.
- Clean up /tmp/{agent}-work-{issue_num} when done.
"""
print(textwrap.dedent(prompt).strip())
PY