Repository navigation
feat(repo): time to first useful result, with a rate in repo compare #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f20d4dd
00be2ab
c0773d6
a221219
4f38f1e
4586da3
c36bc80
2909150
4a816d9
3104341
07c495a
5f404c8
bce5fc6
24ab1d1
8170f8f
30ee48d
65af232
0f29657
48df4a5
5481ef0
47c0880
c6ff529
4a131a7
81484ad
2b0e173
dea1e88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| ### Added | ||
|
|
||
| - **Time to first useful result, per repo-eval run** — a `task.json` can declare | ||
| `"useful": {"command": "<regex>"}` for a task graded by its target tests, or | ||
| `"useful": {"edit": "<glob>"}` (or a list of globs) for one graded by an edit to a target | ||
| file. The first non-denied event with `rc` 0 that hits it (a command whose text matches, or an | ||
| edit whose path matches) is recorded in `result.json` `first_useful` with its `issued_s` (the | ||
| event's issue time) and `steps` (events up to and including it). A run with no hit says | ||
| why (`never`, `unobserved`, or `timeout`: a `command` pattern, searched with the `regex` module, ran past | ||
| 2 s on one event, so the run has no verdict); a task with no declaration is `not_declared`. A hit in a trace | ||
| without timestamps is `untimed`: it keeps its `steps` and counts as observed, with no seconds. | ||
| `repo report` shows a row's observed count, medians and reasons under the row, and | ||
| `repo compare` pairs the per-task medians as `first_useful_s` and `first_useful_steps`, and | ||
| the share of the cells with a verdict that observed a hit as `first_useful_rate` (`unobserved` and `timeout` | ||
| cells are unmeasured: neither a hit nor a miss); its header counts observed of measured cells per arm and | ||
| how many are unmeasured. | ||
| A malformed `useful` is refused by `repo validate`, `repo tasks check` and `repo run`. | ||
| Batches run before this change have no `first_useful` and no such rows. | ||
| - A Codex `file_change` record whose `changes` has a malformed entry (not an object, or no path), or is a | ||
| non-list value other than `null`, is one dropped line, as the other malformed records are. The whole record is dropped: its valid | ||
| entries are not read either, so no edit from it counts. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -531,13 +531,16 @@ def codex_json(raw: str, workdir: str | None, cwd: str = "") -> Transcript: | |
| try: | ||
| rec = json.loads(line) | ||
| except json.JSONDecodeError: | ||
| out.dropped_lines += 1 | ||
| out.errors.append(f"line {n}: not JSON") | ||
| continue | ||
| if not isinstance(rec, dict): | ||
| out.dropped_lines += 1 | ||
| continue | ||
| try: | ||
| _codex_apply(out, rec, messages, workdir, by_id, cwd, event_reads) | ||
| except (AttributeError, TypeError, ValueError) as exc: | ||
| out.dropped_lines += 1 | ||
| out.errors.append(f"line {n}: unexpected shape ({exc})") | ||
|
Comment on lines
+543
to
544
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Real, fixed in 5481ef0. |
||
| out.answer = messages[-1].strip() if messages else "" | ||
| out.agent_messages = messages | ||
|
|
@@ -617,9 +620,18 @@ def _codex_item_completed(out: Transcript, item: dict[str, Any], messages: list[ | |
| else: | ||
| _codex_command_unstarted(out, item, script, workdir, cwd) | ||
| elif itype == "file_change": | ||
| for change in item.get("changes") or []: | ||
| if isinstance(change, dict) and change.get("path"): | ||
| out.events.append({"t": None, "kind": "edit", "path": relative_path(str(change["path"]), workdir)}) | ||
| status = item.get("status") # a failed patch changed nothing | ||
| rc = {"completed": 0, "failed": 1}.get(status) if isinstance(status, str) else None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 07c495a: a Codex |
||
| changes = [] if item.get("changes") is None else item["changes"] | ||
| # an entry that is not a change makes the record malformed: it is dropped whole (counted), not read in part | ||
| if not isinstance(changes, list) or not all( | ||
| isinstance(c, dict) and isinstance(c.get("path"), str) and c["path"] for c in changes): | ||
| raise ValueError("file_change with a malformed changes entry") | ||
| for change in changes: | ||
| event: dict[str, Any] = {"t": None, "kind": "edit", "path": relative_path(str(change["path"]), workdir)} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleting a target file counts as its first useful edit. Every Codex Suggestion: mark a deletion on the event ( |
||
| if rc is not None: | ||
| event["rc"] = rc | ||
| out.events.append(event) | ||
| elif itype in ("mcp_tool_call", "web_search"): | ||
| name = f"{item.get('server')}.{item.get('tool')}" if itype == "mcp_tool_call" else "web_search" | ||
| out.events.append({"t": None, "kind": "tool", "name": name}) | ||
|
|
@@ -712,14 +724,17 @@ def copilot_json(raw: str, workdir: str | None, cwd: str = "") -> Transcript: | |
| try: | ||
| rec = json.loads(line) | ||
| except json.JSONDecodeError: | ||
| out.dropped_lines += 1 | ||
| out.errors.append(f"line {n}: not JSON") | ||
| continue | ||
| if not isinstance(rec, dict): | ||
| out.dropped_lines += 1 | ||
| continue | ||
| t = run.offset(rec) | ||
| try: | ||
| run.apply(out, rec, t, workdir, cwd) | ||
| except (AttributeError, TypeError, ValueError) as exc: | ||
| out.dropped_lines += 1 | ||
| out.errors.append(f"line {n}: unexpected shape ({exc})") | ||
| out.answer = (run.final or run.last).strip() | ||
| out.num_turns = run.turns or None | ||
|
|
@@ -730,14 +745,17 @@ def copilot_json(raw: str, workdir: str | None, cwd: str = "") -> Transcript: | |
| def _copilot_tool(data: dict[str, Any], t: float | None, workdir: str | None, cwd: str = "") -> list[dict[str, Any]]: | ||
| name = str(data.get("toolName") or "") | ||
| args = data.get("arguments") | ||
| if name in _COPILOT_SHELL | _COPILOT_READ | _COPILOT_EDIT and not isinstance(args, dict): | ||
| raise TypeError(f"{name} arguments are not an object") # counted as a dropped line by the caller | ||
| obj: dict[str, Any] = args if isinstance(args, dict) else {} | ||
| if name in _COPILOT_SHELL: | ||
| command = str((args or {}).get("command", "")) if isinstance(args, dict) else "" | ||
| command = str(obj.get("command", "")) | ||
| # only bash is a POSIX script; a PowerShell command is not parsed for the files it reads | ||
| scripts = shell_reads(command, cwd) if name == "bash" else [] | ||
| reads = [{"t": t, "kind": "read", "path": relative_path(p, workdir)} for p in scripts] | ||
| return [*reads, {"t": t, "kind": "command", "text": command, "rc": None}] | ||
| if name in _COPILOT_READ | _COPILOT_EDIT and isinstance(args, dict): | ||
| path = str(args.get("path") or args.get("file_path") or "") | ||
| if name in _COPILOT_READ | _COPILOT_EDIT: | ||
| path = str(obj.get("path") or obj.get("file_path") or "") | ||
| if not path: # a call without a path names no file | ||
| return [] | ||
| return [{"t": t, "kind": "read" if name in _COPILOT_READ else "edit", "path": relative_path(path, workdir)}] | ||
|
|
@@ -752,6 +770,9 @@ def _copilot_result(out: Transcript, events: list[dict[str, Any]], data: dict[st | |
| reads = [e for e in events if e["kind"] == "read"] | ||
| failed = data.get("success") is False | ||
| for event in events: | ||
| if event["kind"] == "edit" and data.get("success") is not None: | ||
| # a failed edit changed nothing: rc 1, so it is no useful result | ||
| event["rc"] = 0 if data.get("success") else 1 | ||
| if event["kind"] != "command": | ||
| continue | ||
| shell = data.get("shellExecution") or {} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An edit made by a shell command is never an
editevent, so such a run readsnevereven when its grade passes.Only edit tools emit
editevents (ClaudeEdit/Write, the Copilot edit tools, Codexfile_change). A run that fixessrc/a.cwithsed -i 's/x/y/' src/a.c(rc 0) has only acommandevent, sofirst_usefulisneverand the cell counts as a miss infirst_useful_rate. An arm that leads the agent to scripts then shows a lower rate for a difference only in what the trace can see.Suggestion: say in this row that an edit made through a shell command is not seen.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented in dea1e88: the §4.1 text after the table now says that an edit made through a shell command (
sed -i) is not aneditevent, so it is not seen.