
Ai Research Explore
- 185k installs
- 512 repo stars
- Updated July 26, 2026
- lllllllama/rigorpilot-skills
ai-research-explore is a Claude Code skill that orchestrates deep learning research exploration with frozen anchors (task, dataset, evaluation) and ranked candidate ideas.
About
A research exploration skill for deep learning candidates within a durable research context. Use it when you have a defined task, dataset, and evaluation method and want to systematically explore ideas while maintaining scientific rigor and comparability.
- Orchestrates exploration with frozen task, dataset, and evaluation anchors
- Ranks candidate ideas with explicit gates before execution
- Preserves scientific rigor and fair comparison with auditable records
Ai Research Explore by the numbers
- 185,188 all-time installs (skills.sh)
- +12,763 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #9 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
ai-research-explore capabilities & compatibility
- Capabilities
- experiment planning · research orchestration · candidate ranking · rigor enforcement
- Use cases
- research · code review
npx skills add https://github.com/lllllllama/rigorpilot-skills --skill ai-research-exploreAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 185k |
|---|---|
| repo stars | ★ 512 |
| Last updated | July 26, 2026 |
| Repository | lllllllama/rigorpilot-skills ↗ |
What it does
Orchestrate bounded exploration of deep learning research candidates with scientific rigor and auditable evidence.
Who is it for?
Meaningful, potentially novel research exploration,Isolated branch experimentation,Fair candidate ranking and comparison
Skip if: README-first trusted reproduction,Open-ended direction finding,Implicit automatic experimentation
When should I use this skill?
The researcher has chosen task family, dataset, benchmark, and evaluation method and explicitly authorizes candidate-only exploration with a durable current_research anchor.
What you get
Ranked candidate results with SCIENTIFIC_CHANGELOG.md and COMPARABILITY_REPORT.md showing methodology, assumptions, and evidence.
- SCIENTIFIC_CHANGELOG.md
- COMPARABILITY_REPORT.md
- explore_outputs/ directory
By the numbers
- Two-loop rhythm: outer (understanding + gating) and inner (candidate change + evidence collection)
Files
ai-research-explore
Purpose
Use this as the Rigor Explore compatible skill slug after the researcher explicitly authorizes candidate-only work on top of a durable current_research anchor. The installed slug remains ai-research-explore for compatibility. Rigor Explore is for meaningful and potentially novel deep learning research candidates while preserving scientific rigor, comparability, reproducibility, and auditable collaboration. Novelty and significance remain hypotheses before literature contrast, ablation evidence, and fair comparison. The skill does not promise autonomous discovery, global benchmark completeness, novelty proof, or trusted reproduction success.
Start from the shared operating principles in ../../references/agent-operating-principles.md, then load ../../references/research-rigor-principles.md for research claims and ../../references/deep-learning-experiment-principles.md when experiment details affect comparability or reproducibility.
Fit
Use this skill only when the request has both:
- Explicit exploration authorization such as candidate-only work, isolated
branch or worktree, sweep, several variants, or exploratory ranking.
- A durable
current_researchcontext such as a branch, commit, checkpoint,
run record, or already-trained local model state.
Keep narrow code-only requests on explore-code. Keep narrow run-only requests on explore-run. Keep passive repository analysis on analyze-project. Keep README-first reproduction on ai-research-reproduction.
Research Rhythm
Use a two-loop rhythm:
- Outer loop: understand the repository, freeze task/dataset/evaluation/budget,
preserve user ideas, map sources, gate ideas, and decide whether the next experiment is worth running.
- Inner loop: make one bounded candidate change or run, smoke-check it, collect
evidence, rank it against the current anchor, and either stop or return to the outer loop with the new evidence.
This rhythm is a guide, not a rigid autonomous loop. Stop at explicit blockers, unclear scientific meaning, exhausted budget, missing anchor/evaluation, or a human checkpoint.
Workflow
1. Confirm current_research and explicit explore-lane authorization. 2. Accept either legacy variant_spec or higher-level research_campaign. 3. In campaign mode, freeze the task, dataset, benchmark, evaluation source, SOTA reference, and budget before candidate work. 4. Build only the repo-understanding artifacts needed for the current campaign, usually through analyze-project. 5. Run bounded, cache-first source lookup when source support matters; prefer local curated literature such as Zotero if available, then seed sources, repo-local locators, public locators, or optional web lookup. Treat lookup as source resolution, not an open-ended literature search. 6. Preserve researcher-provided ideas, optionally add a small bounded set of single-variable seed ideas, and rank ideas with explicit gates and score breakdowns. 7. Prefer one clear candidate at a time. Use explore-code for bounded code adaptation and explore-run for short-cycle trials or sweeps. 8. Use minimal-run-and-audit or run-train only when the exploratory plan requires real execution evidence. 9. Write candidate-only outputs to analysis_outputs/, sources/, and explore_outputs/ as appropriate; never present exploratory gains as trusted reproduction success. Include SCIENTIFIC_CHANGELOG.md and COMPARABILITY_REPORT.md for candidate scientific meaning and comparison boundaries.
Ranking and Evidence
- Before execution, prioritize candidates by expected gain, cost, success
likelihood, patch surface, dependency drag, evaluation risk, and rollback ease.
- After execution, rank by real evidence first: command status, observed
metrics, artifacts, changed paths, smoke results, and reproducibility notes.
- Keep researcher-provided
evaluation_sourceandsota_referencefrozen for
the campaign; do not claim they are globally complete.
- If the top ideas are too close or the implementation cannot be decomposed into
auditable units, stop for a checkpoint instead of silently choosing.
Campaign Inputs
research_campaign is preferred for Rigor Explore campaigns, but it should stay minimal. The durable core is:
current_researchtask_familydatasetbenchmarkevaluation_sourcesota_referencecompute_budget
Use candidate_ideas, variant_spec, research_lookup, idea_policy, idea_generation, source_constraints, feasibility_policy, baseline_gate, and execution_policy as optional guidance, not as fields the agent must fill for every campaign. See references/research-campaign-spec.md for the advanced schema and artifact expectations.
Reference Loading
- Load
references/ai-research-explore-policy.mdfor lane safety and candidate
semantics.
- Load
references/research-campaign-spec.mdonly when a campaign file is
present or the user asks for Rigor Explore campaign governance.
- Load
../../references/explore-variant-spec.mdfor run-level variant matrix
details.
- Load
../../references/research-rigor-principles.mdbefore making novelty,
contribution, SOTA, or comparability statements.
- Load
../../references/deep-learning-experiment-principles.mdwhen training,
evaluation, baseline, ablation, metric, checkpoint, or dataset details matter.
- Use
scripts/orchestrate_explore.pyandscripts/write_outputs.pyfor the
existing deterministic artifact workflow.
display_name: Rigor Explore
short_description: Rigor Explore compatible slug for candidate-only current_research exploration into auditable explore_outputs.
default_prompt: Use current_research as the explicit exploratory context, coordinate isolated code and run exploration conservatively, treat novelty as a hypothesis until evidence supports it, and write CHANGESET.md TOP_RUNS.md and status.json into explore_outputs.
Research Explore Policy
Purpose
Use this skill only when exploratory work has been explicitly authorized on top of current_research. In RigorPilot terms, the goal is meaningful and potentially novel candidate work, not verified novelty.
Requirements
- keep work on an isolated branch or worktree
- record
current_researchin a durable form - treat all outputs as candidate-only exploratory records
- coordinate code and run exploration conservatively instead of freeform rewriting
- keep the trusted lane and exploratory lane clearly separated
- keep improvement mining bounded to the frozen task family, dataset, benchmark, evaluation source, and provided SOTA references
- require source-backed idea cards before transplant-style implementation planning
- keep patch plans minimal, reversible, and auditable
- keep research lookup free-first and provider-optional; missing external keys must not block the flow
- prefer local curated literature, including Zotero when available, before broader lookup, without requiring a provider
- treat
seed_onlylookup records as weak evidence only - distinguish
external_provider,parsed_locator,repo_local_extracted, andseed_onlyevidence in downstream ranking and support summaries
Avoid
- implicit experimentation
- claiming exploratory gains as trusted reproduction success
- claiming novelty, contribution, or SOTA superiority before literature contrast, ablation evidence, and fair comparison
- requiring non-bundled skills to complete the workflow
- using this skill for narrow code-only or run-only asks
- open-ended scientific brainstorming without a frozen campaign anchor
- broad multi-module rewrites or metric-surface edits by default
- presenting cache-first locator parsing as complete current-literature retrieval
Idea Evaluation Framework
ai-research-explore uses a bounded, candidate-only evaluation scheme for Rigor Explore idea ranking.
Hard Gates
- baseline gate must not be
abandon single_variable_fit >= 0.6interface_fit >= 0.5patch_surface <= 0.7dependency_drag <= 0.7eval_risk <= 0.6short_run_feasibility != blocked
Soft Ranking
Positive contributions:
expected_upsidesingle_variable_fitinterface_fitrollback_easeinnovation_story_strengthsource_support_strengthexecution_feasibility
Negative contributions:
implementation_riskeval_riskpatch_surfacedependency_dragexecution_costbaseline_distance
Provenance
Each ranked card should record where each field came from:
- campaign input
- read-only repo analysis
- source lookup cache
- source mapping and patch planning
- execution feasibility or smoke evidence
Guardrails
- ranking is for candidate prioritization only
- ranking does not prove novelty
- ranking does not prove benchmark completeness
- ranking does not prove verified SOTA superiority
Research Campaign Spec
Purpose
Use research_campaign.json or research_campaign.yaml when ai-research-explore is operating as Rigor Explore:
- the task family is already chosen
- the dataset is already chosen
- the evaluation method is already chosen
- the provided SOTA table is already frozen by the researcher
- the remaining work is campaign governance, implementation, and candidate filtering
This file is an advanced reference. The public entrypoint only requires the campaign core to be frozen; the detailed blocks below are guidance for richer campaigns, not fields the agent must invent on every run.
variant_spec still exists, but it is now an optional run-level part of a larger campaign.
Rigor Explore treats novelty as a candidate hypothesis. Novelty and significance remain hypotheses until supported by literature contrast, ablation evidence, and fair comparison.
Minimal Shape
{
"current_research": "seg-branch@abc1234",
"task_family": "segmentation",
"dataset": "DemoSeg",
"benchmark": {
"name": "DemoBench",
"primary_metric": "miou",
"metric_goal": "maximize"
},
"evaluation_source": {
"command": "python eval.py --config configs/demo.yaml",
"path": "eval.py",
"primary_metric": "miou",
"metric_goal": "maximize"
},
"sota_reference": [
{
"name": "Provided SOTA",
"metric": "miou",
"value": 80.0,
"source": "paper-or-table-url"
}
],
"compute_budget": {
"max_runtime_hours": 8
}
}Durable Core Fields
current_research
Durable anchor for the current research state.
task_family
The already-chosen task family, such as segmentation, classification, or depth.
dataset
The dataset name used for this campaign.
benchmark
The benchmark name or descriptor. A dictionary may also carry primary_metric and metric_goal.
evaluation_source
The frozen evaluation contract input. Prefer a command plus an optional path.
sota_reference
The user-provided comparison table. ai-research-explore treats this as authoritative input and does not prove completeness.
compute_budget
The bounded resource envelope for candidate-only work.
Optional Guidance Fields
candidate_ideas
Preferred but optional candidate directions that the researcher already wants to consider. If omitted, or if idea_generation.allow_synthesized_seed_ideas stays enabled, the orchestrator may add a small number of conservative single-variable seed ideas.
variant_spec
Optional run-level candidate matrix used by explore-run.
Optional top-level fields:
baseline_gateexecution_policyresearch_lookupidea_policyidea_generationsource_constraintsfeasibility_policy
evaluation_source
Supported fields:
commandpathprimary_metricmetric_goalexecution_kindartifactsnotessplit
This block feeds both:
analysis_outputs/EVAL_CONTRACT.md- the baseline gate
sota_reference
Each item should preferably contain:
namemetricvalue
Optional fields:
sourcenotesmetric_goal
This is a frozen comparison set for the campaign. It is not a guarantee that the real global SOTA has been fully covered.
candidate_ideas
Each item should contain:
idsummarychange_scopetarget_componentexpected_upsideimplementation_riskeval_riskrollback_easeestimated_runtime_costsingle_variable_fit
Optional fields:
hypothesissupporting_changes
The orchestrator uses these to run the idea gate. It does not treat them as novelty claims. When a researcher idea passes hard gates, final selection stays inside the researcher pool even if synthesized or hybrid ideas are also present for auditability.
Optional Policy Blocks
research_lookup
Use this block to seed auditable lookup records without turning the orchestrator into an open-ended research agent.
Supported fields:
source_preferencelocal_literaturequeriesseed_sourcesenable_repo_local_extractionoptional_providers
Rigor Explore may prefer local literature context first, including Zotero, if available. Local literature should be treated as curated prior knowledge. If local literature is unavailable or too sparse, bounded web/source lookup may be used. Zotero-first is a source lookup strategy, not a separate main skill. It should support meaningful and potentially novel idea generation, not become a generic literature search tool.
Example future-compatible lookup hint:
research_lookup:
source_preference:
- local_literature
- seed_sources
- repo_local
- public_locators
- optional_web
local_literature:
enabled: auto
provider: zotero
fallback_to_web_when:
- unavailable
- too_sparse
- insufficient_candidate_coverageAll lookup artifacts are cached into sources/ with stable names, sources/records/, and an index.json. Missing optional provider keys, including Zotero, must not block this pass.
idea_policy
Optional governance hints for idea selection. Current implementations keep hard gates fixed and treat policy hints as future-compatible metadata.
Suggested fields:
max_patch_surfacemax_dependency_dragrequire_source_backing
idea_generation
Optional hints for bounded idea-space expansion. This block is additive; it should not break the minimal campaign shape.
Supported fields:
allow_synthesized_seed_ideasmax_generated_ideasrequire_diverse_targets
Default behavior keeps generation conservative:
- prefer single-variable ideas
- do not modify the frozen eval contract
- do not jump directly to broad architecture rewrites
- keep synthesized ideas bounded to repo-local components, existing variant axes, or lookup-backed source hints
- bind each generated seed to
current_research,task_family,dataset, andevaluation_sourceinIDEA_SEEDS.json
source_constraints
Optional hints for transplant scope.
Suggested fields:
preferred_reposforbidden_pathsprotected_zones
feasibility_policy
Optional hints for bounded execution.
Suggested fields:
prefer_short_run_onlyrequire_gpumax_short_run_hours
Gates
Baseline gate
Default rules:
maximize: abandon if baseline trails provided SOTA by more than2.0absolute pointsminimize: abandon if baseline is worse than provided SOTA by more than5%
The gate can return:
proceedborderlineabandonnot-applicable
Idea gate
Hard gates:
baseline_gate != abandonsingle_variable_fit >= 0.6interface_fit >= 0.5patch_surface <= 0.7dependency_drag <= 0.7eval_risk <= 0.6short_run_feasibility != blocked
Soft ranking combines:
expected_upsidesingle_variable_fitgroundednessnovelty_estimateinterface_fitrollback_easesource_support_strengthablation_clarityimplementation_story_clarityimplementation_riskeval_riskestimated_runtime_costpatch_surfacedependency_dragbaseline_distance
IDEA_SCORES.json records both raw inputs and explicit score breakdowns. If the active top-two ideas are too close, ai-research-explore records a human checkpoint instead of silently training.
If the selected idea cannot be decomposed into implementable atomic units, ai-research-explore records an explicit blocker/checkpoint such as atomic-decomposition-blocked and stops before broader implementation or execution.
Output Expectations
The following artifacts are the full advanced campaign surface. A minimal campaign should produce only the files justified by the active work; do not inflate the run with empty artifacts just to satisfy this list.
Campaign mode writes:
analysis_outputs/RESEARCH_MAP.mdanalysis_outputs/CHANGE_MAP.mdanalysis_outputs/EVAL_CONTRACT.mdanalysis_outputs/SOURCE_INVENTORY.mdanalysis_outputs/SOURCE_SUPPORT.jsonanalysis_outputs/IMPROVEMENT_BANK.mdanalysis_outputs/IDEA_CARDS.jsonanalysis_outputs/IDEA_SEEDS.jsonanalysis_outputs/IDEA_EVALUATION.mdanalysis_outputs/IDEA_SCORES.jsonanalysis_outputs/MODULE_CANDIDATES.mdanalysis_outputs/INTERFACE_DIFF.mdanalysis_outputs/ATOMIC_IDEA_MAP.mdanalysis_outputs/ATOMIC_IDEA_MAP.jsonanalysis_outputs/IMPLEMENTATION_FIDELITY.mdanalysis_outputs/IMPLEMENTATION_FIDELITY.jsonanalysis_outputs/RESOURCE_PLAN.mdanalysis_outputs/status.jsonsources/index.jsonsources/SUMMARY.mdsources/records/explore_outputs/CHANGESET.mdexplore_outputs/IDEA_GATE.mdexplore_outputs/EXPERIMENT_PLAN.mdexplore_outputs/EXPERIMENT_MANIFEST.mdexplore_outputs/EXPERIMENT_LEDGER.mdexplore_outputs/TRANSPLANT_SMOKE_REPORT.mdexplore_outputs/TOP_RUNS.mdexplore_outputs/status.json
Notes
- Keep idea generation bounded and auditable rather than open-ended.
- Keep evaluation and SOTA inputs human-frozen.
IDEA_SEEDS.jsonshould expose per-seed bindings such ascontext_anchor,task_family_binding,dataset_binding,evaluation_binding, andconstraint_notes.IMPLEMENTATION_FIDELITY.jsonshould separateplanned_implementation_sites,heuristic_implementation_sites, andobserved_implementation_sites, and should recordverification_levelas one ofnot_checked,planned_only,heuristic_only,executor_observed, ordiff_verified.- Let
ai-research-explorefocus on understanding, gating, implementation planning, controlled execution, and comparison.
Smoke Validation Policy
ai-research-explore should prefer cheap, auditable smoke checks before broader exploratory execution.
Required Checks
- syntax parse for candidate Python files
- import-resolution style sanity for touched modules
- config path resolution for frozen commands
- constructor surface availability
- forward surface availability
- short-run command verification or explicit planned state
Reporting
- write a standardized
TRANSPLANT_SMOKE_REPORT.md - record per-check status and blockers
- distinguish
plannedfrompassedandfailed
Guardrails
- smoke success does not imply trusted correctness
- smoke success does not imply global benchmark validity
- smoke failure should block broader candidate execution when the blocker is structural
Source Mapping Policy
Source mapping in ai-research-explore is for bounded, auditable adaptation only.
Required Outputs
- source repo or source reference id
- source file and source symbol when known
- target file and target symbol
- supporting files
- interface diff summary
- minimal reversible patch plan
Patch Classes
config-onlyimport-gluemodule-transplant-shim
These classes are preferred in that order.
Forbidden Defaults
- broad rewrites
- train-loop redesign
- metric or leaderboard mutation without explicit campaign permission
- unscoped multi-module behavior changes
Sources Naming Policy
All internal research lookup results for ai-research-explore are saved under sources/.
Naming
- summary/index stay at
sources/ - canonical source records live under
sources/records/ - record filename format:
kind__slug__sha12.(json|md) kindshould be stable across reruns such aspaper,repo,benchmark,module,queryslugshould be lowercase and human-readablesha12should be derived from the normalized lookup payload
Cache Rules
- check
sources/index.jsonbefore creating a new record - reuse an existing record if the normalized payload hash matches
- preserve source URLs, titles, and lookup queries for auditability
Scope
sources/is an exploratory audit trail, not a trusted literature database- source cache entries must not imply benchmark completeness
- source cache entries must not imply novelty proof
"""Internal lookup helpers for ai-research-explore."""
from .cache_store import load_cache_index, store_records
from .inventory_writer import write_source_inventory, write_sources_summary
from .normalizers import detect_locator, ensure_http_url
from .repo_extractors import extract_repo_local_seeds
from .source_support import build_source_support, write_source_support
__all__ = [
"build_source_support",
"detect_locator",
"ensure_http_url",
"extract_repo_local_seeds",
"load_cache_index",
"store_records",
"write_source_inventory",
"write_source_support",
"write_sources_summary",
]
"""Cache/index helpers for source records."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List
from .normalizers import slugify, stable_digest, stable_filename
from .record_schema import normalize_record, record_priority
def _normalized_id_from_index_item(item: Dict[str, Any]) -> str:
if item.get("normalized_id"):
return str(item.get("normalized_id"))
provider_type = str(item.get("provider_type") or "seed")
identifier = str(item.get("provider_identifier") or item.get("source_url") or item.get("query") or "")
return f"{provider_type}:{identifier}".strip(":")
def load_cache_index(sources_dir: Path) -> Dict[str, Any]:
index_path = sources_dir / "index.json"
if not index_path.exists():
return {
"schema_version": "2.0",
"mode": "free-first-cache-first",
"records_dir": "sources/records",
"records": [],
"record_lookup": {},
}
payload = json.loads(index_path.read_text(encoding="utf-8"))
records = payload.get("records", [])
payload["record_lookup"] = {
_normalized_id_from_index_item(item): dict(item)
for item in records
if _normalized_id_from_index_item(item)
}
return payload
def _merge_lists(left: Iterable[Any], right: Iterable[Any]) -> List[Any]:
merged: List[Any] = []
for item in list(left) + list(right):
if item not in merged and item not in ("", None, []):
merged.append(item)
return merged
def _prefer_value(existing: Any, incoming: Any) -> Any:
if incoming not in ("", None, [], {}):
return incoming
return existing
def merge_records(existing: Dict[str, Any], incoming: Dict[str, Any]) -> Dict[str, Any]:
normalized_existing = normalize_record(existing)
normalized_incoming = normalize_record(incoming)
if record_priority(normalized_incoming) >= record_priority(normalized_existing):
primary, secondary = normalized_incoming, normalized_existing
else:
primary, secondary = normalized_existing, normalized_incoming
merged = dict(primary)
for key in (
"title",
"summary",
"url",
"venue",
"repo_full_name",
"doi",
"arxiv_id",
"source_repo",
"source_file",
"source_symbol",
):
merged[key] = _prefer_value(secondary.get(key), primary.get(key))
merged["authors"] = _merge_lists(secondary.get("authors", []), primary.get("authors", []))
merged["origins"] = _merge_lists(secondary.get("origins", []), primary.get("origins", []))
merged["extracted_from_repo_paths"] = _merge_lists(
secondary.get("extracted_from_repo_paths", []),
primary.get("extracted_from_repo_paths", []),
)
merged["selection_hints"] = _merge_lists(secondary.get("selection_hints", []), primary.get("selection_hints", []))
merged["provider_metadata"] = {**secondary.get("provider_metadata", {}), **primary.get("provider_metadata", {})}
return normalize_record(merged)
def store_records(sources_dir: Path, records: Iterable[Dict[str, Any]]) -> Dict[str, Any]:
sources_dir.mkdir(parents=True, exist_ok=True)
records_dir = sources_dir / "records"
records_dir.mkdir(parents=True, exist_ok=True)
existing_index = load_cache_index(sources_dir)
existing_lookup = existing_index.get("record_lookup", {})
stored_by_id: Dict[str, Dict[str, Any]] = {}
cache_hits = 0
cache_misses = 0
merge_upgrades = 0
for raw_record in records:
record = normalize_record(raw_record)
normalized_id = str(record.get("normalized_id") or "")
if not normalized_id:
identity = {
"source_type": record.get("source_type"),
"provider_type": record.get("provider_type"),
"locator_type": record.get("locator_type"),
"raw_locator": record.get("raw_locator"),
"url": record.get("url"),
"title": record.get("title"),
}
normalized_id = f"seed:{stable_digest(identity)[:16]}"
record["normalized_id"] = normalized_id
if normalized_id in stored_by_id:
stored_by_id[normalized_id] = merge_records(stored_by_id[normalized_id], record)
continue
existing = existing_lookup.get(normalized_id)
if existing:
cache_hits += 1
existing_rel = str(existing.get("artifact_path") or "")
existing_path = None
if existing_rel.startswith("sources/"):
existing_path = sources_dir / Path(existing_rel).relative_to("sources")
existing_payload = dict(existing)
if existing_path and existing_path.exists():
existing_payload = json.loads(existing_path.read_text(encoding="utf-8"))
merged = merge_records(existing_payload, record)
if record_priority(merged) > record_priority(normalize_record(existing_payload)):
merge_upgrades += 1
merged["cache_hit"] = True
stored_by_id[normalized_id] = merged
else:
cache_misses += 1
record["cache_hit"] = False
stored_by_id[normalized_id] = record
timestamp = datetime.now(timezone.utc).isoformat()
stored_records: List[Dict[str, Any]] = []
index_records: List[Dict[str, Any]] = []
for normalized_id in sorted(stored_by_id):
record = normalize_record(stored_by_id[normalized_id])
if not record.get("resolved_at"):
record["resolved_at"] = timestamp
digest = stable_digest(
{
"normalized_id": normalized_id,
"provider_type": record.get("provider_type"),
"source_type": record.get("source_type"),
}
)
source_id = record.get("source_id") or f"{record.get('source_type', 'source')}:{digest[:8]}"
record["source_id"] = source_id
slug = slugify(record.get("title") or normalized_id)
filename = stable_filename(str(record.get("source_type") or "source"), slug, digest)
artifact_path = records_dir / filename
record["artifact_path"] = f"sources/records/{filename}"
record["artifact_abspath"] = str(artifact_path)
record["digest"] = digest
artifact_path.write_text(
json.dumps({"schema_version": "2.0", **record}, indent=2, ensure_ascii=False),
encoding="utf-8",
)
stored_records.append(record)
index_records.append(
{
"source_id": source_id,
"source_type": record.get("source_type"),
"provider_type": record.get("provider_type"),
"locator_type": record.get("locator_type"),
"raw_locator": record.get("raw_locator"),
"normalized_id": normalized_id,
"title": record.get("title"),
"url": record.get("url"),
"repo_full_name": record.get("repo_full_name"),
"doi": record.get("doi"),
"arxiv_id": record.get("arxiv_id"),
"evidence_class": record.get("evidence_class"),
"evidence_weight": record.get("evidence_weight"),
"parse_status": record.get("parse_status"),
"cache_hit": record.get("cache_hit"),
"artifact_path": record.get("artifact_path"),
"source_repo": record.get("source_repo"),
"source_file": record.get("source_file"),
"source_symbol": record.get("source_symbol"),
"resolved_at": record.get("resolved_at"),
}
)
index_payload = {
"schema_version": "2.0",
"mode": "free-first-cache-first",
"records_dir": "sources/records",
"records": index_records,
"stats": {
"record_count": len(index_records),
"cache_hits": cache_hits,
"cache_misses": cache_misses,
"merge_upgrades": merge_upgrades,
},
}
index_path = sources_dir / "index.json"
index_path.write_text(json.dumps(index_payload, indent=2, ensure_ascii=False), encoding="utf-8")
return {
"index_path": str(index_path),
"records_dir": str(records_dir),
"records": stored_records,
"cache_hits": cache_hits,
"cache_misses": cache_misses,
"merge_upgrades": merge_upgrades,
}
"""Human-readable lookup inventory writers."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, Sequence
def write_sources_summary(sources_dir: Path, records: Sequence[Dict[str, Any]]) -> Path:
lines = [
"# Sources Summary",
"",
"Research lookup for `ai-research-explore` is free-first, cache-first, and auditable.",
"",
"## Cached Records",
"",
]
if not records:
lines.append("- None.")
else:
for item in records:
triple = " / ".join(
part
for part in [
str(item.get("source_repo") or ""),
str(item.get("source_file") or ""),
str(item.get("source_symbol") or ""),
]
if part
) or "no-source-triple"
lines.append(
f"- `{item.get('source_id')}` `{item.get('provider_type')}` `{item.get('title')}` -> `{item.get('artifact_path')}` evidence={item.get('evidence_class', 'unknown')} triple={triple}"
)
summary_path = sources_dir / "SUMMARY.md"
summary_path.write_text("\n".join(lines), encoding="utf-8")
return summary_path
def write_source_inventory(
analysis_output_dir: Path,
*,
records: Sequence[Dict[str, Any]],
repo_local_extractions: Sequence[Dict[str, Any]],
cache_stats: Dict[str, Any],
) -> Path:
by_class: Dict[str, int] = {}
for item in records:
key = str(item.get("evidence_class") or "unknown")
by_class[key] = by_class.get(key, 0) + 1
lines = [
"# Source Inventory",
"",
"Human-readable inventory for free-first, provider-optional research lookup.",
"",
"## Evidence Breakdown",
"",
]
if by_class:
for key in sorted(by_class):
lines.append(f"- `{key}`: {by_class[key]}")
else:
lines.append("- None.")
lines.extend(
[
"",
"## Cache Stats",
"",
f"- Cache hits: {cache_stats.get('cache_hits', 0)}",
f"- Cache misses: {cache_stats.get('cache_misses', 0)}",
f"- Merge upgrades: {cache_stats.get('merge_upgrades', 0)}",
"",
"## Repo-local Extractions",
"",
]
)
if repo_local_extractions:
for item in repo_local_extractions[:20]:
paths = ", ".join(item.get("extracted_from_repo_paths", [])) or "unknown-path"
lines.append(f"- `{item.get('raw_locator') or item.get('query')}` from `{paths}`")
else:
lines.append("- None.")
lines.extend(["", "## Resolved Records", ""])
if records:
for item in records:
lines.append(
f"- `{item.get('source_id')}` `{item.get('source_type')}` `{item.get('provider_type')}` `{item.get('title')}` evidence=`{item.get('evidence_class')}`"
)
else:
lines.append("- None.")
path = analysis_output_dir / "SOURCE_INVENTORY.md"
path.write_text("\n".join(lines), encoding="utf-8")
return path
"""Locator parsing and normalization helpers for research lookup."""
from __future__ import annotations
import hashlib
import json
import re
import urllib.parse
from typing import Any, Dict, Optional
ARXIV_ID_RE = re.compile(r"(?:arxiv:|arxiv\.org/(?:abs|pdf)/)?(?P<id>\d{4}\.\d{4,5}(?:v\d+)?)", re.IGNORECASE)
DOI_RE = re.compile(r"(?:https?://(?:dx\.)?doi\.org/)?(?P<doi>10\.\d{4,9}/[-._;()/:A-Z0-9]+)", re.IGNORECASE)
GITHUB_URL_RE = re.compile(
r"^https?://github\.com/(?P<owner>[^/\s]+)/(?P<repo>[^/\s#]+)(?:/(?P<rest>.*))?$",
re.IGNORECASE,
)
HTTP_URL_RE = re.compile(r"^https?://", re.IGNORECASE)
URL_RE = re.compile(r"https?://[^\s<>\]\"')]+", re.IGNORECASE)
def slugify(value: str) -> str:
slug = re.sub(r"[^a-z0-9]+", "-", str(value).lower()).strip("-")
return slug[:48] or "source"
def stable_digest(payload: Dict[str, Any]) -> str:
text = json.dumps(payload, sort_keys=True, ensure_ascii=False)
return hashlib.sha1(text.encode("utf-8")).hexdigest()
def stable_filename(kind: str, slug: str, digest: str, suffix: str = "json") -> str:
return f"{kind}__{slug}__{digest[:12]}.{suffix}"
def ensure_http_url(value: str) -> str:
text = str(value or "").strip()
if not text:
return ""
if HTTP_URL_RE.match(text):
return text
if text.lower().startswith("doi:"):
return f"https://doi.org/{text[4:].strip()}"
return text
def canonicalize_url(value: str) -> str:
text = ensure_http_url(value)
if not text:
return ""
parsed = urllib.parse.urlsplit(text)
path = parsed.path or "/"
return urllib.parse.urlunsplit(
(
parsed.scheme.lower(),
parsed.netloc.lower(),
path.rstrip("/") or "/",
parsed.query,
"",
)
)
def extract_urls(text: str) -> list[str]:
found: list[str] = []
for match in URL_RE.finditer(str(text or "")):
url = match.group(0).rstrip(".,);]")
if url not in found:
found.append(url)
return found
def parse_arxiv_locator(locator: str) -> Optional[Dict[str, Any]]:
text = str(locator or "").strip()
match = ARXIV_ID_RE.search(text)
if not match:
return None
arxiv_id = match.group("id")
locator_type = "arxiv_url" if "arxiv.org" in text.lower() else "arxiv_id"
return {
"provider_type": "arxiv",
"source_type": "paper",
"locator_type": locator_type,
"raw_locator": text,
"normalized_id": f"arxiv:{arxiv_id.lower()}",
"identifier": arxiv_id,
"arxiv_id": arxiv_id,
"url": f"https://arxiv.org/abs/{arxiv_id}",
}
def parse_doi_locator(locator: str) -> Optional[Dict[str, Any]]:
text = str(locator or "").strip()
match = DOI_RE.search(text)
if not match:
return None
doi = match.group("doi").lower()
locator_type = "doi_url" if "doi.org" in text.lower() else "doi"
return {
"provider_type": "doi",
"source_type": "paper",
"locator_type": locator_type,
"raw_locator": text,
"normalized_id": f"doi:{doi}",
"identifier": doi,
"doi": doi,
"url": f"https://doi.org/{doi}",
}
def parse_github_repo_locator(locator: str) -> Optional[Dict[str, Any]]:
text = canonicalize_url(locator)
match = GITHUB_URL_RE.match(text)
if not match:
return None
owner = match.group("owner")
repo = (match.group("repo") or "").removesuffix(".git")
rest = match.group("rest") or ""
source_file = ""
if rest.startswith("blob/"):
parts = rest.split("/", 3)
if len(parts) == 4:
source_file = parts[3]
return {
"provider_type": "github",
"source_type": "repo",
"locator_type": "github_repo_url",
"raw_locator": str(locator or "").strip(),
"normalized_id": f"github:{owner.lower()}/{repo.lower()}",
"identifier": f"{owner}/{repo}",
"repo_full_name": f"{owner}/{repo}",
"owner": owner,
"repo": repo,
"source_file": source_file,
"url": f"https://github.com/{owner}/{repo}",
}
def parse_generic_url(locator: str) -> Optional[Dict[str, Any]]:
text = canonicalize_url(locator)
if not HTTP_URL_RE.match(text):
return None
parsed = urllib.parse.urlsplit(text)
return {
"provider_type": "url",
"source_type": "web",
"locator_type": "url",
"raw_locator": str(locator or "").strip(),
"normalized_id": f"url:{text}",
"identifier": text,
"host": parsed.netloc.lower(),
"url": text,
}
def detect_locator(locator: str) -> Optional[Dict[str, Any]]:
for parser in (parse_github_repo_locator, parse_arxiv_locator, parse_doi_locator, parse_generic_url):
parsed = parser(locator)
if parsed:
return parsed
return None
"""Provider adapters for free-first research lookup."""
from .arxiv_provider import resolve_arxiv_record
from .doi_provider import resolve_doi_record
from .github_provider import resolve_github_record
from .optional_provider import resolve_optional_record
from .url_provider import resolve_url_record
__all__ = [
"resolve_arxiv_record",
"resolve_doi_record",
"resolve_github_record",
"resolve_optional_record",
"resolve_url_record",
]
"""Free arXiv metadata provider."""
from __future__ import annotations
import urllib.parse
import xml.etree.ElementTree as ET
from typing import Any, Dict
from .base import http_get
def resolve_arxiv_record(locator_info: Dict[str, Any]) -> Dict[str, Any]:
arxiv_id = str(locator_info.get("arxiv_id") or locator_info.get("identifier") or "").strip()
record = {
"provider_type": "arxiv",
"source_type": "paper",
"locator_type": locator_info.get("locator_type", "arxiv_id"),
"raw_locator": locator_info.get("raw_locator", ""),
"normalized_id": locator_info.get("normalized_id", f"arxiv:{arxiv_id.lower()}"),
"title": f"arXiv:{arxiv_id}" if arxiv_id else "",
"url": locator_info.get("url", ""),
"authors": [],
"year": None,
"venue": "arXiv",
"doi": "",
"arxiv_id": arxiv_id,
"parse_status": "parsed-only",
"fetch_status": "parsed-only",
"evidence_class": "parsed_locator",
"provider_metadata": {"resolved_via": "arxiv"},
}
if not arxiv_id:
return record
try:
payload = http_get(
f"https://export.arxiv.org/api/query?id_list={urllib.parse.quote(arxiv_id)}",
accept="application/atom+xml, text/xml;q=0.9",
)
root = ET.fromstring(payload.decode("utf-8", errors="ignore"))
namespace = {"atom": "http://www.w3.org/2005/Atom"}
entry = root.find("atom:entry", namespace)
if entry is None:
return {**record, "parse_status": "fetch-failed", "fetch_status": "fetch-failed"}
authors = [
(node.findtext("atom:name", default="", namespaces=namespace) or "").strip()
for node in entry.findall("atom:author", namespace)
if (node.findtext("atom:name", default="", namespaces=namespace) or "").strip()
]
published = (entry.findtext("atom:published", default="", namespaces=namespace) or "").strip()
year = int(published[:4]) if published[:4].isdigit() else None
url = record["url"]
for item in entry.findall("atom:link", namespace):
href = item.attrib.get("href", "")
if href:
url = href
break
return {
**record,
"title": (entry.findtext("atom:title", default="", namespaces=namespace) or "").strip() or record["title"],
"summary": (entry.findtext("atom:summary", default="", namespaces=namespace) or "").strip(),
"authors": authors,
"year": year,
"url": url,
"parse_status": "resolved",
"fetch_status": "network-fetched",
"evidence_class": "external_provider",
}
except Exception:
return {**record, "parse_status": "fetch-failed", "fetch_status": "fetch-failed"}
"""Shared transport and HTML helpers for lookup providers."""
from __future__ import annotations
import json
import urllib.request
from html.parser import HTMLParser
from typing import Any, Dict, List, Optional, Tuple
REQUEST_TIMEOUT_SECONDS = 6
USER_AGENT = "ai-research-explore-lookup/2.0"
class MetadataHTMLParser(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.in_title = False
self.title_parts: List[str] = []
self.meta: Dict[str, str] = {}
self.links: Dict[str, str] = {}
def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
lowered = tag.lower()
attr_map = {key.lower(): (value or "") for key, value in attrs}
if lowered == "title":
self.in_title = True
if lowered == "meta":
name = attr_map.get("name") or attr_map.get("property")
content = attr_map.get("content", "").strip()
if name and content:
self.meta[name.lower()] = content
if lowered == "link":
rel = attr_map.get("rel", "").lower()
href = attr_map.get("href", "").strip()
if rel and href:
self.links[rel] = href
def handle_endtag(self, tag: str) -> None:
if tag.lower() == "title":
self.in_title = False
def handle_data(self, data: str) -> None:
if self.in_title and data.strip():
self.title_parts.append(data.strip())
def title_text(self) -> str:
return " ".join(self.title_parts).strip()
def description_text(self) -> str:
for key in ("og:description", "description", "twitter:description"):
if self.meta.get(key):
return self.meta[key]
return ""
def canonical_url(self) -> str:
return self.links.get("canonical") or self.meta.get("og:url", "")
def http_get(url: str, *, accept: str = "application/json, text/plain;q=0.9, text/html;q=0.8") -> bytes:
request = urllib.request.Request(
url,
headers={
"User-Agent": USER_AGENT,
"Accept": accept,
},
)
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response:
return response.read()
def http_get_json(url: str, *, accept: str = "application/json") -> Dict[str, Any]:
payload = http_get(url, accept=accept)
loaded = json.loads(payload.decode("utf-8", errors="ignore"))
return loaded if isinstance(loaded, dict) else {}
def coerce_author_list(values: Any) -> List[str]:
authors: List[str] = []
if isinstance(values, list):
for item in values:
if isinstance(item, dict):
given = str(item.get("given") or "").strip()
family = str(item.get("family") or "").strip()
full = " ".join(part for part in [given, family] if part).strip()
if full:
authors.append(full)
elif str(item).strip():
authors.append(str(item).strip())
return authors
"""Free DOI metadata provider."""
from __future__ import annotations
import json
import urllib.parse
from typing import Any, Dict
from .base import coerce_author_list, http_get
def resolve_doi_record(locator_info: Dict[str, Any]) -> Dict[str, Any]:
doi = str(locator_info.get("doi") or locator_info.get("identifier") or "").strip().lower()
record = {
"provider_type": "doi",
"source_type": "paper",
"locator_type": locator_info.get("locator_type", "doi"),
"raw_locator": locator_info.get("raw_locator", ""),
"normalized_id": locator_info.get("normalized_id", f"doi:{doi}"),
"title": f"DOI:{doi}" if doi else "",
"url": locator_info.get("url", f"https://doi.org/{doi}" if doi else ""),
"authors": [],
"year": None,
"venue": "",
"doi": doi,
"arxiv_id": "",
"parse_status": "parsed-only",
"fetch_status": "parsed-only",
"evidence_class": "parsed_locator",
"provider_metadata": {"resolved_via": "doi"},
}
if not doi:
return record
try:
payload = http_get(
f"https://doi.org/{urllib.parse.quote(doi, safe='/')}",
accept="application/vnd.citationstyles.csl+json, application/json;q=0.9",
)
loaded = json.loads(payload.decode("utf-8", errors="ignore"))
title = loaded.get("title")
if isinstance(title, list):
title = title[0] if title else ""
venue = loaded.get("container-title")
if isinstance(venue, list):
venue = venue[0] if venue else ""
year = None
issued = loaded.get("issued") or {}
date_parts = issued.get("date-parts") if isinstance(issued, dict) else None
if isinstance(date_parts, list) and date_parts and isinstance(date_parts[0], list) and date_parts[0]:
value = date_parts[0][0]
year = int(value) if isinstance(value, int) or (isinstance(value, str) and str(value).isdigit()) else None
return {
**record,
"title": str(title or record["title"]),
"summary": str(loaded.get("abstract") or ""),
"authors": coerce_author_list(loaded.get("author")),
"year": year,
"venue": str(venue or loaded.get("publisher") or ""),
"url": str(loaded.get("URL") or record["url"]),
"parse_status": "resolved",
"fetch_status": "network-fetched",
"evidence_class": "external_provider",
"provider_metadata": {
"resolved_via": "doi",
"publisher": loaded.get("publisher"),
"type": loaded.get("type"),
},
}
except Exception:
return {**record, "parse_status": "fetch-failed", "fetch_status": "fetch-failed"}
"""Free GitHub repository metadata provider."""
from __future__ import annotations
import base64
from typing import Any, Dict, List
from lookup.normalizers import extract_urls
from .base import http_get_json
def _paper_links(links: List[str]) -> List[str]:
return [
link
for link in links
if "arxiv.org" in link.lower() or "doi.org" in link.lower() or "openreview.net" in link.lower()
]
def _fetch_readme(owner: str, repo: str) -> Dict[str, Any]:
try:
payload = http_get_json(f"https://api.github.com/repos/{owner}/{repo}/readme")
except Exception:
return {"readme_links": [], "paper_links_in_readme": []}
content = payload.get("content")
if not content:
return {"readme_links": [], "paper_links_in_readme": []}
try:
decoded = base64.b64decode(str(content).encode("utf-8"), validate=False).decode("utf-8", errors="ignore")
except Exception:
return {"readme_links": [], "paper_links_in_readme": []}
links = extract_urls(decoded)
return {
"readme_links": links,
"paper_links_in_readme": _paper_links(links),
}
def resolve_github_record(locator_info: Dict[str, Any]) -> Dict[str, Any]:
repo_full_name = str(locator_info.get("repo_full_name") or locator_info.get("identifier") or "").strip()
owner = str(locator_info.get("owner") or "").strip()
repo = str(locator_info.get("repo") or "").strip()
record = {
"provider_type": "github",
"source_type": "repo",
"locator_type": locator_info.get("locator_type", "github_repo_url"),
"raw_locator": locator_info.get("raw_locator", ""),
"normalized_id": locator_info.get("normalized_id", f"github:{repo_full_name.lower()}"),
"title": repo_full_name,
"url": locator_info.get("url", ""),
"authors": [],
"year": None,
"venue": "GitHub",
"repo_full_name": repo_full_name,
"doi": "",
"arxiv_id": "",
"source_file": locator_info.get("source_file", ""),
"parse_status": "parsed-only",
"fetch_status": "parsed-only",
"evidence_class": "parsed_locator",
"provider_metadata": {"resolved_via": "github"},
}
if not owner or not repo:
return record
try:
payload = http_get_json(f"https://api.github.com/repos/{owner}/{repo}")
readme_meta = _fetch_readme(owner, repo)
return {
**record,
"title": str(payload.get("full_name") or record["title"]),
"summary": str(payload.get("description") or ""),
"url": str(payload.get("html_url") or record["url"]),
"repo_full_name": str(payload.get("full_name") or repo_full_name),
"parse_status": "resolved",
"fetch_status": "network-fetched",
"evidence_class": "external_provider",
"provider_metadata": {
"resolved_via": "github",
"default_branch": payload.get("default_branch"),
"homepage": payload.get("homepage"),
"license": (payload.get("license") or {}).get("spdx_id"),
"stargazers_count": payload.get("stargazers_count"),
**readme_meta,
},
}
except Exception:
return {**record, "parse_status": "fetch-failed", "fetch_status": "fetch-failed"}
"""Optional paid-provider adapter placeholder."""
from __future__ import annotations
import os
from typing import Any, Dict, Optional
OPTIONAL_PROVIDER_ENV_VARS = {
"openrouter": "RESEARCH_LOOKUP_OPENROUTER_API_KEY",
"perplexity": "RESEARCH_LOOKUP_PERPLEXITY_API_KEY",
"parallel": "RESEARCH_LOOKUP_PARALLEL_API_KEY",
}
def resolve_optional_record(locator_info: Dict[str, Any], lookup_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
configured = lookup_config.get("optional_providers") if isinstance(lookup_config, dict) else None
providers = [str(item).strip().lower() for item in (configured or []) if str(item).strip()]
for provider_name in providers:
env_name = OPTIONAL_PROVIDER_ENV_VARS.get(provider_name)
if env_name and os.environ.get(env_name):
return None
return None
"""Generic URL metadata provider."""
from __future__ import annotations
import urllib.parse
from typing import Any, Dict
from lookup.normalizers import canonicalize_url
from .base import MetadataHTMLParser, http_get
def resolve_url_record(locator_info: Dict[str, Any]) -> Dict[str, Any]:
url = canonicalize_url(locator_info.get("url") or locator_info.get("raw_locator") or "")
parsed = urllib.parse.urlsplit(url) if url else None
record = {
"provider_type": "url",
"source_type": "web",
"locator_type": locator_info.get("locator_type", "url"),
"raw_locator": locator_info.get("raw_locator", ""),
"normalized_id": locator_info.get("normalized_id", f"url:{url}" if url else ""),
"title": url,
"url": url,
"authors": [],
"year": None,
"venue": parsed.netloc if parsed else "",
"repo_full_name": "",
"doi": "",
"arxiv_id": "",
"parse_status": "parsed-only",
"fetch_status": "parsed-only",
"evidence_class": "parsed_locator",
"provider_metadata": {"resolved_via": "url", "host": parsed.netloc.lower() if parsed else ""},
}
if not url:
return record
try:
payload = http_get(url, accept="text/html, application/xhtml+xml;q=0.9")
parser = MetadataHTMLParser()
parser.feed(payload.decode("utf-8", errors="ignore"))
canonical = parser.canonical_url() or url
return {
**record,
"title": parser.meta.get("og:title") or parser.title_text() or url,
"summary": parser.description_text(),
"url": canonicalize_url(canonical),
"parse_status": "resolved",
"fetch_status": "network-fetched",
"evidence_class": "external_provider",
"provider_metadata": {
**record["provider_metadata"],
"canonical_url": canonicalize_url(canonical),
},
}
except Exception:
return {**record, "parse_status": "fetch-failed", "fetch_status": "fetch-failed"}
"""Lookup record schema helpers."""
from __future__ import annotations
from typing import Any, Dict
EVIDENCE_CLASS_ALIASES = {
"external-provider": "external_provider",
"parsed-locator": "parsed_locator",
"repo-local-extracted": "repo_local_extracted",
"seed-only": "seed_only",
}
EVIDENCE_CLASS_PRIORITY = {
"seed_only": 0,
"repo_local_extracted": 1,
"parsed_locator": 2,
"external_provider": 3,
}
DEFAULT_RECORD_FIELDS = {
"source_type": "web",
"provider_type": "seed",
"locator_type": "seed",
"raw_locator": "",
"normalized_id": "",
"title": "",
"summary": "",
"url": "",
"authors": [],
"year": None,
"venue": "",
"repo_full_name": "",
"doi": "",
"arxiv_id": "",
"evidence_class": "seed_only",
"evidence_weight": 0.2,
"resolved_at": "",
"cache_hit": False,
"parse_status": "seed-only",
"fetch_status": "seed-only",
"provider_metadata": {},
"source_repo": "",
"source_file": "",
"source_symbol": "",
"origins": [],
"extracted_from_repo_paths": [],
"selection_hints": [],
}
def normalize_evidence_class(value: Any, default: str = "seed_only") -> str:
text = str(value or "").strip().lower().replace("-", "_")
text = EVIDENCE_CLASS_ALIASES.get(text, text)
if text in EVIDENCE_CLASS_PRIORITY:
return text
return default
def evidence_weight_for_class(evidence_class: Any, parse_status: Any = "") -> float:
normalized = normalize_evidence_class(evidence_class)
status = str(parse_status or "").strip().lower()
if normalized == "external_provider":
return 1.0 if status in {"resolved", "network-fetched"} else 0.9
if normalized == "parsed_locator":
return 0.65
if normalized == "repo_local_extracted":
return 0.45
return 0.2
def metadata_completeness(record: Dict[str, Any]) -> int:
score = 0
for key in ("title", "summary", "url", "repo_full_name", "doi", "arxiv_id", "venue"):
if record.get(key):
score += 1
authors = record.get("authors")
if isinstance(authors, list) and authors:
score += 1
if record.get("year"):
score += 1
return score
def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]:
normalized = {**DEFAULT_RECORD_FIELDS, **record}
normalized["evidence_class"] = normalize_evidence_class(normalized.get("evidence_class"))
normalized["evidence_weight"] = round(
float(
normalized.get("evidence_weight")
or evidence_weight_for_class(normalized["evidence_class"], normalized.get("parse_status"))
),
4,
)
normalized["authors"] = [str(item) for item in normalized.get("authors", []) if str(item).strip()]
normalized["origins"] = [str(item) for item in normalized.get("origins", []) if str(item).strip()]
normalized["extracted_from_repo_paths"] = [
str(item).replace("\\", "/")
for item in normalized.get("extracted_from_repo_paths", [])
if str(item).strip()
]
normalized["selection_hints"] = [str(item) for item in normalized.get("selection_hints", []) if str(item).strip()]
return normalized
def record_priority(record: Dict[str, Any]) -> tuple[int, int]:
normalized = normalize_record(record)
return (
EVIDENCE_CLASS_PRIORITY.get(normalized["evidence_class"], 0),
metadata_completeness(normalized),
)
"""Repo-local source extraction for free-first research lookup."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List
from .normalizers import ARXIV_ID_RE, DOI_RE, detect_locator, extract_urls
IGNORED_DIRS = {
".git",
".venv",
"__pycache__",
"node_modules",
"dist",
"build",
".mypy_cache",
".pytest_cache",
}
def _candidate_paths(repo_path: Path) -> List[Path]:
patterns = [
"README*",
"*.md",
"*.rst",
"*.yaml",
"*.yml",
"*.toml",
"*.ini",
"*.py",
]
results: List[Path] = []
seen: set[Path] = set()
for pattern in patterns:
for path in repo_path.rglob(pattern):
if not path.is_file():
continue
if any(part in IGNORED_DIRS for part in path.relative_to(repo_path).parts):
continue
if path.stat().st_size > 256_000:
continue
if path in seen:
continue
seen.add(path)
results.append(path)
return sorted(results)[:120]
def _extract_locators(text: str) -> List[str]:
found: List[str] = []
for url in extract_urls(text):
if url not in found:
found.append(url)
for pattern in (ARXIV_ID_RE, DOI_RE):
for match in pattern.finditer(text):
raw = match.group(0).strip()
if raw and raw not in found:
found.append(raw)
return found
def _classify_kind(locator: str) -> str:
parsed = detect_locator(locator)
if not parsed:
return "web"
source_type = parsed.get("source_type")
if source_type in {"paper", "repo", "web"}:
return str(source_type)
return "web"
def extract_repo_local_seeds(repo_path: Path) -> List[Dict[str, Any]]:
repo_root = Path(repo_path).resolve()
seeds: List[Dict[str, Any]] = []
seen_locators: set[str] = set()
for path in _candidate_paths(repo_root):
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
locators = _extract_locators(text)
relative_path = path.relative_to(repo_root).as_posix()
for locator in locators:
if locator in seen_locators:
continue
seen_locators.add(locator)
seeds.append(
{
"kind": _classify_kind(locator),
"title": locator,
"summary": f"Repo-local extracted source from `{relative_path}`.",
"query": locator,
"source_url": locator if locator.lower().startswith("http") else "",
"source_repo": "",
"source_file": "",
"source_symbol": "",
"origin": "repo_local_extracted",
"raw_locator": locator,
"extracted_from_repo_paths": [relative_path],
}
)
return seeds
"""Machine-readable source support artifacts for downstream passes."""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any, Dict, List, Sequence
from .record_schema import normalize_evidence_class
def _tokenize(value: Any) -> List[str]:
return [token for token in re.split(r"[^a-z0-9]+", str(value or "").lower()) if len(token) > 2]
def _record_haystack(record: Dict[str, Any]) -> str:
return " ".join(
[
str(record.get("title") or ""),
str(record.get("summary") or ""),
str(record.get("url") or ""),
str(record.get("repo_full_name") or ""),
str(record.get("doi") or ""),
str(record.get("arxiv_id") or ""),
str(record.get("source_repo") or ""),
str(record.get("source_file") or ""),
str(record.get("source_symbol") or ""),
]
).lower()
def _match_records(tokens: Sequence[str], records: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]:
matches: List[tuple[int, float, Dict[str, Any]]] = []
for record in records:
haystack = _record_haystack(record)
score = sum(1 for token in tokens if token in haystack)
if score > 0:
matches.append((score, float(record.get("evidence_weight") or 0.0), record))
matches.sort(key=lambda item: (-item[0], -item[1], item[2].get("source_id", "")))
return [record for _score, _weight, record in matches[:6]]
def build_source_support(
campaign: Dict[str, Any],
records: Sequence[Dict[str, Any]],
repo_local_extractions: Sequence[Dict[str, Any]],
cache_stats: Dict[str, Any],
) -> Dict[str, Any]:
by_evidence_class: Dict[str, List[str]] = {}
by_type: Dict[str, List[str]] = {}
for item in records:
evidence = normalize_evidence_class(item.get("evidence_class"))
by_evidence_class.setdefault(evidence, []).append(str(item.get("source_id")))
source_type = str(item.get("source_type") or "unknown")
by_type.setdefault(source_type, []).append(str(item.get("source_id")))
support_index_by_candidate_idea: Dict[str, Dict[str, Any]] = {}
support_index_by_target_component: Dict[str, Dict[str, Any]] = {}
for idea in campaign.get("candidate_ideas", []):
idea_id = str(idea.get("id") or "idea")
tokens = _tokenize(idea.get("summary")) + _tokenize(idea.get("target_component")) + _tokenize(idea.get("change_scope"))
matched = _match_records(tokens, records)
support_index_by_candidate_idea[idea_id] = {
"matched_source_ids": [item.get("source_id") for item in matched],
"matched_external_source_ids": [
item.get("source_id")
for item in matched
if normalize_evidence_class(item.get("evidence_class")) == "external_provider"
],
"matched_repo_local_source_ids": [
item.get("source_id")
for item in matched
if normalize_evidence_class(item.get("evidence_class")) == "repo_local_extracted"
],
"matched_parsed_locator_ids": [
item.get("source_id")
for item in matched
if normalize_evidence_class(item.get("evidence_class")) == "parsed_locator"
],
}
component = str(idea.get("target_component") or "unspecified")
support_index_by_target_component.setdefault(component, {"matched_source_ids": []})
for source_id in support_index_by_candidate_idea[idea_id]["matched_source_ids"]:
if source_id not in support_index_by_target_component[component]["matched_source_ids"]:
support_index_by_target_component[component]["matched_source_ids"].append(source_id)
return {
"schema_version": "1.0",
"records": list(records),
"records_by_evidence_class": by_evidence_class,
"records_by_type": by_type,
"support_index_by_candidate_idea": support_index_by_candidate_idea,
"support_index_by_target_component": support_index_by_target_component,
"repo_local_extractions": list(repo_local_extractions),
"cache_stats": cache_stats,
}
def write_source_support(analysis_output_dir: Path, support_bundle: Dict[str, Any]) -> Path:
path = analysis_output_dir / "SOURCE_SUPPORT.json"
path.write_text(json.dumps(support_bundle, indent=2, ensure_ascii=False), encoding="utf-8")
return path
"""Internal passes for ai-research-explore orchestration."""
from .atomic_idea_decomposition import run_atomic_idea_decomposition_pass
from .candidate_idea_generation import run_candidate_idea_generation_pass
from .execution_feasibility import run_execution_feasibility_pass
from .idea_cards import run_idea_card_pass
from .idea_ranking import run_idea_ranking_pass
from .implementation_fidelity import run_implementation_fidelity_pass
from .improvement_bank import run_improvement_bank_pass
from .lookup_sources import run_lookup_pass
from .source_mapping import run_source_mapping_pass
__all__ = [
"run_atomic_idea_decomposition_pass",
"run_candidate_idea_generation_pass",
"run_execution_feasibility_pass",
"run_idea_card_pass",
"run_idea_ranking_pass",
"run_implementation_fidelity_pass",
"run_improvement_bank_pass",
"run_lookup_pass",
"run_source_mapping_pass",
]
"""Atomic academic concept decomposition for ai-research-explore."""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any, Dict, List, Sequence
BLOCKED_SCOPES = {"unspecified", "broad_rewrite", "rewrite-everything"}
def tokenize(value: Any) -> List[str]:
return [token for token in re.split(r"[^a-z0-9]+", str(value or "").lower()) if len(token) > 2]
def unique_preserving(values: Sequence[str], *, limit: int | None = None) -> List[str]:
ordered: List[str] = []
for value in values:
if not value or value in ordered:
continue
ordered.append(value)
if limit is not None and len(ordered) >= limit:
break
return ordered
def humanize_slug(value: str) -> str:
text = str(value or "").replace("_", " ").replace("-", " ").strip()
return " ".join(part.capitalize() for part in text.split()) or "Atomic Concept"
def classify_surface(path: str) -> str:
lowered = str(path).lower()
if any(token in lowered for token in ("config", ".yaml", ".yml", ".json", ".toml", ".ini")):
return "config"
if any(token in lowered for token in ("data", "dataset", "loader", "transform")):
return "data interface"
if any(token in lowered for token in ("eval", "metric", "benchmark", "validation", "test")):
return "evaluation adapter"
if any(token in lowered for token in ("train", "trainer", "loss", "optim", "schedule")):
return "training"
return "model"
def formula_support(selected_idea: Dict[str, Any], lookup_bundle: Dict[str, Any]) -> List[Dict[str, Any]]:
source_ids = list(selected_idea.get("source_reference", []) or [])
records = {item.get("source_id"): item for item in lookup_bundle.get("records", []) if item.get("source_id")}
support: List[Dict[str, Any]] = []
for source_id in source_ids[:3]:
record = records.get(source_id) or {}
support.append(
{
"source_id": source_id,
"title": record.get("title") or "Unresolved source reference",
"evidence_class": record.get("evidence_class") or "unresolved",
"note": "Use this source only as bounded academic support; it is not a novelty proof.",
}
)
if not support:
support.append(
{
"source_id": "none",
"title": "No directly matched formula-level source",
"evidence_class": "none",
"note": "Concept remains grounded in repo-local constraints rather than directly verified source material.",
}
)
return support
def code_support(selected_idea: Dict[str, Any], source_mapping: Dict[str, Any]) -> List[Dict[str, Any]]:
selected_source = source_mapping.get("selected_source_record", {}) or {}
target_location_map = source_mapping.get("target_location_map", []) or []
support: List[Dict[str, Any]] = []
if selected_source:
support.append(
{
"source_id": selected_source.get("source_id") or "selected-source-record",
"source_repo": selected_source.get("source_repo") or "",
"source_file": selected_source.get("source_file") or "",
"source_symbol": selected_source.get("source_symbol") or "",
"note": "Candidate source triple for a bounded transplant or adaptation path.",
}
)
if target_location_map:
support.append(
{
"source_id": "repo-local-target-map",
"source_repo": "current-research",
"source_file": target_location_map[0].get("file") or "",
"source_symbol": target_location_map[0].get("target_symbol") or "",
"note": "Repo-local implementation target inferred from source mapping.",
}
)
if not support:
support.append(
{
"source_id": "none",
"source_repo": "current-research",
"source_file": "",
"source_symbol": "",
"note": "No concrete code support could be resolved.",
}
)
return support
def unit_validation_strategy(surface: str, selected_idea: Dict[str, Any], variant_spec: Dict[str, Any]) -> str:
if surface == "config":
return f"Verify that `{selected_idea.get('change_scope', 'candidate change')}` can be isolated through config or CLI overrides without touching the frozen eval contract."
if surface == "evaluation adapter":
return "Keep evaluation surfaces protected; validate only compatibility and do not change metric semantics."
if variant_spec.get("base_command"):
return f"Smoke the implementation through `{variant_spec['base_command']}` with a short-run gate before any broader candidate run."
return "Use a bounded static and short-run validation path before claiming the idea is implementable."
def implementation_risk(selected_idea: Dict[str, Any], surface: str) -> float:
base = float(selected_idea.get("implementation_risk") or 0.4)
if surface in {"training", "evaluation adapter"}:
base += 0.10
return max(0.0, min(1.0, round(base, 4)))
def scientific_meaning_risk(selected_idea: Dict[str, Any], surface: str) -> float:
base = float(selected_idea.get("eval_risk") or 0.3)
if surface == "evaluation adapter":
base += 0.20
if surface == "training":
base += 0.10
return max(0.0, min(1.0, round(base, 4)))
def build_atomic_units(
*,
selected_idea: Dict[str, Any],
analysis_data: Dict[str, Any],
source_mapping: Dict[str, Any],
lookup_bundle: Dict[str, Any],
variant_spec: Dict[str, Any],
) -> tuple[List[Dict[str, Any]], List[str]]:
blockers: List[str] = []
change_scope = str(selected_idea.get("change_scope") or "unspecified")
target_component = str(selected_idea.get("target_component") or "unspecified")
if change_scope in BLOCKED_SCOPES:
blockers.append("selected-idea-change-scope-too-broad-for-atomic-decomposition")
target_location_map = source_mapping.get("target_location_map", []) or []
module_candidates = source_mapping.get("module_candidates", []) or []
if not target_location_map and not module_candidates:
blockers.append("no-target-surface-for-atomic-decomposition")
code_files = unique_preserving(
[
str(item.get("file") or "")
for item in target_location_map
if str(item.get("role") or "") == "code"
]
+ [str(item.get("target_file") or "") for item in module_candidates],
limit=5,
)
config_files = unique_preserving(
[
str(item.get("file") or "")
for item in target_location_map
if str(item.get("role") or "") == "config"
]
+ list(analysis_data.get("config_binding_hints", [])),
limit=4,
)
code_symbols = unique_preserving(
[str(item.get("target_symbol") or "") for item in target_location_map if item.get("target_symbol")]
+ list(analysis_data.get("constructor_candidates", []))
+ list(analysis_data.get("forward_candidates", [])),
limit=6,
)
units: List[Dict[str, Any]] = []
if code_files:
surface = classify_surface(code_files[0])
units.append(
{
"atomic_id": f"{selected_idea.get('id', 'idea')}-atomic-01",
"concept_name": humanize_slug(change_scope or target_component),
"concept_summary": str(selected_idea.get("summary") or "Bounded implementation concept"),
"why_needed": f"Translate the selected idea into repo-local `{surface}` logic without broad architectural rewrites.",
"formula_support": formula_support(selected_idea, lookup_bundle),
"code_support": code_support(selected_idea, source_mapping),
"expected_code_surface": surface,
"target_file_candidates": code_files,
"target_symbol_candidates": code_symbols or unique_preserving([target_component], limit=3),
"validation_strategy": unit_validation_strategy(surface, selected_idea, variant_spec),
"implementation_risk": implementation_risk(selected_idea, surface),
"scientific_meaning_risk": scientific_meaning_risk(selected_idea, surface),
}
)
if config_files:
units.append(
{
"atomic_id": f"{selected_idea.get('id', 'idea')}-atomic-02",
"concept_name": f"{humanize_slug(change_scope)} Control Surface",
"concept_summary": f"Expose `{change_scope}` as a single-variable ablation surface rather than an entangled rewrite.",
"why_needed": "Keep attribution clear, rollback easy, and short-run feasibility auditable.",
"formula_support": formula_support(selected_idea, lookup_bundle),
"code_support": code_support(selected_idea, source_mapping),
"expected_code_surface": "config",
"target_file_candidates": config_files,
"target_symbol_candidates": unique_preserving(
list(analysis_data.get("config_binding_hints", [])) + [change_scope, target_component],
limit=6,
),
"validation_strategy": unit_validation_strategy("config", selected_idea, variant_spec),
"implementation_risk": implementation_risk(selected_idea, "config"),
"scientific_meaning_risk": scientific_meaning_risk(selected_idea, "config"),
}
)
if not units:
blockers.append("selected-idea-could-not-be-split-into-implementable-atomic-units")
return units, unique_preserving(blockers)
def write_atomic_markdown(output_dir: Path, payload: Dict[str, Any]) -> Path:
lines = [
"# Atomic Idea Map",
"",
f"- Status: `{payload.get('status', 'blocked')}`",
f"- Selected idea: `{payload.get('selected_idea_id', 'none')}`",
"",
"## Blockers",
"",
]
blockers = payload.get("blockers", [])
if blockers:
lines.extend(f"- {item}" for item in blockers)
else:
lines.append("- None.")
lines.extend(["", "## Atomic Units", ""])
atomic_units = payload.get("atomic_units", [])
if not atomic_units:
lines.append("- None.")
else:
for unit in atomic_units:
lines.extend(
[
f"### {unit['atomic_id']} - {unit['concept_name']}",
"",
f"- Summary: {unit['concept_summary']}",
f"- Why needed: {unit['why_needed']}",
f"- Expected code surface: `{unit['expected_code_surface']}`",
f"- Target file candidates: {', '.join(unit.get('target_file_candidates', [])) or 'none'}",
f"- Target symbol candidates: {', '.join(unit.get('target_symbol_candidates', [])) or 'none'}",
f"- Validation strategy: {unit['validation_strategy']}",
f"- Implementation risk: `{unit['implementation_risk']}`",
f"- Scientific meaning risk: `{unit['scientific_meaning_risk']}`",
"",
]
)
path = output_dir / "ATOMIC_IDEA_MAP.md"
path.write_text("\n".join(lines), encoding="utf-8")
return path
def run_atomic_idea_decomposition_pass(
*,
analysis_output_dir: Path,
selected_idea: Dict[str, Any],
analysis_data: Dict[str, Any],
source_mapping: Dict[str, Any],
lookup_bundle: Dict[str, Any],
current_research: str,
variant_spec: Dict[str, Any],
) -> Dict[str, Any]:
del current_research
atomic_units, blockers = build_atomic_units(
selected_idea=selected_idea,
analysis_data=analysis_data,
source_mapping=source_mapping,
lookup_bundle=lookup_bundle,
variant_spec=variant_spec,
)
payload = {
"schema_version": "1.0",
"status": "blocked" if blockers else "ready",
"selected_idea_id": str(selected_idea.get("id") or ""),
"atomic_units": atomic_units,
"atomic_unit_count": len(atomic_units),
"blockers": blockers,
}
json_path = analysis_output_dir / "ATOMIC_IDEA_MAP.json"
json_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
markdown_path = write_atomic_markdown(analysis_output_dir, payload)
return {
**payload,
"artifact_paths": [str(markdown_path), str(json_path)],
"artifact_path": str(json_path),
}
"""Candidate idea generation pass for ai-research-explore."""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any, Dict, List, Sequence, Tuple
DEFAULT_POLICY = {
"allow_synthesized_seed_ideas": True,
"max_generated_ideas": 3,
"require_diverse_targets": True,
}
REWRITE_RISK_TOKENS = {"rewrite", "architecture", "backbone", "full-model", "all-modules", "trainer-core"}
SKIP_COMPONENT_TOKENS = {"eval", "metric", "benchmark", "leaderboard"}
def clamp(value: Any, default: float = 0.5) -> float:
try:
numeric = float(value)
except (TypeError, ValueError):
numeric = default
return max(0.0, min(1.0, numeric))
def tokenize(value: Any) -> List[str]:
return [token for token in re.split(r"[^a-z0-9]+", str(value or "").lower()) if len(token) > 2]
def unique_preserving(values: Sequence[str], *, limit: int | None = None) -> List[str]:
ordered: List[str] = []
for value in values:
if not value or value in ordered:
continue
ordered.append(value)
if limit is not None and len(ordered) >= limit:
break
return ordered
def normalize_policy(raw: Any) -> Dict[str, Any]:
policy = dict(DEFAULT_POLICY)
if isinstance(raw, dict):
policy.update(raw)
policy["allow_synthesized_seed_ideas"] = bool(policy.get("allow_synthesized_seed_ideas", True))
try:
policy["max_generated_ideas"] = max(0, int(policy.get("max_generated_ideas", 3)))
except (TypeError, ValueError):
policy["max_generated_ideas"] = 3
policy["require_diverse_targets"] = bool(policy.get("require_diverse_targets", True))
return policy
def stringify_binding(value: Any) -> str:
if isinstance(value, dict):
for key in ("name", "id", "path", "label"):
if value.get(key):
return str(value[key])
items = [f"{key}={value[key]}" for key in sorted(value) if value.get(key) not in {None, ""}]
return ", ".join(items) or "unspecified"
if isinstance(value, list):
return ", ".join(str(item) for item in value if str(item).strip()) or "unspecified"
text = str(value or "").strip()
return text or "unspecified"
def evaluation_binding_text(evaluation_source: Dict[str, Any]) -> str:
command = str(evaluation_source.get("command") or "").strip()
path = str(evaluation_source.get("path") or "").strip()
metric = str(evaluation_source.get("primary_metric") or "").strip()
parts = []
if path:
parts.append(f"path={path}")
if command:
parts.append(f"command={command}")
if metric:
parts.append(f"metric={metric}")
return " | ".join(parts) or "unspecified"
def normalize_context_bindings(
*,
current_research: str,
task_family: str,
dataset: Any,
evaluation_source: Dict[str, Any],
) -> Dict[str, Any]:
dataset_binding = stringify_binding(dataset)
evaluation_binding = evaluation_binding_text(evaluation_source)
task_binding = str(task_family or "").strip() or "unspecified"
evaluation_tokens = unique_preserving(
tokenize(evaluation_source.get("command"))
+ tokenize(evaluation_source.get("path"))
+ tokenize(evaluation_source.get("primary_metric"))
+ tokenize(evaluation_source.get("split")),
limit=10,
)
task_tokens = unique_preserving(tokenize(task_binding) + tokenize(dataset_binding), limit=10)
return {
"context_anchor": str(current_research or "").strip() or "unspecified",
"task_family_binding": task_binding,
"dataset_binding": dataset_binding,
"evaluation_binding": evaluation_binding,
"evaluation_tokens": evaluation_tokens,
"task_tokens": task_tokens,
}
def context_constraint_notes(context: Dict[str, Any], *, axis: str, target_component: str) -> List[str]:
notes = [
f"Anchor all generated work to current_research `{context['context_anchor']}`.",
f"Keep the candidate inside task family `{context['task_family_binding']}` and dataset `{context['dataset_binding']}`.",
f"Preserve the frozen evaluation binding `{context['evaluation_binding']}`.",
f"Prefer the single-variable axis `{axis}` around `{target_component}` and keep rollback easy.",
]
return unique_preserving(notes, limit=4)
def module_component_candidates(analysis_data: Dict[str, Any]) -> List[str]:
all_candidates: List[str] = []
preferred_candidates: List[str] = []
for path in analysis_data.get("module_files", []):
stem = Path(str(path)).stem.replace("_", "-")
if not stem:
continue
all_candidates.append(stem)
if stem not in {"model", "train", "eval"}:
preferred_candidates.append(stem)
for item in analysis_data.get("constructor_candidates", []):
token = str(item).split(":", 1)[-1].split(".", 1)[0].replace("_", "-")
if token:
preferred_candidates.append(token)
all_candidates.append(token)
for item in analysis_data.get("forward_candidates", []):
token = str(item).split(":", 1)[-1].split(".", 1)[0].replace("_", "-")
if token:
preferred_candidates.append(token)
all_candidates.append(token)
candidates = preferred_candidates or all_candidates
return unique_preserving(candidates, limit=6)
def component_pool(
researcher_candidate_ideas: Sequence[Dict[str, Any]],
analysis_data: Dict[str, Any],
improvement_bank: Sequence[Dict[str, Any]],
) -> List[str]:
candidates: List[str] = []
for idea in researcher_candidate_ideas:
candidates.append(str(idea.get("target_component") or ""))
for item in improvement_bank:
candidates.append(str(item.get("target_component") or ""))
candidates.extend(module_component_candidates(analysis_data))
return unique_preserving([item for item in candidates if item and item != "unspecified"], limit=10)
def contextual_component_pool(components: Sequence[str], context: Dict[str, Any]) -> List[str]:
task_tokens = set(context.get("task_tokens", []))
evaluation_tokens = set(context.get("evaluation_tokens", []))
def score(component: str) -> Tuple[int, int, str]:
tokens = set(tokenize(component))
task_overlap = len(tokens & task_tokens)
evaluation_overlap = len(tokens & evaluation_tokens)
return (task_overlap, -evaluation_overlap, component)
ordered = sorted(unique_preserving(components), key=score, reverse=True)
if ordered:
return ordered
fallback = str(context.get("task_family_binding") or "training-config").replace(" ", "-").lower()
if not fallback or fallback == "unspecified":
fallback = "training-config"
return [fallback]
def source_hint_for_component(component: str, improvement_bank: Sequence[Dict[str, Any]], analysis_data: Dict[str, Any]) -> str:
lowered = component.lower()
for item in improvement_bank:
if lowered and lowered in str(item.get("target_component") or "").lower():
refs = item.get("external_source_reference") or item.get("source_reference") or []
if refs:
return f"Bound to source support from {', '.join(refs[:2])}."
for path in analysis_data.get("module_files", []):
if lowered and lowered in str(path).lower():
return f"Anchored to repo-local component `{path}`."
return "Anchored to repo-local structure and frozen evaluation constraints."
def feasibility_hint_for_scope(change_scope: str, variant_spec: Dict[str, Any], analysis_data: Dict[str, Any]) -> str:
if change_scope in (variant_spec.get("variant_axes") or {}):
return f"Variant axis `{change_scope}` already exists in variant_spec, so short-run feasibility can stay command-level."
if analysis_data.get("config_binding_hints"):
return f"Likely feasible through existing config bindings such as `{analysis_data['config_binding_hints'][0]}`."
return "Feasibility remains heuristic; keep the patch single-variable and reversible."
def make_seed_id(prefix: str, index: int) -> str:
return f"{prefix}-{index:03d}"
def broad_rewrite_risk(change_scope: str, target_component: str) -> bool:
tokens = set(tokenize(change_scope) + tokenize(target_component))
return bool(tokens & REWRITE_RISK_TOKENS)
def eval_contract_risk(target_component: str, evaluation_tokens: Sequence[str]) -> bool:
tokens = set(tokenize(target_component))
return bool(tokens & (SKIP_COMPONENT_TOKENS | set(evaluation_tokens)))
def axis_seed(
*,
axis: str,
axis_values: Sequence[Any],
target_component: str,
source_support_hint: str,
feasibility_hint: str,
index: int,
seed_origin: str,
context: Dict[str, Any],
campaign_idea_id: str = "",
) -> Dict[str, Any]:
value_summary = ", ".join(str(value) for value in list(axis_values)[:3]) or "bounded values"
return {
"id": make_seed_id("idea-seed", index),
"summary": (
f"Probe `{axis}` as a single-variable change around `{target_component}` while keeping "
f"`{context['evaluation_binding']}` unchanged."
),
"change_scope": axis,
"target_component": target_component,
"expected_upside": clamp(0.55 if seed_origin == "synthesized" else 0.60, default=0.55),
"implementation_risk": clamp(0.28 if seed_origin == "hybrid" else 0.22, default=0.25),
"eval_risk": clamp(0.18, default=0.18),
"rollback_ease": clamp(0.88, default=0.88),
"estimated_runtime_cost": clamp(0.30 if len(list(axis_values)) <= 2 else 0.38, default=0.35),
"single_variable_fit": clamp(0.94, default=0.94),
"seed_origin": seed_origin,
"campaign_idea_id": campaign_idea_id or None,
"source_support_hint": source_support_hint,
"feasibility_hint": f"{feasibility_hint} Candidate values: {value_summary}.",
"context_anchor": context["context_anchor"],
"task_family_binding": context["task_family_binding"],
"dataset_binding": context["dataset_binding"],
"evaluation_binding": context["evaluation_binding"],
"constraint_notes": context_constraint_notes(context, axis=axis, target_component=target_component),
}
def fallback_seed(
*,
target_component: str,
source_support_hint: str,
feasibility_hint: str,
index: int,
context: Dict[str, Any],
) -> Dict[str, Any]:
axis = f"single-variable-{target_component.replace(' ', '-').lower()}"
return {
"id": make_seed_id("idea-seed", index),
"summary": (
f"Introduce one bounded follow-up around `{target_component}` while preserving "
f"`{context['evaluation_binding']}` and avoiding architecture rewrites."
),
"change_scope": axis,
"target_component": target_component,
"expected_upside": 0.50,
"implementation_risk": 0.24,
"eval_risk": 0.18,
"rollback_ease": 0.90,
"estimated_runtime_cost": 0.32,
"single_variable_fit": 0.90,
"seed_origin": "synthesized",
"campaign_idea_id": None,
"source_support_hint": source_support_hint,
"feasibility_hint": feasibility_hint,
"context_anchor": context["context_anchor"],
"task_family_binding": context["task_family_binding"],
"dataset_binding": context["dataset_binding"],
"evaluation_binding": context["evaluation_binding"],
"constraint_notes": context_constraint_notes(context, axis=axis, target_component=target_component),
}
def reject_seed(seed: Dict[str, Any], reason: str) -> Dict[str, Any]:
rejected = dict(seed)
rejected["rejection_reason"] = reason
return rejected
def existing_signatures(researcher_candidate_ideas: Sequence[Dict[str, Any]]) -> set[Tuple[str, str]]:
return {
(
str(item.get("change_scope") or "").lower(),
str(item.get("target_component") or "").lower(),
)
for item in researcher_candidate_ideas
}
def build_generated_ideas(
*,
current_research: str,
task_family: str,
dataset: Any,
evaluation_source: Dict[str, Any],
variant_spec: Dict[str, Any],
researcher_candidate_ideas: Sequence[Dict[str, Any]],
improvement_bank: Sequence[Dict[str, Any]],
analysis_data: Dict[str, Any],
policy: Dict[str, Any],
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], Dict[str, Any]]:
context = normalize_context_bindings(
current_research=current_research,
task_family=task_family,
dataset=dataset,
evaluation_source=evaluation_source,
)
if not policy["allow_synthesized_seed_ideas"] or policy["max_generated_ideas"] <= 0:
return [], [], context
generated: List[Dict[str, Any]] = []
rejected: List[Dict[str, Any]] = []
existing = existing_signatures(researcher_candidate_ideas)
used_targets: set[str] = set()
axis_map = dict(variant_spec.get("variant_axes") or {})
components = contextual_component_pool(
component_pool(researcher_candidate_ideas, analysis_data, improvement_bank),
context,
)
if context.get("task_family_binding") == "unspecified" and analysis_data.get("config_binding_hints"):
components = unique_preserving(["training-config", *components], limit=10)
researcher_ideas = list(researcher_candidate_ideas)
evaluation_tokens = context.get("evaluation_tokens", [])
if researcher_ideas:
for idea in researcher_ideas:
for axis, axis_values in sorted(axis_map.items()):
if len(generated) >= policy["max_generated_ideas"]:
break
target_component = str(idea.get("target_component") or components[0] or "training-config")
signature = (str(axis).lower(), str(target_component).lower())
if signature in existing:
continue
seed = axis_seed(
axis=axis,
axis_values=list(axis_values),
target_component=target_component,
source_support_hint=source_hint_for_component(target_component, improvement_bank, analysis_data),
feasibility_hint=feasibility_hint_for_scope(axis, variant_spec, analysis_data),
index=len(generated) + 1,
seed_origin="hybrid",
context=context,
campaign_idea_id=str(idea.get("id") or ""),
)
if broad_rewrite_risk(seed["change_scope"], seed["target_component"]):
rejected.append(reject_seed(seed, "broad-architecture-rewrite-risk"))
continue
if eval_contract_risk(seed["target_component"], evaluation_tokens):
rejected.append(reject_seed(seed, "frozen-eval-contract-risk"))
continue
if policy["require_diverse_targets"] and seed["target_component"] in used_targets:
rejected.append(reject_seed(seed, "diverse-targets-required"))
continue
generated.append(seed)
used_targets.add(seed["target_component"])
if len(generated) >= policy["max_generated_ideas"]:
break
if len(generated) < policy["max_generated_ideas"]:
for axis, axis_values in sorted(axis_map.items()):
if len(generated) >= policy["max_generated_ideas"]:
break
target_component = next(
(
component
for component in components
if not (policy["require_diverse_targets"] and component in used_targets)
and not eval_contract_risk(component, evaluation_tokens)
),
components[0],
)
signature = (str(axis).lower(), str(target_component).lower())
if signature in existing:
continue
seed = axis_seed(
axis=axis,
axis_values=list(axis_values),
target_component=target_component,
source_support_hint=source_hint_for_component(target_component, improvement_bank, analysis_data),
feasibility_hint=feasibility_hint_for_scope(axis, variant_spec, analysis_data),
index=len(generated) + 1,
seed_origin="synthesized",
context=context,
)
if broad_rewrite_risk(seed["change_scope"], seed["target_component"]):
rejected.append(reject_seed(seed, "broad-architecture-rewrite-risk"))
continue
if eval_contract_risk(seed["target_component"], evaluation_tokens):
rejected.append(reject_seed(seed, "frozen-eval-contract-risk"))
continue
if policy["require_diverse_targets"] and seed["target_component"] in used_targets:
rejected.append(reject_seed(seed, "diverse-targets-required"))
continue
generated.append(seed)
used_targets.add(seed["target_component"])
if len(generated) < policy["max_generated_ideas"] and not axis_map:
for component in components:
if len(generated) >= policy["max_generated_ideas"]:
break
if eval_contract_risk(component, evaluation_tokens):
continue
seed = fallback_seed(
target_component=component,
source_support_hint=source_hint_for_component(component, improvement_bank, analysis_data),
feasibility_hint="No explicit variant axis was provided, so this stays a repo-local bounded follow-up.",
index=len(generated) + 1,
context=context,
)
if policy["require_diverse_targets"] and seed["target_component"] in used_targets:
rejected.append(reject_seed(seed, "diverse-targets-required"))
continue
generated.append(seed)
used_targets.add(seed["target_component"])
return generated, rejected, context
def diversity_summary(researcher_ideas: Sequence[Dict[str, Any]], generated: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
all_ideas = list(researcher_ideas) + list(generated)
targets = unique_preserving([str(item.get("target_component") or "") for item in all_ideas if item.get("target_component")])
scopes = unique_preserving([str(item.get("change_scope") or "") for item in all_ideas if item.get("change_scope")])
by_origin: Dict[str, int] = {}
for item in all_ideas:
origin = str(item.get("seed_origin") or "researcher")
by_origin[origin] = by_origin.get(origin, 0) + 1
return {
"unique_target_components": targets,
"unique_change_scopes": scopes,
"by_seed_origin": by_origin,
"researcher_idea_count": len(researcher_ideas),
"generated_idea_count": len(generated),
"synthesized_idea_count": sum(1 for item in generated if item.get("seed_origin") == "synthesized"),
"hybrid_idea_count": sum(1 for item in generated if item.get("seed_origin") == "hybrid"),
}
def write_seed_artifact(
output_dir: Path,
*,
policy: Dict[str, Any],
researcher_ideas: Sequence[Dict[str, Any]],
generated: Sequence[Dict[str, Any]],
rejected: Sequence[Dict[str, Any]],
diversity: Dict[str, Any],
) -> Path:
payload = {
"schema_version": "1.0",
"generation_policy": policy,
"researcher_ideas": list(researcher_ideas),
"generated_ideas": list(generated),
"all_seed_ideas": [*researcher_ideas, *generated],
"diversity_summary": diversity,
"rejected_seed_ideas": list(rejected),
}
path = output_dir / "IDEA_SEEDS.json"
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
return path
def run_candidate_idea_generation_pass(
*,
analysis_output_dir: Path,
current_research: str,
task_family: str,
dataset: Any,
evaluation_source: Dict[str, Any],
variant_spec: Dict[str, Any],
analysis_data: Dict[str, Any],
improvement_bank: Dict[str, Any],
researcher_candidate_ideas: Sequence[Dict[str, Any]],
idea_generation: Any,
) -> Dict[str, Any]:
policy = normalize_policy(idea_generation)
generated, rejected, context = build_generated_ideas(
current_research=current_research,
task_family=task_family,
dataset=dataset,
evaluation_source=evaluation_source,
variant_spec=variant_spec,
researcher_candidate_ideas=researcher_candidate_ideas,
improvement_bank=improvement_bank.get("items", []),
analysis_data=analysis_data,
policy=policy,
)
diversity = diversity_summary(researcher_candidate_ideas, generated)
path = write_seed_artifact(
analysis_output_dir,
policy=policy,
researcher_ideas=researcher_candidate_ideas,
generated=generated,
rejected=rejected,
diversity=diversity,
)
return {
"schema_version": "1.0",
"artifact_path": str(path),
"generation_policy": policy,
"researcher_ideas": list(researcher_candidate_ideas),
"generated_ideas": generated,
"all_seed_ideas": [*researcher_candidate_ideas, *generated],
"diversity_summary": diversity,
"rejected_seed_ideas": rejected,
"context_bindings": {
"context_anchor": context["context_anchor"],
"task_family_binding": context["task_family_binding"],
"dataset_binding": context["dataset_binding"],
"evaluation_binding": context["evaluation_binding"],
},
}
"""Execution feasibility and smoke-validation pass for ai-research-explore."""
from __future__ import annotations
import ast
import hashlib
import importlib.util
import os
import platform
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Sequence
UNSAFE_RUNTIME_IMPORT_FILES = {
"train.py",
"eval.py",
"main.py",
"__main__.py",
}
def safe_float(value: Any) -> float:
try:
return float(value)
except (TypeError, ValueError):
return 0.0
def memory_info() -> Dict[str, Any]:
try:
import psutil # type: ignore
vm = psutil.virtual_memory()
return {
"total_gb": round(vm.total / (1024 ** 3), 2),
"available_gb": round(vm.available / (1024 ** 3), 2),
"percent_used": round(vm.percent, 2),
}
except Exception:
return {
"total_gb": None,
"available_gb": None,
"percent_used": None,
}
def disk_info(root: Path) -> Dict[str, Any]:
usage = shutil.disk_usage(root)
return {
"total_gb": round(usage.total / (1024 ** 3), 2),
"available_gb": round(usage.free / (1024 ** 3), 2),
"percent_used": round((usage.used / usage.total) * 100.0, 2) if usage.total else 0.0,
}
def detect_nvidia() -> List[Dict[str, Any]]:
try:
result = subprocess.run(
[
"nvidia-smi",
"--query-gpu=name,memory.total",
"--format=csv,noheader,nounits",
],
check=True,
capture_output=True,
text=True,
)
except (FileNotFoundError, subprocess.CalledProcessError):
return []
gpus: List[Dict[str, Any]] = []
for line in result.stdout.splitlines():
if not line.strip():
continue
parts = [part.strip() for part in line.split(",")]
if not parts:
continue
gpus.append(
{
"name": parts[0],
"memory_gb": round(safe_float(parts[1]) / 1024.0, 2) if len(parts) > 1 else None,
"backend": "CUDA",
}
)
return gpus
def detect_rocm() -> List[Dict[str, Any]]:
try:
result = subprocess.run(
["rocm-smi", "--showproductname"],
check=True,
capture_output=True,
text=True,
)
except (FileNotFoundError, subprocess.CalledProcessError):
return []
gpus: List[Dict[str, Any]] = []
for line in result.stdout.splitlines():
lowered = line.lower()
if "card series" in lowered:
gpus.append({"name": line.split(":", 1)[-1].strip(), "memory_gb": None, "backend": "ROCm"})
return gpus
def detect_resources(root: Path) -> Dict[str, Any]:
nvidia_gpus = detect_nvidia()
rocm_gpus = detect_rocm()
available_backends = sorted({gpu["backend"] for gpu in nvidia_gpus + rocm_gpus})
return {
"os": {
"system": platform.system(),
"release": platform.release(),
"machine": platform.machine(),
"python_version": platform.python_version(),
},
"cpu": {
"logical_cores": os.cpu_count(),
},
"memory": memory_info(),
"disk": disk_info(root),
"gpu": {
"nvidia_gpus": nvidia_gpus,
"amd_gpus": rocm_gpus,
"available_backends": available_backends,
"total_gpus": len(nvidia_gpus) + len(rocm_gpus),
},
}
def parse_command_paths(command: str) -> List[str]:
paths: List[str] = []
for token in re.findall(r"[\w./\\-]+\.(?:py|ya?ml|json|toml|ini)", command):
cleaned = token.strip().strip("\"'").replace("\\", "/")
if cleaned and cleaned not in paths:
paths.append(cleaned)
return paths
def syntax_check(repo_path: Path, smoke_plan: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
python_files: List[str] = []
for check in smoke_plan:
if check.get("name") == "syntax-parse":
python_files.extend(check.get("scope", []))
unique_files: List[str] = []
for item in python_files:
if item not in unique_files:
unique_files.append(item)
blockers: List[str] = []
passed: List[str] = []
for rel in unique_files:
path = repo_path / rel
if not path.exists():
blockers.append(f"missing:{rel}")
continue
try:
ast.parse(path.read_text(encoding="utf-8", errors="ignore"))
passed.append(rel)
except SyntaxError as exc:
blockers.append(f"syntax:{rel}:{exc.lineno}")
return {
"name": "syntax-parse",
"status": "passed" if not blockers else "failed",
"passed": passed,
"blockers": blockers,
}
def config_check(repo_path: Path, base_command: str) -> Dict[str, Any]:
blockers: List[str] = []
passed: List[str] = []
for rel in parse_command_paths(base_command):
path = repo_path / rel
if path.exists():
passed.append(rel)
else:
blockers.append(rel)
return {
"name": "config-path",
"status": "passed" if not blockers else "failed",
"passed": passed,
"blockers": blockers,
}
def surface_check(name: str, values: Sequence[str], *, optional: bool = False) -> Dict[str, Any]:
if values:
return {
"name": name,
"status": "passed",
"passed": list(values),
"blockers": [],
}
if optional:
return {
"name": name,
"status": "passed",
"passed": [],
"blockers": [],
"notes": [f"missing-{name}"],
}
return {
"name": name,
"status": "planned",
"passed": [],
"blockers": [f"missing-{name}"],
}
def import_resolution_check(target_location_map: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
scopes = [item["file"] for item in target_location_map if str(item["file"]).endswith(".py")]
return {
"name": "import-resolution",
"status": "passed" if scopes else "planned",
"passed": scopes,
"blockers": [] if scopes else ["no-python-targets"],
}
def safe_runtime_targets(target_location_map: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]:
targets: List[Dict[str, Any]] = []
for item in target_location_map:
file_name = Path(str(item.get("file") or "")).name.lower()
if str(item.get("role") or "") != "code":
continue
if not str(item.get("file") or "").endswith(".py"):
continue
if file_name in UNSAFE_RUNTIME_IMPORT_FILES:
continue
targets.append(item)
return targets
def import_probe_check(repo_path: Path, target_location_map: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
targets = safe_runtime_targets(target_location_map)
if not targets:
return {
"name": "import-probe",
"status": "passed",
"passed": [],
"blockers": [],
"notes": ["no-safe-import-targets"],
}
passed: List[str] = []
blockers: List[str] = []
sys_path_added = False
repo_root = str(repo_path)
if repo_root not in sys.path:
sys.path.insert(0, repo_root)
sys_path_added = True
try:
for item in targets:
rel = str(item.get("file") or "")
module_path = repo_path / rel
if not module_path.exists():
blockers.append(f"missing:{rel}")
continue
module_name = f"_research_explore_smoke_{hashlib.sha1(rel.encode('utf-8')).hexdigest()[:12]}"
try:
spec = importlib.util.spec_from_file_location(module_name, module_path)
if spec is None or spec.loader is None:
blockers.append(f"import-spec:{rel}")
continue
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
passed.append(rel)
except ModuleNotFoundError as exc:
blockers.append(f"missing-dependency:{rel}:{exc.name or 'unknown'}")
except Exception as exc: # pragma: no cover - defensive, exercised via repo fixtures
blockers.append(f"import-error:{rel}:{exc.__class__.__name__}")
finally:
sys.modules.pop(module_name, None)
finally:
if sys_path_added:
try:
sys.path.remove(repo_root)
except ValueError:
pass
hard_blockers = [item for item in blockers if not item.startswith("missing-dependency:")]
soft_blockers = [item for item in blockers if item.startswith("missing-dependency:")]
return {
"name": "import-probe",
"status": "failed" if hard_blockers else "planned" if soft_blockers else "passed",
"passed": passed,
"blockers": hard_blockers,
"notes": soft_blockers,
}
def constructor_probe_check(repo_path: Path, target_location_map: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
targets = safe_runtime_targets(target_location_map)
if not targets:
return {
"name": "constructor-probe",
"status": "passed",
"passed": [],
"blockers": [],
"notes": ["constructor-probe-not-applicable"],
}
passed: List[str] = []
blockers: List[str] = []
soft_notes: List[str] = []
sys_path_added = False
repo_root = str(repo_path)
if repo_root not in sys.path:
sys.path.insert(0, repo_root)
sys_path_added = True
try:
for item in targets:
rel = str(item.get("file") or "")
target_symbol = str(item.get("target_symbol") or "")
symbol_root = target_symbol
if ":" in symbol_root:
symbol_root = symbol_root.split(":", 1)[1]
symbol_root = symbol_root.split(".", 1)[0].strip()
if not symbol_root or symbol_root == "unspecified-symbol":
soft_notes.append(f"unresolved-target-symbol:{rel}")
continue
module_path = repo_path / rel
module_name = f"_research_explore_ctor_{hashlib.sha1(rel.encode('utf-8')).hexdigest()[:12]}"
try:
spec = importlib.util.spec_from_file_location(module_name, module_path)
if spec is None or spec.loader is None:
blockers.append(f"constructor-spec:{rel}")
continue
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if hasattr(module, symbol_root):
passed.append(f"{rel}:{symbol_root}")
else:
blockers.append(f"missing-symbol:{rel}:{symbol_root}")
except ModuleNotFoundError as exc:
soft_notes.append(f"missing-dependency:{rel}:{exc.name or 'unknown'}")
except Exception as exc: # pragma: no cover - defensive, exercised via repo fixtures
blockers.append(f"constructor-error:{rel}:{exc.__class__.__name__}")
finally:
sys.modules.pop(module_name, None)
finally:
if sys_path_added:
try:
sys.path.remove(repo_root)
except ValueError:
pass
return {
"name": "constructor-probe",
"status": "failed" if blockers else "passed",
"passed": passed,
"blockers": blockers,
"notes": soft_notes,
}
def short_run_check(executed_runs: Sequence[Dict[str, Any]], variant_matrix: Dict[str, Any]) -> Dict[str, Any]:
if executed_runs:
statuses = [item.get("status", "unknown") for item in executed_runs]
return {
"name": "short-run-command",
"status": "passed" if any(status in {"success", "partial"} for status in statuses) else "failed",
"passed": [item.get("id", "unknown") for item in executed_runs],
"blockers": [] if any(status in {"success", "partial"} for status in statuses) else statuses,
}
if variant_matrix.get("base_command"):
return {
"name": "short-run-command",
"status": "planned",
"passed": [],
"blockers": ["not-executed-yet"],
}
return {
"name": "short-run-command",
"status": "failed",
"passed": [],
"blockers": ["missing-base-command"],
}
def recommend_strategy(resources: Dict[str, Any]) -> Dict[str, Any]:
logical_cores = resources["cpu"].get("logical_cores") or 1
available_memory = resources["memory"].get("available_gb") or 0.0
backends = resources["gpu"].get("available_backends", [])
if logical_cores >= 8:
parallel_strategy = "high-parallelism"
suggested_workers = max(1, logical_cores - 2)
elif logical_cores >= 4:
parallel_strategy = "moderate-parallelism"
suggested_workers = max(1, logical_cores - 1)
else:
parallel_strategy = "low-parallelism"
suggested_workers = 1
memory_strategy = "memory-abundant" if available_memory >= 16 else "moderate-memory" if available_memory >= 4 else "memory-constrained"
acceleration = (
f"Use {', '.join(backends)} acceleration for short-run probes."
if backends
else "No GPU backend detected; keep early exploratory runs small and CPU-safe."
)
return {
"parallel_strategy": parallel_strategy,
"suggested_workers": suggested_workers,
"memory_strategy": memory_strategy,
"acceleration_suggestion": acceleration,
}
def feasibility_decision(
*,
campaign: Dict[str, Any],
variant_matrix: Dict[str, Any],
resources: Dict[str, Any],
) -> Dict[str, Any]:
budget_hours = safe_float((campaign.get("compute_budget") or {}).get("max_runtime_hours")) or 0.0
executed_budget = safe_float((campaign.get("execution_policy") or {}).get("max_executed_variants")) * safe_float(
(campaign.get("execution_policy") or {}).get("variant_timeout")
)
estimated_hours = executed_budget / 3600.0 if executed_budget else 0.0
short_run_status = "proceed"
full_run_status = "proceed"
blockers: List[str] = []
if not variant_matrix.get("base_command"):
short_run_status = "blocked"
full_run_status = "blocked"
blockers.append("missing-base-command")
if budget_hours and estimated_hours > budget_hours:
full_run_status = "borderline"
if resources["gpu"].get("total_gpus", 0) == 0 and variant_matrix.get("variant_count", 0) > 2:
full_run_status = "borderline"
return {
"short_run_feasibility": short_run_status,
"full_run_feasibility": full_run_status,
"estimated_short_run_hours": round(estimated_hours, 4),
"budget_hours": budget_hours,
"blockers": blockers,
}
def write_resource_plan(output_dir: Path, resources: Dict[str, Any], recommendations: Dict[str, Any], feasibility: Dict[str, Any]) -> Path:
lines = [
"# Resource Plan",
"",
f"- OS: `{resources['os']['system']} {resources['os']['release']}`",
f"- CPU logical cores: `{resources['cpu'].get('logical_cores')}`",
f"- Memory available (GB): `{resources['memory'].get('available_gb')}`",
f"- Disk available (GB): `{resources['disk'].get('available_gb')}`",
f"- GPU backends: `{', '.join(resources['gpu'].get('available_backends', [])) or 'none'}`",
f"- Short-run feasibility: `{feasibility['short_run_feasibility']}`",
f"- Full-run feasibility: `{feasibility['full_run_feasibility']}`",
"",
"## Recommendations",
"",
f"- Parallel strategy: `{recommendations['parallel_strategy']}` with `{recommendations['suggested_workers']}` workers",
f"- Memory strategy: `{recommendations['memory_strategy']}`",
f"- Acceleration: {recommendations['acceleration_suggestion']}",
"",
]
if feasibility["blockers"]:
lines.extend(["## Blockers", "", *[f"- {item}" for item in feasibility["blockers"]], ""])
path = output_dir / "RESOURCE_PLAN.md"
path.write_text("\n".join(lines), encoding="utf-8")
return path
def summarize_smoke(checks: Sequence[Dict[str, Any]], ignored_blockers: Sequence[str]) -> Dict[str, Any]:
blockers = [
blocker
for item in checks
for blocker in item.get("blockers", [])
if blocker not in ignored_blockers
]
statuses = {item["status"] for item in checks}
if statuses <= {"passed"}:
status = "passed"
elif statuses <= {"passed", "planned"}:
status = "planned"
else:
status = "failed"
return {
"checks": list(checks),
"status": status,
"blockers": blockers,
}
def run_execution_feasibility_pass(
*,
analysis_output_dir: Path,
repo_path: Path,
campaign: Dict[str, Any],
analysis_data: Dict[str, Any],
variant_matrix: Dict[str, Any],
source_mapping: Dict[str, Any],
executed_runs: Sequence[Dict[str, Any]],
) -> Dict[str, Any]:
resources = detect_resources(analysis_output_dir.parent)
recommendations = recommend_strategy(resources)
feasibility = feasibility_decision(campaign=campaign, variant_matrix=variant_matrix, resources=resources)
static_checks = [
syntax_check(repo_path, source_mapping.get("smoke_plan", [])),
import_resolution_check(source_mapping.get("target_location_map", [])),
config_check(repo_path, str(variant_matrix.get("base_command") or "")),
surface_check("constructor-surface", analysis_data.get("constructor_candidates", [])[:4], optional=True),
surface_check("forward-surface", analysis_data.get("forward_candidates", [])[:4], optional=True),
]
runtime_checks = [
import_probe_check(repo_path, source_mapping.get("target_location_map", [])),
constructor_probe_check(repo_path, source_mapping.get("target_location_map", [])),
short_run_check(executed_runs, variant_matrix),
]
static_smoke = summarize_smoke(
static_checks,
ignored_blockers=("no-python-targets", "missing-constructor-surface", "missing-forward-surface"),
)
runtime_smoke = summarize_smoke(runtime_checks, ignored_blockers=("not-executed-yet",))
overall_status = "failed"
if static_smoke["status"] == "passed" and runtime_smoke["status"] == "passed":
overall_status = "passed"
elif static_smoke["status"] in {"passed", "planned"} and runtime_smoke["status"] in {"passed", "planned"}:
overall_status = "planned"
smoke_report = {
"static_smoke": static_smoke,
"runtime_smoke": runtime_smoke,
"status": overall_status,
"blockers": [*static_smoke["blockers"], *runtime_smoke["blockers"]],
}
resource_plan_path = write_resource_plan(analysis_output_dir, resources, recommendations, feasibility)
return {
"schema_version": "1.0",
"artifact_path": str(resource_plan_path),
"resources": resources,
"recommendations": recommendations,
"feasibility": feasibility,
"static_smoke": static_smoke,
"runtime_smoke": runtime_smoke,
"smoke_report": smoke_report,
}
"""Hypothesis-card pass for ai-research-explore."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, List, Sequence
REQUIRED_FIELDS = [
"id",
"summary",
"rationale",
"target_component",
"source_reference",
"expected_upside",
"single_variable_fit",
"implementation_risk",
"eval_risk",
"rollback_ease",
"patch_surface",
"dependency_drag",
"validation_path",
"innovation_note",
]
ALLOWED_PATCH_CLASSES = {
"config-only",
"import-glue",
"module-transplant-shim",
}
def build_cards(improvement_items: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]:
cards: List[Dict[str, Any]] = []
for item in improvement_items:
card = dict(item)
missing = [field for field in REQUIRED_FIELDS if field not in card]
if missing:
raise ValueError(f"Improvement item `{item.get('id', 'unknown')}` is missing required card fields: {missing}")
patch_class = str(card.get("patch_class") or "").strip().lower()
card["patch_class"] = patch_class if patch_class in ALLOWED_PATCH_CLASSES else "config-only"
card["patch_class_source"] = "campaign" if patch_class in ALLOWED_PATCH_CLASSES else "default"
card.setdefault("short_run_feasibility", "plausible")
cards.append(card)
return cards
def run_idea_card_pass(*, analysis_output_dir: Path, improvement_items: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
cards = build_cards(improvement_items)
path = analysis_output_dir / "IDEA_CARDS.json"
path.write_text(json.dumps(cards, indent=2, ensure_ascii=False), encoding="utf-8")
return {
"schema_version": "1.0",
"artifact_path": str(path),
"cards": cards,
}
"""Idea evaluation and ranking pass for ai-research-explore."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, List, Sequence, Tuple
POSITIVE_WEIGHTS = {
"expected_upside": 14.0,
"single_variable_fit": 10.0,
"groundedness": 10.0,
"source_support_strength": 9.0,
"interface_fit": 9.0,
"rollback_ease": 6.0,
"novelty_estimate": 5.0,
"ablation_clarity": 8.0,
"implementation_story_clarity": 8.0,
"execution_feasibility": 7.0,
}
NEGATIVE_WEIGHTS = {
"implementation_risk": 8.0,
"eval_risk": 7.0,
"patch_surface": 5.0,
"dependency_drag": 5.0,
"execution_cost": 4.0,
"baseline_distance": 3.0,
}
def clamp(value: Any, default: float = 0.5) -> float:
try:
numeric = float(value)
except (TypeError, ValueError):
numeric = default
return max(0.0, min(1.0, numeric))
def hard_gate_failures(card: Dict[str, Any], baseline_gate: Dict[str, Any]) -> List[str]:
failures: List[str] = []
if baseline_gate.get("decision") == "abandon":
failures.append("baseline-gate-abandon")
if clamp(card.get("single_variable_fit"), default=0.8) < 0.6:
failures.append("single-variable-fit")
if clamp(card.get("interface_fit"), default=0.5) < 0.5:
failures.append("interface-fit")
if clamp(card.get("patch_surface"), default=0.4) > 0.7:
failures.append("patch-surface")
if clamp(card.get("dependency_drag"), default=0.2) > 0.7:
failures.append("dependency-drag")
if clamp(card.get("eval_risk"), default=0.5) > 0.6:
failures.append("eval-risk")
if str(card.get("short_run_feasibility") or "plausible") == "blocked":
failures.append("short-run-feasibility")
return failures
def normalized_score(score_points: float) -> float:
max_positive = sum(POSITIVE_WEIGHTS.values())
max_negative = sum(NEGATIVE_WEIGHTS.values())
return round((score_points + max_negative) / (max_positive + max_negative), 4)
def score_payload(value: float, weight: float, direction: str) -> Dict[str, Any]:
signed = round(weight * value, 4)
contribution = signed if direction == "positive" else -signed
return {
"value": round(value, 4),
"weight": weight,
"direction": direction,
"contribution": round(contribution, 4),
}
def evaluate_card(card: Dict[str, Any], baseline_gate: Dict[str, Any]) -> Dict[str, Any]:
raw_scores: Dict[str, float] = {}
breakdown: Dict[str, Dict[str, Any]] = {}
score_points = 0.0
execution_feasibility = card.get("execution_feasibility_score", 1.0 - clamp(card.get("execution_cost"), default=0.5))
for key, weight in POSITIVE_WEIGHTS.items():
raw_value = execution_feasibility if key == "execution_feasibility" else clamp(card.get(key), default=0.5)
raw_scores[key] = round(raw_value, 4)
item = score_payload(raw_value, weight, "positive")
breakdown[key] = item
score_points += item["contribution"]
for key, weight in NEGATIVE_WEIGHTS.items():
raw_value = clamp(card.get(key), default=0.5)
raw_scores[key] = round(raw_value, 4)
item = score_payload(raw_value, weight, "negative")
breakdown[key] = item
score_points += item["contribution"]
failures = hard_gate_failures(card, baseline_gate)
evaluated = dict(card)
evaluated["hard_gate_failures"] = failures
evaluated["hard_gate_passed"] = not failures
evaluated["score_inputs"] = raw_scores
evaluated["score_breakdown"] = breakdown
evaluated["weighted_total"] = round(score_points, 4)
evaluated["idea_score"] = normalized_score(score_points)
evaluated["seed_origin"] = str(card.get("seed_origin") or "researcher")
return evaluated
def selection_pool(eligible: Sequence[Dict[str, Any]]) -> Tuple[str, List[Dict[str, Any]], str]:
researcher_eligible = [
item
for item in eligible
if str(item.get("seed_origin") or "researcher") == "researcher"
]
if researcher_eligible:
return (
"researcher",
researcher_eligible,
"researcher hard precedence kept final selection inside the researcher-provided pool.",
)
return (
"all-eligible",
list(eligible),
"No researcher idea passed hard gates, so the full eligible pool remained available.",
)
def ranking_sort_key(item: Dict[str, Any]) -> Tuple[int, float, float, float, float, str]:
return (
1 if item["hard_gate_passed"] else 0,
item["idea_score"],
item.get("expected_upside", 0.0),
item.get("groundedness", 0.0),
1.0 - item.get("implementation_risk", 1.0),
item.get("id", ""),
)
def pool_priority(item: Dict[str, Any], active_pool: str) -> int:
if active_pool == "researcher":
return 1 if str(item.get("seed_origin") or "researcher") == "researcher" else 0
return 1 if item.get("hard_gate_passed") else 0
def write_evaluation_markdown(
output_dir: Path,
ranked_cards: Sequence[Dict[str, Any]],
baseline_gate: Dict[str, Any],
*,
selected_idea: Dict[str, Any] | None,
active_selection_pool: str,
selection_reason: str,
) -> Path:
lines = [
"# Idea Evaluation",
"",
f"- Baseline gate: `{baseline_gate.get('decision', 'not-applicable')}`",
"- Hard gates: baseline_gate != abandon, single_variable_fit >= 0.6, interface_fit >= 0.5, patch_surface <= 0.7, dependency_drag <= 0.7, eval_risk <= 0.6, short_run_feasibility != blocked.",
"- Soft scoring uses explicit breakdown fields rather than a black-box total.",
f"- Active selection pool: `{active_selection_pool}`",
f"- Selection reason: {selection_reason}",
"",
"## Ranked Cards",
"",
]
if not ranked_cards:
lines.append("- None.")
else:
for item in ranked_cards:
lines.append(
f"- `{item['id']}` origin=`{item.get('seed_origin', 'researcher')}` score=`{item['idea_score']}` hard_gate=`{item['hard_gate_passed']}` failures={','.join(item['hard_gate_failures']) or 'none'} summary={item['summary']}"
)
lines.extend(["", "## Selected Idea", ""])
if selected_idea is None:
lines.append("- None.")
else:
lines.append(
f"- `{selected_idea['id']}` origin=`{selected_idea.get('seed_origin', 'researcher')}` score=`{selected_idea['idea_score']}`"
)
path = output_dir / "IDEA_EVALUATION.md"
path.write_text("\n".join(lines), encoding="utf-8")
return path
def run_idea_ranking_pass(
*,
analysis_output_dir: Path,
cards: Sequence[Dict[str, Any]],
baseline_gate: Dict[str, Any],
) -> Dict[str, Any]:
ranked = [evaluate_card(card, baseline_gate) for card in cards]
eligible = [item for item in ranked if item["hard_gate_passed"]]
active_selection_pool, active_candidates, selection_reason = selection_pool(eligible)
active_candidates.sort(key=ranking_sort_key, reverse=True)
selected = active_candidates[0] if active_candidates else None
top_diff = None
if len(active_candidates) >= 2:
top_diff = round(active_candidates[0]["idea_score"] - active_candidates[1]["idea_score"], 4)
ranked.sort(
key=lambda item: (
pool_priority(item, active_selection_pool),
*ranking_sort_key(item),
),
reverse=True,
)
if selected is not None:
selected = dict(selected)
selected["selection_pool"] = active_selection_pool
selected["selection_reason"] = selection_reason
selected["selected_via_hard_precedence"] = active_selection_pool == "researcher"
scores_path = analysis_output_dir / "IDEA_SCORES.json"
scores_path.write_text(json.dumps(ranked, indent=2, ensure_ascii=False), encoding="utf-8")
markdown_path = write_evaluation_markdown(
analysis_output_dir,
ranked,
baseline_gate,
selected_idea=selected,
active_selection_pool=active_selection_pool,
selection_reason=selection_reason,
)
return {
"schema_version": "1.0",
"artifact_paths": [str(markdown_path), str(scores_path)],
"ranked_ideas": ranked,
"selected_idea": selected,
"decision": "selected" if selected else "not-configured",
"top_idea_score_diff": top_diff,
"active_selection_pool": active_selection_pool,
"selection_reason": selection_reason,
"selected_idea_breakdown": selected.get("score_breakdown") if selected else {},
}
Related skills
Forks & variants (3)
Ai Research Explore has 3 known copies in the catalog totaling 194 installs. They canonicalize to this original listing.
- lllllllama - 192 installs
- lllllllama - 1 installs
- lllllllama - 1 installs
How it compares
Use ai-research-explore for authorized research-context experiments; use explore-code when the task is isolated branch code adaptation rather than current_research coordination.
FAQ
What is current_research?
A durable anchor (branch, commit, checkpoint, or trained model) representing the baseline for exploration.
Does it promise novelty?
No. Novelty and significance remain hypotheses before literature contrast, ablation, and fair comparison.