Files
2026-08-25 20:22:11 +02:00

935 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT="${XH1_RESEARCH_ROOT:-research}"
CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}"
STATE="${ROOT}/.xh1"
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=""
mkdir -p "$STATE/logs" "$STATE/responses" "$STATE/runs"
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
}
get_api_key() {
local key="${!API_KEY_ENV:-}"
[[ -n "$key" ]] ||
die "Missing API key. Set ${API_KEY_ENV}."
printf '%s' "$key"
}
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"
}
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
}
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_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 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
}
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.
Check that the document distinguishes:
FACT
ASSUMPTION
PROPOSAL
RECOMMENDATION
OPEN QUESTION
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 a PASS only when the research is technically acceptable,
internally consistent, and sufficiently supported.
EOF
}
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
}
api_call() {
local system_prompt="$1"
local user_prompt="$2"
local output="$3"
local model="$4"
local key
key="$(get_api_key)"
[[ -n "$model" ]] ||
die "No model 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_API_RETRIES )); do
local json="${output}.json"
log "API request: model=$model attempt=$attempt"
if curl \
--fail \
--silent \
--show-error \
--connect-timeout 30 \
--max-time 1800 \
-H "Authorization: Bearer ${key}" \
-H "Content-Type: application/json" \
-H "HTTP-Referer: https://github.com/riscvcxh1/xh1-research" \
-H "X-Title: XH-1 Research Harness" \
"${API_BASE_URL%/}/chat/completions" \
-d "$payload" > "$json"
then
if jq -er \
'.choices[0].message.content' \
"$json" > "$output"
then
log "API response received: $(wc -c < "$output" | tr -d ' ') bytes"
return 0
fi
fi
log "API request failed."
sleep $((attempt * attempt))
((attempt++))
done
return 1
}
replace_soon() {
local target="$1"
local replacement="$2"
is_soon "$target" ||
die "Safety check failed: $target is no longer SOON."
cp "$replacement" "$target"
}
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_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"
if (( round == 1 )); then
log "Running researcher."
if ! api_call \
"$(research_system_prompt)" \
"$(research_user_prompt "$file")" \
"$candidate" \
"$RESEARCH_MODEL"
then
record_attempt "$file" "$round" "research" "api-failure"
mark_failed "$file"
return 1
fi
record_attempt "$file" "$round" "research" "completed"
else
log "Running revision agent."
if ! api_call \
"$(revision_system_prompt)" \
"$(revision_user_prompt "$current" "$review")" \
"$candidate" \
"$REVISION_MODEL"
then
record_attempt "$file" "$round" "revision" "api-failure"
mark_failed "$file"
return 1
fi
record_attempt "$file" "$round" "revision" "completed"
fi
cp "$candidate" "$current"
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_system_prompt)" \
"$(review_user_prompt "$candidate")" \
"$review" \
"$REVIEW_MODEL"
then
record_attempt "$file" "$round" "review" "api-failure"
mark_failed "$file"
return 1
fi
echo
echo "=========================================="
echo "RESEARCH REVIEW"
echo "=========================================="
cat "$review"
echo "=========================================="
echo
local verdict
verdict="$(grep -m1 '^VERDICT:' "$review" || true)"
record_attempt "$file" "$round" "review" "$verdict"
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
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_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() {
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() {
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() {
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"
}
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_system_prompt)" \
"$(review_user_prompt "$file")" \
"$output" \
"$REVIEW_MODEL"
cat "$output"
}
help() {
cat <<'HELP'
XH-1 RESEARCH HARNESS
COMMANDS
init
Create configuration.
status
Show research status.
next
Show next SOON document.
run
Research the next topic.
run <file>
Research a specific topic.
run-all
Process all SOON documents.
Each topic:
RESEARCH
|
v
REVIEW
|
+---+---+
| |
PASS FAIL
| |
v v
WRITE REVISE
|
v
REVIEW
Maximum rounds are controlled by:
MAX_RESEARCH_ROUNDS
review <file>
Manually review a document.
reset-failure <file>
Clear a failed topic from the current run.
help
Show this help.
SAFETY
A document is only replaced after:
VERDICT: PASS
If all rounds fail:
The original SOON file remains untouched.
The failed topic is skipped for the remainder of that run.
CONFIGURATION
API_BASE_URL
API_KEY_ENV
RESEARCH_MODEL
REVIEW_MODEL
REVISION_MODEL
MAX_RESEARCH_ROUNDS
MAX_API_RETRIES
DELAY_SECONDS
MAX_ITERATIONS
REVIEW_ENABLED
AUTO_COMMIT
EXAMPLE
export OPENROUTER_API_KEY="..."
./xh1-research status
./xh1-research run
./xh1-research run-all
HELP
}
load_config
require_dependencies
case "${1:-status}" in
init)
echo "Configuration is created manually in $CONFIG."
exit 0
;;
status)
status
;;
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. Use ./xh1-research help"
;;
esac