
Autoresearch
- 97 installs
- 101 repo stars
- Updated August 4, 2026
- factory-ai/factory-plugins
Helps with ai & agent building tasks.
About
autoresearch is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- autoresearch
- AI & Agent Building
- AI-coding skill
Autoresearch by the numbers
- 97 all-time installs (skills.sh)
- Ranked #4,486 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/factory-ai/factory-plugins --skill autoresearchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 97 |
|---|---|
| repo stars | ★ 101 |
| Last updated | August 4, 2026 |
| Repository | factory-ai/factory-plugins ↗ |
What it does
Helps with ai & agent building tasks.
Files
Autoresearch
Autonomous experiment loop: try ideas, keep what works, discard what doesn't, never stop.
Overview
You are running an autonomous optimization loop. Your job is to systematically improve a measurable metric by making changes, running experiments, and keeping only the improvements. You maintain structured state files so that any session — including a fresh one with no memory — can resume exactly where you left off.
If the user is asking you to do this and you are not currently in mission mode, suggest that they might want to run this inside a mission (/enter-mission) for better progress tracking, milestone validation, and multi-session continuity. Don't block on it — just mention it once during setup.
If you are already in mission mode, invoke the mission planning skills first (mission-planning and define-mission-skills) before diving into this skill's procedure. Use the mission system's planning, decomposition, and worker design to structure the autoresearch work — then combine that guidance with this skill's experiment loop procedure. This skill defines how to run experiments; the mission system defines how to plan, track, and validate them.
Setup
Before the loop starts, you need to establish the experiment.
Step 1: Gather Information
Ask the user (or infer from context) for:
- Goal: What are we optimizing? (e.g., "minimize val_bpb", "reduce test runtime", "shrink bundle size")
- Command: What to run (e.g.,
uv run train.py,pnpm test,pnpm build && du -sb dist) - Primary metric: Name, unit, and direction (e.g.,
val_bpb, unitless, lower is better) - Files in scope: Which files may be modified
- Constraints: Hard rules (tests must pass, no new deps, etc.)
- Termination condition: When to stop. Ask the user — options are:
- Fixed experiment count (e.g., 20 experiments)
- Fixed time budget (e.g., 2 hours)
- Target metric (e.g., val_bpb < 1.0)
- Run until interrupted (default)
Step 2: Create Branch and State Files
git checkout autoresearch/<goal>-<date> 2>/dev/null || git checkout -b autoresearch/<goal>-<date>Read the source files thoroughly. Understand the workload deeply before writing anything.
Create three files:
autoresearch.md
The living research document. A fresh agent with no context should be able to read this file and run the loop effectively. Invest time making it excellent.
# Autoresearch: <goal>
## Objective
<Specific description of what we're optimizing and the workload.>
## Metrics
- **Primary**: <name> (<unit>, lower/higher is better) — the optimization target
- **Secondary**: <name>, <name>, ... — independent tradeoff monitors
## How to Run
`./autoresearch.sh` — outputs `METRIC name=number` lines.
## Files in Scope
<Every file the agent may modify, with a brief note on what it does.>
## Off Limits
<What must NOT be touched.>
## Constraints
<Hard rules: tests must pass, no new deps, etc.>
## Termination
<When to stop: experiment count, time budget, target metric, or run until interrupted.>
## What's Been Tried
<Update this section as experiments accumulate. Note key wins, dead ends,
and architectural insights so the agent doesn't repeat failed approaches.>autoresearch.sh
Bash script (set -euo pipefail) that: pre-checks fast (syntax errors in <1s), runs the benchmark, and outputs structured METRIC name=value lines to stdout. Keep the script fast.
For fast, noisy benchmarks (< 5s), run the workload multiple times inside the script and report the median. Slow workloads (ML training, large builds) don't need this.
Example:
#!/bin/bash
set -euo pipefail
# Pre-check: syntax validation
python3 -c "import ast; ast.parse(open('train.py').read())" 2>&1 || { echo "SYNTAX ERROR"; exit 1; }
# Run the workload
output=$(uv run train.py 2>&1)
# Extract and output metrics
val_bpb=$(echo "$output" | grep -oP 'val_bpb=\K[0-9.]+' | tail -1)
echo "METRIC val_bpb=$val_bpb"autoresearch.checks.sh (optional)
Only create this when the user's constraints require correctness validation (e.g., "tests must pass", "types must check"). Bash script (set -euo pipefail) for backpressure checks.
#!/bin/bash
set -euo pipefail
pnpm test --run --reporter=dot 2>&1 | tail -50
pnpm typecheck 2>&1 | grep -i error || trueStep 3: Initialize JSONL and Commit State Files
Initialize the experiment log:
python3 autoresearch_helper.py init --jsonl autoresearch.jsonl --name '<goal>' --metric-name '<metric_name>' --direction <lower|higher>Commit all state files:
git add autoresearch.md autoresearch.sh autoresearch.jsonl
git commit -m "autoresearch: initialize experiment session"Step 4: Run Baseline
Run the benchmark and record the baseline result:
bash autoresearch.shParse the METRIC lines from the output, then log the baseline as a keep:
python3 autoresearch_helper.py log --jsonl autoresearch.jsonl \
--commit $(git rev-parse --short=7 HEAD) \
--metric <baseline_value> \
--status keep \
--description "baseline" \
--asi '{"hypothesis": "baseline measurement"}'This is experiment #1 — it establishes the starting point for all future comparisons.
The Experiment Loop
LOOP FOREVER. Never ask "should I continue?" — the user expects autonomous work. Only stop when:
- The termination condition from setup is met
- The user interrupts
- You detect you're running low on context (see Context Management below)
For Each Experiment:
1. Choose What to Try
Read autoresearch.md (especially "What's Been Tried") and autoresearch.ideas.md (if it exists) to pick the next hypothesis. Think about what the data tells you. The best ideas come from deep understanding, not random variations.
2. Make Changes
Edit the files in scope. Keep changes focused — one hypothesis per experiment.
3. Run the Experiment
Execute the benchmark:
timeout 600 bash autoresearch.shCapture the full output. Parse METRIC name=value lines from the output.
If the run crashes or times out, log it as a crash and revert.
If autoresearch.checks.sh exists and the benchmark passed, run it:
timeout 300 bash autoresearch.checks.shIf checks fail, log as checks_failed and revert.
4. Evaluate Results
Compare the primary metric against the current best (or baseline if no keeps yet) using the helper script:
python3 autoresearch_helper.py evaluate --jsonl autoresearch.jsonl --metric <value> --direction <lower|higher>This outputs whether to keep or discard, the confidence score, and delta from baseline.
Decision rules:
- Primary metric improved ->
keep - Primary metric worse or unchanged ->
discard - Simpler code for equal performance ->
keep(removing code for same perf is a win) - Ugly complexity for tiny gain -> probably
discard - Secondary metrics rarely affect the keep/discard decision. Only discard a primary improvement if a secondary metric degraded catastrophically.
5. Record Results
On keep:
Log to JSONL first (so the entry is included in the commit):
python3 autoresearch_helper.py log --jsonl autoresearch.jsonl \
--commit $(git rev-parse --short=7 HEAD) \
--metric <value> \
--status keep \
--description "<what was tried>" \
--asi '{"hypothesis": "<what you tried>"}' \
# --metrics '{"compile_us": <value>, "render_us": <value>}' # optional secondary metrics
--direction <lower|higher>Then commit all changes (including the JSONL entry):
git add -A
git commit -m "<description>
Result: {\"status\": \"keep\", \"<metric_name>\": <value>}"On discard/crash/checks_failed:
Log to JSONL first (before reverting, so the entry is preserved):
python3 autoresearch_helper.py log --jsonl autoresearch.jsonl \
--commit "0000000" \
--metric <value_or_0> \
--status <discard|crash|checks_failed> \
--description "<what was tried>" \
--asi '{"hypothesis": "<what you tried>", "rollback_reason": "<why it failed>"}' \
# --metrics '{"compile_us": <value>, "render_us": <value>}' # optional secondary metrics
--direction <lower|higher>Then revert changes, backing up state files so git clean -fd doesn't destroy them:
# Backup state files
cp autoresearch.jsonl autoresearch.jsonl.bak 2>/dev/null || true
cp autoresearch.md autoresearch.md.bak 2>/dev/null || true
cp autoresearch.ideas.md autoresearch.ideas.md.bak 2>/dev/null || true
# Revert all changes
git checkout -- .
git clean -fd 2>/dev/null
# Restore state files
cp autoresearch.jsonl.bak autoresearch.jsonl 2>/dev/null || true
cp autoresearch.md.bak autoresearch.md 2>/dev/null || true
cp autoresearch.ideas.md.bak autoresearch.ideas.md 2>/dev/null || true
rm -f autoresearch.jsonl.bak autoresearch.md.bak autoresearch.ideas.md.bak6. Update Research Journal
After every few experiments (or after significant findings), update the "What's Been Tried" section in autoresearch.md. Include:
- What worked and why
- What didn't work and why
- Dead ends to avoid
- Current best result and how it was achieved
7. Maintain Ideas Backlog
When you discover promising but deferred optimizations, append them as bullet points to autoresearch.ideas.md. Don't let good ideas get lost. Prune stale or tried entries.
8. Loop
Go back to step 1.
State Files Reference
| File | Format | Purpose |
|---|---|---|
autoresearch.jsonl | JSON Lines | Append-only experiment log. One JSON object per line. |
autoresearch.md | Markdown | Living research document. Objective, what's been tried, current best. |
autoresearch.ideas.md | Markdown | Hypothesis backlog. Bullet points of promising ideas to try. |
autoresearch.sh | Bash | Benchmark script. Outputs METRIC name=value lines. |
autoresearch.checks.sh | Bash | Optional correctness checks (tests, types, lint). |
JSONL Schema
Each line in autoresearch.jsonl is either a config header or an experiment result:
Config header (first line, or on re-init):
{"type": "config", "name": "...", "metricName": "...", "metricUnit": "...", "bestDirection": "lower|higher"}Experiment result:
{
"run": 1,
"commit": "abc1234",
"metric": 1.234,
"metrics": {"compile_us": 4200, "render_us": 9800},
"status": "keep|discard|crash|checks_failed",
"description": "what was tried",
"timestamp": 1711600000000,
"segment": 0,
"confidence": 2.1,
"asi": {"hypothesis": "...", "rollback_reason": "...", "next_action_hint": "..."}
}ASI (Actionable Side Information)
Always record ASI with every experiment. At minimum: {"hypothesis": "what you tried"}. On discard/crash, also include rollback_reason and next_action_hint. Add any other key/value pairs that capture what you learned — dead ends, surprising findings, error details, bottlenecks.
ASI is the only structured memory that survives reverts. Without it, future iterations waste time re-discovering the same dead ends.
Confidence Scoring
After 3+ experiments, the helper script computes a confidence score using Median Absolute Deviation (MAD):
| Confidence | Meaning |
|---|---|
| >= 2.0x | Improvement is likely real |
| 1.0-2.0x | Above noise but marginal |
| < 1.0x | Within noise — consider re-running to confirm |
The score is advisory — it never auto-discards. If confidence is below 1.0x, consider re-running the same experiment to confirm before keeping.
Context Management
Droid sessions have finite context. To handle this gracefully:
1. Track experiment count in the current session. After ~15 experiments, context is getting heavy. 2. Save state proactively — all state lives in files (jsonl, md), so a new session can resume immediately. 3. When context is getting exhausted: update autoresearch.md with current findings, commit state files, and stop. The next session reads the files and continues. 4. On resume: read autoresearch.md, autoresearch.jsonl, and git log --oneline -20 to understand where things stand. Check current status:
python3 autoresearch_helper.py status --jsonl autoresearch.jsonlLoop Rules Summary
- LOOP FOREVER. Never ask "should I continue?"
- Primary metric is king. Improved -> keep. Worse/equal -> discard.
- Annotate every run with ASI. Record what you learned, not just what you did.
- Watch the confidence score. < 1.0x means within noise — re-run to confirm.
- Simpler is better. Removing code for equal perf = keep.
- Don't thrash. Repeatedly reverting the same idea? Try something structurally different.
- Crashes: fix if trivial, otherwise log and move on.
- Think longer when stuck. Re-read source files, study the data, reason about what's actually happening. The best ideas come from deep understanding.
- Resuming: read autoresearch.md + git log, continue looping.
Finalization
When the experiment loop ends (termination condition met, user interrupts, or context exhausted), finalize the results into clean, reviewable branches. This is the last phase of an autoresearch session.
Step 1: Summarize Results
python3 autoresearch_helper.py summary --jsonl autoresearch.jsonlReview the git log for actual commits:
git log --oneline --stat $(git merge-base HEAD main)..HEADStep 2: Group Changes
Group kept experiments into logical changesets. Each group should:
- Represent a single coherent optimization or change
- Not share modified files with other groups (so branches can merge independently)
- Have a clear description of what it achieves and the metric improvement
Present the proposed grouping to the user for approval:
Group 1: "Reduce model depth from 8 to 6"
Files: train.py (DEPTH, HEAD_DIM, N_EMBED)
Metric improvement: val_bpb 1.15 -> 1.08 (-6.1%)
Experiments: #3, #7, #12
Group 2: "Switch to cosine LR schedule"
Files: train.py (lr_schedule, warmup_steps)
Metric improvement: val_bpb 1.08 -> 1.05 (-2.8%)
Experiments: #15, #18Wait for user confirmation before proceeding. In mission worker mode, proceed with the best grouping without waiting for confirmation.
Step 3: Resolve File Conflicts
If groups share files, resolve before creating branches:
- Merge the groups into one (if changes are related)
- Split the file changes more carefully (if they're truly independent modifications to different parts)
- Ask the user which group gets priority
Groups must not share files — each branch must be independently mergeable. If all changes touch the same file and can't be separated, create a single finalized branch with all improvements combined.
Step 4: Create Clean Branches
For each group:
merge_base=$(git merge-base HEAD main)
git checkout -b autoresearch/finalize/<group-name> $merge_base
git checkout autoresearch/<session-branch> -- <file1> <file2> ...
git commit -m "<group description>
Autoresearch results:
- Metric: <name> improved from <baseline> to <best> (<delta>%)
- Confidence: <score>x noise floor
- Experiments: <count> total, <kept> kept"Step 5: Verify and Report
For each finalized branch, run the benchmark to confirm the improvement holds, run any checks if applicable, and verify it merges cleanly with main.
Present a summary to the user:
Created 2 clean branches from 20 experiments:
autoresearch/finalize/reduce-depth
val_bpb: 1.15 -> 1.08 (-6.1%)
Ready for review
autoresearch/finalize/cosine-schedule
val_bpb: 1.08 -> 1.05 (-2.8%)
Ready for review
Original experiment branch preserved: autoresearch/<session-branch>The original experiment branch is always preserved — finalization creates new branches.
Mission Worker Mode
When running as a mission worker, the feature description specifies the optimization goal, termination condition, files in scope, and constraints. Read it carefully, follow the same loop procedure above, and respect the termination condition. When the condition is met, run finalization and report results in the handoff.
#!/usr/bin/env python3
"""
autoresearch_helper.py — CLI helper for autoresearch experiment tracking.
Handles JSONL state management, MAD-based confidence scoring, and experiment logging.
No external dependencies — stdlib only.
Usage:
python3 autoresearch_helper.py init --jsonl FILE --name NAME --metric-name NAME [--metric-unit UNIT] [--direction lower|higher]
python3 autoresearch_helper.py log --jsonl FILE --commit SHA --metric VALUE --status STATUS --description DESC [--direction lower|higher] [--metrics '{"k":v}'] [--asi '{"k":"v"}']
python3 autoresearch_helper.py evaluate --jsonl FILE --metric VALUE --direction lower|higher
python3 autoresearch_helper.py summary --jsonl FILE
python3 autoresearch_helper.py status --jsonl FILE
"""
import argparse
import json
import os
import statistics
import sys
import time
def read_jsonl(path):
"""Read a JSONL file, returning (config, results) where config is the latest config header."""
config = None
results = []
segment = 0
if not os.path.exists(path):
return config, results
with open(path, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
if entry.get("type") == "config":
if results:
segment += 1
config = entry
config["_segment"] = segment
continue
entry.setdefault("segment", segment)
entry.setdefault("metrics", {})
entry.setdefault("confidence", None)
entry.setdefault("asi", None)
results.append(entry)
return config, results
def current_segment_results(results, segment):
"""Filter results to the current segment only."""
return [r for r in results if r.get("segment", 0) == segment]
def compute_mad(values):
"""Compute Median Absolute Deviation."""
if len(values) < 2:
return 0.0
median = statistics.median(values)
deviations = [abs(v - median) for v in values]
return statistics.median(deviations)
def compute_confidence(results, segment, direction):
"""
Compute confidence score: |best_improvement| / MAD.
Returns None if fewer than 3 data points or MAD is 0.
"""
cur = [r for r in current_segment_results(results, segment) if r.get("status") not in ("crash", "checks_failed")]
if len(cur) < 3:
return None
values = [r["metric"] for r in cur]
mad = compute_mad(values)
if mad == 0:
return None
baseline = find_baseline(results, segment)
if baseline is None:
return None
best_kept = None
for r in cur:
if r.get("status") == "keep":
val = r["metric"]
if best_kept is None:
best_kept = val
elif direction == "lower" and val < best_kept:
best_kept = val
elif direction == "higher" and val > best_kept:
best_kept = val
if best_kept is None or best_kept == baseline:
return None
delta = abs(best_kept - baseline)
return round(delta / mad, 2)
def find_baseline(results, segment):
"""Find the baseline metric (first experiment in current segment)."""
cur = current_segment_results(results, segment)
return cur[0]["metric"] if cur else None
def find_best_kept(results, segment, direction):
"""Find the best kept metric in the current segment."""
cur = current_segment_results(results, segment)
best = None
for r in cur:
if r.get("status") == "keep":
val = r["metric"]
if best is None:
best = val
elif direction == "lower" and val < best:
best = val
elif direction == "higher" and val > best:
best = val
return best
def is_better(current, best, direction):
return current < best if direction == "lower" else current > best
def cmd_init(args):
"""Write a config header to the JSONL file."""
config = {
"type": "config",
"name": args.name,
"metricName": args.metric_name,
"metricUnit": args.metric_unit or "",
"bestDirection": args.direction or "lower",
}
mode = "a" if os.path.exists(args.jsonl) else "w"
with open(args.jsonl, mode) as f:
f.write(json.dumps(config) + "\n")
print(f"Initialized: {args.name} (metric: {args.metric_name}, direction: {args.direction or 'lower'})")
def cmd_log(args):
"""Append an experiment result to the JSONL file."""
config, results = read_jsonl(args.jsonl)
if config is None:
print("Error: No config found. Run 'init' first.", file=sys.stderr)
sys.exit(1)
segment = config.get("_segment", 0) if config else 0
direction = args.direction or (config.get("bestDirection", "lower") if config else "lower")
extra_metrics = {}
if args.metrics:
try:
extra_metrics = json.loads(args.metrics)
except json.JSONDecodeError:
print(f"Warning: could not parse --metrics JSON: {args.metrics}", file=sys.stderr)
asi = None
if args.asi:
try:
asi = json.loads(args.asi)
except json.JSONDecodeError:
print(f"Warning: could not parse --asi JSON: {args.asi}", file=sys.stderr)
entry = {
"run": len(results) + 1,
"commit": args.commit[:7] if args.commit else "0000000",
"metric": args.metric,
"metrics": extra_metrics,
"status": args.status,
"description": args.description,
"timestamp": int(time.time() * 1000),
"segment": segment,
"confidence": None,
"asi": asi,
}
results.append(entry)
confidence = compute_confidence(results, segment, direction)
entry["confidence"] = confidence
with open(args.jsonl, "a") as f:
out = {k: v for k, v in entry.items() if v is not None or k in ("confidence",)}
f.write(json.dumps(out) + "\n")
baseline = find_baseline(results, segment)
best = find_best_kept(results, segment, direction)
print(f"Logged #{entry['run']}: {args.status} — {args.description}")
print(f" Metric: {args.metric}")
if baseline is not None:
print(f" Baseline: {baseline}")
if best is not None and baseline is not None and baseline != 0:
delta_pct = ((best - baseline) / baseline) * 100
print(f" Best kept: {best} ({delta_pct:+.1f}%)")
if confidence is not None:
label = "likely real" if confidence >= 2.0 else "marginal" if confidence >= 1.0 else "within noise"
print(f" Confidence: {confidence}x ({label})")
def cmd_evaluate(args):
"""Evaluate whether a new metric value should be kept or discarded."""
config, results = read_jsonl(args.jsonl)
if not config:
print("No config found in JSONL. Run init first.", file=sys.stderr)
sys.exit(1)
segment = config.get("_segment", 0)
direction = args.direction or config.get("bestDirection", "lower")
baseline = find_baseline(results, segment)
best = find_best_kept(results, segment, direction)
compare_against = best if best is not None else baseline
if compare_against is None:
print("DECISION: keep (first experiment — this is the baseline)")
print(f" Metric: {args.metric}")
sys.exit(0)
improved = is_better(args.metric, compare_against, direction)
results_with_new = results + [{"metric": args.metric, "status": "keep", "segment": segment}]
confidence = compute_confidence(results_with_new, segment, direction)
delta = args.metric - compare_against
delta_pct = (delta / compare_against) * 100 if compare_against != 0 else 0
if improved:
print(f"DECISION: keep")
else:
print(f"DECISION: discard")
print(f" Metric: {args.metric}")
print(f" Compare against: {compare_against} ({'best kept' if best is not None else 'baseline'})")
print(f" Delta: {delta:+.4f} ({delta_pct:+.1f}%)")
print(f" Direction: {direction} is better")
if confidence is not None:
label = "likely real" if confidence >= 2.0 else "marginal" if confidence >= 1.0 else "within noise"
print(f" Confidence: {confidence}x ({label})")
if confidence < 1.0 and improved:
print(f" Warning: improvement is within noise floor. Consider re-running to confirm.")
def cmd_summary(args):
"""Print a summary of the experiment session."""
config, results = read_jsonl(args.jsonl)
if not config:
print("No experiments found.")
return
segment = config.get("_segment", 0)
cur = current_segment_results(results, segment)
direction = config.get("bestDirection", "lower")
total = len(cur)
kept = [r for r in cur if r.get("status") == "keep"]
discarded = [r for r in cur if r.get("status") == "discard"]
crashed = [r for r in cur if r.get("status") in ("crash", "checks_failed")]
baseline = find_baseline(results, segment)
best = find_best_kept(results, segment, direction)
confidence = compute_confidence(results, segment, direction)
print(f"Session: {config.get('name', 'unnamed')}")
print(f"Metric: {config.get('metricName', 'metric')} ({config.get('metricUnit', '')}), {direction} is better")
print(f"Experiments: {total} total, {len(kept)} kept, {len(discarded)} discarded, {len(crashed)} crashed")
print()
if baseline is not None:
print(f"Baseline: {baseline}")
if best is not None and baseline is not None and baseline != 0:
delta_pct = ((best - baseline) / baseline) * 100
print(f"Best kept: {best} ({delta_pct:+.1f}% from baseline)")
if confidence is not None:
label = "likely real" if confidence >= 2.0 else "marginal" if confidence >= 1.0 else "within noise"
print(f"Confidence: {confidence}x ({label})")
print()
print("Kept experiments:")
for r in kept:
desc = r.get("description", "")
metric = r.get("metric", 0)
commit = r.get("commit", "?")
print(f" #{r.get('run', '?')} [{commit}] {config.get('metricName', 'metric')}={metric} {desc}")
if crashed:
print()
print("Crashed/failed:")
for r in crashed:
desc = r.get("description", "")
status = r.get("status", "crash")
print(f" #{r.get('run', '?')} [{status}] {desc}")
def cmd_status(args):
"""Print current status (baseline, best, confidence) as JSON for programmatic use."""
config, results = read_jsonl(args.jsonl)
if not config:
print(json.dumps({"error": "no config found"}))
return
segment = config.get("_segment", 0)
direction = config.get("bestDirection", "lower")
cur = current_segment_results(results, segment)
baseline = find_baseline(results, segment)
best = find_best_kept(results, segment, direction)
confidence = compute_confidence(results, segment, direction)
status = {
"name": config.get("name"),
"metricName": config.get("metricName"),
"direction": direction,
"totalExperiments": len(cur),
"keptCount": len([r for r in cur if r.get("status") == "keep"]),
"baseline": baseline,
"bestKept": best,
"confidence": confidence,
"deltaPercent": round(((best - baseline) / baseline) * 100, 2) if best is not None and baseline is not None and baseline != 0 else None,
}
print(json.dumps(status, indent=2))
def main():
parser = argparse.ArgumentParser(description="Autoresearch experiment helper")
subparsers = parser.add_subparsers(dest="command", required=True)
# init
p_init = subparsers.add_parser("init", help="Initialize experiment session")
p_init.add_argument("--jsonl", required=True, help="Path to autoresearch.jsonl")
p_init.add_argument("--name", required=True, help="Session name")
p_init.add_argument("--metric-name", required=True, help="Primary metric name")
p_init.add_argument("--metric-unit", default="", help="Metric unit (e.g., us, ms, s, kb)")
p_init.add_argument("--direction", default="lower", choices=["lower", "higher"])
# log
p_log = subparsers.add_parser("log", help="Log an experiment result")
p_log.add_argument("--jsonl", required=True, help="Path to autoresearch.jsonl")
p_log.add_argument("--commit", required=True, help="Git commit hash")
p_log.add_argument("--metric", required=True, type=float, help="Primary metric value")
p_log.add_argument("--status", required=True, choices=["keep", "discard", "crash", "checks_failed"])
p_log.add_argument("--description", required=True, help="What was tried")
p_log.add_argument("--direction", choices=["lower", "higher"], help="Override direction from config")
p_log.add_argument("--metrics", help="Additional metrics as JSON object")
p_log.add_argument("--asi", help="Actionable Side Information as JSON object")
# evaluate
p_eval = subparsers.add_parser("evaluate", help="Evaluate whether to keep or discard")
p_eval.add_argument("--jsonl", required=True, help="Path to autoresearch.jsonl")
p_eval.add_argument("--metric", required=True, type=float, help="New metric value to evaluate")
p_eval.add_argument("--direction", choices=["lower", "higher"], help="Override direction from config")
# summary
p_summary = subparsers.add_parser("summary", help="Print experiment summary")
p_summary.add_argument("--jsonl", required=True, help="Path to autoresearch.jsonl")
# status
p_status = subparsers.add_parser("status", help="Print current status as JSON")
p_status.add_argument("--jsonl", required=True, help="Path to autoresearch.jsonl")
args = parser.parse_args()
commands = {
"init": cmd_init,
"log": cmd_log,
"evaluate": cmd_evaluate,
"summary": cmd_summary,
"status": cmd_status,
}
commands[args.command](args)
if __name__ == "__main__":
main()