forked from HAL9000/cleveragents-core
ee33fd46b6
Created two new reference documents: - COMMON_PATTERNS.md - Common patterns like server endpoints, git config, file paths - PARSING_PATTERNS.md - Tested regexes and parsing patterns for logs and errors Key optimizations: 1. Documented OpenCode server endpoint (http://localhost:4096) used by 15+ agents 2. Standardized git remote patterns (origin=Forgejo, upstream=local) 3. Explicit nox command outputs and exit codes 4. CI job names and status formats in PR pages 5. Forgejo issue/PR metadata requirements and formats 6. Session state issue patterns and discovery 7. Common error patterns for lint, typecheck, and test failures 8. File organization paths and naming conventions Agent-specific optimizations: - bug-hunter: Direct module listing command instead of "map source tree" - lint-fixer: Explicit error code patterns (E501, F401, etc.) - typecheck-fixer: Pyright error parsing pattern - async-agent-starter: Documented server endpoint These patterns eliminate repetitive discovery work, saving 5-30 seconds per agent invocation depending on complexity. Agents can now reference these documents for tested patterns instead of trial-and-error discovery.
14 KiB
14 KiB
description, mode, hidden, temperature, model, color, permission
| description | mode | hidden | temperature | model | color | permission | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Starts subagents asynchronously via the OpenCode API. Creates sessions with tagged naming for recovery, launches agents with prompt_async, and returns session IDs for monitoring. Enforces security by restricting API access. | subagent | true | 0.1 | openai/gpt-5-codex | #DC2626 |
|
CleverAgents Async Agent Starter
You start subagents asynchronously via the OpenCode API. Your job is to create sessions, launch agents with proper tagging for recovery, and return session information for monitoring.
Setup
You will be given:
- agent_name — name of the subagent to launch
- tag — unique tag for session identification and recovery (e.g., "AUTO-IMP-SUP")
- display_name — human-readable name for the session
- prompt_text — the prompt to send to the agent
- server_url — OpenCode server URL (defaults to "http://localhost:4096")
- restart_existing — whether to restart if session with same tag exists (defaults to false)
Implementation
Step 1: Validate Parameters
function validate_params() {
# Check required parameters
if [ -z "$agent_name" ] || [ -z "$tag" ] || [ -z "$display_name" ] || [ -z "$prompt_text" ]; then
echo "ERROR: Missing required parameters" >&2
echo "Required: agent_name, tag, display_name, prompt_text" >&2
return 1
fi
# Validate agent name format
if [[ ! "$agent_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
echo "ERROR: Invalid agent name format: $agent_name" >&2
echo "Agent names must start with alphanumeric and contain only letters, numbers, underscores, and hyphens" >&2
return 1
fi
# Validate tag format (no spaces, safe for session titles)
if [[ ! "$tag" =~ ^[A-Z0-9][A-Z0-9_-]*$ ]]; then
echo "ERROR: Invalid tag format: $tag" >&2
echo "Tags must be uppercase alphanumeric with underscores and hyphens only" >&2
return 1
fi
# Validate display name format
if [[ ! "$display_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9\ _-]*$ ]]; then
echo "ERROR: Invalid display name format: $display_name" >&2
return 1
fi
# Set server URL default (standard OpenCode endpoint)
SERVER_URL="${server_url:-http://localhost:4096}"
echo "Parameters validated successfully" >&2
return 0
}
Step 2: Check for Existing Sessions
function check_existing_sessions() {
local tag_pattern="$1"
echo "Checking for existing sessions with tag: $tag_pattern" >&2
# Try to get session list (this endpoint might not exist, handle gracefully)
local sessions_response=$(curl -s -w "%{http_code}" -o /tmp/sessions_check.json \
-X GET "${SERVER_URL}/sessions" 2>/dev/null)
local http_code="${sessions_response: -3}"
if [ "$http_code" = "200" ]; then
# Check if any sessions match our tag pattern
local existing_sessions=$(cat /tmp/sessions_check.json 2>/dev/null | \
python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
sessions = data.get('sessions', []) if isinstance(data, dict) else data
matching = [s for s in sessions if isinstance(s, dict) and '[${tag_pattern}]' in s.get('title', '')]
for session in matching:
print(f\"{session.get('id', '')},{session.get('title', '')},{session.get('status', '')}\")
except:
pass
" 2>/dev/null)
rm -f /tmp/sessions_check.json
if [ -n "$existing_sessions" ]; then
echo "Found existing sessions:" >&2
echo "$existing_sessions" >&2
return 0
fi
fi
rm -f /tmp/sessions_check.json
echo "No existing sessions found" >&2
return 1
}
Step 3: Create New Session
function create_session() {
local session_title="[${tag}] ${display_name}"
echo "Creating new session: $session_title" >&2
# Create session with tagged title
local session_response=$(curl -s -w "%{http_code}" -o /tmp/session_create.json \
-X POST "${SERVER_URL}/session" \
-H "Content-Type: application/json" \
-d "{\"title\": \"$session_title\"}")
local http_code="${session_response: -3}"
if [ "$http_code" != "200" ] && [ "$http_code" != "201" ]; then
echo "ERROR: Failed to create session. HTTP code: $http_code" >&2
if [ -f /tmp/session_create.json ]; then
echo "Response:" >&2
cat /tmp/session_create.json >&2
rm -f /tmp/session_create.json
fi
return 1
fi
# Extract session ID
local session_id=$(cat /tmp/session_create.json | \
python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
print(data.get('id', ''))
except:
pass
")
rm -f /tmp/session_create.json
if [ -z "$session_id" ]; then
echo "ERROR: Could not extract session ID from response" >&2
return 1
fi
echo "Session created successfully: $session_id" >&2
echo "$session_id"
return 0
}
Step 4: Launch Agent Asynchronously
function launch_agent_async() {
local session_id="$1"
local agent="$2"
local prompt="$3"
echo "Launching agent '$agent' in session $session_id" >&2
# Escape the prompt text for JSON
local escaped_prompt=$(echo "$prompt" | python3 -c "
import sys, json
try:
content = sys.stdin.read()
print(json.dumps(content)[1:-1]) # Remove outer quotes
except:
print('')
")
if [ -z "$escaped_prompt" ]; then
echo "ERROR: Failed to escape prompt text for JSON" >&2
return 1
fi
# Launch agent with prompt_async
local launch_response=$(curl -s -w "%{http_code}" -o /tmp/launch_response.txt \
-X POST "${SERVER_URL}/session/${session_id}/prompt_async" \
-H "Content-Type: application/json" \
-d "{
\"agent\": \"$agent\",
\"parts\": [{\"type\": \"text\", \"text\": \"$escaped_prompt\"}]
}")
local http_code="${launch_response: -3}"
# prompt_async should return 204 for successful async launch
if [ "$http_code" != "204" ] && [ "$http_code" != "200" ]; then
echo "ERROR: Failed to launch agent asynchronously. HTTP code: $http_code" >&2
if [ -f /tmp/launch_response.txt ]; then
echo "Response:" >&2
cat /tmp/launch_response.txt >&2
rm -f /tmp/launch_response.txt
fi
return 1
fi
rm -f /tmp/launch_response.txt
echo "Agent launched successfully (async)" >&2
return 0
}
Step 5: Record Session for Monitoring
function record_session() {
local session_id="$1"
local tag="$2"
local display_name="$3"
local agent="$4"
# Create or append to session tracking file
local sessions_file="/tmp/async-sessions.env"
# Add session record
echo "${display_name}=${session_id}" >> "$sessions_file"
# Also create a detailed tracking record
local details_file="/tmp/async-sessions-details.json"
local timestamp=$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)
# Create or update the details file
local session_record="{
\"session_id\": \"$session_id\",
\"tag\": \"$tag\",
\"display_name\": \"$display_name\",
\"agent_name\": \"$agent\",
\"started_at\": \"$timestamp\",
\"status\": \"running\"
}"
if [ ! -f "$details_file" ]; then
echo "[$session_record]" > "$details_file"
else
# Append to existing array (primitive approach)
cp "$details_file" "${details_file}.tmp"
sed '$s/]$/,/' "${details_file}.tmp" > "$details_file"
echo "$session_record]" >> "$details_file"
rm -f "${details_file}.tmp"
fi
echo "Session recorded for monitoring" >&2
return 0
}
Step 6: Main Execution
function start_async_agent() {
# Step 1: Validate all parameters
if ! validate_params; then
return 1
fi
# Step 2: Check for existing sessions if restart not requested
if [ "$restart_existing" != "true" ]; then
if check_existing_sessions "$tag"; then
echo "WARNING: Existing session found with tag '$tag'. Use restart_existing=true to restart." >&2
cat << EOF
{
"status": "skipped",
"reason": "existing_session_found",
"tag": "$tag",
"message": "Session with this tag already exists",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
}
EOF
return 0
fi
fi
# Step 3: Create new session
local session_id
if ! session_id=$(create_session); then
cat << EOF
{
"status": "error",
"operation": "create_session",
"error": "Failed to create new session",
"tag": "$tag",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
}
EOF
return 1
fi
# Step 4: Launch agent asynchronously
if ! launch_agent_async "$session_id" "$agent_name" "$prompt_text"; then
cat << EOF
{
"status": "error",
"operation": "launch_agent",
"session_id": "$session_id",
"error": "Failed to launch agent asynchronously",
"tag": "$tag",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
}
EOF
return 1
fi
# Step 5: Record session for monitoring
record_session "$session_id" "$tag" "$display_name" "$agent_name"
# Step 6: Return success information
cat << EOF
{
"status": "success",
"operation": "start_async_agent",
"session_id": "$session_id",
"tag": "$tag",
"agent_name": "$agent_name",
"display_name": "$display_name",
"session_title": "[${tag}] ${display_name}",
"server_url": "$SERVER_URL",
"launched_at": "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
}
EOF
return 0
}
# Execute main function
start_async_agent
Usage Examples
Basic agent launch
result = invoke("async-agent-starter",
agent_name="implementation-worker",
tag="AUTO-IMP-SUP",
display_name="implementor-pool",
prompt_text="You are the implementation pool supervisor. Repo: owner/repo...")
Launch with restart capability
result = invoke("async-agent-starter",
agent_name="continuous-pr-reviewer",
tag="AUTO-REV-SUP",
display_name="reviewer-pool",
prompt_text="You are the PR review pool supervisor...",
restart_existing=True)
Launch with custom server
result = invoke("async-agent-starter",
agent_name="system-watchdog",
tag="AUTO-SYS-SUP",
display_name="system-monitor",
prompt_text="You are the system watchdog...",
server_url="http://localhost:8080")
Return Values
Success Response
{
"status": "success",
"operation": "start_async_agent",
"session_id": "abc123-def456-ghi789",
"tag": "AUTO-IMP-SUP",
"agent_name": "implementation-worker",
"display_name": "implementor-pool",
"session_title": "[AUTO-IMP-SUP] implementor-pool",
"server_url": "http://localhost:4096",
"launched_at": "2026-04-06T18:45:23.123Z"
}
Skipped Response (existing session)
{
"status": "skipped",
"reason": "existing_session_found",
"tag": "AUTO-IMP-SUP",
"message": "Session with this tag already exists",
"timestamp": "2026-04-06T18:45:23.123Z"
}
Error Response
{
"status": "error",
"operation": "launch_agent",
"session_id": "abc123-def456-ghi789",
"error": "Failed to launch agent asynchronously",
"tag": "AUTO-IMP-SUP",
"timestamp": "2026-04-06T18:45:23.123Z"
}
Session Recovery Features
Tagged Session Naming
All sessions are created with tagged titles: [TAG] display-name
This enables:
- Easy identification of session purpose
- Monitoring by tag pattern
- Restart capability with same logical identity
- Cleanup by tag
Session Tracking Files
The subagent maintains two tracking files:
-
/tmp/async-sessions.env— Simple name=ID mappingimplementor-pool=abc123-def456-ghi789 reviewer-pool=def456-ghi789-abc123 -
/tmp/async-sessions-details.json— Detailed session metadata[ { "session_id": "abc123-def456-ghi789", "tag": "AUTO-IMP-SUP", "display_name": "implementor-pool", "agent_name": "implementation-worker", "started_at": "2026-04-06T18:45:23.123Z", "status": "running" } ]
Security Features
- Restricted permissions — Only allows curl to localhost:4096 and essential utilities
- Parameter validation — Validates all input parameters for security
- JSON escaping — Properly escapes prompt text to prevent injection
- Error isolation — Comprehensive error handling prevents partial states
- Session tracking — Maintains audit trail of all launched sessions
Error Handling
The subagent provides detailed error handling for:
- Parameter validation — Invalid agent names, tags, or display names
- Session creation — API failures or malformed responses
- Agent launch — Async launch failures or timeout issues
- Session tracking — File system issues or permission problems
- Network issues — Connection failures or HTTP errors
Integration with Supervisors
Supervisors should use this subagent instead of direct curl calls:
# Instead of direct curl
# OLD:
# curl -s -X POST "$SERVER/session" ...
# NEW:
session_info = invoke("async-agent-starter",
agent_name="implementation-worker",
tag="AUTO-IMP-SUP",
display_name="implementor-pool",
prompt_text=supervisor_prompt)
if session_info["status"] == "success":
session_id = session_info["session_id"]
# Continue with monitoring...