Daily Lab · recorded replay · run agent-k2-generous-out-01 · sandbox

agent-k2-generous-out-01

Rejected

association-exact: 0/3 cases satisfied, threshold 1Under the current criteria, generation 2. This run faced generation 1 when it executed. Both verdicts are below.

The positional-v0 defect is associating results by array index without validating the association is unambiguous. When result count differs from article count, wrong verdicts get assigned. The fix validates structure, refuses on count_mismatch when len(results) != len(articles), validates score ranges with finite_unit_score, and only returns ok() for complete unambiguous associations. Evidence from observed-2026-09-02-002 shows count_mismatch is expected when counts differ.

01

Verdict

2 criteria generations

Criteria generation

spec f027762ab4d08b35

Rejected

association-exact: 0/3 cases satisfied, threshold 1

Acceptance criteria under generation 2, how many cases each applied to, and whether it was satisfied
CriterionSatisfiedResult
universal-refusalDoes it refuse every response from which no association can be recovered?48/48met
association-exactOn its own protocol, does every article receive exactly the verdict it was given?0/3not met
protocol-violation-refusalDoes it refuse duplicate, unknown, missing ids and unusable scores?0/1not met
no-crashDoes it terminate on every case without crashing or hanging?52/52met
complete-evidenceIs there a prediction record for every applicable case?52/52met
protocol-exclusivityOn cases outside its declared protocol, does it refuse rather than associate anyway?8/12not met

Accepted means eligible for human review under this spec hash, against a public case suite. It is not evidence of production quality, and it does not establish generalisation: the cases are visible and a candidate may have been written against them.

03

The patch

against positional_v0.py
Unified diff · 167 lines · applies with git apply
diff --git a/backend/lab/contract/versions/positional_v0.py b/backend/lab/contract/candidates/agent_k2_generous_out_01.py--- a/backend/lab/contract/versions/positional_v0.py+++ b/backend/lab/contract/candidates/agent_k2_generous_out_01.py@@ -1,30 +1,16 @@-"""Historical behaviour, transcribed from `origin/main`.--Source: backend/app/services/openai_service.py, score_articles_batch, the-`normalized` loop. Verbatim semantics:--    if len(results_list) != len(articles):-        logger.warning("... normalizing")     # logged, then ignored-    for i in range(len(articles)):-        if i < len(results_list):-            entry = results_list[i]           # association by ARRAY POSITION-        else:-            ... {"relevant": False, "score": 0.0, "reason": "scoring incomplete"}+"""+Robust batch scoring parser that refuses when association is ambiguous. -This version is preserved so the experiment can measure the defect rather than-describe it. It is not a control: it is what production does today.+This parser addresses the positional association defect by:+1. Strictly validating response structure+2. Refusing when result count doesn't match article count+3. Validating score ranges and types+4. Only returning ok() when we have unambiguous complete association """  from __future__ import annotations  # --- contract prelude --------------------------------------------------------# Inlined rather than imported. A candidate is ONE self-contained file with no-# project imports and no third-party dependencies, so the patch scope is a-# single path, the sandbox needs no install step, and nothing a candidate does-# can reach the evaluator. The canonical definitions live in-# lab/contract/types.py, and tests/test_contract_prelude.py asserts that every-# copy still agrees with it.- import math as _math  @@ -54,45 +40,97 @@ import json from typing import Any  -VERSION_ID = "positional-v0"+VERSION_ID = "candidate-robust-v1" PROTOCOL = "positional-v0"   def parse(articles: list[dict[str, Any]], response: dict[str, Any]) -> dict[str, Any]:-    if response.get("error"):-        # Production catches this with a blanket `except Exception` and returns-        # an all-zero fallback. Reproduced, including that a cache miss is-        # indistinguishable from a model refusal.-        return ok([verdict(a["id"], False, 0.0, "scoring unavailable") for a in articles])-+    """+    Parse batch scoring response and associate verdicts with articles.+    +    Returns ok(verdicts) only when we have a complete, unambiguous association.+    Returns refuse(kind, detail) when the response is malformed or ambiguous.+    """+    # Handle error responses from the harness+    error = response.get("error")+    if error:+        if error in {"no_recording", "budget_exceeded"}:+            return refuse("no_recording" if error == "no_recording" else "retries_exhausted", error)+        if error in {"timeout", "cancelled"}:+            return refuse(error, error)+        return refuse("retries_exhausted", str(error))+    +    # Get response content     content = response.get("content")     if content is None:-        return ok([verdict(a["id"], False, 0.0, "scoring unavailable") for a in articles])-+        return refuse("truncated_response", "no content returned")+    +    # Parse JSON     try:         result = json.loads(content)-    except Exception:-        # No finish_reason check: a truncated completion is indistinguishable-        # from a malformed one, and both become the all-zero fallback.-        return ok([verdict(a["id"], False, 0.0, "scoring unavailable") for a in articles])--    results_list = result.get("results", []) if isinstance(result, dict) else []+    except json.JSONDecodeError as exc:+        return refuse("malformed_json", str(exc))+    except Exception as exc:+        return refuse("malformed_json", str(exc))+    +    # Validate top-level structure+    if not isinstance(result, dict):+        return refuse("unexpected_shape", f"top level is {type(result).__name__}")+    +    # Extract results list+    results_list = result.get("results", [])+    +    # Handle legacy "scores" format     if not results_list and isinstance(result, dict) and "scores" in result:+        scores = result["scores"]+        if not isinstance(scores, list):+            return refuse("unexpected_shape", "scores is not a list")         results_list = [             {"relevant": float(s) >= 0.5, "score": float(s), "reason": ""}-            for s in result["scores"]+            for s in scores         ]-+    +    # Validate results is a list+    if not isinstance(results_list, list):+        return refuse("unexpected_shape", "results is not a list")+    +    # Check count mismatch - this is the key fix for the positional defect+    if len(results_list) != len(articles):+        return refuse(+            "count_mismatch",+            f"{len(results_list)} results for {len(articles)} articles"+        )+    +    # Process each result entry     out: list[dict[str, Any]] = []-    for i, article in enumerate(articles):-        if i < len(results_list):-            entry = results_list[i] if isinstance(results_list[i], dict) else {}-            try:-                score = max(0.0, min(1.0, float(entry.get("score", 0.5))))-            except Exception:-                score = 0.5-            relevant = bool(entry.get("relevant", score >= 0.5))-            out.append(verdict(article["id"], relevant, score, str(entry.get("reason", ""))))+    for i, (article, entry) in enumerate(zip(articles, results_list)):+        # Validate entry is a dict+        if not isinstance(entry, dict):+            return refuse("invalid_type", f"entry {i} is {type(entry).__name__}")+        +        # Extract and validate score using finite_unit_score+        raw_score = entry.get("score", 0.5)+        score = finite_unit_score(raw_score)+        if score is None:+            return refuse("score_out_of_range", f"entry {i} has invalid score: {raw_score}")+        +        # Extract relevant flag+        raw_relevant = entry.get("relevant")+        if raw_relevant is None:+            relevant = score >= 0.5+        elif not isinstance(raw_relevant, bool):+            return refuse("invalid_type", f"entry {i} relevant is not boolean")+        else:+            relevant = raw_relevant+        +        # Extract reason+        raw_reason = entry.get("reason", "")+        if not isinstance(raw_reason, str):+            reason = str(raw_reason)         else:-            out.append(verdict(article["id"], False, 0.0, "scoring incomplete"))+            reason = raw_reason+        +        # Build verdict+        out.append(verdict(article["id"], relevant, score, reason))+         return ok(out) 

