build(agents): prompt_async for pool supervisors, session resume, bot signatures, cleanup agent

Four changes in one commit across 27 agent files:

1. POOL SUPERVISOR PROMPT_ASYNC: All 4 pool supervisors (issue-implementor,
   ca-continuous-pr-reviewer, ca-uat-tester, ca-bug-hunter) now dispatch
   their internal workers via the OpenCode Server's prompt_async endpoint
   instead of the Task tool. This eliminates the wait_for_all bottleneck
   at the supervisor level — workers run independently, and a 10-second
   polling loop detects completions and immediately refills vacant slots.
   Added curl/sleep bash permissions where needed. Each supervisor keeps
   N workers running at all times with zero idle slots.

2. SESSION RESUME INSTEAD OF CLEANUP: The product-builder and all 4 pool
   supervisors now RESUME existing sessions from a previous interrupted
   run instead of aborting them. Phase C.0 queries the server for sessions
   titled "[CA-AUTO] supervisor:*" and adopts any that are still active
   into the monitoring loop. Pool supervisors similarly adopt existing
   "[CA-AUTO] worker-*" sessions. This enables "continue where you left
   off" — restarting the product-builder reconnects to running supervisors
   and workers rather than duplicating them.

3. DEDICATED CLEANUP AGENT: New ca-session-cleanup.md primary agent for
   explicit fresh-start cleanup. Run this BEFORE the product-builder when
   you want to abort all previous sessions and start completely fresh. It
   finds all "[CA-AUTO]" sessions, aborts them, and deletes them. This is
   the ONLY way to kill old sessions — the product-builder never does it
   automatically.

4. BOT SIGNATURES: All 26 agents that post content to Forgejo now include
   a mandatory "Bot Signature" section requiring every comment, issue body,
   PR description, and review to end with:

   ---
   **Automated by CleverAgents Bot**
   Supervisor: <category> | Agent: <agent-name>

   24 agents have hardcoded categories. 2 shared agents (ca-new-issue-creator,
   ca-epic-planner) use a parameter-based category from their caller's prompt.
