From fbc47bd4f4ead8c058d61ec5d72fe57e57e2425f Mon Sep 17 00:00:00 2001 From: riscvcxh1 Date: Tue, 25 Aug 2026 19:52:29 +0200 Subject: [PATCH] A Harnass --- xh1-research | 1017 +++++++++++++++++++++++++++++++++++++++++++++ xh1-research.conf | 37 ++ 2 files changed, 1054 insertions(+) create mode 100755 xh1-research create mode 100644 xh1-research.conf diff --git a/xh1-research b/xh1-research new file mode 100755 index 0000000..5fcc263 --- /dev/null +++ b/xh1-research @@ -0,0 +1,1017 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================ +# XH-1 Research Harness +# Pure Bash / OpenAI-compatible API +# ============================================================ + +ROOT="${XH1_RESEARCH_ROOT:-research}" +STATE="${ROOT}/.xh1" +CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}" + +mkdir -p "$STATE"/{logs,responses} + +# ------------------------------------------------------------ +# Defaults +# ------------------------------------------------------------ + +API_BASE_URL="${API_BASE_URL:-https://api.openai.com/v1}" +API_KEY_ENV="${API_KEY_ENV:-OPENAI_API_KEY}" +MODEL="${MODEL:-}" + +MAX_RETRIES="${MAX_RETRIES:-3}" +DELAY_SECONDS="${DELAY_SECONDS:-5}" +MAX_ITERATIONS="${MAX_ITERATIONS:-0}" + +REVIEW_ENABLED="${REVIEW_ENABLED:-true}" +AUTO_COMMIT="${AUTO_COMMIT:-false}" + +# ------------------------------------------------------------ +# Helpers +# ------------------------------------------------------------ + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +log() { + local msg="$*" + echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $msg" \ + | tee -a "$STATE/logs/harness.log" +} + +require() { + command -v "$1" >/dev/null 2>&1 || + die "Required command not found: $1" +} + +load_config() { + if [[ -f "$CONFIG" ]]; then + # shellcheck disable=SC1090 + source "$CONFIG" + fi +} + +require_dependencies() { + require bash + require curl + require jq +} + +get_api_key() { + local key="${!API_KEY_ENV:-}" + + [[ -n "$key" ]] || + die "API key missing. Set ${API_KEY_ENV}." + + printf '%s' "$key" +} + +timestamp() { + date -u '+%Y%m%dT%H%M%SZ' +} + +# ------------------------------------------------------------ +# Configuration +# ------------------------------------------------------------ + +init_config() { + + if [[ -f "$CONFIG" ]]; then + echo "Configuration already exists:" + echo " $CONFIG" + return + fi + + cat > "$CONFIG" <<'EOF' +# ============================================================ +# XH-1 Research Harness Configuration +# ============================================================ + +# OpenAI-compatible API +API_BASE_URL="https://api.openai.com/v1" + +# Environment variable containing API key +API_KEY_ENV="OPENAI_API_KEY" + +# Model name +MODEL="" + +# Research behavior +MAX_RETRIES=3 +DELAY_SECONDS=5 +MAX_ITERATIONS=0 + +# Run reviewer after researcher +REVIEW_ENABLED=true + +# Automatically create git commits +AUTO_COMMIT=false +EOF + + echo "Created $CONFIG" +} + +# ------------------------------------------------------------ +# API +# ------------------------------------------------------------ + +api_call() { + + local system_prompt="$1" + local user_prompt="$2" + local output="$3" + + local key + key="$(get_api_key)" + + [[ -n "$MODEL" ]] || + die "MODEL is not configured." + + local payload + + payload="$( + jq -n \ + --arg model "$MODEL" \ + --arg system "$system_prompt" \ + --arg user "$user_prompt" \ + '{ + model: $model, + messages: [ + { + role: "system", + content: $system + }, + { + role: "user", + content: $user + } + ], + temperature: 0.2 + }' + )" + + local attempt=1 + + while (( attempt <= MAX_RETRIES )); do + + if curl \ + --fail \ + --silent \ + --show-error \ + --connect-timeout 30 \ + --max-time 1800 \ + -H "Authorization: Bearer ${key}" \ + -H "Content-Type: application/json" \ + "${API_BASE_URL%/}/chat/completions" \ + -d "$payload" \ + > "${output}.json" + then + + if jq -er \ + '.choices[0].message.content' \ + "${output}.json" \ + > "$output" + then + return 0 + fi + fi + + log "API attempt $attempt failed." + + sleep $((attempt * attempt)) + + ((attempt++)) + done + + return 1 +} + +# ------------------------------------------------------------ +# Research discovery +# ------------------------------------------------------------ + +is_research_file() { + + [[ "$1" == *.md ]] && + [[ "$1" != "$STATE"/* ]] +} + +is_soon() { + + grep -qE \ + '^SOON[[:space:]]*$' \ + "$1" +} + +find_next_task() { + + while IFS= read -r -d '' file; do + + if is_research_file "$file" && + is_soon "$file" + then + printf '%s\n' "$file" + return 0 + fi + + done < <( + find "$ROOT" \ + -type f \ + -name '*.md' \ + -not -path "$STATE/*" \ + -print0 + ) + + return 1 +} + +# ------------------------------------------------------------ +# Research context +# ------------------------------------------------------------ + +build_context() { + + local file="$1" + + local relative + relative="${file#"$ROOT"/}" + + local area + area="${relative%%/*}" + + cat < "$temporary" + + mv "$temporary" "$target" +} + +# ------------------------------------------------------------ +# Git +# ------------------------------------------------------------ + +git_commit() { + + local file="$1" + + [[ "$AUTO_COMMIT" == "true" ]] || + return 0 + + git rev-parse \ + --is-inside-work-tree \ + >/dev/null 2>&1 || + return 0 + + git add "$file" + + local title + + title="$( + basename "$file" .md | + tr '-' ' ' + )" + + git commit \ + -m "research: ${title}" \ + || true +} + +# ------------------------------------------------------------ +# Research one document +# ------------------------------------------------------------ + +run_one() { + + local file="${1:-}" + + if [[ -z "$file" ]]; then + file="$(find_next_task || true)" + fi + + [[ -n "$file" ]] || + { + echo "No incomplete research topics." + return 0 + } + + [[ -f "$file" ]] || + die "Research file does not exist: $file" + + log "Starting research: $file" + + local id + id="$(timestamp)_$(basename "$file" .md)" + + local response + response="$STATE/responses/${id}.md" + + log "Running researcher." + + if ! api_call \ + "$(research_system_prompt)" \ + "$(research_user_prompt "$file")" \ + "$response" + then + + log "Research agent failed." + + return 1 + fi + + log "Research completed." + + # -------------------------------------------------------- + # Review + # -------------------------------------------------------- + + if [[ "$REVIEW_ENABLED" == "true" ]]; then + + local review + review="${response}.review" + + log "Running reviewer." + + if ! review_document \ + "$response" \ + "$review" + then + + log "Reviewer failed." + + return 1 + fi + + local verdict + + verdict="$( + grep -m1 '^VERDICT:' "$review" | + tr -d '\r' + )" + + echo + echo "==========================================" + echo "RESEARCH REVIEW" + echo "==========================================" + cat "$review" + echo "==========================================" + echo + + if [[ "$verdict" != "VERDICT: PASS" ]]; then + + log "Research rejected by reviewer." + + return 2 + fi + + log "Research passed review." + + fi + + # -------------------------------------------------------- + # Replace SOON + # -------------------------------------------------------- + + replace_soon "$file" "$response" + + printf '%s\t%s\t%s\n' \ + "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \ + "completed" \ + "$file" \ + >> "$STATE/state.tsv" + + git_commit "$file" + + log "Completed: $file" + + return 0 +} + +# ------------------------------------------------------------ +# Loop +# ------------------------------------------------------------ + +run_loop() { + + local iteration=0 + + while true; do + + if (( MAX_ITERATIONS > 0 && + iteration >= MAX_ITERATIONS )); then + + log "Maximum iterations reached." + + break + fi + + local file + + file="$(find_next_task || true)" + + if [[ -z "$file" ]]; then + + log "No SOON documents remain." + + break + fi + + echo + echo "==========================================" + echo "XH-1 RESEARCH ITERATION $((iteration + 1))" + echo "==========================================" + echo + echo "Topic:" + echo " $file" + echo + + if run_one "$file"; then + + log "Iteration successful." + + else + + log "Iteration failed." + + echo + echo "Research failed." + echo "The document was NOT modified." + echo + + fi + + ((iteration += 1)) + + sleep "$DELAY_SECONDS" + + done +} + +# ------------------------------------------------------------ +# Status +# ------------------------------------------------------------ + +status() { + + [[ -d "$ROOT" ]] || + die "Research directory not found: $ROOT" + + local total + local soon + local researched + + total="$( + find "$ROOT" \ + -type f \ + -name '*.md' \ + -not -path "$STATE/*" | + wc -l | + tr -d ' ' + )" + + soon="$( + grep -RIl \ + '^SOON[[:space:]]*$' \ + "$ROOT" \ + --include='*.md' \ + --exclude-dir='.xh1' \ + 2>/dev/null | + wc -l | + tr -d ' ' + )" + + researched=$((total - soon)) + + echo + echo "╔══════════════════════════════════════╗" + echo "║ XH-1 RESEARCH STATUS ║" + echo "╚══════════════════════════════════════╝" + echo + + printf " Total documents : %s\n" "$total" + printf " Research ready : %s\n" "$researched" + printf " Remaining SOON : %s\n" "$soon" + + if (( total > 0 )); then + + awk \ + -v done="$researched" \ + -v total="$total" \ + 'BEGIN { + printf " Progress : %.1f%%\n", + (done / total) * 100 + }' + + fi + + echo + + echo "Next topic:" + + find_next_task || echo " None" + + echo +} + +# ------------------------------------------------------------ +# Review command +# ------------------------------------------------------------ + +review_command() { + + local file="${1:-}" + + [[ -n "$file" ]] || + die "Usage: $0 review " + + [[ -f "$file" ]] || + die "File does not exist: $file" + + local output + + output="$STATE/responses/manual_review_$(timestamp).txt" + + review_document "$file" "$output" + + cat "$output" +} + +# ------------------------------------------------------------ +# Initialization +# ------------------------------------------------------------ + +initialize() { + + mkdir -p \ + "$STATE/logs" \ + "$STATE/responses" + + touch \ + "$STATE/state.tsv" + + init_config + + echo + echo "XH-1 research harness initialized." + echo + echo "Configuration:" + echo " $CONFIG" + echo + echo "State:" + echo " $STATE" + echo +} + +# ------------------------------------------------------------ +# Help +# ------------------------------------------------------------ + +help() { + +cat <<'EOF' + +XH-1 RESEARCH HARNESS +===================== + +Pure Bash autonomous research system. + +USAGE + + ./xh1-research + + +COMMANDS + + init + + Initialize the harness. + + status + + Show research progress. + + next + + Show the next SOON document. + + run + + Research the next topic. + + run + + Research a specific document. + + run-all + + Continuously research every SOON document. + + loop + + Alias for run-all. + + resume + + Resume autonomous research. + + review + + Manually review a research document. + + help + + Show this help. + + +EXAMPLES + + ./xh1-research init + + ./xh1-research status + + ./xh1-research next + + ./xh1-research run + + ./xh1-research run research/04-128-core/topology.md + + ./xh1-research run-all + + ./xh1-research review research/05-memory/cache-coherency.md + + +ENVIRONMENT + + XH1_RESEARCH_ROOT + + Research directory. + + Default: + research + + + XH1_RESEARCH_CONFIG + + Configuration file. + + Default: + xh1-research.conf + + +CONFIGURATION + + API_BASE_URL + + OpenAI-compatible API base. + + API_KEY_ENV + + Environment variable containing API key. + + MODEL + + Model identifier. + + MAX_RETRIES + + API retry count. + + DELAY_SECONDS + + Delay between research iterations. + + MAX_ITERATIONS + + Maximum autonomous iterations. + + 0 = unlimited. + + REVIEW_ENABLED + + Enable reviewer agent. + + AUTO_COMMIT + + Automatically commit completed research. + + +SAFETY + + Existing research is never overwritten. + + A document is only modified if it still contains + a standalone SOON marker. + + Git push is never performed automatically. + + +EOF +} + +# ------------------------------------------------------------ +# Main +# ------------------------------------------------------------ + +load_config +require_dependencies + +COMMAND="${1:-status}" + +case "$COMMAND" in + + init) + initialize + ;; + + status) + status + ;; + + next) + find_next_task || true + ;; + + run) + run_one "${2:-}" + ;; + + run-all|loop|resume) + run_loop + ;; + + review) + review_command "${2:-}" + ;; + + help|-h|--help) + help + ;; + + *) + die "Unknown command: $COMMAND. Use --help." + +esac diff --git a/xh1-research.conf b/xh1-research.conf new file mode 100644 index 0000000..6259d44 --- /dev/null +++ b/xh1-research.conf @@ -0,0 +1,37 @@ +# ============================================================ +# XH-1 Research Harness +# ============================================================ + +# Any provider exposing: +# POST /v1/chat/completions +# +# Examples: +# +# OpenAI: +# https://api.openai.com/v1 +# +# Other OpenAI-compatible providers: +# https://example.com/v1 + +API_BASE_URL="https://api.openai.com/v1" + +# Environment variable containing your API key. +API_KEY_ENV="OPENAI_API_KEY" + +# Model to use. +MODEL="YOUR_MODEL_HERE" + +# Retry failed API calls. +MAX_RETRIES=3 + +# Delay between research tasks. +DELAY_SECONDS=5 + +# 0 = unlimited. +MAX_ITERATIONS=0 + +# Have a second agent review the research. +REVIEW_ENABLED=true + +# Automatically create commits. +AUTO_COMMIT=false