diff --git a/.agent/scripts/runner-helper.sh b/.agent/scripts/runner-helper.sh new file mode 100755 index 00000000..d43aec3b --- /dev/null +++ b/.agent/scripts/runner-helper.sh @@ -0,0 +1,853 @@ +#!/usr/bin/env bash +# runner-helper.sh - Named headless AI agent instances with persistent identity +# +# Runners are named, persistent agent instances that can be dispatched headlessly. +# Each runner gets its own AGENTS.md (personality), config, and optional memory namespace. +# +# Usage: +# runner-helper.sh create [--description "desc"] [--model provider/model] [--workdir path] +# runner-helper.sh run "prompt" [--attach URL] [--model provider/model] [--format json] [--timeout N] +# runner-helper.sh status +# runner-helper.sh list [--format json] +# runner-helper.sh edit # Open AGENTS.md in $EDITOR +# runner-helper.sh logs [--tail N] [--follow] +# runner-helper.sh stop # Abort running session +# runner-helper.sh destroy [--force] +# runner-helper.sh help +# +# Directory: ~/.aidevops/.agent-workspace/runners// +# ├── AGENTS.md # Runner personality/instructions +# ├── config.json # Runner configuration (model, workdir, etc.) +# ├── memory.db # Runner-specific memories (optional, via --namespace) +# ├── session.id # Last session ID (for --continue) +# └── runs/ # Run logs +# +# Integration: +# - Memory: memory-helper.sh --namespace +# - Mailbox: mail-helper.sh --to +# - Cron: cron-helper.sh --task "runner-helper.sh run 'prompt'" +# +# Security: +# - Uses HTTPS by default for remote OpenCode servers +# - Supports basic auth via OPENCODE_SERVER_PASSWORD +# - Runner AGENTS.md files are local-only (not committed to repos) + +set -euo pipefail + +# Configuration +readonly RUNNERS_DIR="${AIDEVOPS_RUNNERS_DIR:-$HOME/.aidevops/.agent-workspace/runners}" +readonly MEMORY_HELPER="$HOME/.aidevops/agents/scripts/memory-helper.sh" +# shellcheck disable=SC2034 # MAIL_HELPER reserved for mailbox integration +readonly MAIL_HELPER="$HOME/.aidevops/agents/scripts/mail-helper.sh" +readonly OPENCODE_PORT="${OPENCODE_PORT:-4096}" +readonly OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}" +readonly DEFAULT_MODEL="anthropic/claude-sonnet-4-20250514" +readonly DEFAULT_TIMEOUT=600 + +# Colors +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly BOLD='\033[1m' +readonly NC='\033[0m' + +####################################### +# Logging +####################################### +log_info() { echo -e "${BLUE}[RUNNER]${NC} $*"; } +log_success() { echo -e "${GREEN}[RUNNER]${NC} $*"; } +log_warn() { echo -e "${YELLOW}[RUNNER]${NC} $*"; } +log_error() { echo -e "${RED}[RUNNER]${NC} $*" >&2; } + +####################################### +# Check if jq is available +####################################### +check_jq() { + if ! command -v jq &>/dev/null; then + log_error "jq is required but not installed. Install with: brew install jq" + return 1 + fi + return 0 +} + +####################################### +# Check if opencode is available +####################################### +check_opencode() { + if ! command -v opencode &>/dev/null; then + log_error "opencode is required but not installed. Install from: https://opencode.ai" + return 1 + fi + return 0 +} + +####################################### +# Validate runner name (alphanumeric, hyphens, underscores) +####################################### +validate_name() { + local name="$1" + if [[ ! "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then + log_error "Invalid runner name: '$name' (must start with letter, contain only alphanumeric, hyphens, underscores)" + return 1 + fi + if [[ ${#name} -gt 40 ]]; then + log_error "Runner name too long: '$name' (max 40 characters)" + return 1 + fi + return 0 +} + +####################################### +# Get runner directory +####################################### +runner_dir() { + local name="$1" + echo "$RUNNERS_DIR/$name" +} + +####################################### +# Check if runner exists +####################################### +runner_exists() { + local name="$1" + local dir + dir=$(runner_dir "$name") + [[ -d "$dir" && -f "$dir/config.json" ]] +} + +####################################### +# Get runner config value +####################################### +runner_config() { + local name="$1" + local key="$2" + local dir + dir=$(runner_dir "$name") + jq -r --arg key "$key" '.[$key] // empty' "$dir/config.json" 2>/dev/null +} + +####################################### +# Determine protocol based on host +####################################### +get_protocol() { + local host="$1" + if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "::1" ]]; then + echo "http" + else + echo "https" + fi +} + +####################################### +# Build curl arguments array for secure requests +####################################### +build_curl_args() { + CURL_ARGS=(-sf) + + if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then + local user="${OPENCODE_SERVER_USERNAME:-opencode}" + CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}") + fi + + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + if [[ "$protocol" == "https" && -n "${OPENCODE_INSECURE:-}" ]]; then + CURL_ARGS+=(-k) + fi +} + +####################################### +# Create a new runner +####################################### +cmd_create() { + check_jq || return 1 + + local name="${1:-}" + shift || true + + if [[ -z "$name" ]]; then + log_error "Runner name required" + echo "Usage: runner-helper.sh create [--description \"desc\"] [--model provider/model]" + return 1 + fi + + validate_name "$name" || return 1 + + if runner_exists "$name"; then + log_error "Runner already exists: $name" + echo "Use 'runner-helper.sh edit $name' to modify, or 'runner-helper.sh destroy $name' to recreate." + return 1 + fi + + local description="" model="$DEFAULT_MODEL" workdir="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --description) [[ $# -lt 2 ]] && { log_error "--description requires a value"; return 1; }; description="$2"; shift 2 ;; + --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; + --workdir) [[ $# -lt 2 ]] && { log_error "--workdir requires a value"; return 1; }; workdir="$2"; shift 2 ;; + *) log_error "Unknown option: $1"; return 1 ;; + esac + done + + if [[ -z "$description" ]]; then + description="Runner: $name" + fi + + local dir + dir=$(runner_dir "$name") + mkdir -p "$dir/runs" + + # Create config + local timestamp + timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) + jq -n \ + --arg name "$name" \ + --arg description "$description" \ + --arg model "$model" \ + --arg workdir "${workdir:-}" \ + --arg created "$timestamp" \ + '{ + name: $name, + description: $description, + model: $model, + workdir: $workdir, + created: $created, + lastRun: null, + lastStatus: null, + runCount: 0 + }' > "$dir/config.json" + + # Create default AGENTS.md + cat > "$dir/AGENTS.md" << EOF +# $name + +$description + +## Instructions + +Add your runner-specific instructions here. This file defines the runner's +personality, rules, and output format. + +## Rules + +- Follow the task prompt precisely +- Output structured results when possible +- Report errors clearly with context + +## Output Format + +Respond with clear, actionable output appropriate to the task. +EOF + + log_success "Created runner: $name" + echo "" + echo "Directory: $dir" + echo "Model: $model" + echo "" + echo "Next steps:" + echo " 1. Edit instructions: runner-helper.sh edit $name" + echo " 2. Test run: runner-helper.sh run $name \"your prompt\"" + + return 0 +} + +####################################### +# Run a task on a runner +####################################### +cmd_run() { + check_jq || return 1 + check_opencode || return 1 + + local name="${1:-}" + shift || true + + if [[ -z "$name" ]]; then + log_error "Runner name required" + echo "Usage: runner-helper.sh run \"prompt\" [--attach URL] [--model provider/model]" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + echo "Create it with: runner-helper.sh create $name" + return 1 + fi + + local prompt="${1:-}" + shift || true + + if [[ -z "$prompt" ]]; then + log_error "Prompt required" + echo "Usage: runner-helper.sh run $name \"your prompt here\"" + return 1 + fi + + local attach="" model="" format="" cmd_timeout="$DEFAULT_TIMEOUT" continue_session=false + + while [[ $# -gt 0 ]]; do + case "$1" in + --attach) [[ $# -lt 2 ]] && { log_error "--attach requires a value"; return 1; }; attach="$2"; shift 2 ;; + --model) [[ $# -lt 2 ]] && { log_error "--model requires a value"; return 1; }; model="$2"; shift 2 ;; + --format) [[ $# -lt 2 ]] && { log_error "--format requires a value"; return 1; }; format="$2"; shift 2 ;; + --timeout) [[ $# -lt 2 ]] && { log_error "--timeout requires a value"; return 1; }; cmd_timeout="$2"; shift 2 ;; + --continue|-c) continue_session=true; shift ;; + *) log_error "Unknown option: $1"; return 1 ;; + esac + done + + local dir + dir=$(runner_dir "$name") + + # Resolve model (flag > config > default) + if [[ -z "$model" ]]; then + model=$(runner_config "$name" "model") + if [[ -z "$model" ]]; then + model="$DEFAULT_MODEL" + fi + fi + + # Resolve workdir + local workdir + workdir=$(runner_config "$name" "workdir") + if [[ -z "$workdir" ]]; then + workdir="$(pwd)" + fi + + # Build opencode run command + local -a cmd_args=("opencode" "run") + + # Attach to server if specified + if [[ -n "$attach" ]]; then + cmd_args+=("--attach" "$attach") + fi + + # Model + cmd_args+=("-m" "$model") + + # Session title + cmd_args+=("--title" "runner/$name") + + # Continue previous session if requested + if [[ "$continue_session" == "true" ]]; then + local session_id="" + if [[ -f "$dir/session.id" ]]; then + session_id=$(cat "$dir/session.id") + fi + if [[ -n "$session_id" ]]; then + cmd_args+=("-s" "$session_id") + else + log_warn "No previous session found for $name, starting fresh" + fi + fi + + # Output format + if [[ -n "$format" ]]; then + cmd_args+=("--format" "$format") + fi + + # Build the full prompt with runner context + local agents_md="$dir/AGENTS.md" + local full_prompt + if [[ -f "$agents_md" ]]; then + local instructions + instructions=$(cat "$agents_md") + full_prompt="$instructions + +--- + +## Task + +$prompt" + else + full_prompt="$prompt" + fi + + cmd_args+=("$full_prompt") + + # Log the run + local run_timestamp + run_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) + local run_id + run_id="run-$(date +%s)" + local log_file="$dir/runs/${run_id}.log" + + log_info "Dispatching to runner: $name" + log_info "Model: $model" + log_info "Run ID: $run_id" + + # Execute with timeout + local exit_code=0 + local start_time + start_time=$(date +%s) + + if timeout "$cmd_timeout" "${cmd_args[@]}" 2>&1 | tee "$log_file"; then + exit_code=0 + else + exit_code=$? + fi + + local end_time duration + end_time=$(date +%s) + duration=$((end_time - start_time)) + + # Update config with run metadata + local temp_file + temp_file=$(mktemp) + local status="success" + if [[ $exit_code -ne 0 ]]; then + status="failed" + fi + + jq --arg timestamp "$run_timestamp" \ + --arg status "$status" \ + --argjson duration "$duration" \ + '.lastRun = $timestamp | .lastStatus = $status | .lastDuration = $duration | .runCount += 1' \ + "$dir/config.json" > "$temp_file" + mv "$temp_file" "$dir/config.json" + + if [[ $exit_code -eq 0 ]]; then + log_success "Run complete (${duration}s)" + else + log_error "Run failed after ${duration}s (exit code: $exit_code)" + fi + + return "$exit_code" +} + +####################################### +# Show runner status +####################################### +cmd_status() { + check_jq || return 1 + + local name="${1:-}" + + if [[ -z "$name" ]]; then + log_error "Runner name required" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + return 1 + fi + + local dir + dir=$(runner_dir "$name") + local config="$dir/config.json" + + local description model workdir created last_run last_status run_count last_duration + local config_values + config_values=$(jq -r '[ + .description // "N/A", + .model // "N/A", + .workdir // "N/A", + .created // "N/A", + .lastRun // "never", + .lastStatus // "N/A", + (.runCount // 0 | tostring), + (.lastDuration // "N/A" | tostring) + ] | join("\n")' "$config") + + { + read -r description + read -r model + read -r workdir + read -r created + read -r last_run + read -r last_status + read -r run_count + read -r last_duration + } <<< "$config_values" + + local status_color="$NC" + case "$last_status" in + success) status_color="$GREEN" ;; + failed) status_color="$RED" ;; + esac + + echo -e "${BOLD}Runner: $name${NC}" + echo "──────────────────────────────────" + echo "Description: $description" + echo "Model: $model" + echo "Workdir: $workdir" + echo "Created: $created" + echo "" + echo "Total runs: $run_count" + echo "Last run: $last_run" + echo -e "Last status: ${status_color}${last_status}${NC}" + echo "Last duration: ${last_duration}s" + echo "" + echo "Directory: $dir" + + # Check for session file + if [[ -f "$dir/session.id" ]]; then + echo "Session ID: $(cat "$dir/session.id")" + fi + + # Check for memory namespace + if [[ -x "$MEMORY_HELPER" ]]; then + local mem_count + mem_count=$("$MEMORY_HELPER" stats --namespace "$name" 2>/dev/null | grep -c "Total" || echo "0") + if [[ "$mem_count" -gt 0 ]]; then + echo "Memory entries: $mem_count" + fi + fi + + return 0 +} + +####################################### +# List all runners +####################################### +cmd_list() { + local output_format="table" + + while [[ $# -gt 0 ]]; do + case "$1" in + --format) [[ $# -lt 2 ]] && { log_error "--format requires a value"; return 1; }; output_format="$2"; shift 2 ;; + *) log_error "Unknown option: $1"; return 1 ;; + esac + done + + if [[ ! -d "$RUNNERS_DIR" ]]; then + log_info "No runners configured" + echo "" + echo "Create one with:" + echo " runner-helper.sh create my-runner --description \"What it does\"" + return 0 + fi + + local runners + runners=$(find "$RUNNERS_DIR" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort) + + if [[ -z "$runners" ]]; then + log_info "No runners configured" + return 0 + fi + + if [[ "$output_format" == "json" ]]; then + local -a config_files=() + for runner_path in $runners; do + local config_file="$runner_path/config.json" + if [[ -f "$config_file" ]]; then + config_files+=("$config_file") + fi + done + + if (( ${#config_files[@]} > 0 )); then + jq -s . "${config_files[@]}" + else + echo "[]" + fi + return 0 + fi + + printf "${BOLD}%-20s %-35s %-12s %s${NC}\n" "Name" "Description" "Runs" "Last Status" + printf "%-20s %-35s %-12s %s\n" "──────────────────" "─────────────────────────────────" "──────────" "───────────" + + for runner_path in $runners; do + local rname + rname=$(basename "$runner_path") + local config_file="$runner_path/config.json" + + if [[ ! -f "$config_file" ]]; then + continue + fi + + local description run_count last_status + description=$(jq -r '.description // "N/A"' "$config_file") + run_count=$(jq -r '.runCount // 0' "$config_file") + last_status=$(jq -r '.lastStatus // "N/A"' "$config_file") + + local status_color="$NC" + case "$last_status" in + success) status_color="$GREEN" ;; + failed) status_color="$RED" ;; + esac + + printf "%-20s %-35s %-12s ${status_color}%s${NC}\n" \ + "$rname" "${description:0:35}" "$run_count" "$last_status" + done + + return 0 +} + +####################################### +# Edit runner AGENTS.md +####################################### +cmd_edit() { + local name="${1:-}" + + if [[ -z "$name" ]]; then + log_error "Runner name required" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + return 1 + fi + + local dir + dir=$(runner_dir "$name") + local agents_file="$dir/AGENTS.md" + + local editor="${EDITOR:-vim}" + "$editor" "$agents_file" + + log_success "Updated AGENTS.md for runner: $name" + return 0 +} + +####################################### +# View runner logs +####################################### +cmd_logs() { + local name="${1:-}" + shift || true + + if [[ -z "$name" ]]; then + log_error "Runner name required" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + return 1 + fi + + local tail_lines=50 follow=false + + while [[ $# -gt 0 ]]; do + case "$1" in + --tail) [[ $# -lt 2 ]] && { log_error "--tail requires a value"; return 1; }; tail_lines="$2"; shift 2 ;; + --follow|-f) follow=true; shift ;; + *) log_error "Unknown option: $1"; return 1 ;; + esac + done + + local dir + dir=$(runner_dir "$name") + local runs_dir="$dir/runs" + + if [[ ! -d "$runs_dir" ]]; then + log_info "No run logs found for runner: $name" + return 0 + fi + + local log_files + log_files=$(find "$runs_dir" -name "*.log" -type f 2>/dev/null | sort -r) + + if [[ -z "$log_files" ]]; then + log_info "No run logs found for runner: $name" + return 0 + fi + + if [[ "$follow" == "true" ]]; then + local latest + latest=$(echo "$log_files" | head -1) + log_info "Following latest log: $(basename "$latest")" + tail -f "$latest" + else + local latest + latest=$(echo "$log_files" | head -1) + echo -e "${BOLD}Latest run: $(basename "$latest" .log)${NC}" + tail -n "$tail_lines" "$latest" + fi + + return 0 +} + +####################################### +# Stop a running session (abort) +####################################### +cmd_stop() { + check_jq || return 1 + + local name="${1:-}" + + if [[ -z "$name" ]]; then + log_error "Runner name required" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + return 1 + fi + + local dir + dir=$(runner_dir "$name") + + if [[ ! -f "$dir/session.id" ]]; then + log_warn "No active session found for runner: $name" + return 0 + fi + + local session_id + session_id=$(cat "$dir/session.id") + + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/abort" + + build_curl_args + + if curl "${CURL_ARGS[@]}" -X POST "$url" &>/dev/null; then + log_success "Aborted session for runner: $name" + else + log_warn "Could not abort session (may not be running)" + fi + + return 0 +} + +####################################### +# Destroy a runner +####################################### +cmd_destroy() { + local name="${1:-}" + shift || true + + if [[ -z "$name" ]]; then + log_error "Runner name required" + return 1 + fi + + if ! runner_exists "$name"; then + log_error "Runner not found: $name" + return 1 + fi + + local force=false + + while [[ $# -gt 0 ]]; do + case "$1" in + --force) force=true; shift ;; + *) log_error "Unknown option: $1"; return 1 ;; + esac + done + + if [[ "$force" != "true" ]]; then + echo -n "Destroy runner '$name' and all its data? [y/N] " + read -r confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + log_info "Cancelled" + return 0 + fi + fi + + local dir + dir=$(runner_dir "$name") + rm -rf "$dir" + + log_success "Destroyed runner: $name" + return 0 +} + +####################################### +# Show help +####################################### +cmd_help() { + cat << 'EOF' +runner-helper.sh - Named headless AI agent instances + +USAGE: + runner-helper.sh [options] + +COMMANDS: + create Create a new runner + run "prompt" Dispatch a task to a runner + status Show runner status and metadata + list List all runners + edit Open runner AGENTS.md in $EDITOR + logs View run logs + stop Abort running session + destroy Remove a runner and all its data + help Show this help + +CREATE OPTIONS: + --description "DESC" Runner description + --model PROVIDER/MODEL AI model (default: anthropic/claude-sonnet-4-20250514) + --workdir PATH Default working directory + +RUN OPTIONS: + --attach URL Attach to running OpenCode server (avoids MCP cold boot) + --model PROVIDER/MODEL Override model for this run + --format json Output format (default or json) + --timeout SECONDS Max execution time (default: 600) + --continue, -c Continue previous session + +LIST OPTIONS: + --format json Output as JSON + +LOGS OPTIONS: + --tail N Number of lines (default: 50) + --follow, -f Follow log output + +EXAMPLES: + # Create a code reviewer + runner-helper.sh create code-reviewer \ + --description "Reviews code for security and quality" \ + --model anthropic/claude-sonnet-4-20250514 + + # Run a review task + runner-helper.sh run code-reviewer "Review src/auth/ for vulnerabilities" + + # Run against warm server (faster, no MCP cold boot) + runner-helper.sh run code-reviewer "Review src/auth/" \ + --attach http://localhost:4096 + + # Continue a previous conversation + runner-helper.sh run code-reviewer "Now check the error handling" --continue + + # Edit runner instructions + runner-helper.sh edit code-reviewer + + # View recent logs + runner-helper.sh logs code-reviewer --tail 100 + + # List all runners as JSON + runner-helper.sh list --format json + +DIRECTORY: + Runners: ~/.aidevops/.agent-workspace/runners/ + Each runner: AGENTS.md, config.json, runs/ + +INTEGRATION: + Memory: memory-helper.sh store --namespace --content "..." + Mailbox: mail-helper.sh send --to --type task_dispatch --payload "..." + Cron: cron-helper.sh add --task "runner-helper.sh run 'prompt'" + +REQUIREMENTS: + - opencode (https://opencode.ai) + - jq (brew install jq) + +EOF +} + +####################################### +# Main +####################################### +main() { + local command="${1:-help}" + shift || true + + case "$command" in + create) cmd_create "$@" ;; + run) cmd_run "$@" ;; + status) cmd_status "$@" ;; + list) cmd_list "$@" ;; + edit) cmd_edit "$@" ;; + logs) cmd_logs "$@" ;; + stop) cmd_stop "$@" ;; + destroy) cmd_destroy "$@" ;; + help|--help|-h) cmd_help ;; + *) log_error "Unknown command: $command"; cmd_help; return 1 ;; + esac +} + +main "$@" diff --git a/.agent/subagent-index.toon b/.agent/subagent-index.toon index 3a618e4a..19354e9f 100644 --- a/.agent/subagent-index.toon +++ b/.agent/subagent-index.toon @@ -31,7 +31,7 @@ tools/content/,Content tools - summarization and extraction,summarize tools/social-media/,Social media tools - X/Twitter CLI,bird tools/build-agent/,Agent design - composing efficient agents,build-agent|agent-review tools/build-mcp/,MCP development - creating MCP servers,build-mcp|server-patterns|api-wrapper|transports|deployment -tools/ai-assistants/,AI tool integration - configuring assistants,agno|capsolver|windsurf|claude-code|configuration|opencode-server +tools/ai-assistants/,AI tool integration - configuring assistants and headless dispatch,agno|capsolver|windsurf|claude-code|configuration|opencode-server|headless-dispatch tools/ai-orchestration/,AI orchestration - visual builders and multi-agent,overview|langflow|crewai|autogen|openprose tools/browser/,Browser automation - scraping and testing,agent-browser|stagehand|playwright|playwriter|crawl4ai|watercrawl|pagespeed|peekaboo tools/mobile/,Mobile development - iOS/Android emulators,minisim @@ -77,7 +77,7 @@ bug-fixing,workflows/bug-fixing.md,Bug fix workflow feature-development,workflows/feature-development.md,Feature development workflow --> - diff --git a/.agent/tools/ai-assistants/headless-dispatch.md b/.agent/tools/ai-assistants/headless-dispatch.md new file mode 100644 index 00000000..6af86f94 --- /dev/null +++ b/.agent/tools/ai-assistants/headless-dispatch.md @@ -0,0 +1,496 @@ +--- +description: Headless dispatch patterns for parallel AI agent execution via OpenCode +mode: subagent +tools: + read: true + write: false + edit: false + bash: true + glob: true + grep: true + webfetch: true + task: true +--- + +# Headless Dispatch + + + +## Quick Reference + +- **One-shot dispatch**: `opencode run "prompt"` +- **Warm server dispatch**: `opencode run --attach http://localhost:4096 "prompt"` +- **Server mode**: `opencode serve [--port 4096]` +- **SDK**: `npm install @opencode-ai/sdk` +- **Runner management**: `runner-helper.sh [create|run|status|list|stop|destroy]` +- **Runner directory**: `~/.aidevops/.agent-workspace/runners/` + +**When to use headless dispatch**: + +- Parallel tasks (code review + test gen + docs simultaneously) +- Scheduled/cron-triggered AI work +- CI/CD integration (PR review, code analysis) +- Chat-triggered dispatch (Matrix, Discord, Slack via OpenClaw) +- Background tasks that don't need interactive TUI + +**When NOT to use**: + +- Interactive development (use TUI directly) +- Tasks requiring human-in-the-loop decisions mid-execution +- Single quick questions (just use `opencode run` without server overhead) + + + +## Architecture + +```text + ┌─────────────────────────────────┐ + │ OpenCode Server │ + │ (opencode serve :4096) │ + ├─────────────────────────────────┤ + │ Sessions (isolated contexts) │ + │ ├── runner/code-reviewer │ + │ ├── runner/seo-analyst │ + │ └── runner/test-generator │ + └──────────┬──────────────────────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + opencode run SDK client cron-dispatch + --attach :4096 (TypeScript) (scheduled) +``` + +## Dispatch Methods + +### Method 1: Direct CLI (`opencode run`) + +Simplest approach. Each invocation starts a fresh session (or resumes one). + +```bash +# One-shot task +opencode run "Review src/auth.ts for security issues" + +# With specific model +opencode run -m anthropic/claude-sonnet-4-20250514 "Generate unit tests for src/utils/" + +# With specific agent +opencode run --agent plan "Analyze the database schema" + +# JSON output (for parsing) +opencode run --format json "List all exported functions in src/" + +# Attach files for context +opencode run -f ./schema.sql -f ./migration.ts "Generate types from this schema" + +# Set a session title +opencode run --title "Auth review" "Review the auth middleware" +``` + +### Method 2: Warm Server (`opencode serve` + `--attach`) + +Avoids MCP server cold boot on every dispatch. Recommended for repeated tasks. + +```bash +# Terminal 1: Start persistent server +opencode serve --port 4096 + +# Terminal 2+: Dispatch tasks against it +opencode run --attach http://localhost:4096 "Task 1" +opencode run --attach http://localhost:4096 --agent plan "Review task" +``` + +### Method 3: SDK (TypeScript) + +Full programmatic control. Best for parallel orchestration. + +```typescript +import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk" + +// Start server + client together +const { client, server } = await createOpencode({ + port: 4096, + config: { model: "anthropic/claude-sonnet-4-20250514" }, +}) + +// Or connect to existing server +const client = createOpencodeClient({ + baseUrl: "http://localhost:4096", +}) +``` + +### Method 4: HTTP API (curl) + +Direct API calls for shell scripts and non-JS integrations. + +```bash +SERVER="http://localhost:4096" + +# Create session +SESSION_ID=$(curl -sf -X POST "$SERVER/session" \ + -H "Content-Type: application/json" \ + -d '{"title": "API task"}' | jq -r '.id') + +# Send prompt (sync - waits for response) +curl -sf -X POST "$SERVER/session/$SESSION_ID/message" \ + -H "Content-Type: application/json" \ + -d '{ + "model": {"providerID": "anthropic", "modelID": "claude-sonnet-4-20250514"}, + "parts": [{"type": "text", "text": "Explain this codebase"}] + }' + +# Send prompt (async - returns 204 immediately) +curl -sf -X POST "$SERVER/session/$SESSION_ID/prompt_async" \ + -H "Content-Type: application/json" \ + -d '{"parts": [{"type": "text", "text": "Run tests in background"}]}' + +# Monitor via SSE +curl -N "$SERVER/event" +``` + +## Session Management + +### Resuming Sessions + +```bash +# Continue last session +opencode run -c "Continue where we left off" + +# Resume specific session by ID +opencode run -s ses_abc123 "Add error handling to the auth module" +``` + +### Forking Sessions + +Create a branch from an existing conversation: + +```bash +# Via HTTP API +curl -sf -X POST "http://localhost:4096/session/$SESSION_ID/fork" \ + -H "Content-Type: application/json" \ + -d '{"messageID": "msg-123"}' +``` + +```typescript +// Via SDK - create child session +const child = await client.session.create({ + body: { parentID: parentSession.id, title: "Subtask" }, +}) +``` + +### Context Injection (No Reply) + +Inject context without triggering an AI response: + +```typescript +await client.session.prompt({ + path: { id: sessionId }, + body: { + noReply: true, + parts: [{ + type: "text", + text: "Context: This project uses Express.js with TypeScript.", + }], + }, +}) +``` + +## Parallel Execution + +### CLI Parallel (Background Jobs) + +```bash +# Start server once +opencode serve --port 4096 & + +# Dispatch parallel tasks +opencode run --attach http://localhost:4096 --title "Review" \ + "Review src/auth/ for security issues" & +opencode run --attach http://localhost:4096 --title "Tests" \ + "Generate unit tests for src/utils/" & +opencode run --attach http://localhost:4096 --title "Docs" \ + "Generate API documentation for src/api/" & + +wait # Wait for all to complete +``` + +### SDK Parallel (Promise.all) + +```typescript +const client = createOpencodeClient({ baseUrl: "http://localhost:4096" }) + +// Create parallel sessions +const [review, tests, docs] = await Promise.all([ + client.session.create({ body: { title: "Code Review" } }), + client.session.create({ body: { title: "Test Generation" } }), + client.session.create({ body: { title: "Documentation" } }), +]) + +// Dispatch tasks concurrently +await Promise.all([ + client.session.promptAsync({ + path: { id: review.data.id }, + body: { parts: [{ type: "text", text: "Review src/auth.ts" }] }, + }), + client.session.promptAsync({ + path: { id: tests.data.id }, + body: { parts: [{ type: "text", text: "Generate tests for src/utils/" }] }, + }), + client.session.promptAsync({ + path: { id: docs.data.id }, + body: { parts: [{ type: "text", text: "Generate API docs for src/api/" }] }, + }), +]) + +// Monitor via SSE +const events = await client.event.subscribe() +for await (const event of events.stream) { + if (event.type === "session.status") { + console.log(`Session ${event.properties.id}: ${event.properties.status}`) + } +} +``` + +## Runners + +Runners are named, persistent agent instances with their own identity, instructions, and optionally isolated memory. Managed by `runner-helper.sh`. + +### Directory Structure + +```text +~/.aidevops/.agent-workspace/runners/ +├── code-reviewer/ +│ ├── AGENTS.md # Runner personality/instructions +│ ├── config.json # Runner configuration +│ └── memory.db # Runner-specific memories (optional) +└── seo-analyst/ + ├── AGENTS.md + ├── config.json + └── memory.db +``` + +### Runner Lifecycle + +```bash +# Create a runner +runner-helper.sh create code-reviewer \ + --description "Reviews code for security and quality" \ + --model anthropic/claude-sonnet-4-20250514 + +# Run a task +runner-helper.sh run code-reviewer "Review src/auth/ for vulnerabilities" + +# Run against warm server (faster) +runner-helper.sh run code-reviewer "Review src/auth/" --attach http://localhost:4096 + +# Check status +runner-helper.sh status code-reviewer + +# List all runners +runner-helper.sh list + +# Destroy a runner +runner-helper.sh destroy code-reviewer +``` + +### Custom Runner Instructions + +Each runner gets its own `AGENTS.md` that defines its personality: + +```markdown +# Code Reviewer + +You are a senior code reviewer focused on security and maintainability. + +## Rules + +- Flag any use of eval(), innerHTML, or raw SQL +- Check for proper input validation +- Verify error handling covers edge cases +- Note missing tests for critical paths + +## Output Format + +For each file reviewed, output: +1. Severity (critical/warning/info) +2. Line reference (file:line) +3. Issue description +4. Suggested fix +``` + +### Integration with Memory + +Runners can use isolated or shared memory: + +```bash +# Store a memory for a specific runner +memory-helper.sh store \ + --content "WORKING_SOLUTION: Use parameterized queries for SQL" \ + --tags "security,sql" \ + --namespace "code-reviewer" + +# Recall from runner namespace +memory-helper.sh recall \ + --query "SQL injection" \ + --namespace "code-reviewer" +``` + +### Integration with Mailbox + +Runners communicate via the existing mailbox system: + +```bash +# Coordinator dispatches to runner +mail-helper.sh send \ + --to "code-reviewer" \ + --type "task_dispatch" \ + --payload "Review PR #123 for security issues" + +# Runner reports back +mail-helper.sh send \ + --to "coordinator" \ + --type "status_report" \ + --from "code-reviewer" \ + --payload "Review complete. 2 critical, 5 warnings found." +``` + +## Custom Agents for Dispatch + +OpenCode supports custom agents via markdown files or JSON config. These complement runners by defining tool access and permissions. + +### Markdown Agent (Project-Level) + +Place in `.opencode/agents/security-reviewer.md`: + +```markdown +--- +description: Security-focused code reviewer +mode: subagent +model: anthropic/claude-sonnet-4-20250514 +temperature: 0.1 +tools: + write: false + edit: false + bash: false +permission: + bash: + "git diff*": allow + "git log*": allow + "grep *": allow + "*": deny +--- + +You are a security expert. Identify vulnerabilities, check for OWASP Top 10 +issues, and verify proper input validation and output encoding. +``` + +### JSON Agent (Global Config) + +In `opencode.json`: + +```json +{ + "agent": { + "security-reviewer": { + "description": "Security-focused code reviewer", + "mode": "subagent", + "model": "anthropic/claude-sonnet-4-20250514", + "tools": { "write": false, "edit": false } + } + } +} +``` + +### Using Custom Agents + +```bash +# CLI +opencode run --agent security-reviewer "Audit the auth module" + +# SDK +const result = await client.session.prompt({ + path: { id: session.id }, + body: { + agent: "security-reviewer", + parts: [{ type: "text", text: "Audit the auth module" }], + }, +}) +``` + +## Model Provider Flexibility + +OpenCode supports any provider via `opencode auth login`. Runners inherit the configured provider or override per-runner. + +```bash +# Configure providers +opencode auth login # Interactive provider selection + +# Override model per dispatch +opencode run -m openrouter/anthropic/claude-sonnet-4-20250514 "Task" +opencode run -m groq/llama-4-scout-17b-16e-instruct "Quick task" +``` + +Environment variables for non-interactive setup: + +```bash +# Provider credentials (stored in ~/.local/share/opencode/auth.json) +opencode auth login + +# Or set via environment +export ANTHROPIC_API_KEY="sk-ant-..." +export OPENAI_API_KEY="sk-..." +``` + +## Security + +1. **Network**: Use `--hostname 127.0.0.1` (default) for local-only access +2. **Auth**: Set `OPENCODE_SERVER_PASSWORD` when exposing to network +3. **Permissions**: Use `OPENCODE_PERMISSION` env var for headless autonomy +4. **Credentials**: Never pass secrets in prompts - use environment variables +5. **Cleanup**: Delete sessions after use to prevent data leakage + +### Autonomous Mode (CI/CD) + +```bash +# Grant all permissions (only in trusted environments) +OPENCODE_PERMISSION='{"*":"allow"}' opencode run "Fix the failing tests" +``` + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: AI Code Review +on: + pull_request: + types: [opened, synchronize] + +jobs: + ai-review: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install OpenCode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Run AI Review + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENCODE_PERMISSION: '{"*":"allow"}' + run: | + opencode run --format json \ + "Review the changes in this PR for security and quality. Output as markdown." \ + > review.md +``` + +## Related + +- `tools/ai-assistants/opencode-server.md` - Full server API reference +- `tools/ai-assistants/overview.md` - AI assistant comparison +- `scripts/runner-helper.sh` - Runner management CLI +- `scripts/cron-dispatch.sh` - Cron-triggered dispatch +- `scripts/cron-helper.sh` - Cron job management +- `scripts/coordinator-helper.sh` - Multi-agent coordination +- `scripts/mail-helper.sh` - Inter-agent mailbox +- `memory/README.md` - Memory system (supports namespaces) diff --git a/README.md b/README.md index d7c6fa4b..5a1e0767 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ The result: AI agents that work *with* your development process, not around it. - Primary agents (Build+, SEO, Marketing, etc.) with @plan-plus subagent for planning-only mode - 614+ subagent markdown files organized by domain -- 163 helper scripts in `.agent/scripts/` +- 164 helper scripts in `.agent/scripts/` - 28 slash commands for common workflows @@ -480,7 +480,7 @@ aidevops implements proven agent design patterns identified by [Lance Martin (La | Pattern | Description | aidevops Implementation | |---------|-------------|------------------------| -| **Give Agents a Computer** | Filesystem + shell for persistent context | `~/.aidevops/.agent-workspace/`, 163 helper scripts | +| **Give Agents a Computer** | Filesystem + shell for persistent context | `~/.aidevops/.agent-workspace/`, 164 helper scripts | | **Multi-Layer Action Space** | Few tools, push actions to computer | Per-agent MCP filtering (~12-20 tools each) | | **Progressive Disclosure** | Load context on-demand | Subagent routing with content summaries, YAML frontmatter, read-on-demand | | **Offload Context** | Write results to filesystem | `.agent-workspace/work/[project]/` for persistence | @@ -598,7 +598,7 @@ Cron-based agent dispatch for automated workflows: ```bash # Example: Daily SEO report at 9am -0 9 * * * ~/.aidevops/agents/scripts/droid-helper.sh dispatch "seo-analyst" "Generate daily SEO report" +0 9 * * * ~/.aidevops/agents/scripts/runner-helper.sh run "seo-analyst" "Generate daily SEO report" ``` **See:** [TODO.md](TODO.md) tasks t109-t118 for implementation status. diff --git a/TODO.md b/TODO.md index 1ed652d8..e90b398b 100644 --- a/TODO.md +++ b/TODO.md @@ -211,17 +211,17 @@ Tasks with no open blockers - ready to work on. Use `/ready` to refresh this lis - Notes: Armin Ronacher's blog post on Pi (minimal coding agent by Mario Zechner). Key inspirations to evaluate: 1) Minimal core with only 4 tools (Read, Write, Edit, Bash) - compare to aidevops tool sprawl. 2) Extension system with persistent state across sessions - compare to aidevops memory system. 3) Session trees with branching/rewinding - could improve context management. 4) Hot-reloading extensions - agent can modify and reload its own tools. 5) TUI extensions (spinners, progress bars, file pickers) - enhance CLI experience. 6) /answer command for structured Q&A. 7) /review command with branch-based review context. 8) Skills as agent-generated code, not downloaded packages. 9) No MCP - uses mcporter CLI bridge instead. 10) CDP-based browser skill replacing MCP/Playwright. Evaluate which patterns could simplify or enhance aidevops. - [ ] t104 Add Tirith terminal security guard for homograph/injection attacks #security #tools #terminal ~2h (ai:1.5h test:20m read:10m) logged:2026-02-03 ref:https://github.com/sheeki03/tirith - Notes: Tirith (740 stars, Rust, AGPL-3.0) - terminal security tool that catches attacks browsers block but terminals don't. **30 rules across 7 categories:** 1) Homograph attacks (Cyrillic/Greek lookalikes, punycode, mixed-script). 2) Terminal injection (ANSI escapes, bidi overrides, zero-width chars). 3) Pipe-to-shell (`curl|bash`, `wget|sh`, `eval $(wget ...)`). 4) Dotfile attacks (downloads targeting ~/.bashrc, ~/.ssh/authorized_keys). 5) Insecure transport (HTTP piped to shell, `curl -k`). 6) Ecosystem threats (git clone typosquats, untrusted Docker registries, pip/npm URL installs). 7) Credential exposure (userinfo tricks, shortened URLs). **Integration options:** 1) Add to aidevops setup/onboarding as recommended install. 2) Create tirith.md subagent at tools/security/. 3) Document shell hook setup (`eval "$(tirith init)"`). 4) Consider MCP wrapper for `tirith check` command validation. **Key features:** Sub-millisecond overhead, local-only (no network calls), YAML policy config, bypass with `TIRITH=0` prefix. Install: `brew install sheeki03/tap/tirith` or `npm install -g tirith` or `cargo install tirith`. -- [ ] t109 Parallel Agents & Headless Dispatch #plan → [todo/PLANS.md#2026-02-03-parallel-agents--headless-dispatch] ~3d (ai:1.5d test:1d read:0.5d) logged:2026-02-03 - - [ ] t109.1 Document headless dispatch patterns ~4h blocked-by:none - - Notes: Create tools/ai-assistants/headless-dispatch.md. Document `claude -p` flags, streaming JSON format, session resumption with `--resume`, model provider configuration examples. - - [ ] t109.2 Create droid-helper.sh ~4h blocked-by:none - - Notes: Namespaced agent dispatch with per-droid AGENTS.md. Deterministic session IDs per droid. Integration with existing memory system. Support for parallel execution. +- [ ] t109 Parallel Agents & Headless Dispatch #plan → [todo/PLANS.md#2026-02-03-parallel-agents--headless-dispatch] ~3d (ai:1.5d test:1d read:0.5d) logged:2026-02-03 started:2026-02-05T00:00Z + - [x] t109.1 Document headless dispatch patterns ~4h blocked-by:none completed:2026-02-05 + - Notes: Created tools/ai-assistants/headless-dispatch.md. Documents `opencode run` headless flags, `opencode serve` server mode, `--attach` warm server pattern, SDK parallel dispatch, runner lifecycle, custom agents, CI/CD integration. + - [x] t109.2 Create runner-helper.sh ~4h blocked-by:none completed:2026-02-05 + - Notes: Created runner-helper.sh (create/run/status/list/edit/logs/stop/destroy). Renamed from "droid" to "runner" to avoid Factory.ai naming conflict. Runners are named headless agent instances with per-runner AGENTS.md, config.json, and run logs. Uses `opencode run` for dispatch with `--attach` support for warm server. - [ ] t109.3 Memory namespace integration ~3h blocked-by:t109.2 - - Notes: Extend memory-helper.sh with `--namespace` flag. Per-droid memory isolation (optional). Shared memory access when needed. + - Notes: Extend memory-helper.sh with `--namespace` flag. Per-runner memory isolation (optional). Shared memory access when needed. - [ ] t109.4 Matrix bot integration (optional) ~6h blocked-by:t109.2 - - Notes: Document Matrix bot setup on Cloudron. Create matrix-dispatch-helper.sh. Room-to-droid mapping. Message → claude -p → response flow. + - Notes: Document Matrix bot setup on Cloudron. Create matrix-dispatch-helper.sh. Room-to-runner mapping. Message → opencode run → response flow. - [ ] t109.5 Documentation & examples ~3h blocked-by:t109.1,t109.2,t109.3 - - Notes: Update AGENTS.md with parallel agent guidance. Create example droids (code-reviewer, seo-analyst). Document when to use parallel vs sequential. + - Notes: Update AGENTS.md with parallel agent guidance. Create example runners (code-reviewer, seo-analyst). Document when to use parallel vs sequential. - [x] t110 Cron agent for scheduled task management #tools #automation #agents ~3h actual:1h (ai:2h test:45m read:15m) logged:2026-02-04 started:2026-02-04T03:47Z completed:2026-02-04 - Notes: Implemented cron-agent.md subagent, cron-helper.sh (list/add/remove/pause/resume/logs/debug/status/run), cron-dispatch.sh (OpenCode server API). Security hardened for remote use (HTTPS by default, proper array expansion). PRs #304, #305 merged. - [ ] t111 Objective runner with safety guardrails #tools #automation #agents ~4h (ai:2.5h test:1h read:30m) logged:2026-02-04 diff --git a/todo/PLANS.md b/todo/PLANS.md index 52b7b0bc..4232f214 100644 --- a/todo/PLANS.md +++ b/todo/PLANS.md @@ -1971,19 +1971,21 @@ d038,p015,Use all-MiniLM-L6-v2 via ONNX for embeddings,Small fast no Python requ ### [2026-02-03] Parallel Agents & Headless Dispatch -**Status:** Planning +**Status:** In Progress (Phase 2/5) **Estimate:** ~3d (ai:1.5d test:1d read:0.5d) **Source:** [alexfazio's X post on droids](https://gist.github.com/alexfazio/dcf2f253d346d8ed2702935b57184582) #### Purpose -Document and implement patterns for running parallel Claude Code sessions locally, with optional Matrix chat integration. Inspired by alexfazio's "droids" architecture but adapted for local-first, low-complexity use. +Document and implement patterns for running parallel OpenCode sessions locally, with optional Matrix chat integration. Inspired by alexfazio's "droids" architecture but adapted for local-first, low-complexity use. + +**Naming decision:** Renamed from "droids" to "runners" to avoid conflict with Factory.ai's branded "Droids" product. "Runner" maps to the CI/CD mental model (named execution environments that pick up tasks). -**Key insight from source:** `claude -p "prompt" --output-format stream-json` enables headless dispatch without containers or hosting costs. Each session can have its own AGENTS.md and memory namespace. +**Key insight from source:** `opencode run "prompt"` enables headless dispatch without containers or hosting costs. `opencode run --attach` connects to a warm server for faster dispatch. Each session can have its own AGENTS.md and memory namespace. **What we're NOT doing:** - Fly.io Sprites or cloud hosting (overkill for local use) @@ -1991,11 +1993,11 @@ Document and implement patterns for running parallel Claude Code sessions locall - New orchestration frameworks (extend existing mailbox) **What we ARE doing:** -- Document `claude -p` headless patterns -- Create droid-helper.sh for namespaced agent dispatch -- Integrate with existing memory system (per-agent namespaces) +- Document `opencode run` headless patterns and `opencode serve` server mode +- Create runner-helper.sh for namespaced agent dispatch +- Integrate with existing memory system (per-runner namespaces) - Optional Matrix bot for chat-triggered dispatch -- Document model provider flexibility (any OpenAI-compatible endpoint) +- Document model provider flexibility (any provider via `opencode auth login`) #### Context from Discussion @@ -2014,55 +2016,64 @@ Document and implement patterns for running parallel Claude Code sessions locall ```text ~/.aidevops/.agent-workspace/ -├── droids/ +├── runners/ │ ├── code-reviewer/ -│ │ ├── AGENTS.md # Agent personality/instructions -│ │ └── memory.db # Agent-specific memories (optional) +│ │ ├── AGENTS.md # Runner personality/instructions +│ │ ├── config.json # Runner configuration +│ │ ├── session.id # Last session ID (for --continue) +│ │ └── runs/ # Run logs │ └── seo-analyst/ │ ├── AGENTS.md -│ └── memory.db +│ ├── config.json +│ └── runs/ ``` -**Key patterns from source post:** -1. `claude -p --output-format stream-json` - headless dispatch -2. `--resume $session_id` - deterministic session mapping -3. Self-editing AGENTS.md - agents that improve themselves -4. Chat-triggered dispatch - reduce friction vs terminal +**Key patterns from source post (adapted for OpenCode):** +1. `opencode run "prompt"` - headless dispatch +2. `opencode run --attach http://localhost:4096` - warm server dispatch +3. `opencode run -s $session_id` - session resumption +4. `opencode serve` - persistent server for parallel sessions +5. Self-editing AGENTS.md - agents that improve themselves +6. Chat-triggered dispatch - reduce friction vs terminal **Model provider flexibility:** ```bash -# Any OpenAI-compatible endpoint works -export ANTHROPIC_BASE_URL="https://your-provider/v1" -export ANTHROPIC_API_KEY="your-key" +# Configure via opencode auth login (interactive) +opencode auth login + +# Or override per-dispatch +opencode run -m openrouter/anthropic/claude-sonnet-4-20250514 "task" ``` -Users can choose: local (ollama, llama.cpp), cloud (together.ai, openrouter, groq), or self-hosted. +Users can choose any provider supported by OpenCode via `opencode auth login`. #### Progress -- [ ] (2026-02-03) Phase 1: Document headless dispatch patterns ~4h - - Create `tools/ai-assistants/headless-dispatch.md` - - Document `claude -p` flags and streaming JSON format - - Document session resumption with `--resume` - - Add model provider configuration examples -- [ ] (2026-02-03) Phase 2: Create droid-helper.sh ~4h - - Namespaced agent dispatch with per-droid AGENTS.md - - Deterministic session IDs per droid - - Integration with existing memory system - - Support for parallel execution +- [x] (2026-02-05) Phase 1: Document headless dispatch patterns ~4h + - Created `tools/ai-assistants/headless-dispatch.md` + - Documented `opencode run` flags and `--format json` output + - Documented session resumption with `-s` and `-c` + - Documented `opencode serve` + `--attach` warm server pattern + - Added SDK parallel dispatch examples + - Added CI/CD integration (GitHub Actions) +- [x] (2026-02-05) Phase 2: Create runner-helper.sh ~4h + - Namespaced agent dispatch with per-runner AGENTS.md + - Commands: create, run, status, list, edit, logs, stop, destroy + - Integration with `opencode run --attach` for warm server dispatch + - Run logging and metadata tracking - [ ] (2026-02-03) Phase 3: Memory namespace integration ~3h - Extend memory-helper.sh with `--namespace` flag - - Per-droid memory isolation (optional) + - Per-runner memory isolation (optional) - Shared memory access when needed - [ ] (2026-02-03) Phase 4: Matrix bot integration (optional) ~6h - Document Matrix bot setup on Cloudron - Create matrix-dispatch-helper.sh - - Room-to-droid mapping - - Message → claude -p → response flow + - Room-to-runner mapping + - Message → opencode run → response flow - [ ] (2026-02-03) Phase 5: Documentation & examples ~3h - Update AGENTS.md with parallel agent guidance - - Create example droids (code-reviewer, seo-analyst) + - Create example runners (code-reviewer, seo-analyst) - Document when to use parallel vs sequential