Skip to content

feat: auto-switch to rank-weighted selection based on archive state - #1412

Merged
lambdabaa merged 1 commit into
akashgit:mainfrom
lambdabaa:feat/auto-rank-weighted-selection
Aug 30, 2026
Merged

feat: auto-switch to rank-weighted selection based on archive state#1412
lambdabaa merged 1 commit into
akashgit:mainfrom
lambdabaa:feat/auto-rank-weighted-selection

Conversation

@lambdabaa

@lambdabaa lambdabaa commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Builds on #1394 (rank-weighted tournament selection). sample_parent() now auto-activates rank-weighted selection when the archive reaches auto_rank_cell_threshold (default 8) occupied cells, indicating enough diversity that biasing toward stronger parents is beneficial.

This mirrors the on_plateau() pattern — adapting selection pressure based on search state rather than requiring static configuration. Auto-switch is on by default; callers can opt out with auto_rank_weighted=False.

Cell count is the sole trigger — score variance was considered but dropped because a fixed variance threshold is application-dependent (chess scores range -500 to +500, factory's default scores are 0-1 floats). Cell count is application-agnostic: enough diverse individuals in the archive → rank-weighted helps regardless of score scale.

Test plan

  • test_auto_activates_by_cell_count — triggers at 10 cells with threshold 8
  • test_auto_stays_uniform_below_thresholds — 2 cells → stays uniform
  • test_auto_disabledauto_rank_weighted=False keeps uniform
  • All 26 population tests pass
  • ruff check + mypy clean

🤖 Generated with Claude Code

sample_parent() now automatically enables rank-weighted tournament
selection when the archive reaches auto_rank_cell_threshold (default 8)
occupied cells, indicating enough diversity that biasing toward stronger
parents is beneficial.

This mirrors the on_plateau() pattern of adapting strategy when the
search state calls for it. On by default; opt out with
auto_rank_weighted=False.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lambdabaa
lambdabaa force-pushed the feat/auto-rank-weighted-selection branch from d09c115 to 1671650 Compare August 30, 2026 20:48
@lambdabaa

Copy link
Copy Markdown
Collaborator Author

@ceo-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Factory Review: KEEP

Verdict: KEEP
Reason: QA: CLEAN — 588 tests pass (1 pre-existing failure unrelated), lint clean, mypy clean. Auto-switch logic correct: activates at threshold, produces expected rank-proportional distribution, respects overrides. NOTE: PR description claims score_variance() and variance-based threshold that do not exist in code — description should be updated to match implementation. Stale test comment at test_population.py:226 references non-existent variance threshold.

QA Analysis

Adversarial QA Report — PR #1412

Feature: Auto-switch to rank-weighted selection based on archive size
Project type: Library (Python CLI with evolutionary search engine)
Date: 2026-08-30


Smoke Test

Command: uv run pytest tests/test_outer_loop/test_population.py -v
Result: PASS — 26/26 tests passed in 0.22s


Test Plan (derived from PR acceptance criteria)

  1. Existing test suite passes
  2. Full suite regression check
  3. Auto-switch activates with 10+ cells (rank bias observed)
  4. Auto-switch does NOT activate below threshold (uniform distribution)
  5. Explicit rank_weighted=True works independently of auto
  6. auto_rank_weighted=False disables auto-switching
  7. Engine.py call-site compatibility
  8. Lint and type-check pass

Feature Tests with Evidence

Test 1: Population test suite

Status: VERIFIED
Command: uv run pytest tests/test_outer_loop/test_population.py -v
Output: 26 passed in 0.22s — all tests pass including new TestAutoRankWeighted class (3 tests) and TestRankWeightedSelection class (2 tests).

Test 2: Full test suite regression check

Status: VERIFIED (with pre-existing failure)
Command: uv run pytest -x -v
Output: 588 passed, 1 failed in 9.50s
Note: The single failure is test_templatized_skill_validates[design-v2] — a pre-existing issue where the design-v2 skill body exceeds 600 lines (1123). This is NOT related to PR #1412 (no population/archive code involved). Confirmed by checking the failing test: it validates skill template line count, unrelated to tournament selection.

Test 3: Auto-switch activates with 10+ cells

Status: VERIFIED
Command: Custom Python script — MAPElitesArchive with 10 individuals, 2000 samples with tournament_size=1 (isolates weighting from tournament effect).
Output:

Archive size: 10
Best individual (ind-9) selected 357/2000 times = 17.8%
Uniform baseline would be 10.0%
All counts: [('ind-9', 357), ('ind-8', 331), ('ind-7', 281), ('ind-6', 276), ('ind-5', 234), ('ind-4', 163), ('ind-3', 151), ('ind-2', 111), ('ind-1', 76), ('ind-0', 20)]

Analysis: With rank weighting on 10 items, best (rank 10) should get ~18.2% (10/55). Observed 17.8% — matches expected distribution. Clear rank-proportional gradient from worst (1.0%) to best (17.8%).

Test 4: Auto-switch does NOT activate below threshold

Status: VERIFIED
Command: Custom Python script — MAPElitesArchive with 5 individuals (below default threshold of 8), 2000 samples with tournament_size=1.
Output:

Archive size: 5
Best individual (ind-4) selected 398/2000 times = 19.9%
Uniform baseline would be 20.0%
All counts: [('ind-2', 412), ('ind-3', 407), ('ind-0', 399), ('ind-4', 398), ('ind-1', 384)]

Analysis: All individuals between 19.2%-20.6% — textbook uniform distribution. No rank bias detected below threshold.

Test 5: Explicit rank_weighted=True works independently

Status: VERIFIED
Command: Custom Python script — 3 individuals (below threshold), rank_weighted=True explicitly, 3000 samples.
Output:

Archive size: 3 (below default threshold of 8)
Best (ind-2, score=2.0): 49.5%
Worst (ind-0, score=0.0): 16.7%

Analysis: With 3 items and weights [1,2,3], expected: best=50%, worst=16.7%. Observed: 49.5% and 16.7% — exact match. Explicit rank_weighted=True overrides the auto threshold check.

Test 6: auto_rank_weighted=False disables auto-switching

Status: VERIFIED
Command: Custom Python script — 10 individuals (above threshold), auto_rank_weighted=False, 2000 samples.
Output:

Archive size: 10 (above threshold, but auto disabled)
Best (ind-9): 9.8%
Uniform baseline: 10.0%
All counts: [('ind-1', 224), ('ind-4', 210), ..., ('ind-0', 175)]

Analysis: All individuals between 8.8%-11.2% — uniform distribution. Auto-switching correctly disabled despite being above threshold.

Test 7: Engine.py integration compatibility

Status: VERIFIED
Command: Custom Python script — inspected signature + called with engine.py's pattern: archive.sample_parent(3, rank_weighted=False).
Output:

sample_parent signature: (self, tournament_size: 'int' = 3, rank_weighted: 'bool' = False, auto_rank_weighted: 'bool' = True, auto_rank_cell_threshold: 'int' = 8)
Engine-style call (rank_weighted=False): got e-9
Engine-style call (rank_weighted=True): got e-8
auto_rank_weighted default: True
auto_rank_cell_threshold default: 8

Analysis: Engine.py (line 284) calls sample_parent(tournament_size, rank_weighted=config.rank_weighted_selection) — positional + keyword. The new params have defaults, so no breakage. The engine automatically benefits from auto-switching when rank_weighted=False and archive has 8+ cells.

Test 8: Lint and type-check

Status: VERIFIED
Command: uv run ruff check factory/outer_loop/population.py factory/outer_loop/engine.py
Output: All checks passed!
Command: uv run mypy factory/outer_loop/population.py factory/outer_loop/engine.py
Output: Success: no issues found in 2 source files


Edge Cases Probed

Edge case Result
Empty archive + auto_rank_weighted=True Returns None (existing behavior preserved)
1 individual + auto_rank_weighted=True Falls through to random.sample (len < 2 guard on line 159)
Exactly 8 cells (threshold boundary) Auto activates (>= check on line 156)
rank_weighted=True + auto_rank_weighted=True rank_weighted takes priority (line 155: use_rank = rank_weighted)

Acceptance Criteria Verification

Criterion Status
sample_parent() auto-activates rank-weighted when archive >= 8 cells VERIFIED
auto_rank_weighted defaults to True VERIFIED
auto_rank_cell_threshold defaults to 8 VERIFIED
Explicit rank_weighted=True still works VERIFIED
auto_rank_weighted=False disables the feature VERIFIED
No regressions in engine.py integration VERIFIED
Lint and type-check pass VERIFIED
Existing tests pass VERIFIED

Adversarial Verdict: PASS

All 8 acceptance criteria verified with evidence. The auto-switch logic is correct: it activates at the right threshold, produces the expected rank-proportional distribution, respects both explicit overrides and the disable flag, and maintains backward compatibility with engine.py's existing call pattern. The single test failure in the full suite (design-v2 template size) is pre-existing and unrelated.


Posted by Factory CEO

@lambdabaa
lambdabaa merged commit 6928865 into akashgit:main Aug 30, 2026
1 of 6 checks passed
@github-actions

Copy link
Copy Markdown

Benchmark Results

devopsgym

Field Value
Benchmark devopsgym
Instance build-maven-dependency-resolution
Result ❌ NOT RESOLVED
Score 0
Duration 6s
Full JSON
{
  "benchmark": "devopsgym",
  "instance_id": "build-maven-dependency-resolution",
  "solver": "claude-code",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 6,
  "status": "failed",
  "timestamp": "20260830T211530Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

terminalbench

Field Value
Benchmark terminalbench
Instance fix-git
Result ✅ RESOLVED
Score 1
Duration 126s
Full JSON
{
  "benchmark": "terminalbench",
  "instance_id": "fix-git",
  "solver": "claude-code",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 126,
  "status": "success",
  "timestamp": "20260830T211530Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0.62895675,
    "input_tokens": 197447,
    "output_tokens": 1346,
    "cache_read_tokens": 111081,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

swebench

Field Value
Benchmark swebench
Instance sympy__sympy-20590
Result ✅ RESOLVED
Score 1
Duration 204s
Full JSON
{
  "benchmark": "swebench",
  "instance_id": "sympy__sympy-20590",
  "solver": "claude-code",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 204,
  "status": "success",
  "timestamp": "20260830T211531Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0.603611,
    "input_tokens": 267296,
    "output_tokens": 3968,
    "cache_read_tokens": 202807,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

harborindex

Field Value
Benchmark harborindex
Instance bix-filter-chip-variants
Result ❌ NOT RESOLVED
Score 0
Duration 7s
Full JSON
{
  "benchmark": "harborindex",
  "instance_id": "bix-filter-chip-variants",
  "solver": "claude-code",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 7,
  "status": "failed",
  "timestamp": "20260830T211532Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

featurebench

Field Value
Benchmark featurebench
Instance pypa__packaging.013f3b03.test_metadata.e00b5801.lv1
Result ✅ RESOLVED
Score 1
Duration 656s
Full JSON
{
  "benchmark": "featurebench",
  "instance_id": "pypa__packaging.013f3b03.test_metadata.e00b5801.lv1",
  "solver": "claude-code",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 656,
  "status": "success",
  "timestamp": "20260830T211533Z",
  "details": {
    "pass_rate": 1,
    "solver": "claude-code",
    "cost_usd": 1.18995675,
    "input_tokens": 1031127,
    "output_tokens": 12113,
    "cache_read_tokens": 966501,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

legacybench

Field Value
Benchmark legacybench
Instance 1907c2-c-debug-legacy-buddy-fix
Result ✅ RESOLVED
Score 1
Duration 247s
Full JSON
{
  "benchmark": "legacybench",
  "instance_id": "1907c2-c-debug-legacy-buddy-fix",
  "solver": "claude-code",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 247,
  "status": "success",
  "timestamp": "20260830T211533Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0.8522289999999999,
    "input_tokens": 482698,
    "output_tokens": 8697,
    "cache_read_tokens": 414228,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

featurebench

Field Value
Benchmark featurebench
Instance pypa__packaging.013f3b03.test_metadata.e00b5801.lv1
Result ✅ RESOLVED
Score 1
Duration 593s
Full JSON
{
  "benchmark": "featurebench",
  "instance_id": "pypa__packaging.013f3b03.test_metadata.e00b5801.lv1",
  "solver": "factory",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 593,
  "status": "success",
  "timestamp": "20260830T211534Z",
  "details": {
    "pass_rate": 1,
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "d4482eea7e6cee0d978fca70609e2a1a"
  }
}

programbench

Field Value
Benchmark programbench
Instance abishekvashok__cmatrix.5c082c6
Result ❌ NOT RESOLVED
Score 0
Duration 423s
Full JSON
{
  "benchmark": "programbench",
  "instance_id": "abishekvashok__cmatrix.5c082c6",
  "solver": "claude-code",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 423,
  "status": "success",
  "timestamp": "20260830T211535Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 1.1269920000000002,
    "input_tokens": 470362,
    "output_tokens": 11204,
    "cache_read_tokens": 406189,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

terminalbench

Field Value
Benchmark terminalbench
Instance fix-git
Result ✅ RESOLVED
Score 1
Duration 100s
Full JSON
{
  "benchmark": "terminalbench",
  "instance_id": "fix-git",
  "solver": "factory",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 100,
  "status": "success",
  "timestamp": "20260830T211535Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "d29cd92743d7ab5eaba4841b5acfbc5f"
  }
}

programbench

Field Value
Benchmark programbench
Instance abishekvashok__cmatrix.5c082c6
Result ❌ NOT RESOLVED
Score 0
Duration 1184s
Full JSON
{
  "benchmark": "programbench",
  "instance_id": "abishekvashok__cmatrix.5c082c6",
  "solver": "factory",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 1184,
  "status": "success",
  "timestamp": "20260830T211536Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "a8158997ab14b91dd6e01ceec8d57c24"
  }
}

tomswe

Field Value
Benchmark tomswe
Instance sympy__sympy-20590
Result ✅ RESOLVED
Score 1
Duration 259s
Full JSON
{
  "benchmark": "tomswe",
  "instance_id": "sympy__sympy-20590",
  "solver": "claude-code",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 259,
  "status": "success",
  "timestamp": "20260830T211536Z",
  "details": {
    "solver": "claude-code",
    "cost_usd": 0.432367,
    "input_tokens": 255998,
    "output_tokens": 3632,
    "cache_read_tokens": 218834,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

devopsgym

Field Value
Benchmark devopsgym
Instance build-maven-dependency-resolution
Result ❌ NOT RESOLVED
Score 0
Duration 6s
Full JSON
{
  "benchmark": "devopsgym",
  "instance_id": "build-maven-dependency-resolution",
  "solver": "factory",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 6,
  "status": "failed",
  "timestamp": "20260830T211538Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

legacybench

Field Value
Benchmark legacybench
Instance 1907c2-c-debug-legacy-buddy-fix
Result ✅ RESOLVED
Score 1
Duration 1850s
Full JSON
{
  "benchmark": "legacybench",
  "instance_id": "1907c2-c-debug-legacy-buddy-fix",
  "solver": "factory",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 1850,
  "status": "success",
  "timestamp": "20260830T211538Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "0f312f16d8b6e4d68ed48e3c6e0d99a1"
  }
}

swebench

Field Value
Benchmark swebench
Instance sympy__sympy-20590
Result ✅ RESOLVED
Score 1
Duration 177s
Full JSON
{
  "benchmark": "swebench",
  "instance_id": "sympy__sympy-20590",
  "solver": "factory",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 177,
  "status": "success",
  "timestamp": "20260830T211538Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "ff62f94f421bb87fc491c5b61c4c5033"
  }
}

tomswe

Field Value
Benchmark tomswe
Instance sympy__sympy-20590
Result ✅ RESOLVED
Score 1
Duration 171s
Full JSON
{
  "benchmark": "tomswe",
  "instance_id": "sympy__sympy-20590",
  "solver": "factory",
  "passed": 1,
  "total": 1,
  "score": 1,
  "resolved": true,
  "duration_seconds": 171,
  "status": "success",
  "timestamp": "20260830T211540Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": "62213370f0253e80115ae9dc31ebc973"
  }
}

harborindex

Field Value
Benchmark harborindex
Instance bix-filter-chip-variants
Result ❌ NOT RESOLVED
Score 0
Duration 8s
Full JSON
{
  "benchmark": "harborindex",
  "instance_id": "bix-filter-chip-variants",
  "solver": "factory",
  "passed": 0,
  "total": 1,
  "score": 0,
  "resolved": false,
  "duration_seconds": 8,
  "status": "failed",
  "timestamp": "20260830T211547Z",
  "details": {
    "solver": "factory",
    "cost_usd": 0,
    "input_tokens": 0,
    "output_tokens": 0,
    "cache_read_tokens": 0,
    "cache_creation_tokens": 0,
    "trace_id": ""
  }
}

devopsgym-claude-code: The benchmark never ran: Harbor couldn't resolve the dataset devops-gym/devops-gym-build because no latest tag exists for it, so no job started.

Detailed analysis

Failure Analysis: devopsgym / build-maven-dependency-resolution

Solver: claude-code
Duration: 6s

Diagnosis

What went wrong

This isn't a bug in your code or in the agent's work on the task — the run never got as far as running the agent. It died during Harbor's job setup, while resolving the dataset.

The actual error

The bottom line is the whole story:

ValueError: Tag 'latest' not found for dataset 'devops-gym/devops-gym-build'

The chain that produced it

Reading the traceback top-to-bottom, Harbor's CLI tried to start a job and resolve its dataset:

  1. jobs.py:start_run_jobJob.create(config) — normal job bootstrap.
  2. Job._resolve_task_configs iterates over config.datasets and calls dataset.get_task_configs(...).
  3. config.py:get_task_configs sees the dataset is a package (is_package()_get_package_task_configs), so it goes to the registry to fetch metadata.
  4. PackageDatasetClient.get_dataset_metadata("devops-gym/devops-gym-build@latest")package.py:_get_dataset_metadata parses the reference and calls self._db.resolve_dataset_version(org="devops-gym", short_name="devops-gym-build", ref="latest").
  5. That DB call is wrapped in tenacity retry (you can see the tenacity/asyncio frames — it retried, then re-raised the last attempt's exception).
  6. db/client.py:230 queried the registry for the version tagged latest, got zero rows back (if not data:), and raised the ValueError.

The name_string is built as f"{self.name}@{self.ref or 'latest'}" (config.py:270), so no explicit ref was pinned — it defaulted to latest, and the registry has no version tagged latest for devops-gym/devops-gym-build.

Root cause

The devops-gym/devops-gym-build dataset package in Harbor's registry has no latest tag. This is an environment/registry problem, not a problem with the build-maven-dependency-resolution instance or with the solution the agent would have produced. Common reasons:

  • The dataset was published without a latest tag, or every version is yanked/prerelease so none was promoted to latest.
  • A version was expected to be pinned (e.g. devops-gym-build@<specific-version>) but the harness passed no ref.
  • Registry auth/visibility: the client can reach the registry but the org/dataset resolves to an empty version set for this account.

How to fix / move forward

  • Check what versions actually exist: harbor datasets list (the tip at the top of the log) and look for devops-gym/devops-gym-build.
  • Pin an explicit, existing version instead of relying on latest — pass the dataset as devops-gym/devops-gym-build@<version> in the benchmark's Harbor config.
  • If you own/publish the dataset, tag a version as latest (or un-yank one).
  • If none of these are yours to change, this is a benchmark-infra issue to report upstream — the run is not scoreable and shouldn't count as an agent/solution failure.

Note also the earlier docstring text visible in the frames (ValueError: No datasets or tasks remain after resolution. and EmptyDatasetError) — those are the next failure modes Harbor guards against; you hit the one before them, the version tag simply not resolving.

Want me to grep the benchmark harness config in this repo to find where the devops-gym dataset ref is set, so you can pin a version?

harborindex-claude-code: The task ID filter bix-filter-chip-variants matched none of the 82 tasks in the harbor-index dataset, which use a harbor-index/ prefix, so no job ran.

Detailed analysis

Failure Analysis: harborindex / bix-filter-chip-variants

Solver: claude-code
Duration: 7s

Diagnosis

What went wrong

This isn't a failure of the agent or the code being benchmarked — the job never started. It aborted during Harbor's task-resolution phase, before any work was dispatched.

The chain

Reading the traceback bottom-up:

  1. harbor jobs start_run_job()Job.create(config)_resolve_task_configs() iterates the configured datasets and calls dataset.get_task_configs().
  2. The dataset is a package dataset, so it goes through _get_package_task_configs(), which calls _filter_task_ids(metadata.task_ids) to narrow the package's 82 tasks down to the one you asked for.
  3. _filter_task_ids applied the filter ['bix-filter-chip-variants'], got an empty result, and raised:
ValueError: No tasks matched the filter(s) ['bix-filter-chip-variants'].
There are 82 tasks available in this dataset.
Example task names: ['harbor-index/algotune-optimize-lti-sim',
 'harbor-index/algotune-optimize-matrix-sqrt', ...]

The root cause

The task-ID filter didn't match because of a naming/namespace mismatch. The instance was requested as the bare name bix-filter-chip-variants, but every task in the harbor-index package is namespaced with a harbor-index/ prefix (e.g. harbor-index/algotune-optimize-lti-sim). The filter is doing an exact match against the fully-qualified task IDs, so a bare, unprefixed name matches nothing.

Two possibilities, both of which produce this exact error:

  • Wrong prefix: the filter should have been harbor-index/bix-filter-chip-variants (or whatever prefix form Harbor expects) rather than the bare bix-filter-chip-variants.
  • Task doesn't exist in this package/version: the 5 example tasks shown are all algotune-* optimization tasks. If bix-filter-chip-variants was never part of this harbor-index dataset (renamed, removed, or belongs to a different dataset entirely), no prefix would help.

The secondary symptom (why it looks so ugly)

The ValueError propagates all the way up as an unhandled traceback with a RuntimeError: asyncio.run() cannot be called from a running event loop cosmetic noise in the frame. That RuntimeError text is just the source line shown in asyncio/runners.py:191 — it's not what actually fired. The real, only error is the ValueError at the bottom. Note also that jobs.py:1982 only catches DATASET_RESOLUTION_ERRORS; this ValueError isn't in that set, so it escapes as a raw crash instead of a clean "no job was started" message.

What to do

  • Verify the exact task identifier: harbor datasets list (as the tip at the top suggests) and inspect the harbor-index package's task IDs.
  • Fix the filter to use the fully-qualified name, most likely harbor-index/bix-filter-chip-variants.
  • If no such task exists in the package, the benchmark harness is pointing at a task that isn't in this dataset — the instance name in your run config is stale or wrong.

Bottom line: a task-selection/config error, not a code or agent failure. The benchmarked change was never even exercised.

programbench-claude-code: The trace shows only harness setup and artifact collection—no test results or error output—so there's no evidence the agent ran or why it failed.

Under 140 chars:

"Trace only shows harness setup and trajectory collection; no test output or error, so the actual failure cause isn't captured." (137 chars)

Detailed analysis

Failure Analysis: programbench / abishekvashok__cmatrix.5c082c6

Solver: claude-code
Duration: 423s

Diagnosis

What happened

This run did not fail in the harness — it failed the task. The agent produced a reverse-engineered cmatrix that compiles and packages cleanly but is behaviorally wrong, so the real scorer gave it 0. The confusion in the trace comes from a two-stage scoring pipeline where the two stages disagree, and each artifact only shows you one half.

The task

abishekvashok__cmatrix.5c082c6 is a ProgramBench reverse-engineering task (task.toml: difficulty = "hard"). The agent is handed an execute-only binary at /workspace/executable, cannot read it, and must infer its behavior by running it, then write source + a compile.sh that reproduces it. Grading is a differential test against the original binary on hidden inputs (instruction.md).

The apparent contradiction

  • ...harbor.logMean 1.000, Reward 1.0, Exceptions 0 — looks like a pass.
  • ...claude-code.jsonpassed: 0, score: 0.0, resolved: false — a fail.

Both are correct, because they measure different things.

Why Harbor said 1.0

The Harbor verifier (tests/test.sh) is not a correctness check. It only:

  1. confirms compile.sh exists,
  2. runs it and checks it exits 0,
  3. tars the result into submission.tar.gz.

If all three succeed it writes {"reward": 1.0}. So Harbor's 1.0 means "the agent produced something that builds and packages" — a gate, not a grade.

Why the recorded result is 0.0

For ProgramBench, config.sh sets BENCH_POST_EVAL_CMD="uvx programbench eval". run-harbor.sh (lines 471–535) collects the submission.tar.gz, runs the real evaluator, then reads <instance>.eval.json and computes resolved = 1 if passed == total else 0. That post-eval — the differential test against the original binary — returned 0 passing tests, which is what lands in the summary JSON (score: 0.0).

So: the reverse-engineered implementation compiled and packaged (Harbor gate = pass) but did not match the original binary's behavior on the hidden tests (real eval = fail).

Corroborating signals

  • status: "success", duration_seconds: 423 (~7 min) vs. an agent.timeout_sec = 3600 budget — no crash, no timeout. The agent stopped early, using ~12% of its time.
  • output_tokens: 11204 is low for a hard reverse-engineering task. Combined with the early stop, this points to a shallow effort: it observed some behavior, wrote a plausible source + compile.sh, and submitted without the deep differential testing the instructions explicitly ask for. That's a task-quality failure, not an infrastructure one.

About the trace you were given

The trial.log is only the harness command wrapper — mkdir of config dirs, the piped claude ... --print invocation, and "Collecting artifacts." The agent's actual reasoning went to agent/trajectory.json and /logs/agent/claude-code.txt, neither of which is in this bundle. That's why the auto-generated ...summary.md concluded "no evidence the agent ran" — that summary is wrong; it only looked at trial.log and missed the .json (score 0) and harbor.log (reward 1.0) that together tell the real story.

Bottom line

Nothing broke in the pipeline. The agent solved the packaging gate but not the behavioral task — its reconstructed cmatrix diverges from the original binary on the hidden differential tests. The Reward 1.0 in harbor.log is a misleading "it builds" signal; the authoritative score is the ProgramBench post-eval's 0.0. Given the early exit and low output volume, the likely root cause is insufficient differential testing/behavioral fidelity during reverse-engineering, not a harness or scoring bug.

One incidental note: there's a gha-creds-*.json file untracked in the working tree — unrelated to this failure, but you probably don't want that committed.

programbench-factory: Despite agents self-reporting all 16 discoveries "verified," the reverse-engineered cmatrix source still diverged from the binary on untested behavior, failing hidden tests. (139 chars)

Detailed analysis

Failure Analysis: programbench / abishekvashok__cmatrix.5c082c6

Solver: factory
Duration: 1184s
Trace: a8158997ab14b91dd6e01ceec8d57c24

Diagnosis

What went wrong

Bottom line: The run "succeeded" as a process (status: success) but scored 0.0 — the reverse‑engineered cmatrix source matched the binary on everything the agents chose to probe, and diverged on the hidden test cases they never thought to probe. This is a false‑confidence failure, not a crash.

The task

Reverse‑engineer an execute‑only cmatrix binary (abishekvashok__cmatrix.5c082c6): probe its behavior by running it, then write source + compile.sh that produces a behaviorally‑equivalent executable. Grading is differential testing against the original on hidden inputs.

The timeline (from the trace)

  1. Builder (280s) — probed version/help/flags/error messages/exit codes/byte‑level output, wrote the implementation.
  2. Researcher / adversarial review (378s) — validated 13 discoveries, caught 3 real bugs in -t and -l handling.
  3. Builder fix (73s) — fixed all three (-t error message leading space, exit(0)exit(1), -t /dev/null case).
  4. Researcher review (300s) — declared all 16 discoveries "verified," matching ground truth "across every tested behavior."

Then: passed: 0, score: 0.0, resolved: false.

The root cause

The validation loop is self‑referential. The adversarial reviewer only checks the builder's own recorded discoveries, re‑running the same class of probes (flags, error strings, exit codes). Nothing in the loop generates behavior neither agent thought to test. So "all 16 verified" means "the 16 things we looked at agree" — it says nothing about coverage.

For cmatrix that gap is enormous. It's an ncurses terminal animation: the bulk of its observable behavior is the rendered screen — random character streams, color/bold/rainbow modes (-C, -r, -b/-B), async/old‑style scroll (-a, -o), update delay (-u), screensaver (-s), terminal resize/SIGWINCH handling, and the exact ncurses escape sequences and RNG‑seeded output. The agents anchored on the easy‑to‑diff surface (flags, --help, error messages, exit codes) precisely because animated TUI output is hard to capture and compare byte‑for‑byte. The hidden tests almost certainly hit exactly that unprobed animation/rendering behavior.

Two secondary issues worth flagging

  • Reward discrepancy. The harbor log reports Mean: 1.000 / Reward 1.0 while the factory result reports score: 0.0. Those are measuring different things — harbor's 1.000 reflects the trial/agent phase completing without exception, not the programbench grade. The real grade is 0.0. Anyone reading only the harbor summary would wrongly conclude success.
  • Telemetry is blank. cost_usd: 0, all token counts 0, and the trace header shows Total cost: $0.0000 despite 316 observations and 4 agent runs (~17 min wall clock). Cost/token accounting wasn't captured for this run — an observability gap independent of the scoring failure.

The lesson

The workflow's weakness on this benchmark is that its "adversarial" review validates claims already made rather than hunting for untested behavior. For a rich interactive program, differential testing is only as good as the diversity of inputs generated — and here the agents never expanded the probe space to the animation/rendering surface that the hidden tests grade. "All discoveries verified" was a coverage illusion, and the benchmark graded the gap.

If you want, I can dig into whether the adversarial reviewer prompt (researcher/QA role) could be pushed to generate novel differential inputs (e.g., run both binaries under a fixed RNG seed / fixed terminal size in a PTY and diff frames) rather than re‑validating the builder's recorded list.

devopsgym-factory: The dataset devops-gym/devops-gym-build has no latest tag, so Harbor couldn't resolve it and aborted before any job started—no agent ran.

Detailed analysis

Failure Analysis: devopsgym / build-maven-dependency-resolution

Solver: factory
Duration: 6s

Diagnosis

What went wrong

The run never started. It failed during Harbor's job-setup phase — before any agent, container, or the actual build-maven-dependency-resolution task ever executed. Nothing about the factory, the CEO agent, or the Maven task itself is at fault here.

The failure, traced

The bottom line is the whole story:

ValueError: Tag 'latest' not found for dataset 'devops-gym/devops-gym-build'

Reading the stack top-down:

  1. harbor jobs startrun_async(_run_job())Job.create(config) — Harbor is resolving the job config.
  2. _resolve_task_configs iterates config.datasets and calls dataset.get_task_configs().
  3. The dataset is a package (is_package()_get_package_task_configs()), so Harbor builds a name string:
    name_string = f"{self.name}@{self.ref or 'latest'}"
    No explicit ref was pinned, so it defaulted to @latest.
  4. PackageDatasetClient.get_dataset_metadata("devops-gym/devops-gym-build@latest")_db.resolve_dataset_version(org, short_name, ref).
  5. The DB query for the version tagged latest returned no rows, so resolve_dataset_version raised:
    if not data:
        raise ValueError(f"Tag '{parsed.value}' not found for dataset '{org}/{name}'")

The tenacity frames in between are just the retry wrapper around the DB call — it retried, kept getting an empty result, and re-raised the same ValueError.

Root cause

The Harbor package registry has no version published under the tag latest for the devops-gym/devops-gym-build dataset. Either:

  • the dataset exists but has never had a latest tag assigned (versions may exist under specific version tags but not aliased to latest), or
  • the published version was yanked (there's a yanked_at check right after resolution in package.py:26), or
  • the org/name is slightly off and nothing resolves.

This is a benchmark infrastructure / dataset-availability problem in Harbor's registry, not a code defect in this repo and not an agent failure.

How to fix / move forward

  • Check what's actually published: harbor datasets list (the log even hints at this) and look for devops-gym/devops-gym-build and its available tags/versions.
  • Pin an explicit version instead of relying on latest — reference the dataset as devops-gym/devops-gym-build@<version> in the job config so it doesn't fall through to the missing latest alias.
  • If you own/publish the dataset, publish a version and tag it latest (or re-tag an existing version), and confirm it isn't yanked.

One side note unrelated to the failure: a gha-creds-*.json file is sitting untracked in your working tree (from the git status). That's a GitHub Actions credentials file — make sure it's gitignored and not committed.

Want me to grep the benchmark configs in this repo to find where the devops-gym/devops-gym-build dataset is referenced and pin a concrete version?

harborindex-factory: Task filter bix-filter-chip-variants matched none of the 82 harbor-index tasks (names are prefixed harbor-index/), so no tasks remained to run.

Detailed analysis

Failure Analysis: harborindex / bix-filter-chip-variants

Solver: factory
Duration: 8s

Diagnosis

What went wrong

This run never started a single task — it failed during job setup, before any agent or environment was provisioned. The root cause is a task-filter mismatch, not a bug in the factory or the agent.

The failure chain

Reading the traceback bottom-up:

  1. harbor jobs startJob.create()_resolve_task_configs() → for the harborindex package dataset, _get_package_task_configs()_filter_task_ids().

  2. _filter_task_ids took the requested filter — ['bix-filter-chip-variants'] — and matched it against the 82 task IDs actually present in the dataset. Nothing matched, so it raised:

    ValueError: No tasks matched the filter(s) ['bix-filter-chip-variants'].
    There are 82 tasks available in this dataset.
    
  3. That ValueError propagated up through the async machinery and aborted the CLI with a Rich traceback.

The scary-looking asyncio frames (asyncio.run() cannot be called from a running event loop, run_until_complete, etc.) are just the call stack, not the problem — run_async is Harbor's normal wrapper for running the job coroutine. The real error is the final ValueError.

Why the filter didn't match

The instance was requested as:

bix-filter-chip-variants

But every task in this dataset is namespaced under harbor-index/, and the examples Harbor printed are all algotune-*:

harbor-index/algotune-optimize-lti-sim
harbor-index/algotune-optimize-matrix-sqrt
harbor-index/algotune-optimize-ode-seirs
...

So there are two things off:

  • Missing the harbor-index/ prefix. Package task IDs are fully-qualified; the bare bix-filter-chip-variants can't match harbor-index/<name>.
  • The task likely isn't in this dataset at all. All 5 sampled names are algotune-* optimization tasks. A bix-filter-chip-variants task (sounds like a UI/frontend chip-filter task) doesn't fit that family, and the filter would have matched even without the prefix if the unqualified name existed (Harbor's filter typically compares against get_name()). It didn't — strongly suggesting the instance name is either wrong, belongs to a different Harbor dataset, or is stale relative to the current harborindex registry contents.

Bottom line

This is a benchmark harness configuration error, not a factory/agent failure. The run was invoked with a task instance name (bix-filter-chip-variants) that doesn't exist in the resolved harborindex dataset, so Harbor correctly refused to start with zero matching tasks.

To fix it:

  1. Run harbor datasets list (and inspect the harborindex/harbor-index package) to get the authoritative task IDs.
  2. Verify bix-filter-chip-variants is actually a member of this dataset — the 82 tasks shown look like an AlgoTune-style set, which may be the wrong dataset for that instance.
  3. Pass the fully-qualified ID (harbor-index/<task>) if that's what the filter expects.

Also unrelated but worth noting: the run wrote gha-creds-*.json into the repo root (visible in git status) — that's a credentials file that should be gitignored/cleaned up, not committed.

Would you like me to grep the factory's benchmark-runner code to see where bix-filter-chip-variants is being generated/passed, so we can trace how the wrong instance name got in?

Overall: 62.5% accuracy (= +0.0% vs main) | $0.81 avg cost | 376s avg duration

Comparison vs Main

Benchmark Solver Score vs Main Cost vs Main Duration vs Main
devopsgym claude-code 0 = 0% N/A N/A 6s = 0s
terminalbench claude-code 1 +0.0% = $0.63 = $0.00 126s = 0s
swebench claude-code 1 +0.0% = $0.60 = $0.00 204s = 0s
harborindex claude-code 0 = 0% N/A N/A 7s = 0s
featurebench claude-code 1 +0.0% = $1.19 = $0.00 656s = 0s
legacybench claude-code 1 +0.0% = $0.85 = $0.00 247s = 0s
featurebench factory 1 +0.0% = N/A N/A 593s = 0s
programbench claude-code 0 = 0% $1.13 = $0.00 423s = 0s
terminalbench factory 1 +0.0% = N/A N/A 100s = 0s
programbench factory 0 = 0% N/A N/A 1184s = 0s
tomswe claude-code 1 +0.0% = $0.43 = $0.00 259s = 0s
devopsgym factory 0 = 0% N/A N/A 6s = 0s
legacybench factory 1 +0.0% = N/A N/A 1850s = 0s
swebench factory 1 +0.0% = N/A N/A 177s = 0s
tomswe factory 1 +0.0% = N/A N/A 171s = 0s
harborindex factory 0 = 0% N/A N/A 8s = 0s

Baseline: latest main branch run per benchmark+solver. ▲ = improvement, ▼ = regression.

How these benchmarks run

Factory solver: Runs factory ceo . --headless --no-github --prompt <task> — full factory loop (research → strategize → build → review). See benchmarks/run-swebench.sh.

Claude Code solver: Runs claude -p <task> --model claude-opus-4-6[1m] --max-turns 200 — single-shot solve. Same script files as factory, switched via --solver flag.

TerminalBench: Uses Harbor framework. Factory runs via custom factory_harbor_agent.py, Claude Code uses Harbor's built-in agent.

ProgramBench: Both solvers run inside a Docker cleanroom container. See benchmarks/run-programbench.sh.

Config: claude-opus-4-6[1m], effort=XHIGH, thinking=128K tokens. See benchmarks/lib.sh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant