mirror of
https://github.com/allexanderbergmns/xh1-research.git
synced 2026-08-26 22:07:01 +00:00
288 lines
5.7 KiB
Bash
Executable File
288 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================
|
|
# XH-1 Research Harness
|
|
# Autonomous Research / Review / Revision Pipeline
|
|
#
|
|
# Pure Bash
|
|
# OpenAI-compatible APIs
|
|
# Designed for OpenRouter
|
|
# ============================================================
|
|
|
|
ROOT="${XH1_RESEARCH_ROOT:-research}"
|
|
CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}"
|
|
STATE="${ROOT}/.xh1"
|
|
|
|
mkdir -p \
|
|
"$STATE/logs" \
|
|
"$STATE/responses" \
|
|
"$STATE/runs"
|
|
|
|
# ------------------------------------------------------------
|
|
# Defaults
|
|
# ------------------------------------------------------------
|
|
|
|
API_BASE_URL="${API_BASE_URL:-https://openrouter.ai/api/v1}"
|
|
API_KEY_ENV="${API_KEY_ENV:-OPENROUTER_API_KEY}"
|
|
|
|
RESEARCH_MODEL="${RESEARCH_MODEL:-}"
|
|
REVIEW_MODEL="${REVIEW_MODEL:-$RESEARCH_MODEL}"
|
|
REVISION_MODEL="${REVISION_MODEL:-$RESEARCH_MODEL}"
|
|
|
|
MAX_RESEARCH_ROUNDS="${MAX_RESEARCH_ROUNDS:-3}"
|
|
MAX_API_RETRIES="${MAX_API_RETRIES:-3}"
|
|
|
|
DELAY_SECONDS="${DELAY_SECONDS:-5}"
|
|
MAX_ITERATIONS="${MAX_ITERATIONS:-0}"
|
|
|
|
REVIEW_ENABLED="${REVIEW_ENABLED:-true}"
|
|
AUTO_COMMIT="${AUTO_COMMIT:-false}"
|
|
|
|
RUN_ID=""
|
|
|
|
# ------------------------------------------------------------
|
|
# General
|
|
# ------------------------------------------------------------
|
|
|
|
die() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
log() {
|
|
local message="$*"
|
|
|
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $message" \
|
|
| tee -a "$STATE/logs/harness.log"
|
|
}
|
|
|
|
require() {
|
|
command -v "$1" >/dev/null 2>&1 ||
|
|
die "Required command not found: $1"
|
|
}
|
|
|
|
require_dependencies() {
|
|
require bash
|
|
require curl
|
|
require jq
|
|
require find
|
|
require grep
|
|
require sed
|
|
require awk
|
|
}
|
|
|
|
timestamp() {
|
|
date -u '+%Y%m%dT%H%M%SZ'
|
|
}
|
|
|
|
load_config() {
|
|
if [[ -f "$CONFIG" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG"
|
|
fi
|
|
}
|
|
|
|
get_api_key() {
|
|
local key="${!API_KEY_ENV:-}"
|
|
|
|
[[ -n "$key" ]] ||
|
|
die "Missing API key. Set ${API_KEY_ENV}."
|
|
|
|
printf '%s' "$key"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Configuration
|
|
# ------------------------------------------------------------
|
|
|
|
init_config() {
|
|
|
|
if [[ -f "$CONFIG" ]]; then
|
|
echo "Configuration already exists:"
|
|
echo " $CONFIG"
|
|
return
|
|
fi
|
|
|
|
cat > "$CONFIG" <<'CONFIG'
|
|
# ============================================================
|
|
# XH-1 Research Harness Configuration
|
|
# ============================================================
|
|
|
|
# OpenAI-compatible API endpoint.
|
|
# OpenRouter:
|
|
API_BASE_URL="https://openrouter.ai/api/v1"
|
|
|
|
# Environment variable containing the API key.
|
|
API_KEY_ENV="OPENROUTER_API_KEY"
|
|
|
|
# ------------------------------------------------------------
|
|
# Models
|
|
# ------------------------------------------------------------
|
|
|
|
# Model used for initial research.
|
|
RESEARCH_MODEL=""
|
|
|
|
# Model used for independent review.
|
|
REVIEW_MODEL=""
|
|
|
|
# Model used to revise rejected research.
|
|
REVISION_MODEL=""
|
|
|
|
# ------------------------------------------------------------
|
|
# Research behavior
|
|
# ------------------------------------------------------------
|
|
|
|
# Maximum Research -> Review -> Revision cycles for ONE topic.
|
|
MAX_RESEARCH_ROUNDS=3
|
|
|
|
# API retries per individual request.
|
|
MAX_API_RETRIES=3
|
|
|
|
# Delay between topics.
|
|
DELAY_SECONDS=5
|
|
|
|
# 0 = unlimited.
|
|
MAX_ITERATIONS=0
|
|
|
|
# Enable independent reviewer.
|
|
REVIEW_ENABLED=true
|
|
|
|
# Automatically create git commits.
|
|
AUTO_COMMIT=false
|
|
CONFIG
|
|
|
|
echo "Created $CONFIG"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Run state
|
|
# ------------------------------------------------------------
|
|
|
|
start_run() {
|
|
|
|
RUN_ID="$(timestamp)"
|
|
|
|
mkdir -p "$STATE/runs/$RUN_ID"
|
|
|
|
: > "$STATE/runs/$RUN_ID/completed"
|
|
: > "$STATE/runs/$RUN_ID/failed"
|
|
: > "$STATE/runs/$RUN_ID/attempts"
|
|
|
|
log "Started run: $RUN_ID"
|
|
}
|
|
|
|
run_has_completed() {
|
|
local file="$1"
|
|
|
|
grep -Fxq "$file" \
|
|
"$STATE/runs/$RUN_ID/completed" 2>/dev/null
|
|
}
|
|
|
|
run_has_failed() {
|
|
local file="$1"
|
|
|
|
grep -Fxq "$file" \
|
|
"$STATE/runs/$RUN_ID/failed" 2>/dev/null
|
|
}
|
|
|
|
mark_completed() {
|
|
local file="$1"
|
|
|
|
printf '%s\n' "$file" \
|
|
>> "$STATE/runs/$RUN_ID/completed"
|
|
}
|
|
|
|
mark_failed() {
|
|
local file="$1"
|
|
|
|
printf '%s\n' "$file" \
|
|
>> "$STATE/runs/$RUN_ID/failed"
|
|
}
|
|
|
|
record_attempt() {
|
|
|
|
local file="$1"
|
|
local round="$2"
|
|
local phase="$3"
|
|
local result="$4"
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
|
"$file" \
|
|
"$round" \
|
|
"$phase" \
|
|
"$result" \
|
|
>> "$STATE/runs/$RUN_ID/attempts"
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Research discovery
|
|
# ------------------------------------------------------------
|
|
|
|
is_soon() {
|
|
grep -qE '^SOON[[:space:]]*$' "$1"
|
|
}
|
|
|
|
find_next_task() {
|
|
|
|
while IFS= read -r -d '' file; do
|
|
|
|
[[ "$file" == "$STATE"/* ]] && continue
|
|
|
|
if is_soon "$file"; then
|
|
|
|
if ! run_has_completed "$file" &&
|
|
! run_has_failed "$file"
|
|
then
|
|
printf '%s\n' "$file"
|
|
return 0
|
|
fi
|
|
|
|
fi
|
|
|
|
done < <(
|
|
find "$ROOT" \
|
|
-type f \
|
|
-name '*.md' \
|
|
-not -path "$STATE/*" \
|
|
-print0
|
|
)
|
|
|
|
return 1
|
|
}
|
|
|
|
# ------------------------------------------------------------
|
|
# Context
|
|
# ------------------------------------------------------------
|
|
|
|
build_context() {
|
|
|
|
local file="$1"
|
|
|
|
local relative="${file#"$ROOT"/}"
|
|
local area="${relative%%/*}"
|
|
|
|
cat <<EOF
|
|
XH-1 RESEARCH PROJECT
|
|
=====================
|
|
|
|
Project:
|
|
XH-1
|
|
|
|
Architecture:
|
|
Custom 128-core RISC-V processor
|
|
|
|
Research repository:
|
|
XH-1 Research
|
|
|
|
Current document:
|
|
$file
|
|
|
|
Research area:
|
|
$area
|
|
|
|
CURRENT DOCUMENT
|
|
================
|
|
|