Daily Lab · recorded replay · run agent-k2-generous-02 · sandbox
agent-k2-generous-02
protocol-violation-refusal: 7/9 cases satisfied, threshold 1Under the current criteria, generation 2. This run faced generation 1 when it executed. Both verdicts are below.
Implement a keyed protocol parser (keyed-v2) that requires each verdict to explicitly identify its article via article_id. This eliminates the positional association defect by: 1) Validating that every article has exactly one verdict, 2) Detecting duplicate or unknown article_ids, 3) Rejecting when the association is ambiguous or incomplete. The parser uses PROTOCOL_KEYED ('keyed-v2') and refuses with appropriate kinds (count_mismatch, duplicate_id, unknown_id, missing_id) when association cannot be guaranteed.
Verdict
2 criteria generationsCriteria generation
spec f027762ab4d08b35
protocol-violation-refusal: 7/9 cases satisfied, threshold 1
| Criterion | Satisfied | Result |
|---|---|---|
| universal-refusalDoes it refuse every response from which no association can be recovered? | 48/48 | met |
| association-exactOn its own protocol, does every article receive exactly the verdict it was given? | 3/3 | met |
| protocol-violation-refusalDoes it refuse duplicate, unknown, missing ids and unusable scores? | 7/9 | not met |
| no-crashDoes it terminate on every case without crashing or hanging? | 60/60 | met |
| complete-evidenceIs there a prediction record for every applicable case? | 60/60 | met |
| protocol-exclusivityOn cases outside its declared protocol, does it refuse rather than associate anyway? | 4/4 | 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.
The patch
against positional_v0.pyUnified diff · 184 lines · applies with git apply
diff --git a/backend/lab/contract/versions/positional_v0.py b/backend/lab/contract/candidates/agent_k2_generous_02.py--- a/backend/lab/contract/versions/positional_v0.py+++ b/backend/lab/contract/candidates/agent_k2_generous_02.py@@ -1,29 +1,14 @@-"""Historical behaviour, transcribed from `origin/main`.+"""Keyed protocol parser (keyed-v2) - eliminates positional association defects. -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"}--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.+Each verdict must explicitly identify its article via article_id.+This allows out-of-order responses and detects mismatches unambiguously. """ 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.+# project imports and no third-party dependencies. import math as _math @@ -54,45 +39,113 @@ import json from typing import Any -VERSION_ID = "positional-v0"-PROTOCOL = "positional-v0"+VERSION_ID = "keyed-v2"+PROTOCOL = "keyed-v2" 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 a keyed response where each verdict contains article_id.+ + Refuses when:+ - Response has an error+ - JSON is malformed+ - Response shape is unexpected+ - Count mismatch (fewer or more verdicts than articles)+ - Duplicate article_ids in response+ - Unknown article_ids (not in input)+ - Missing article_ids (input article not in response)+ - Invalid score values+ """+ # Handle response-level errors+ 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)) 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 []- if not results_list and isinstance(result, dict) and "scores" in result:- results_list = [- {"relevant": float(s) >= 0.5, "score": float(s), "reason": ""}- for s in result["scores"]- ]-- 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", ""))))+ except Exception as exc:+ return refuse("malformed_json", str(exc))++ # Validate top-level shape+ if not isinstance(result, dict):+ return refuse("unexpected_shape", f"top level is {type(result).__name__}")++ results_list = result.get("results", [])+ if not isinstance(results_list, list):+ return refuse("unexpected_shape", "results is not a list")++ # Build expected article ID set+ expected_ids = {a["id"] for a in articles}+ + # Check count first (fast path for obvious mismatches)+ if len(results_list) != len(articles):+ return refuse(+ "count_mismatch",+ f"{len(results_list)} results for {len(articles)} articles",+ )++ # Process results and validate associations+ verdicts_by_id: dict[str, dict[str, Any]] = {}+ + for idx, entry in enumerate(results_list):+ if not isinstance(entry, dict):+ return refuse("invalid_type", f"entry at index {idx} is {type(entry).__name__}")+ + # Extract article_id (required for keyed protocol)+ article_id = entry.get("article_id")+ if article_id is None:+ return refuse("missing_id", f"entry at index {idx} missing article_id")+ + if not isinstance(article_id, str):+ return refuse("invalid_type", f"article_id at index {idx} is not a string")+ + # Check for duplicates+ if article_id in verdicts_by_id:+ return refuse("duplicate_id", f"duplicate article_id: {article_id}")+ + # Check for unknown IDs+ if article_id not in expected_ids:+ return refuse("unknown_id", f"unknown article_id: {article_id}")+ + # Validate score+ score_raw = entry.get("score")+ if score_raw is not None:+ score = finite_unit_score(score_raw)+ if score is None:+ return refuse("score_out_of_range", f"invalid score for {article_id}") else:- out.append(verdict(article["id"], False, 0.0, "scoring incomplete"))- return ok(out)+ score = 0.5 # default when missing+ + # Validate relevant boolean+ relevant = bool(entry.get("relevant", score >= 0.5))+ + # Extract reason+ reason = str(entry.get("reason", ""))+ + verdicts_by_id[article_id] = verdict(article_id, relevant, score, reason)+ + # Check for missing articles+ missing_ids = expected_ids - set(verdicts_by_id.keys())+ if missing_ids:+ return refuse("missing_id", f"missing verdicts for: {', '.join(sorted(missing_ids))}")+ + # Build output in same order as input articles+ verdicts = []+ for article in articles:+ article_id = article["id"]+ v = verdicts_by_id.get(article_id)+ if v is None:+ # Should not happen due to missing_ids check above+ return refuse("missing_id", f"missing verdict for {article_id}")+ verdicts.append(v)+ + return ok(verdicts)
Reproduce this run
cd backend EVAL_OFFLINE=1 venv/bin/python -m lab.orchestrate \ --candidate agent-k2-generous-02 --tag clean cd ../web && npm run export:lab -- --check
Source under test
backend/lab/contract/candidates/agent_k2_generous_02.pysha256 20167851e71116d6… · 5251 bytestranscribed from unknown
Timeline
1 attempt- 01succeededsandbox chocolate-rapid-crab-4NmX2Q in iad1, exit 0 in 458msvercel-sandbox · started 2026-09-30T03:45:35.111Z · ended 2026-09-30T03:45:41.566Z
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.
Cases
60 scored, 4 not applicable- Recorded cases
- 39real batches, replayed
- Fault-injected
- 21labelled synthetic
- Correct
- 58of the scored cases
- Wrong
- 2see the table
| Case | Group | What happened | Why it is wrong |
|---|---|---|---|
| syn-score-null | synthetic | should-have-refused | score is null; production clamps it into a real verdict |
| syn-relevant-not-bool | synthetic | should-have-refused | relevant is a string |
Unscored
Measured, and deliberately not gradedA 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.
- 0/4
On cases outside its declared protocol, did it refuse — or associate anyway?
It produced no association on any case outside its declared protocol.
Provenance
What can and cannot be established- Executed at revision
- 1c97bc0483f43673fc9f31820d8187d53659a0ff+dirtyrecorded when the harness ran, not re-derived at export
- Inputs sha256
- 35b3ddd70512fcd8cases, 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, 275229 tokens, $0.06255452 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: 2672ms of active CPU across 739ms wall clock.
- Recording cost
- unknown
- Sandbox
- chocolate-rapid-crab-4NmX2Qpython3.13 in iad1, booted in 281ms
- 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-02-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.