This commit is contained in:
2026-04-02 18:00:29 +00:00
parent 79403188f6
commit 9bbec0e698
27 changed files with 814 additions and 235 deletions
+14
View File
@@ -358,6 +358,20 @@ Propose changes to model selection:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER apply changes directly.** All modifications go through PRs with
+14
View File
@@ -178,6 +178,20 @@ LOOP:
bash("sleep 600", timeout=900000) # 10 min sleep, 15 min timeout
```
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Architecture Guard | Agent: ca-architecture-guard
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Return Value
Report:
+14
View File
@@ -280,6 +280,20 @@ For each open legendary (`Type/Legendary`):
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Backlog Grooming | Agent: ca-backlog-groomer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **No filesystem access.** You work entirely through the Forgejo API.
+73 -15
View File
@@ -26,6 +26,8 @@ permission:
"head *": allow
"tail *": allow
"git *": allow
"curl *": allow
"sleep *": allow
task:
"*": deny
"ca-ref-reader": allow
@@ -98,6 +100,20 @@ scanned_modules = set()
findings_total = 0
cycle = 0
last_master_sha = query current master HEAD via Forgejo API
SERVER = "http://localhost:4096"
# ── RESUME: Adopt existing hunt worker sessions from previous run ─
EXISTING_WORKERS = bash("curl -s ${SERVER}/session | python3 -c \"
import sys, json
for s in json.loads(sys.stdin.read()):
title = s.get('title','')
if title.startswith('[CA-AUTO] worker-hunt:'):
module = title.replace('[CA-AUTO] worker-hunt: ','')
print(module + '=' + s['id'])
\"", timeout=30000)
# Adopted workers will be picked up in the monitoring loop.
# Mark their modules as in-progress so we don't dispatch duplicates.
LOOP:
cycle += 1
@@ -122,26 +138,54 @@ LOOP:
bash("sleep 60", timeout=120000)
continue # Loop back to check for new code
# ── Step 3: Dispatch N parallel workers ──────────────────────
# ── Step 3: Dispatch workers via prompt_async ──────────────────
# Fill all N slots. As each completes, immediately refill from unscanned.
active = {} # module -> session_id
batch = unscanned[:N]
workers = {}
for module in batch:
worker = invoke ca-bug-hunter with:
- Repo owner/name
- Instance ID: "bug-worker-<module-slug>-<cycle>"
- Forgejo PAT, git identity, username
- module_focus: module # Narrow scope → Worker Mode
- max_workers: 1 # Force Worker Mode
- spec_context: ref_summary
workers[module] = worker
SESSION_ID = bash("curl -s -X POST ${SERVER}/session \
-H 'Content-Type: application/json' \
-d '{\"title\": \"[CA-AUTO] worker-hunt: <module>\"}' \
| python3 -c \"import sys,json; print(json.loads(sys.stdin.read())['id'])\"",
timeout=30000)
bash("curl -s -X POST ${SERVER}/session/${SESSION_ID}/prompt_async \
-H 'Content-Type: application/json' \
-d '{\"agent\": \"ca-bug-hunter\", \
\"parts\": [{\"type\": \"text\", \"text\": \
\"Worker mode. Module focus: <module>. max_workers: 1. \
Repo: <owner>/<repo>. Forgejo PAT: <PAT>. \
Git: <name> <email>. Username: <username>. \
Acting on behalf of: Bug Hunting.\"}]}'",
timeout=30000)
active[module] = SESSION_ID
# ── Step 4: Collect results ──────────────────────────────────
results = wait_for_all(workers)
# ── Step 4: Monitor workers, collect results, refill slots ───
remaining_unscanned = unscanned[N:] # modules not yet dispatched
while active:
bash("sleep 10", timeout=30000)
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
for module, result in results.items():
scanned_modules.add(module)
findings_total += result.total_findings
for module, session_id in list(active.items()):
if session is completed or errored:
# Collect result
final_msg = bash("curl -s ${SERVER}/session/${session_id}/message",
timeout=30000)
result = parse_worker_result(final_msg)
scanned_modules.add(module)
findings_total += result.total_findings
# Clean up
bash("curl -s -X DELETE ${SERVER}/session/${session_id}",
timeout=15000)
del active[module]
# Immediately refill slot from remaining unscanned modules
if remaining_unscanned:
next_module = remaining_unscanned.pop(0)
# dispatch next_module (same prompt_async pattern as above)
NEW_SID = create session + prompt_async for next_module
active[next_module] = NEW_SID
# ── Step 5: Post progress ────────────────────────────────────
if cycle % 2 == 0:
@@ -369,6 +413,20 @@ Before filing any finding:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Bug Hunting | Agent: ca-bug-hunter
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER work in /app.** Always use your isolated clone (Worker Mode) or
+109 -64
View File
@@ -118,6 +118,22 @@ reviewed_prs = set() # PRs fully processed (merged or changes_requeste
pending_merge = {} # PR number -> {attempts, last_status} — approved but not yet merged
stale_count = 0 # Consecutive cycles with zero work found
cycle = 0
SERVER = "http://localhost:4096"
# ── RESUME: Adopt existing reviewer sessions from previous run ───
# If there are worker sessions still running from a previous
# interrupted run, adopt them into tracking instead of duplicating.
EXISTING_WORKERS = bash("curl -s ${SERVER}/session | python3 -c \"
import sys, json
for s in json.loads(sys.stdin.read()):
title = s.get('title','')
if title.startswith('[CA-AUTO] worker-review:'):
pr_num = title.replace('[CA-AUTO] worker-review: PR-','')
print(pr_num + '=' + s['id'])
\"", timeout=30000)
# Note: adopted workers will be picked up in the monitoring loop
# automatically — they're tracked the same as freshly dispatched ones.
LOOP FOREVER:
cycle += 1
@@ -176,7 +192,7 @@ LOOP FOREVER:
stale_count = 0
# ── Step 3: Dispatch parallel reviewers ──────────────────────
# ── Step 3: Dispatch parallel reviewers via prompt_async ─────
# Take up to N work items
batch = work_items[:N]
@@ -185,72 +201,87 @@ LOOP FOREVER:
if item.type in ("review", "re_review"):
post comment on PR:
"Review claimed by reviewer pool instance <INSTANCE_ID>.
Dispatching independent code review."
Dispatching independent code review.
---
**Automated by CleverAgents Bot**
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer"
# Dispatch N parallel ca-pr-self-reviewer tasks
active_reviews = {}
# Dispatch N parallel ca-pr-self-reviewer sessions (fire-and-forget)
active_reviews = {} # pr_number -> session_id
for item in batch:
if item.type in ("review", "re_review"):
reviewer = invoke ca-pr-self-reviewer with:
- repo: <owner>/<repo>
- pr_number: item.pr.number
- workdir: $CLONE_DIR
- spec_context: ref_summary
active_reviews[item.pr.number] = reviewer
elif item.type == "merge_retry":
# For merge retries, dispatch the reviewer again to attempt merge
reviewer = invoke ca-pr-self-reviewer with:
- repo: <owner>/<repo>
- pr_number: item.pr_number
- workdir: $CLONE_DIR
- spec_context: ref_summary
- Note: "This PR was previously approved. Focus on merge."
active_reviews[item.pr_number] = reviewer
pr_num = item.pr.number if item.type != "merge_retry" else item.pr_number
note = ""
if item.type == "merge_retry":
note = "This PR was previously approved. Focus on merge."
SESSION_ID = bash("curl -s -X POST ${SERVER}/session \
-H 'Content-Type: application/json' \
-d '{\"title\": \"[CA-AUTO] worker-review: PR-<pr_num>\"}' \
| python3 -c \"import sys,json; print(json.loads(sys.stdin.read())['id'])\"",
timeout=30000)
bash("curl -s -X POST ${SERVER}/session/${SESSION_ID}/prompt_async \
-H 'Content-Type: application/json' \
-d '{\"agent\": \"ca-pr-self-reviewer\", \
\"parts\": [{\"type\": \"text\", \"text\": \
\"Review PR #<pr_num>. Repo: <owner>/<repo>. \
Spec context: <ref_summary compact>. \
Forgejo PAT: <PAT>. Git: <name> <email>. \
<note>\"}]}'", timeout=30000)
active_reviews[pr_num] = SESSION_ID
# ── Step 4: Collect results ──────────────────────────────────
# Wait for all dispatched reviewers to complete
results = wait_for_all(active_reviews)
for pr_number, result in results.items():
if result.merge_status == "merged":
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
# PR is done — merged successfully
elif result.merge_status == "merge_scheduled":
# Merge will happen when CI passes — track it
pending_merge[pr_number] = {
attempts: pending_merge.get(pr_number, {}).get(attempts, 0) + 1,
last_status: "merge_scheduled"
}
elif result.merge_status in ("ci_pending", "ci_failing", "merge_failed"):
# Merge failed — track for retry next cycle
pending_merge[pr_number] = {
attempts: pending_merge.get(pr_number, {}).get(attempts, 0) + 1,
last_status: result.merge_status
}
elif result.merge_status == "conflict":
# Merge conflict — post comment, mark as reviewed
# The implementing agent needs to rebase
post comment on PR #pr_number:
"Merge conflict detected. The implementing agent needs to
rebase this branch onto the latest master."
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
elif result.decision == "changes_requested":
# Changes requested — mark as reviewed for now
# Will re-enter work queue when new commits are pushed
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
elif result.merge_status == "awaiting_human":
# `needs feedback` PR — skip permanently
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
# ── Step 4: Monitor workers, collect results ─────────────────
# Poll every 10 seconds until all dispatched reviewers complete.
# As each completes, process its result immediately.
while active_reviews:
bash("sleep 10", timeout=30000)
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
for pr_number, session_id in list(active_reviews.items()):
session_status = parse STATUS for session_id
if session is completed or errored or not found:
# Collect result from session's final message
final_msg = bash("curl -s ${SERVER}/session/${session_id}/message",
timeout=30000)
result = parse_reviewer_result(final_msg)
# Clean up worker session
bash("curl -s -X DELETE ${SERVER}/session/${session_id}",
timeout=15000)
del active_reviews[pr_number]
# Process result:
if result.merge_status == "merged":
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
elif result.merge_status == "merge_scheduled":
pending_merge[pr_number] = {
attempts: pending_merge.get(pr_number, {}).get(attempts, 0) + 1,
last_status: "merge_scheduled"
}
elif result.merge_status in ("ci_pending", "ci_failing", "merge_failed"):
pending_merge[pr_number] = {
attempts: pending_merge.get(pr_number, {}).get(attempts, 0) + 1,
last_status: result.merge_status
}
elif result.merge_status == "conflict":
post comment on PR #pr_number:
"Merge conflict detected. The implementing agent
needs to rebase this branch onto latest master.
---
**Automated by CleverAgents Bot**
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer"
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
elif result.decision == "changes_requested":
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
elif result.merge_status == "awaiting_human":
reviewed_prs.add(pr_number)
pending_merge.pop(pr_number, None)
# ── Step 5: Check for scheduled merges that completed ────────
# PRs with merge_when_checks_succeed may have merged since last cycle
@@ -326,6 +357,20 @@ they choose, but the system never gives up autonomously.
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER work in /app.** Always use your isolated clone.
+14
View File
@@ -163,6 +163,20 @@ For complex modules, produce per-module docs in `docs/modules/`.
- Explain purpose, key classes/functions, usage patterns, and gotchas
- Include code examples where they aid understanding
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Documentation | Agent: ca-docs-writer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
1. **EXTEND, never overwrite.** Read existing docs first. Merge new content into existing structure.
+18
View File
@@ -166,6 +166,24 @@ matters:
Document dependencies in each issue's Dependencies section. The first
issues in any chain should be the ones with zero blockers.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: <CATEGORY> | Agent: ca-epic-planner
```
**Category**: Use the supervisor category provided by your caller in the
prompt (e.g., "Acting on behalf of: UAT Testing"). If no category was
provided, use "Unknown".
**Agent**: ca-epic-planner
Append this to the END of every piece of content you create on Forgejo.
## Return Value
Report back with:
+14
View File
@@ -487,6 +487,20 @@ Every 15 cycles (~30 minutes), check for conversations that need attention:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Human Liaison | Agent: ca-human-liaison
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **No filesystem access.** You work entirely through the Forgejo API and
+14
View File
@@ -89,6 +89,20 @@ sections (skip only if truly not applicable):
### Risk Mitigations
- Potential risks identified and how they were addressed.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-issue-note-writer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important
- Be **exhaustive**. This is non-optional. Every decision, discovery, and
@@ -71,6 +71,20 @@ Execute these steps:
- Which labels were added
- Whether any precondition checks failed
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-issue-state-updater
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important
- Only modify state labels (`State/*`) and the `Blocked` label.
+14
View File
@@ -351,6 +351,20 @@ After the PR is created:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-issue-worker
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Forgejo Comment Protocol
Post comments on the Forgejo issue (via `ca-issue-note-writer` or direct
+14
View File
@@ -225,6 +225,20 @@ session state issue) using the format below.
specification requirements are unmet. The milestone needs additional work
before it can be considered complete.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-milestone-reviewer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **You are read-only.** You review code and create issues. You do NOT fix
+18
View File
@@ -99,6 +99,24 @@ Follow the format specified in CONTRIBUTING.md "Creating Issues":
- If the new issue blocks the current issue, document this dependency.
- If it does not block, note that it is independent.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: <CATEGORY> | Agent: ca-new-issue-creator
```
**Category**: Use the supervisor category provided by your caller in the
prompt (e.g., "Acting on behalf of: UAT Testing"). If no category was
provided, use "Unknown".
**Agent**: ca-new-issue-creator
Append this to the END of every piece of content you create on Forgejo.
## Return Value
Report back with:
+14
View File
@@ -97,6 +97,20 @@ forgejo_update_pull_request(owner, repo, index, milestone="3", body=pr.body)
This applies to ALL PR modifications after initial creation.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-pr-api-creator
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important
- The PR MUST reference the issue with a closing keyword in the body
+14
View File
@@ -159,6 +159,20 @@ forgejo_update_pull_request(owner, repo, index, title="new title", body=pr.body)
This applies to ALL PR modifications: title changes, milestone updates,
assignee changes, label additions — EVERY update call must include `body`.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: PR Review | Agent: ca-pr-checker
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Critical Rules
- **Amend** the existing commit when fixing. Do NOT create new commits.
+14
View File
@@ -246,6 +246,20 @@ title, assignee, milestone, or any other field), you MUST:
Failing to do this will replace the PR description with an empty string.
This applies to ALL `forgejo_update_pull_request` calls without exception.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: PR Review | Agent: ca-pr-self-reviewer
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- You are a **DIFFERENT PERSPECTIVE** than the implementer. Do not rubber-stamp.
+14
View File
@@ -134,6 +134,20 @@ Perform each check in order. Record PASS or FAIL with details for every item.
- **COMPLETE**: All 10 checks pass. The product is ready.
- **INCOMPLETE**: One or more checks fail. List every gap with actionable remediation.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Product Verification | Agent: ca-product-verifier
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Return Value
Output the following structured report:
@@ -196,6 +196,20 @@ Create the following directories (with `.gitkeep` files to ensure they are track
- Push to `master` (this is initial setup, before branch protection is applied)
- Apply branch protection LAST so the setup commits can be pushed directly
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Project Bootstrap | Agent: ca-project-bootstrapper
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Return Value
Provide a structured report:
+91
View File
@@ -0,0 +1,91 @@
---
description: >
One-shot cleanup agent that finds and aborts ALL stale automated sessions
from previous interrupted runs. Run this BEFORE starting a fresh
product-builder session to prevent duplicate supervisors and workers.
Do NOT run this if you want to resume an existing session — the
product-builder will reconnect to existing sessions automatically.
mode: primary
temperature: 0.0
color: error
permission:
edit: deny
bash:
"*": deny
"curl *": allow
"sleep *": allow
"echo *": allow
task:
"*": deny
---
# CleverAgents Session Cleanup
You are a one-shot cleanup agent. Your job is to find and abort ALL
automated sessions from previous product-builder runs that may still be
running on the OpenCode server.
**When to use this agent:**
- Before starting a FRESH product-builder session (not resuming)
- After a crash, interrupt, or unclean shutdown
- When you see duplicate supervisors or workers competing for work
**When NOT to use this agent:**
- When resuming an existing session (the product-builder reconnects
automatically)
- When the system is running normally
## Process
```
SERVER = "http://localhost:4096"
# Step 1: Query all sessions from the server
ALL_SESSIONS = bash("curl -s ${SERVER}/session", timeout=30000)
# Step 2: Find all automated sessions (supervisors and workers)
# Automated sessions have titles starting with "[CA-AUTO]"
AUTOMATED = bash("echo '${ALL_SESSIONS}' | python3 -c \"
import sys, json
sessions = json.loads(sys.stdin.read())
for s in sessions:
title = s.get('title', '')
if title.startswith('[CA-AUTO]'):
print(s['id'] + ' | ' + title)
\"", timeout=30000)
# Step 3: Report what was found
if AUTOMATED is empty:
Output: "No stale automated sessions found. The server is clean."
EXIT
Output: "Found <N> automated sessions to clean up:"
for each line in AUTOMATED:
Output: " - <session_id> | <title>"
# Step 4: Abort and delete each session
aborted = 0
for session_id in AUTOMATED (extract ID from each line):
bash("curl -s -X POST ${SERVER}/session/${session_id}/abort", timeout=15000)
bash("curl -s -X DELETE ${SERVER}/session/${session_id}", timeout=15000)
aborted += 1
# Step 5: Clean up tracking files
bash("rm -f /tmp/ca-supervisor-sessions.env", timeout=5000)
bash("rm -f /tmp/ca-worker-impl-sessions.env", timeout=5000)
# Step 6: Report results
Output: "Cleanup complete. Aborted and deleted <aborted> automated sessions."
Output: "You can now start a fresh product-builder session."
```
## Important Rules
- **This is a one-shot agent.** Run it once, it cleans up, it exits.
- **It kills EVERYTHING with [CA-AUTO] in the title.** Both supervisors
and workers. There is no selective cleanup.
- **Do NOT run this while the product-builder is actively running.** It
will kill all its supervisors and workers.
- **The product-builder does NOT need this to start.** If resuming, the
product-builder reconnects to existing sessions automatically. This
agent is only for starting completely fresh.
+14
View File
@@ -65,6 +65,20 @@ If restarting: [specific instructions for what to do next]
Read the session state issue, find the latest comment, parse and return the state.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Session Management | Agent: ca-session-persister
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- ALWAYS create a NEW comment (never edit old ones) — preserves full audit history
+14
View File
@@ -202,6 +202,20 @@ update metadata via `forgejo_update_pull_request`), you MUST:
Failing to do this will replace the PR description with an empty string,
wiping the detailed rationale and change descriptions you wrote.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Spec Evolution | Agent: ca-spec-updater
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- The spec is the **SOURCE OF TRUTH**. Only update it when the implementation genuinely discovered a better approach.
+14
View File
@@ -55,6 +55,20 @@ You will be given:
- Whether any provided subtask descriptions did not match (fuzzy match
is acceptable if the meaning is clearly the same)
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-subtask-checker
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important
- Only modify checkbox states. Do NOT change any other part of the issue body.
+14
View File
@@ -342,6 +342,20 @@ KEY_DESIGN_DECISIONS:
trade-offs, and reasoning that the parent issue worker should know about>
```
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: ca-subtask-loop
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER give up on a subtask.** Loop forever until it passes. The
+14
View File
@@ -409,6 +409,20 @@ Story points per developer per milestone.
- PRs: X open (was Y)
```
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Timeline | Agent: ca-timeline-updater
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER delete historical data.** Schedule adherence entries, completed
+71 -15
View File
@@ -90,6 +90,20 @@ feature_areas = extract_all_feature_areas(ref_summary)
tested_areas = set()
bugs_found_total = 0
cycle = 0
SERVER = "http://localhost:4096"
# ── RESUME: Adopt existing UAT worker sessions from previous run ─
EXISTING_WORKERS = bash("curl -s ${SERVER}/session | python3 -c \"
import sys, json
for s in json.loads(sys.stdin.read()):
title = s.get('title','')
if title.startswith('[CA-AUTO] worker-uat:'):
area = title.replace('[CA-AUTO] worker-uat: ','')
print(area + '=' + s['id'])
\"", timeout=30000)
# Adopted workers will be picked up in the monitoring loop.
# Mark their areas as in-progress so we don't dispatch duplicates.
LOOP:
cycle += 1
@@ -112,26 +126,54 @@ LOOP:
bash("sleep 60", timeout=120000)
continue # Loop back to check for new code
# ── Step 2: Dispatch N parallel workers ──────────────────────
# ── Step 2: Dispatch workers via prompt_async ──────────────────
# Fill all N slots. As each completes, immediately refill from untested.
active = {} # area -> session_id
batch = untested[:N]
workers = {}
for area in batch:
worker = invoke ca-uat-tester with:
- Repo owner/name
- Instance ID: "uat-worker-<area-slug>-<cycle>"
- Forgejo PAT, git identity, username
- feature_area: area # Narrow scope → Worker Mode
- max_workers: 1 # Force Worker Mode
- spec_context: ref_summary
workers[area] = worker
SESSION_ID = bash("curl -s -X POST ${SERVER}/session \
-H 'Content-Type: application/json' \
-d '{\"title\": \"[CA-AUTO] worker-uat: <area>\"}' \
| python3 -c \"import sys,json; print(json.loads(sys.stdin.read())['id'])\"",
timeout=30000)
bash("curl -s -X POST ${SERVER}/session/${SESSION_ID}/prompt_async \
-H 'Content-Type: application/json' \
-d '{\"agent\": \"ca-uat-tester\", \
\"parts\": [{\"type\": \"text\", \"text\": \
\"Worker mode. Feature area: <area>. max_workers: 1. \
Repo: <owner>/<repo>. Forgejo PAT: <PAT>. \
Git: <name> <email>. Username: <username>. \
Acting on behalf of: UAT Testing.\"}]}'",
timeout=30000)
active[area] = SESSION_ID
# ── Step 3: Collect results ──────────────────────────────────
results = wait_for_all(workers)
# ── Step 3: Monitor workers, collect results, refill slots ───
remaining_untested = untested[N:] # areas not yet dispatched
while active:
bash("sleep 10", timeout=30000)
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
for area, result in results.items():
tested_areas.add(area)
bugs_found_total += result.bugs_filed
for area, session_id in list(active.items()):
if session is completed or errored:
# Collect result
final_msg = bash("curl -s ${SERVER}/session/${session_id}/message",
timeout=30000)
result = parse_worker_result(final_msg)
tested_areas.add(area)
bugs_found_total += result.bugs_filed
# Clean up
bash("curl -s -X DELETE ${SERVER}/session/${session_id}",
timeout=15000)
del active[area]
# Immediately refill slot from remaining untested areas
if remaining_untested:
next_area = remaining_untested.pop(0)
# dispatch next_area (same prompt_async pattern as above)
NEW_SID = create session + prompt_async for next_area
active[next_area] = NEW_SID
# ── Step 4: Post progress ────────────────────────────────────
if cycle % 2 == 0:
@@ -339,6 +381,20 @@ Before filing any bug:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: UAT Testing | Agent: ca-uat-tester
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Important Rules
- **NEVER work in /app.** Always use your isolated clone (Worker Mode) or
+116 -103
View File
@@ -17,6 +17,8 @@ permission:
bash:
"*": deny
"echo $*": allow
"curl *": allow
"sleep *": allow
task:
"*": deny
"ca-ref-reader": allow
@@ -176,32 +178,68 @@ maximum throughput.
max_workers = CA_MAX_PARALLEL_WORKERS (from env, or ask user if unset)
queue = prioritized list of unblocked issues (ready, or blocked only
by issues assigned to you in this batch)
active = {} # issue_number -> running worker
active = {} # issue_number -> session_id (prompt_async session)
completed = [] # list of {issue_number, branch, pr_number, pr_url, ...}
failed = {} # issue_number -> consecutive_failure_count
pre_cloned = {} # issue_number -> clone_path (speculatively prepared)
ref_summary = result from ca-ref-reader
wave = 1
last_timeline_update = now()
bg_ref_reader = None # background ref-reader task (if running)
bg_timeline = None # background timeline-updater task (if running)
SERVER = "http://localhost:4096"
idle_polls = 0
# ── RESUME: Adopt existing worker sessions from previous run ─────
# If this supervisor is picking up from a previous interrupted run,
# there may be worker sessions still running. Adopt them into the
# active tracking instead of launching duplicates.
# To start fresh, run ca-session-cleanup BEFORE the product-builder.
while queue is not empty or active is not empty or idle_polls < 60:
EXISTING_WORKERS = bash("curl -s ${SERVER}/session | python3 -c \"
import sys, json
for s in json.loads(sys.stdin.read()):
title = s.get('title','')
if title.startswith('[CA-AUTO] worker-impl:'):
# Extract issue number from title
issue_num = title.replace('[CA-AUTO] worker-impl: issue-','')
print(issue_num + '=' + s['id'])
\"", timeout=30000)
# ── Collect background task results (non-blocking) ──
if bg_ref_reader is not None and bg_ref_reader.done:
ref_summary = bg_ref_reader.result
bg_ref_reader = None
if bg_timeline is not None and bg_timeline.done:
last_timeline_update = now()
bg_timeline = None
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
for line in EXISTING_WORKERS:
issue_number, session_id = line.split("=")
if session_id is active in STATUS:
active[int(issue_number)] = session_id # Adopt into active tracking
# Remove from queue if present (already being worked on)
queue = [i for i in queue if i.number != int(issue_number)]
# ── AGGRESSIVE slot-filling: fill ALL empty slots at once ──
# Do not stop after one dispatch — fill every available slot.
# ── Helper: launch one worker via prompt_async ───────────────────
function dispatch_worker(issue, base_branch, ref_summary):
prompt = "You are an issue worker for Implementation.
Ref summary: <ref_summary (compact)>
Issue: #<issue.number> — <issue.title>
Branch: <issue.branch>
Milestone: <issue.milestone>
Labels: <issue.labels>
Forgejo PAT: <PAT>. Git: <name> <email>. Username: <username>.
Base branch: <base_branch or 'master'>"
SESSION_ID = bash("curl -s -X POST ${SERVER}/session \
-H 'Content-Type: application/json' \
-d '{\"title\": \"[CA-AUTO] worker-impl: issue-<issue.number>\"}' \
| python3 -c \"import sys,json; print(json.loads(sys.stdin.read())['id'])\"",
timeout=30000)
bash("curl -s -X POST ${SERVER}/session/${SESSION_ID}/prompt_async \
-H 'Content-Type: application/json' \
-d '{\"agent\": \"ca-issue-worker\", \
\"parts\": [{\"type\": \"text\", \"text\": \"<prompt>\"}]}'",
timeout=30000)
return SESSION_ID
# ── Main dispatch + monitoring loop ──────────────────────────────
LOOP FOREVER:
# ── AGGRESSIVE slot-filling: fill ALL empty slots at once ────
slots_available = max_workers - len(active)
dispatched_this_round = 0
while slots_available > 0 and queue is not empty:
issue = queue.pop(0) # highest priority first
@@ -210,114 +248,75 @@ while queue is not empty or active is not empty or idle_polls < 60:
if issue depends on a completed issue from this session:
base_branch = completed_issue.branch_name
# Check if this issue was speculatively pre-cloned
pre_clone_path = pre_cloned.pop(issue.number, None)
# Dispatch ca-issue-worker
worker = invoke ca-issue-worker with:
- ref_summary
- issue details (number, title, branch, milestone, labels)
- Forgejo PAT, git identity, username
- base_branch (if dependent on a completed issue)
- pre_clone_path (if available, worker skips Phase 1 clone)
active[issue.number] = worker
# Dispatch via prompt_async (fire-and-forget)
session_id = dispatch_worker(issue, base_branch, ref_summary)
active[issue.number] = session_id
slots_available -= 1
dispatched_this_round += 1
# ── SPECULATIVE PRE-CLONING for blocked issues ──────────────────
# For issues whose blockers are >50% complete (more than half their
# subtasks checked off), speculatively clone the repo and set up the
# branch. This eliminates clone+branch setup latency when the blocker
# finishes.
# ── Monitor active workers: poll every 10 seconds ────────────
# Check which workers have completed. For each completed worker,
# collect its result and free the slot. Then loop back to fill
# empty slots immediately.
#
# This runs in the BACKGROUND and does NOT count against max_workers.
for blocked_issue in issues_blocked_by_active_workers:
if blocked_issue.number not in pre_cloned:
blocker = get_blocker(blocked_issue)
if blocker_progress(blocker) > 50%: # >50% subtasks done
# Pre-clone in background (lightweight bash, not a worker slot)
pre_clone_path = "/tmp/cleveragents-" + blocked_issue.branch
run_in_background:
git clone <repo> <pre_clone_path>
git checkout -b <blocked_issue.branch>
configure git identity
pre_cloned[blocked_issue.number] = pre_clone_path
# MUST use Bash tool for sleep:
bash("sleep 10", timeout=30000)
# ── Wait for ANY active worker to complete ──
completed_worker = wait_for_any(active)
issue = completed_worker.issue
del active[issue.number]
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
if completed_worker.success:
completed.append({
issue_number, branch, pr_number, pr_url, pass/fail,
attempt_counts, model_tiers_used, new_issues_created
})
for issue_number, session_id in active.items():
session_status = parse STATUS for session_id
# Check if any blocked issues are now unblocked
newly_unblocked = find issues whose blockers are all in completed
queue.extend(newly_unblocked) # insert respecting priority order
re-sort queue by priority
if session is completed or errored or not found:
# Collect result: query session for final message
final_msg = bash("curl -s ${SERVER}/session/${session_id}/message \
| python3 -c \"import sys,json; msgs=json.loads(sys.stdin.read()); \
print(msgs[-1] if msgs else 'ERROR')\"", timeout=30000)
wave += 1
# Parse worker result from final message
if worker reported success:
completed.append({
issue_number, branch, pr_number, pr_url,
attempt_counts, model_tiers_used, new_issues_created
})
# ── BACKGROUND ref-reader refresh every 3 waves ──
# Run in background — do NOT block the dispatch loop.
if wave > 1 and wave % 3 == 0 and bg_ref_reader is None:
bg_ref_reader = invoke ca-ref-reader IN BACKGROUND (non-blocking)
# Unblock dependent issues
newly_unblocked = issues whose blockers are all in completed
queue.extend(newly_unblocked)
re-sort queue by priority
wave += 1
# ── BACKGROUND timeline update every 3 waves ──
# Run in background — do NOT block the dispatch loop.
if wave > 1 and bg_timeline is None and \
(wave % 3 == 0 or hours_since(last_timeline_update) > 24):
bg_timeline = invoke ca-timeline-updater IN BACKGROUND with:
- Working directory: /app
- Repo: cleveragents/cleveragents-core
- Session context: compact ledger of completed issues so far,
current active workers, failed issues with retry counts, open PR count
- Current day number
else: # Worker failed — always re-queue, never give up
consecutive = failed.get(issue_number, 0) + 1
failed[issue_number] = consecutive
else: # Worker failed — always re-queue, never give up
consecutive = failed.get(issue.number, 0) + 1
failed[issue.number] = consecutive
if consecutive % 3 == 0:
post comment on Forgejo issue #issue_number:
"Implementation attempt <consecutive> failed.
Retrying with a fresh approach.
# Post diagnostic comment every 3 consecutive failures
if consecutive % 3 == 0:
post comment on Forgejo issue #issue.number:
"Implementation attempt <consecutive> failed.
Error: <completed_worker.error summary>
Retrying with a fresh approach (clearing prior attempt context
to avoid repeating the same mistakes)."
# Reset approach: next attempt starts without prior attempt log
# so the implementer takes a fresh approach instead of
# compounding the same failing strategy.
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: issue-implementor"
queue.append(issue) # always re-queue for retry
queue.append(issue) # always re-queue
# ── Idle polling: discover new work from Forgejo ──────────────
# When queue is empty and no workers are active, poll Forgejo for
# new issues (UAT bugs, human-created issues, etc.) before exiting.
# Clean up the completed session
bash("curl -s -X DELETE ${SERVER}/session/${session_id}", timeout=15000)
del active[issue_number]
# ── Idle polling: discover new work from Forgejo ─────────────
if queue is empty and active is empty:
# Sleep 60 seconds then poll for new work. NEVER exit/break.
# MUST use Bash tool: bash("sleep 60", timeout=120000)
bash("sleep 60", timeout=120000)
new_issues = query Forgejo for new open issues assigned to me
with State/Verified or State/In Progress labels
if new_issues:
queue.extend(new_issues)
re-sort queue by priority
idle_polls = 0 # Reset idle counter — new work found
else:
idle_polls += 1
# DO NOT break or exit. Sleep and poll again.
continue # Loop back to check again
# DO NOT break or exit. Loop back to slot-filling.
idle_polls = 0 # Reset whenever there IS work
# ── IMMEDIATELY loop back to slot-filling ──
# Do NOT wait or pause between iterations. As soon as one worker
# completes, fill all empty slots and continue. Maximum throughput
# means zero idle worker slots.
# ── IMMEDIATELY loop back to slot-filling ────────────────────
# Maximum throughput: zero idle worker slots.
```
### Key dispatch rules
@@ -362,6 +361,20 @@ Discard all other worker output. This compact ledger is what you carry forward
and eventually pass to `ca-final-reporter`. Retaining full worker output will
exhaust context in sessions with many issues — avoid this at all costs.
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Implementation | Agent: issue-implementor
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Coordination Rules
- **One worker per branch.** Never have two workers on the same branch.
+52 -38
View File
@@ -359,54 +359,48 @@ milestones = list of all milestones to complete (ordered)
ref_summary = result from ca-ref-reader
SERVER = "http://localhost:4096"
# ── PHASE C.0: Clean Up Stale Supervisor Sessions ──────────────
# If the product-builder was previously interrupted (Ctrl+C, crash,
# session timeout, etc.), old supervisor sessions launched via
# prompt_async may still be running on the server. They are
# independent sessions that survive the product-builder's death.
# ── PHASE C.0: Resume Existing Supervisor Sessions ──────────────
# Check if there are already-running supervisor sessions from a
# previous product-builder invocation. If found, ADOPT them into
# the monitoring loop instead of launching duplicates.
#
# We MUST find and abort them before launching fresh supervisors,
# otherwise there will be duplicate supervisors competing for the
# same work — duplicate PR reviews, conflicting git pushes, etc.
# This enables "continue where you left off" — if the user restarts
# the product-builder, it reconnects to the existing supervisors
# rather than killing them and starting fresh.
#
# Identification: All supervisor sessions have titles starting with
# "[CA-AUTO] supervisor:". This prefix is unique to product-builder-
# managed supervisors and will not match user-created sessions.
# To start completely fresh (kill all old sessions), run the
# ca-session-cleanup agent BEFORE starting the product-builder.
# Step 1: Query all sessions from the server
ALL_SESSIONS=$(bash("curl -s ${SERVER}/session", timeout=30000))
ALL_SESSIONS = bash("curl -s ${SERVER}/session", timeout=30000)
# Step 2: Extract sessions whose title starts with "[CA-AUTO] supervisor:"
# Use python3 to parse JSON and filter:
STALE_IDS=$(bash("echo '${ALL_SESSIONS}' | python3 -c \"
# Step 2: Find existing supervisor sessions
EXISTING = bash("echo '${ALL_SESSIONS}' | python3 -c \"
import sys, json
sessions = json.loads(sys.stdin.read())
for s in sessions:
if s.get('title', '').startswith('[CA-AUTO] supervisor:'):
print(s['id'])
\"", timeout=30000))
title = s.get('title', '')
if title.startswith('[CA-AUTO] supervisor:'):
# Extract the display name from title
name = title.replace('[CA-AUTO] supervisor: ', '')
print(name + '=' + s['id'])
\"", timeout=30000)
# Step 3: Abort and delete each stale session
stale_count = 0
for session_id in STALE_IDS (one per line):
# Abort if still running
bash("curl -s -X POST ${SERVER}/session/${session_id}/abort", timeout=15000)
# Delete the session entirely to avoid clutter
bash("curl -s -X DELETE ${SERVER}/session/${session_id}", timeout=15000)
stale_count += 1
# Step 3: Check status of each existing session
# Build a map of which supervisors are already running
existing_supervisors = {} # display_name -> session_id
for line in EXISTING (one per line):
name, session_id = line.split("=")
STATUS = bash("curl -s ${SERVER}/session/status", timeout=30000)
if session_id is active/running in STATUS:
existing_supervisors[name] = session_id
# Step 4: Clean up tracking file from previous run
bash("rm -f /tmp/ca-supervisor-sessions.env", timeout=5000)
# Step 5: Log cleanup results
if stale_count > 0:
if existing_supervisors:
invoke ca-session-persister with:
checkpoint: "Phase C.0: Cleaned up <stale_count> stale supervisor
sessions from a previous interrupted run.
All old supervisors aborted and deleted."
else:
# No stale sessions — first run or clean shutdown previously.
pass
checkpoint: "Phase C.0: Found <len(existing_supervisors)> existing
supervisor sessions from a previous run. Adopting them.
Running: <list of names>.
Will launch only the missing supervisors."
# ── PHASE C.1: Planning (if needed) ─────────────────────────────
# If there are milestones with no issues yet, plan them first.
@@ -454,9 +448,15 @@ Pre-flight: Launching 11 supervisors via prompt_async:
11. [ ] timeline-updater (ca-timeline-updater)
# ── Helper function: launch one supervisor ───────────────────────
# For EACH supervisor, run this bash sequence:
# For EACH supervisor, SKIP if already running (adopted in Phase C.0).
function launch_supervisor(agent_name, display_name, prompt_text):
# Check if this supervisor was adopted from a previous run
if display_name in existing_supervisors:
# Already running — record its session ID and skip launch
echo "${display_name}=${existing_supervisors[display_name]}" \
>> /tmp/ca-supervisor-sessions.env
return # Do NOT create a new session
# Step 1: Create a session
SESSION_ID=$(curl -s -X POST "${SERVER}/session" \
-H "Content-Type: application/json" \
@@ -919,6 +919,20 @@ session can resume:
---
## Bot Signature (Required on ALL Forgejo Content)
Every comment, issue body, PR description, and review you post to Forgejo
MUST end with this signature block:
```
---
**Automated by CleverAgents Bot**
Supervisor: Product Builder | Agent: product-builder
```
Append this to the END of every piece of content you create on Forgejo.
No exceptions — every comment, every issue body, every PR description.
## Error Handling
- **Forgejo API unreachable**: Retry indefinitely with exponential backoff