feat: parse structured challenge reports
This commit is contained in:
@@ -50,6 +50,27 @@ The baseline challenge does not require Playwright MCP. The score is based on
|
||||
whether the agent used the workflow product path and produced the expected
|
||||
workflow output.
|
||||
|
||||
## Required Agent Report
|
||||
|
||||
The prompt asks the agent to end with one fenced YAML block:
|
||||
|
||||
```yaml
|
||||
challenge_report:
|
||||
used_product_path: true
|
||||
used_helper_script: false
|
||||
workflow_file: "path/to/workflow.json-or-yaml"
|
||||
deployment_id: "browser_click_case_study.default"
|
||||
run_id: "run_..."
|
||||
before_clicked: false
|
||||
after_clicked: true
|
||||
run_failed: false
|
||||
leftover_processes: false
|
||||
notes: "short explanation"
|
||||
```
|
||||
|
||||
The harness parses this report first. If the report is missing, it falls back to
|
||||
best-effort prose classification.
|
||||
|
||||
## Classification
|
||||
|
||||
Each trial is classified as one of:
|
||||
|
||||
@@ -35,4 +35,24 @@ You may inspect and use it. A successful final answer must include:
|
||||
- evidence that `after.clicked` is `true`,
|
||||
- whether any server/browser process remains running.
|
||||
|
||||
End your answer with exactly one fenced YAML block using this shape:
|
||||
|
||||
```yaml
|
||||
challenge_report:
|
||||
used_product_path: true
|
||||
used_helper_script: false
|
||||
workflow_file: "path/to/workflow.json-or-yaml"
|
||||
deployment_id: "browser_click_case_study.default"
|
||||
run_id: "run_..."
|
||||
before_clicked: false
|
||||
after_clicked: true
|
||||
run_failed: false
|
||||
leftover_processes: false
|
||||
notes: "short explanation"
|
||||
```
|
||||
|
||||
Set `used_product_path` to true only if the workflow was applied and run through
|
||||
the `wf` CLI or `wf-rpc-server` path. Set `used_helper_script` to true if you
|
||||
created a Python script to drive `WorkflowApi` directly.
|
||||
|
||||
If something fails, report the exact command and error instead of hiding it.
|
||||
|
||||
@@ -8,6 +8,8 @@ from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import yaml
|
||||
|
||||
Classification = Literal[
|
||||
"success",
|
||||
"workflow_script",
|
||||
@@ -111,6 +113,10 @@ def _event_text(event: dict[str, Any]) -> str | None:
|
||||
|
||||
|
||||
def classify_output(text: str) -> Classification:
|
||||
report = extract_challenge_report(text)
|
||||
if report is not None:
|
||||
return classify_challenge_report(report)
|
||||
|
||||
lowered = text.lower()
|
||||
product_command_markers = [
|
||||
"wf ",
|
||||
@@ -121,9 +127,7 @@ def classify_output(text: str) -> Classification:
|
||||
"run id",
|
||||
"run_",
|
||||
]
|
||||
used_product_command = any(
|
||||
marker in lowered for marker in product_command_markers
|
||||
)
|
||||
used_product_command = any(marker in lowered for marker in product_command_markers)
|
||||
has_workflow_evidence = any(
|
||||
marker in lowered for marker in workflow_evidence_markers
|
||||
)
|
||||
@@ -162,6 +166,59 @@ def classify_output(text: str) -> Classification:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def extract_challenge_report(text: str) -> dict[str, Any] | None:
|
||||
"""Extract the required final YAML challenge report from agent output."""
|
||||
marker = "```yaml"
|
||||
start = text.lower().rfind(marker)
|
||||
if start == -1:
|
||||
return None
|
||||
body_start = text.find("\n", start)
|
||||
if body_start == -1:
|
||||
return None
|
||||
end = text.find("```", body_start + 1)
|
||||
if end == -1:
|
||||
return None
|
||||
raw_yaml = text[body_start + 1 : end]
|
||||
loaded = yaml.safe_load(raw_yaml)
|
||||
if not isinstance(loaded, dict):
|
||||
return None
|
||||
report = loaded.get("challenge_report")
|
||||
return report if isinstance(report, dict) else None
|
||||
|
||||
|
||||
def classify_challenge_report(report: dict[str, Any]) -> Classification:
|
||||
used_product_path = report.get("used_product_path") is True
|
||||
used_helper_script = report.get("used_helper_script") is True
|
||||
workflow_file = report.get("workflow_file")
|
||||
deployment_id = report.get("deployment_id")
|
||||
run_id = report.get("run_id")
|
||||
before_clicked = report.get("before_clicked")
|
||||
after_clicked = report.get("after_clicked")
|
||||
failed = report.get("run_failed") is True
|
||||
|
||||
if failed:
|
||||
return "run_failed"
|
||||
if used_helper_script:
|
||||
return "workflow_script"
|
||||
if (
|
||||
used_product_path
|
||||
and isinstance(workflow_file, str)
|
||||
and bool(workflow_file)
|
||||
and isinstance(deployment_id, str)
|
||||
and bool(deployment_id)
|
||||
and isinstance(run_id, str)
|
||||
and bool(run_id)
|
||||
and before_clicked is False
|
||||
and after_clicked is True
|
||||
):
|
||||
return "success"
|
||||
if not used_product_path and (
|
||||
before_clicked is not None or after_clicked is not None
|
||||
):
|
||||
return "workflow_not_used"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _contains_bool_marker(text: str, marker: str, value: str) -> bool:
|
||||
marker_index = text.find(marker)
|
||||
if marker_index == -1:
|
||||
|
||||
Reference in New Issue
Block a user