mirror of
https://github.com/allexanderbergmns/xh1-research.git
synced 2026-08-26 22:07:01 +00:00
1348 lines
25 KiB
Bash
Executable File
1348 lines
25 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================
|
|
# XH-1 RESEARCH HARNESS
|
|
#
|
|
# Multi-provider / multi-model autonomous research system.
|
|
#
|
|
# Supports any OpenAI-compatible Chat Completions API:
|
|
#
|
|
# OpenRouter
|
|
# Ollama
|
|
# LM Studio
|
|
# vLLM
|
|
# LocalAI
|
|
# OpenAI
|
|
# Other compatible providers
|
|
#
|
|
# Pipeline:
|
|
#
|
|
# RESEARCH
|
|
# ↓
|
|
# REVIEW
|
|
# ↓
|
|
# PASS ───────────────→ COMMIT
|
|
# │
|
|
# FAIL
|
|
# ↓
|
|
# REVISION
|
|
# ↓
|
|
# REVIEW
|
|
# ↓
|
|
# ...
|
|
#
|
|
# ============================================================
|
|
|
|
ROOT="${XH1_RESEARCH_ROOT:-research}"
|
|
CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}"
|
|
STATE="${ROOT}/.xh1"
|
|
|
|
mkdir -p \
|
|
"$STATE/logs" \
|
|
"$STATE/runs" \
|
|
"$STATE/responses"
|
|
|
|
# ============================================================
|
|
# Defaults
|
|
# ============================================================
|
|
|
|
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=""
|
|
|
|
# ============================================================
|
|
# Helpers
|
|
# ============================================================
|
|
|
|
die() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
log() {
|
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" |
|
|
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
|
|
}
|
|
|
|
# ============================================================
|
|
# Provider abstraction
|
|
# ============================================================
|
|
|
|
#
|
|
# Provider variables are defined in xh1-research.conf.
|
|
#
|
|
# Example:
|
|
#
|
|
# PROVIDER_openrouter_URL="https://openrouter.ai/api/v1"
|
|
# PROVIDER_openrouter_KEY_ENV="OPENROUTER_API_KEY"
|
|
#
|
|
# PROVIDER_ollama_URL="http://127.0.0.1:11434/v1"
|
|
# PROVIDER_ollama_KEY_ENV=""
|
|
#
|
|
# PROVIDER_openai_URL="https://api.openai.com/v1"
|
|
# PROVIDER_openai_KEY_ENV="OPENAI_API_KEY"
|
|
#
|
|
|
|
provider_url() {
|
|
local provider="$1"
|
|
|
|
local variable="PROVIDER_${provider}_URL"
|
|
|
|
printf '%s' "${!variable:-}"
|
|
}
|
|
|
|
provider_key_env() {
|
|
local provider="$1"
|
|
|
|
local variable="PROVIDER_${provider}_KEY_ENV"
|
|
|
|
printf '%s' "${!variable:-}"
|
|
}
|
|
|
|
provider_api_key() {
|
|
local provider="$1"
|
|
|
|
local key_env
|
|
key_env="$(provider_key_env "$provider")"
|
|
|
|
# Local providers such as Ollama normally require no key.
|
|
if [[ -z "$key_env" ]]; then
|
|
printf ''
|
|
return 0
|
|
fi
|
|
|
|
local key="${!key_env:-}"
|
|
|
|
[[ -n "$key" ]] ||
|
|
die "Provider '$provider' requires environment variable '$key_env'."
|
|
|
|
printf '%s' "$key"
|
|
}
|
|
|
|
provider_check() {
|
|
local provider="$1"
|
|
|
|
local url
|
|
url="$(provider_url "$provider")"
|
|
|
|
[[ -n "$url" ]] ||
|
|
die "Provider '$provider' has no URL."
|
|
|
|
local key
|
|
key="$(provider_api_key "$provider")"
|
|
|
|
local -a headers
|
|
|
|
headers=(
|
|
-H "Content-Type: application/json"
|
|
)
|
|
|
|
if [[ -n "$key" ]]; then
|
|
headers+=(
|
|
-H "Authorization: Bearer $key"
|
|
)
|
|
fi
|
|
|
|
curl \
|
|
--fail \
|
|
--silent \
|
|
--show-error \
|
|
--connect-timeout 10 \
|
|
--max-time 20 \
|
|
"${headers[@]}" \
|
|
"${url%/}/models" \
|
|
>/dev/null
|
|
}
|
|
|
|
# ============================================================
|
|
# API call
|
|
# ============================================================
|
|
|
|
api_call() {
|
|
|
|
local provider="$1"
|
|
local model="$2"
|
|
local system_prompt="$3"
|
|
local user_prompt="$4"
|
|
local output="$5"
|
|
|
|
local url
|
|
url="$(provider_url "$provider")"
|
|
|
|
[[ -n "$url" ]] ||
|
|
die "Unknown provider: $provider"
|
|
|
|
local key
|
|
key="$(provider_api_key "$provider")"
|
|
|
|
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_API_RETRIES )); do
|
|
|
|
local json="${output}.json"
|
|
|
|
log "API: provider=$provider model=$model attempt=$attempt"
|
|
|
|
local -a headers
|
|
|
|
headers=(
|
|
-H "Content-Type: application/json"
|
|
)
|
|
|
|
if [[ -n "$key" ]]; then
|
|
headers+=(
|
|
-H "Authorization: Bearer $key"
|
|
)
|
|
fi
|
|
|
|
# OpenRouter-specific attribution headers.
|
|
if [[ "$provider" == "openrouter" ]]; then
|
|
headers+=(
|
|
-H "HTTP-Referer: https://github.com/riscvcxh1/xh1-research"
|
|
-H "X-Title: XH-1 Research Harness"
|
|
)
|
|
fi
|
|
|
|
if curl \
|
|
--fail \
|
|
--silent \
|
|
--show-error \
|
|
--connect-timeout 30 \
|
|
--max-time 1800 \
|
|
"${headers[@]}" \
|
|
"${url%/}/chat/completions" \
|
|
-d "$payload" \
|
|
> "$json"
|
|
then
|
|
|
|
if jq -er \
|
|
'.choices[0].message.content' \
|
|
"$json" \
|
|
> "$output"
|
|
then
|
|
|
|
log "Response received: $(wc -c < "$output" | tr -d ' ') bytes"
|
|
|
|
return 0
|
|
fi
|
|
|
|
log "Provider returned an invalid response."
|
|
else
|
|
log "API request failed."
|
|
fi
|
|
|
|
sleep $((attempt * attempt))
|
|
|
|
((attempt++))
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# ============================================================
|
|
# 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() {
|
|
grep -Fxq "$1" \
|
|
"$STATE/runs/$RUN_ID/completed" \
|
|
2>/dev/null
|
|
}
|
|
|
|
run_has_failed() {
|
|
grep -Fxq "$1" \
|
|
"$STATE/runs/$RUN_ID/failed" \
|
|
2>/dev/null
|
|
}
|
|
|
|
mark_completed() {
|
|
printf '%s\n' "$1" \
|
|
>> "$STATE/runs/$RUN_ID/completed"
|
|
}
|
|
|
|
mark_failed() {
|
|
printf '%s\n' "$1" \
|
|
>> "$STATE/runs/$RUN_ID/failed"
|
|
}
|
|
|
|
record_attempt() {
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
|
"$1" \
|
|
"$2" \
|
|
"$3" \
|
|
"$4" \
|
|
>> "$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" &&
|
|
! run_has_completed "$file" &&
|
|
! run_has_failed "$file"
|
|
then
|
|
printf '%s\n' "$file"
|
|
return 0
|
|
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
|
|
|
|
Repository:
|
|
XH-1 Research
|
|
|
|
Current document:
|
|
$file
|
|
|
|
Research area:
|
|
$area
|
|
|
|
CURRENT DOCUMENT
|
|
================
|
|
|
|
$(cat "$file")
|
|
|
|
RELATED DOCUMENTS
|
|
=================
|
|
|
|
EOF
|
|
|
|
if [[ -d "$ROOT/$area" ]]; then
|
|
|
|
find "$ROOT/$area" \
|
|
-maxdepth 1 \
|
|
-type f \
|
|
-name '*.md' \
|
|
! -path "$file" |
|
|
sort |
|
|
head -20 |
|
|
while read -r related; do
|
|
|
|
echo
|
|
echo "----- $related -----"
|
|
cat "$related"
|
|
|
|
done
|
|
fi
|
|
}
|
|
|
|
# ============================================================
|
|
# Research prompts
|
|
# ============================================================
|
|
|
|
research_system_prompt() {
|
|
|
|
cat <<'EOF'
|
|
You are a senior computer architecture researcher working
|
|
on the XH-1 processor project.
|
|
|
|
XH-1 is a custom 128-core RISC-V CPU.
|
|
|
|
Produce rigorous engineering research.
|
|
|
|
Never invent:
|
|
- citations
|
|
- papers
|
|
- measurements
|
|
- benchmarks
|
|
- processor capabilities
|
|
- URLs
|
|
- experimental results
|
|
|
|
Clearly distinguish:
|
|
|
|
FACT
|
|
ASSUMPTION
|
|
PROPOSAL
|
|
RECOMMENDATION
|
|
OPEN QUESTION
|
|
|
|
If evidence is unavailable, say:
|
|
|
|
INSUFFICIENT EVIDENCE
|
|
|
|
Quantitative claims must have an identifiable source or must
|
|
be explicitly labeled as estimates.
|
|
|
|
Always consider:
|
|
|
|
- latency
|
|
- throughput
|
|
- area
|
|
- power
|
|
- energy
|
|
- bandwidth
|
|
- scalability
|
|
- contention
|
|
- implementation complexity
|
|
- verification complexity
|
|
- software implications
|
|
- 128-core replication
|
|
|
|
Do not assume XH-1 is in-order or out-of-order unless the
|
|
repository context establishes that fact.
|
|
|
|
Return ONLY the Markdown research document.
|
|
EOF
|
|
}
|
|
|
|
research_user_prompt() {
|
|
|
|
local file="$1"
|
|
|
|
cat <<EOF
|
|
Research the following XH-1 topic.
|
|
|
|
$(build_context "$file")
|
|
|
|
Produce a complete engineering research document.
|
|
|
|
Use sections where applicable:
|
|
|
|
# Topic
|
|
|
|
## Status
|
|
|
|
## Abstract
|
|
|
|
## Research Question
|
|
|
|
## Background
|
|
|
|
## Existing Approaches
|
|
|
|
## Alternative Designs
|
|
|
|
## Comparison
|
|
|
|
## Advantages
|
|
|
|
## Disadvantages
|
|
|
|
## XH-1 Considerations
|
|
|
|
## 128-Core Scalability
|
|
|
|
## Performance Considerations
|
|
|
|
## Area Considerations
|
|
|
|
## Power and Energy Considerations
|
|
|
|
## Implementation Considerations
|
|
|
|
## Verification Considerations
|
|
|
|
## Software Considerations
|
|
|
|
## Recommendation
|
|
|
|
## Confidence
|
|
|
|
## Open Questions
|
|
|
|
## Sources
|
|
|
|
Do not force a recommendation if evidence is insufficient.
|
|
EOF
|
|
}
|
|
|
|
# ============================================================
|
|
# Reviewer
|
|
# ============================================================
|
|
|
|
review_system_prompt() {
|
|
|
|
cat <<'EOF'
|
|
You are the independent technical reviewer for XH-1.
|
|
|
|
XH-1 is a custom 128-core RISC-V processor.
|
|
|
|
Aggressively check research for:
|
|
|
|
- factual errors
|
|
- incorrect RISC-V information
|
|
- unsupported claims
|
|
- fabricated citations
|
|
- fabricated measurements
|
|
- contradictory recommendations
|
|
- missing alternatives
|
|
- missing assumptions
|
|
- failure to consider 128-core scaling
|
|
- unrealistic implementation claims
|
|
- unsupported performance claims
|
|
- unsupported area claims
|
|
- unsupported power claims
|
|
- weak verification reasoning
|
|
- incorrect terminology
|
|
- insufficient source specificity
|
|
- assumptions presented as facts
|
|
|
|
Check quantitative claims especially carefully.
|
|
|
|
Check whether recommendations actually follow from
|
|
the analysis.
|
|
|
|
Return exactly:
|
|
|
|
VERDICT: PASS
|
|
|
|
or:
|
|
|
|
VERDICT: FAIL
|
|
|
|
Then:
|
|
|
|
ISSUES:
|
|
- ...
|
|
|
|
REQUIRED_FIXES:
|
|
- ...
|
|
|
|
CONFIDENCE: HIGH/MEDIUM/LOW
|
|
EOF
|
|
}
|
|
|
|
review_user_prompt() {
|
|
|
|
local document="$1"
|
|
|
|
cat <<EOF
|
|
Review this XH-1 research document independently.
|
|
|
|
DOCUMENT
|
|
========
|
|
|
|
$(cat "$document")
|
|
|
|
END DOCUMENT
|
|
|
|
Identify every substantive technical problem.
|
|
|
|
Do not rewrite the document.
|
|
|
|
Return PASS only when the research is technically acceptable,
|
|
internally consistent, and sufficiently supported.
|
|
EOF
|
|
}
|
|
|
|
# ============================================================
|
|
# Revision
|
|
# ============================================================
|
|
|
|
revision_system_prompt() {
|
|
|
|
cat <<'EOF'
|
|
You are a senior computer architecture researcher revising
|
|
research for the XH-1 128-core RISC-V CPU.
|
|
|
|
The previous version was rejected by an independent reviewer.
|
|
|
|
Fix every substantive reviewer issue.
|
|
|
|
You must:
|
|
|
|
- correct factual errors
|
|
- remove unsupported quantitative claims
|
|
- add missing alternatives
|
|
- reconcile contradictions
|
|
- make assumptions explicit
|
|
- improve citations
|
|
- reconsider unsupported recommendations
|
|
- consider 128-core scaling
|
|
- consider area
|
|
- consider power
|
|
- consider energy
|
|
- consider implementation
|
|
- consider verification
|
|
|
|
Never invent evidence.
|
|
|
|
If evidence cannot be established, write:
|
|
|
|
INSUFFICIENT EVIDENCE
|
|
|
|
Do not merely respond to the reviewer.
|
|
|
|
Produce a clean replacement document.
|
|
|
|
Return ONLY Markdown.
|
|
EOF
|
|
}
|
|
|
|
revision_user_prompt() {
|
|
|
|
local document="$1"
|
|
local review="$2"
|
|
|
|
cat <<EOF
|
|
Revise this XH-1 research document.
|
|
|
|
ORIGINAL
|
|
========
|
|
|
|
$(cat "$document")
|
|
|
|
REVIEW
|
|
======
|
|
|
|
$(cat "$review")
|
|
|
|
Fix every substantive issue.
|
|
|
|
Do not add a response-to-reviewer section.
|
|
|
|
Return the complete corrected Markdown document only.
|
|
EOF
|
|
}
|
|
|
|
# ============================================================
|
|
# Replace SOON
|
|
# ============================================================
|
|
|
|
replace_soon() {
|
|
|
|
local target="$1"
|
|
local replacement="$2"
|
|
|
|
is_soon "$target" ||
|
|
die "Safety check failed: $target is no longer SOON."
|
|
|
|
cp "$replacement" "$target"
|
|
}
|
|
|
|
# ============================================================
|
|
# Git
|
|
# ============================================================
|
|
|
|
git_commit() {
|
|
|
|
[[ "$AUTO_COMMIT" == "true" ]] || return 0
|
|
|
|
git rev-parse --is-inside-work-tree \
|
|
>/dev/null 2>&1 ||
|
|
return 0
|
|
|
|
git add "$1"
|
|
|
|
git commit \
|
|
-m "research: $(basename "$1" .md | tr '-' ' ')" ||
|
|
true
|
|
}
|
|
|
|
# ============================================================
|
|
# Research one topic
|
|
# ============================================================
|
|
|
|
research_topic() {
|
|
|
|
local file="$1"
|
|
|
|
log "=================================================="
|
|
log "Researching: $file"
|
|
log "=================================================="
|
|
|
|
local round=1
|
|
|
|
local current="$STATE/runs/$RUN_ID/${file//\//_}.current.md"
|
|
local review="$STATE/runs/$RUN_ID/${file//\//_}.review.md"
|
|
|
|
while (( round <= MAX_RESEARCH_ROUNDS )); do
|
|
|
|
log "Research round $round/$MAX_RESEARCH_ROUNDS"
|
|
|
|
local candidate="$STATE/runs/$RUN_ID/${file//\//_}.round${round}.md"
|
|
|
|
# ----------------------------------------------------
|
|
# Research
|
|
# ----------------------------------------------------
|
|
|
|
if (( round == 1 )); then
|
|
|
|
log "Running researcher."
|
|
|
|
if ! api_call \
|
|
"$RESEARCH_PROVIDER" \
|
|
"$RESEARCH_MODEL" \
|
|
"$(research_system_prompt)" \
|
|
"$(research_user_prompt "$file")" \
|
|
"$candidate"
|
|
then
|
|
|
|
record_attempt \
|
|
"$file" "$round" "research" "api-failure"
|
|
|
|
mark_failed "$file"
|
|
|
|
return 1
|
|
fi
|
|
|
|
record_attempt \
|
|
"$file" "$round" "research" "completed"
|
|
|
|
# ----------------------------------------------------
|
|
# Revision
|
|
# ----------------------------------------------------
|
|
|
|
else
|
|
|
|
log "Running revision agent."
|
|
|
|
if ! api_call \
|
|
"$REVISION_PROVIDER" \
|
|
"$REVISION_MODEL" \
|
|
"$(revision_system_prompt)" \
|
|
"$(revision_user_prompt "$current" "$review")" \
|
|
"$candidate"
|
|
then
|
|
|
|
record_attempt \
|
|
"$file" "$round" "revision" "api-failure"
|
|
|
|
mark_failed "$file"
|
|
|
|
return 1
|
|
fi
|
|
|
|
record_attempt \
|
|
"$file" "$round" "revision" "completed"
|
|
fi
|
|
|
|
cp "$candidate" "$current"
|
|
|
|
# ----------------------------------------------------
|
|
# Review
|
|
# ----------------------------------------------------
|
|
|
|
if [[ "$REVIEW_ENABLED" != "true" ]]; then
|
|
|
|
replace_soon "$file" "$candidate"
|
|
|
|
mark_completed "$file"
|
|
|
|
git_commit "$file"
|
|
|
|
return 0
|
|
fi
|
|
|
|
log "Running reviewer."
|
|
|
|
if ! api_call \
|
|
"$REVIEW_PROVIDER" \
|
|
"$REVIEW_MODEL" \
|
|
"$(review_system_prompt)" \
|
|
"$(review_user_prompt "$candidate")" \
|
|
"$review"
|
|
then
|
|
|
|
record_attempt \
|
|
"$file" "$round" "review" "api-failure"
|
|
|
|
mark_failed "$file"
|
|
|
|
return 1
|
|
fi
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo "RESEARCH REVIEW"
|
|
echo "=========================================="
|
|
|
|
cat "$review"
|
|
|
|
echo
|
|
echo "=========================================="
|
|
echo
|
|
|
|
local verdict
|
|
|
|
verdict="$(
|
|
grep -m1 '^VERDICT:' "$review" || true
|
|
)"
|
|
|
|
record_attempt \
|
|
"$file" "$round" "review" "$verdict"
|
|
|
|
# ----------------------------------------------------
|
|
# PASS
|
|
# ----------------------------------------------------
|
|
|
|
if [[ "$verdict" == "VERDICT: PASS" ]]; then
|
|
|
|
log "Research PASSED review."
|
|
|
|
replace_soon "$file" "$candidate"
|
|
|
|
mark_completed "$file"
|
|
|
|
git_commit "$file"
|
|
|
|
log "Completed: $file"
|
|
|
|
return 0
|
|
fi
|
|
|
|
# ----------------------------------------------------
|
|
# FAIL
|
|
# ----------------------------------------------------
|
|
|
|
log "Research rejected by reviewer."
|
|
|
|
if (( round >= MAX_RESEARCH_ROUNDS )); then
|
|
|
|
log "Maximum research rounds reached."
|
|
log "Leaving original document unchanged."
|
|
log "Marking topic failed for this run."
|
|
|
|
mark_failed "$file"
|
|
|
|
return 2
|
|
fi
|
|
|
|
log "Preparing revision round $((round + 1))."
|
|
|
|
((round++))
|
|
|
|
sleep "$DELAY_SECONDS"
|
|
done
|
|
|
|
mark_failed "$file"
|
|
|
|
return 2
|
|
}
|
|
|
|
# ============================================================
|
|
# Run one
|
|
# ============================================================
|
|
|
|
run_single() {
|
|
|
|
start_run
|
|
|
|
local file="${1:-}"
|
|
|
|
if [[ -z "$file" ]]; then
|
|
file="$(find_next_task || true)"
|
|
fi
|
|
|
|
[[ -n "$file" ]] ||
|
|
die "No research topics remain."
|
|
|
|
research_topic "$file"
|
|
}
|
|
|
|
# ============================================================
|
|
# Run all
|
|
# ============================================================
|
|
|
|
run_all() {
|
|
|
|
start_run
|
|
|
|
local iteration=0
|
|
|
|
while true; do
|
|
|
|
if (( MAX_ITERATIONS > 0 &&
|
|
iteration >= MAX_ITERATIONS )); then
|
|
|
|
log "Maximum run iterations reached."
|
|
|
|
break
|
|
fi
|
|
|
|
local file
|
|
|
|
file="$(find_next_task || true)"
|
|
|
|
if [[ -z "$file" ]]; then
|
|
|
|
log "No remaining research topics."
|
|
|
|
break
|
|
fi
|
|
|
|
((iteration += 1))
|
|
|
|
echo
|
|
echo "##################################################"
|
|
echo "XH-1 AUTONOMOUS RESEARCH"
|
|
echo "Iteration: $iteration"
|
|
echo "Topic: $file"
|
|
echo "##################################################"
|
|
echo
|
|
|
|
research_topic "$file" || true
|
|
|
|
sleep "$DELAY_SECONDS"
|
|
done
|
|
|
|
status
|
|
}
|
|
|
|
# ============================================================
|
|
# Status
|
|
# ============================================================
|
|
|
|
status() {
|
|
|
|
local total
|
|
local soon
|
|
|
|
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 ' '
|
|
)"
|
|
|
|
local completed=$((total - soon))
|
|
|
|
echo
|
|
echo "╔══════════════════════════════════════════╗"
|
|
echo "║ XH-1 RESEARCH STATUS ║"
|
|
echo "╚══════════════════════════════════════════╝"
|
|
echo
|
|
|
|
printf " Total documents : %s\n" "$total"
|
|
printf " Completed : %s\n" "$completed"
|
|
printf " Remaining SOON : %s\n" "$soon"
|
|
|
|
if (( total > 0 )); then
|
|
awk \
|
|
-v done="$completed" \
|
|
-v total="$total" \
|
|
'BEGIN {
|
|
printf " Progress : %.1f%%\n",
|
|
(done / total) * 100
|
|
}'
|
|
fi
|
|
|
|
echo
|
|
echo "Next topic:"
|
|
|
|
find_next_task || echo " None"
|
|
|
|
echo
|
|
}
|
|
|
|
# ============================================================
|
|
# Reset failure
|
|
# ============================================================
|
|
|
|
reset_failure() {
|
|
|
|
local file="${1:-}"
|
|
|
|
[[ -n "$file" ]] ||
|
|
die "Usage: $0 reset-failure <file>"
|
|
|
|
[[ -f "$file" ]] ||
|
|
die "File does not exist: $file"
|
|
|
|
if [[ -n "$RUN_ID" &&
|
|
-f "$STATE/runs/$RUN_ID/failed" ]]; then
|
|
|
|
sed -i "\|^${file}$|d" \
|
|
"$STATE/runs/$RUN_ID/failed"
|
|
fi
|
|
|
|
log "Failure reset: $file"
|
|
}
|
|
|
|
# ============================================================
|
|
# Manual review
|
|
# ============================================================
|
|
|
|
review_command() {
|
|
|
|
local file="${1:-}"
|
|
|
|
[[ -n "$file" ]] ||
|
|
die "Usage: $0 review <file>"
|
|
|
|
[[ -f "$file" ]] ||
|
|
die "File does not exist: $file"
|
|
|
|
local output="$STATE/manual-review-$(timestamp).md"
|
|
|
|
api_call \
|
|
"$REVIEW_PROVIDER" \
|
|
"$REVIEW_MODEL" \
|
|
"$(review_system_prompt)" \
|
|
"$(review_user_prompt "$file")" \
|
|
"$output"
|
|
|
|
cat "$output"
|
|
}
|
|
|
|
# ============================================================
|
|
# Provider status
|
|
# ============================================================
|
|
|
|
providers() {
|
|
|
|
echo
|
|
echo "XH-1 PROVIDERS"
|
|
echo "=============="
|
|
echo
|
|
|
|
local provider
|
|
|
|
for provider in $PROVIDERS; do
|
|
|
|
local url
|
|
url="$(provider_url "$provider")"
|
|
|
|
printf "%-15s %s" "$provider" "$url"
|
|
|
|
if provider_check "$provider" >/dev/null 2>&1; then
|
|
echo " [OK]"
|
|
else
|
|
echo " [OFFLINE/ERROR]"
|
|
fi
|
|
|
|
done
|
|
|
|
echo
|
|
}
|
|
|
|
# ============================================================
|
|
# Help
|
|
# ============================================================
|
|
|
|
help() {
|
|
|
|
cat <<'HELP'
|
|
|
|
XH-1 RESEARCH HARNESS
|
|
|
|
MULTI-PROVIDER AUTONOMOUS RESEARCH SYSTEM
|
|
|
|
|
|
COMMANDS
|
|
--------
|
|
|
|
init
|
|
|
|
Create a configuration template.
|
|
|
|
status
|
|
|
|
Show research progress.
|
|
|
|
providers
|
|
|
|
Test configured API providers.
|
|
|
|
next
|
|
|
|
Show next SOON document.
|
|
|
|
run
|
|
|
|
Research the next topic.
|
|
|
|
run <file>
|
|
|
|
Research a specific topic.
|
|
|
|
run-all
|
|
|
|
Process all SOON documents.
|
|
|
|
review <file>
|
|
|
|
Manually review a document.
|
|
|
|
reset-failure <file>
|
|
|
|
Retry a failed topic.
|
|
|
|
help
|
|
|
|
Show this help.
|
|
|
|
|
|
PIPELINE
|
|
--------
|
|
|
|
RESEARCH
|
|
|
|
|
v
|
|
REVIEW
|
|
|
|
|
+--+--+
|
|
| |
|
|
PASS FAIL
|
|
| |
|
|
v v
|
|
WRITE REVISE
|
|
|
|
|
v
|
|
REVIEW
|
|
|
|
|
|
PROVIDERS
|
|
---------
|
|
|
|
Each agent can use a different provider.
|
|
|
|
Example:
|
|
|
|
Researcher -> OpenRouter
|
|
Reviewer -> Ollama
|
|
Revision -> OpenRouter
|
|
|
|
|
|
SUPPORTED PROVIDER STYLE
|
|
------------------------
|
|
|
|
Any OpenAI-compatible API.
|
|
|
|
Examples:
|
|
|
|
OpenRouter
|
|
Ollama
|
|
LM Studio
|
|
vLLM
|
|
LocalAI
|
|
OpenAI
|
|
Other compatible APIs
|
|
|
|
|
|
CONFIGURATION
|
|
-------------
|
|
|
|
PROVIDERS
|
|
|
|
Space-separated provider names.
|
|
|
|
Example:
|
|
|
|
PROVIDERS="openrouter ollama"
|
|
|
|
|
|
Provider URL:
|
|
|
|
PROVIDER_<name>_URL
|
|
|
|
|
|
Provider API key environment variable:
|
|
|
|
PROVIDER_<name>_KEY_ENV
|
|
|
|
|
|
AGENTS
|
|
|
|
RESEARCH_PROVIDER
|
|
RESEARCH_MODEL
|
|
|
|
REVIEW_PROVIDER
|
|
REVIEW_MODEL
|
|
|
|
REVISION_PROVIDER
|
|
REVISION_MODEL
|
|
|
|
|
|
RESEARCH SETTINGS
|
|
|
|
MAX_RESEARCH_ROUNDS
|
|
MAX_API_RETRIES
|
|
DELAY_SECONDS
|
|
MAX_ITERATIONS
|
|
REVIEW_ENABLED
|
|
AUTO_COMMIT
|
|
|
|
|
|
EXAMPLE
|
|
-------
|
|
|
|
export OPENROUTER_API_KEY="..."
|
|
|
|
./xh1-research providers
|
|
|
|
./xh1-research status
|
|
|
|
./xh1-research run
|
|
|
|
./xh1-research run-all
|
|
|
|
HELP
|
|
}
|
|
|
|
# ============================================================
|
|
# Main
|
|
# ============================================================
|
|
|
|
load_config
|
|
require_dependencies
|
|
|
|
case "${1:-status}" in
|
|
|
|
init)
|
|
echo "Edit $CONFIG to configure providers."
|
|
;;
|
|
|
|
status)
|
|
status
|
|
;;
|
|
|
|
providers)
|
|
providers
|
|
;;
|
|
|
|
next)
|
|
find_next_task || true
|
|
;;
|
|
|
|
run)
|
|
run_single "${2:-}"
|
|
;;
|
|
|
|
run-all|loop|resume)
|
|
run_all
|
|
;;
|
|
|
|
review)
|
|
review_command "${2:-}"
|
|
;;
|
|
|
|
reset-failure)
|
|
reset_failure "${2:-}"
|
|
;;
|
|
|
|
help|-h|--help)
|
|
help
|
|
;;
|
|
|
|
*)
|
|
die "Unknown command: $1"
|
|
;;
|
|
|
|
esac
|