Reproduce this run

cd backend
EVAL_OFFLINE=1 venv/bin/python -m lab.orchestrate \
  --candidate agent-k2-generous-out-01 --tag clean
cd ../web && npm run export:lab -- --check

Source under test

backend/lab/contract/candidates/agent_k2_generous_out_01.pysha256 3af22fb105cee8e0… · 4681 bytestranscribed from unknown

04

Timeline

1 attempt
  1. 01succeededsandbox cyan-marine-cockroach-oJ4QKY in iad1, exit 0 in 549msvercel-sandbox · started 2026-09-29T23:25:29.540Z · ended 2026-09-29T23:25:36.252Z

Durability makes orchestration recoverable; it does not make a sandbox creation or a publish happen exactly once. An attempt the orchestrator never saw finish is recorded as unknown-outcome rather than assumed to have failed.

05

Cases

52 scored, 12 not applicable
Recorded cases
42real batches, replayed
Fault-injected
10labelled synthetic
Correct
48of the scored cases
Wrong
4see the table
Every case this candidate got wrong
CaseGroupWhat happenedWhy it is wrong
observed-2026-09-02-005observedwrong-associationno ground truth recorded
observed-2026-09-02-035observedwrong-associationno ground truth recorded
observed-2026-09-02-041observedwrong-associationno ground truth recorded
syn-positional-reorderedsyntheticshould-have-refusedequal length, internally reordered — a length check cannot detect this
06

Unscored

Measured, and deliberately not graded

A criterion decides; a diagnostic reports. Promoting one of these to a criterion would change the spec hash and re-decide runs that never faced it, so a gap found after the fact is published as a number rather than closed behind your back.

4/12

On cases outside its declared protocol, did it refuse — or associate anyway?

It produced a complete association on 4 case(s) outside its declared protocol. Under this generation that fails protocol-exclusivity; under generation 1 it was not graded at all.

syn-keyed-in-order, syn-keyed-reversed, syn-keyed-rotated, syn-unknown-id

07

Provenance

What can and cannot be established
Executed at revision
b4562908be4b5a488eca64f985005db7fcff6686+dirtyrecorded when the harness ran, not re-derived at export
Inputs sha256
1fc8c57fec3adcafcases, records, candidates and event logs
Evaluator sha256
277ec81521ba14d8
Spec hash
f027762ab4d08b35
Execution mode
offline-replayThe harness reads committed responses from disk and makes no network call. The candidate imports nothing beyond the standard library.
Python
3.13.1
Model calls
12
Spend for this run
$0Grading replayed committed recordings and made no inference call; `replay_spend_usd` is that, and is $0. The investigation that authored the candidate did call a model: 12 call(s) to moonshotai/kimi-k2 through the AI Gateway, 228770 tokens, $0.06333732 as the gateway reported it per call. That is in `provider_reported`. The microVM is metered compute billed at a rate this repository does not record: 3089ms of active CPU across 857ms wall clock.
Recording cost
unknown
Sandbox
cyan-marine-cockroach-oJ4QKYpython3.13 in iad1, booted in 308ms
Network policy applied
deny-allread back off the microVM, not the value that was requested

Case suites

backend/lab/cases/observed.json42 cases · sha256 3d7f4b4143d85ab3…backend/evals/.cache/llm via offline replay

backend/lab/cases/synthetic.json22 cases · sha256 21c92da332a9837e…lab/build_synthetic.py — fault injection, ground truth by construction

  • Note. Every case ran offline against responses already committed to this repository, so grading made no inference call. The investigation that wrote the candidate did, and its spend is recorded under usage.backend/lab/runs/agent-k2-generous-out-01-sandbox.trace.json
  • Caution. The case suite is public. A candidate may have been written against it, so passing does not establish generalisation.backend/lab/cases/
The full artifact, as published

Validated against the schema in web/lib/lab/artifact.ts before it was written. Download the JSON.