feat: migrate agent challenges to manifests
This commit is contained in:
@@ -1723,15 +1723,28 @@ not yet claimed.
|
|||||||
|
|
||||||
Evidence:
|
Evidence:
|
||||||
|
|
||||||
- `examples/agent_challenges/browser_click_challenge/workspace_template/prompt.md`:
|
- `examples/agent_challenges/browser_click_challenge/challenge.yaml`:
|
||||||
challenge prompt and self-report schema.
|
browser-click challenge manifest with success assertions.
|
||||||
- `examples/agent_challenges/browser_click_challenge/run_opencode_trials.py`:
|
- `examples/agent_challenges/browser_click_challenge/challenge-prompt.md`:
|
||||||
trial runner.
|
task-specific prompt for the browser-click challenge.
|
||||||
- `examples/agent_challenges/browser_click_challenge/classification.py`:
|
- `examples/agent_challenges/report_workflow_challenge/challenge.yaml`:
|
||||||
convenience classifier for returned reports.
|
report-workflow challenge manifest with success assertions.
|
||||||
- `examples/agent_challenges/browser_click_challenge/reports.py`: report
|
- `examples/agent_challenges/report_workflow_challenge/challenge-prompt.md`:
|
||||||
extraction and saving.
|
task-specific prompt for the report-workflow challenge.
|
||||||
|
- `examples/agent_challenges/run_trials.py`:
|
||||||
|
central trial runner accepting any challenge manifest and instruction profile.
|
||||||
|
- `examples/agent_challenges/manifests.py`:
|
||||||
|
generic manifest loading and path resolution.
|
||||||
|
- `examples/agent_challenges/prompts.py`:
|
||||||
|
prompt composition with base, profile, and challenge fragments.
|
||||||
- `tests/examples/test_opencode_browser_click_challenge.py`
|
- `tests/examples/test_opencode_browser_click_challenge.py`
|
||||||
|
- `tests/examples/test_report_workflow_challenge.py`
|
||||||
|
- `tests/examples/test_agent_challenge_harness_v2.py`
|
||||||
|
|
||||||
|
Two data-driven challenges now exist (browser-click and report-workflow), both
|
||||||
|
supporting `none`, `skills`, and `all` instruction profiles. Normalized metrics
|
||||||
|
and manual audit are implemented. Repeated audited model results are still
|
||||||
|
pending; no aggregate success claim is made.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
@@ -1804,5 +1817,7 @@ Evidence:
|
|||||||
|
|
||||||
- `examples/browser_click_workflow/`
|
- `examples/browser_click_workflow/`
|
||||||
- `examples/agent_challenges/browser_click_challenge/`
|
- `examples/agent_challenges/browser_click_challenge/`
|
||||||
|
- `examples/agent_challenges/report_workflow_challenge/`
|
||||||
- `tests/examples/test_browser_click_workflow_example.py`
|
- `tests/examples/test_browser_click_workflow_example.py`
|
||||||
- `tests/examples/test_opencode_browser_click_challenge.py`
|
- `tests/examples/test_opencode_browser_click_challenge.py`
|
||||||
|
- `tests/examples/test_report_workflow_challenge.py`
|
||||||
|
|||||||
@@ -218,7 +218,9 @@ stable.
|
|||||||
- Completed: the generic agent challenge harness now supports data-driven
|
- Completed: the generic agent challenge harness now supports data-driven
|
||||||
manifests, layered prompts, explicit `none|skills|all` profiles, one-hour hard
|
manifests, layered prompts, explicit `none|skills|all` profiles, one-hour hard
|
||||||
ceilings, normalized OpenCode tool/token evidence, policy findings, and
|
ceilings, normalized OpenCode tool/token evidence, policy findings, and
|
||||||
manual-audited reports.
|
manual-audited reports. Two data-driven challenges exist: browser-click and
|
||||||
|
report-workflow. The central `run_trials.py` runner accepts any challenge
|
||||||
|
manifest.
|
||||||
|
|
||||||
## Historical References
|
## Historical References
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
# Opencode Browser Click Challenge Harness
|
# Browser Click Challenge
|
||||||
|
|
||||||
This harness runs agent trials against the browser-click workflow challenge.
|
This challenge tests whether an agent can discover and use the workflow product
|
||||||
It is evidence tooling, not product runtime code.
|
path to build and run a browser-click workflow. It is evidence tooling, not
|
||||||
|
product runtime code.
|
||||||
The harness mechanics (workspace preparation, opencode I/O, report handling,
|
|
||||||
trial runner) live in the shared modules under
|
|
||||||
[`examples/agent_challenges/`](../). The browser-click challenge provides
|
|
||||||
challenge-specific metadata, prompt template, classification, and defaults.
|
|
||||||
|
|
||||||
The deterministic workflow example is:
|
The deterministic workflow example is:
|
||||||
|
|
||||||
@@ -14,142 +10,51 @@ The deterministic workflow example is:
|
|||||||
examples/browser_click_workflow/
|
examples/browser_click_workflow/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Default Behavior
|
## Running Trials
|
||||||
|
|
||||||
By default the harness does not start a `wf-rpc-server`. It prompts agents to
|
Use the central runner from the repository root:
|
||||||
use a per-trial configured local CLI path:
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
uv run wf --config examples/agent_challenges/browser_click_challenge/workspaces/<trial>/wf.config.json --local
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
```
|
--challenge examples/agent_challenges/browser_click_challenge/challenge.yaml `
|
||||||
|
--instruction-profile skills `
|
||||||
For each local-mode trial, the harness copies `workspace_template/` into
|
|
||||||
`workspaces/<model>-trial-<n>/`, generates a config whose Python source path is
|
|
||||||
relative to that copied config, and injects the config path into the prompt. This
|
|
||||||
builds the configured workflow server in the CLI process for each command and
|
|
||||||
uses the copied workspace's durable store. It does not reuse in-memory source
|
|
||||||
sessions across CLI invocations.
|
|
||||||
|
|
||||||
Use `--workspace-template` and `--source-root` to run a same-shape challenge with
|
|
||||||
a different prompt template or Python source root. Both default to the bundled
|
|
||||||
browser-click example settings.
|
|
||||||
|
|
||||||
Use `--start-server` when the trial should exercise the JSON-RPC server path.
|
|
||||||
With `--start-server`, the harness starts:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
uv run wf-rpc-server --config examples/browser_click_workflow/wf.config.json --host 127.0.0.1 --port 8772
|
|
||||||
```
|
|
||||||
|
|
||||||
It waits until `uv run wf --url http://127.0.0.1:8772/rpc status` passes,
|
|
||||||
injects that URL into the prompt, runs opencode, then stops the server. Use
|
|
||||||
`--server-url` to target an already-running server.
|
|
||||||
|
|
||||||
## One Trial
|
|
||||||
|
|
||||||
From the repository root:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
uv run python examples/agent_challenges/browser_click_challenge/run_opencode_trials.py `
|
|
||||||
--model opencode/mimo-v2.5-free `
|
--model opencode/mimo-v2.5-free `
|
||||||
--variant high `
|
--variant high `
|
||||||
--trials 1
|
--trials 1 `
|
||||||
```
|
|
||||||
|
|
||||||
Variant challenge example:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
uv run python examples/agent_challenges/browser_click_challenge/run_opencode_trials.py `
|
|
||||||
--workspace-template examples/agent_challenges/browser_click_challenge/workspace_template `
|
|
||||||
--source-root examples/browser_click_workflow `
|
|
||||||
--workspaces-dir examples/agent_challenges/browser_click_challenge/workspaces_alt `
|
|
||||||
--results-dir examples/agent_challenges/browser_click_challenge/results_alt
|
|
||||||
```
|
|
||||||
|
|
||||||
Results are written to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
examples/agent_challenges/browser_click_challenge/results/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Authoring Workspace
|
|
||||||
|
|
||||||
For manual authoring trials, use:
|
|
||||||
|
|
||||||
```text
|
|
||||||
examples/agent_challenges/browser_click_challenge/workspace_template/
|
|
||||||
```
|
|
||||||
|
|
||||||
It contains the prompt template and local store ignore rules without exposing a
|
|
||||||
generated draft patch answer file. The harness copies it automatically for
|
|
||||||
normal local-mode trials and writes `wf.config.json` into the copied workspace.
|
|
||||||
For manual experiments, copy it under:
|
|
||||||
|
|
||||||
```text
|
|
||||||
examples/agent_challenges/browser_click_challenge/workspaces/
|
|
||||||
```
|
|
||||||
|
|
||||||
`workspaces/` is ignored by Git so trial-created patches, plans, and scratch
|
|
||||||
files can be graded by hand without polluting the repository. The template's
|
|
||||||
store directory is also ignored.
|
|
||||||
|
|
||||||
## Saving Trial Reports
|
|
||||||
|
|
||||||
To save an agent's final answer from a harness result into its trial workspace:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
uv run python examples/agent_challenges/browser_click_challenge/save_trial_report.py `
|
|
||||||
--from-result examples/agent_challenges/browser_click_challenge/results/<trial>.json
|
|
||||||
```
|
|
||||||
|
|
||||||
The script infers the workspace from the result file and only writes
|
|
||||||
`<trial>/final-report.md`. Add evaluator commentary by editing that file after it
|
|
||||||
is saved. For manually copied reports, pass an explicit workspace and
|
|
||||||
`--input-file final-answer.md`.
|
|
||||||
|
|
||||||
## Saving Manual Audits
|
|
||||||
|
|
||||||
Keep `final-report.md` as captured agent output. If manual review finds that
|
|
||||||
the agent's YAML self-report was wrong or incomplete, write a sidecar audit:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
uv run python examples/agent_challenges/browser_click_challenge/save_manual_audit.py `
|
|
||||||
--from-result examples/agent_challenges/browser_click_challenge/results/<trial>.json `
|
|
||||||
--manual-classification success_code_assisted `
|
|
||||||
--set-read product_code=true `
|
|
||||||
--set-evidence trace_count=3 `
|
|
||||||
--correction "read.product_code: agent reported false, audited true" `
|
|
||||||
--notes "Valid product run, but raw plan shape came from product code/tests."
|
|
||||||
```
|
|
||||||
|
|
||||||
The script infers the workspace, run id, deployment id, automatic
|
|
||||||
classification, read flags, attempts, and notes from the result file. It writes
|
|
||||||
`<trial>/manual-audit.yaml`. Later benchmark summaries should treat the sidecar
|
|
||||||
as the human override, not mutate the captured agent report.
|
|
||||||
|
|
||||||
## Optional Opencode Server Attachment
|
|
||||||
|
|
||||||
`--attach` is opencode's server attach flag. It connects this non-interactive
|
|
||||||
run to an already-running opencode server, for example:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
--attach http://127.0.0.1:4096
|
--attach http://127.0.0.1:4096
|
||||||
```
|
```
|
||||||
|
|
||||||
It is not a direct MCP server URL. If that opencode server is configured with
|
Profiles `none` and `all` are separate invocations:
|
||||||
Playwright MCP tools, then the attached run can use those tools through
|
|
||||||
opencode. One possible MCP server command for such an opencode setup is:
|
|
||||||
|
|
||||||
```json
|
```powershell
|
||||||
{
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
"command": "npx",
|
--challenge examples/agent_challenges/browser_click_challenge/challenge.yaml `
|
||||||
"args": ["-y", "@playwright/mcp@latest"]
|
--instruction-profile none `
|
||||||
}
|
--model opencode/mimo-v2.5-free `
|
||||||
|
--trials 1
|
||||||
|
|
||||||
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
|
--challenge examples/agent_challenges/browser_click_challenge/challenge.yaml `
|
||||||
|
--instruction-profile all `
|
||||||
|
--model opencode/mimo-v2.5-free `
|
||||||
|
--trials 1
|
||||||
```
|
```
|
||||||
|
|
||||||
The baseline challenge does not require Playwright MCP. The score is based on
|
The hard timeout ceiling is 3,600 seconds per trial.
|
||||||
whether the agent used the workflow product path and produced the expected
|
|
||||||
workflow output.
|
## Default Behavior
|
||||||
|
|
||||||
|
By default the harness does not start a `wf-rpc-server`. It prompts agents to
|
||||||
|
use a per-trial configured local CLI path. Use `--start-server` when the trial
|
||||||
|
should exercise the JSON-RPC server path.
|
||||||
|
|
||||||
|
## Workspace Layout
|
||||||
|
|
||||||
|
- `workspace_template/` contains files copied into each isolated trial workspace.
|
||||||
|
- `workspaces/` holds per-trial workspaces (gitignored).
|
||||||
|
- `results/` holds per-trial result JSON files (gitignored).
|
||||||
|
- `challenge.yaml` declares the manifest, source, server, and report schema.
|
||||||
|
- `challenge-prompt.md` contains the task-specific prompt.
|
||||||
|
|
||||||
## Required Agent Report
|
## Required Agent Report
|
||||||
|
|
||||||
@@ -198,22 +103,12 @@ Each trial is classified as one of:
|
|||||||
- `parse_error`: the harness could not read opencode JSON/JSONL output.
|
- `parse_error`: the harness could not read opencode JSON/JSONL output.
|
||||||
- `unknown`: no clear success or failure signal was found.
|
- `unknown`: no clear success or failure signal was found.
|
||||||
|
|
||||||
|
Automatic success assertions are provisional until manual audit.
|
||||||
|
|
||||||
## Shared Harness Modules
|
## Shared Harness Modules
|
||||||
|
|
||||||
The generic modules in `examples/agent_challenges/` provide reusable harness
|
The generic modules in `examples/agent_challenges/` provide reusable harness
|
||||||
logic:
|
logic. See `workspace.py`, `runner.py`, `opencode_io.py`, `reports.py`,
|
||||||
|
`classification.py`, and `manifests.py`.
|
||||||
| Module | Purpose |
|
|
||||||
|--------|---------|
|
|
||||||
| `workspace.py` | `ChallengeDef`, `TrialConfig`, `TrialWorkspace`, `prepare_trial_workspace`, `write_trial_config`, `starting_trial_index`, `wf_command_prefix_for_config`, `render_prompt`, `server_command` |
|
|
||||||
| `runner.py` | `ManagedServer`, `start_server`, `run_trial`, `main` — generic trial runner parameterized by `ChallengeDef` and a classification function |
|
|
||||||
| `opencode_io.py` | `build_opencode_command`, `parse_opencode_output`, `result_text` |
|
|
||||||
| `reports.py` | `save_report`, `report_from_result`, `save_report_from_result_payload` |
|
|
||||||
| `classification.py` | `extract_challenge_report` (generic YAML extraction), `_contains_bool_marker` |
|
|
||||||
|
|
||||||
The browser-click challenge's `run_opencode_trials.py` and `save_trial_report.py`
|
|
||||||
are thin wrappers that pass `BROWSER_CLICK_DEF` and the browser-click
|
|
||||||
classification function to the generic runner. The same pattern can be used to
|
|
||||||
add new challenges without duplicating the harness.
|
|
||||||
|
|
||||||
Committed tests cover harness logic only. They do not invoke opencode.
|
Committed tests cover harness logic only. They do not invoke opencode.
|
||||||
|
|||||||
+9
-30
@@ -7,30 +7,11 @@ Build and successfully run a workflow that:
|
|||||||
3. Captures a before snapshot and an after snapshot.
|
3. Captures a before snapshot and an after snapshot.
|
||||||
4. Returns both snapshots as workflow output.
|
4. Returns both snapshots as workflow output.
|
||||||
|
|
||||||
Use this command prefix for product-facing operations:
|
Discover the `local.browser_click` source capabilities through `wf cap list`,
|
||||||
|
`wf cap inspect`, and `wf schema`. Repository implementation inspection is
|
||||||
|
profile-controlled; do not read source files unless your profile permits it.
|
||||||
|
|
||||||
```powershell
|
## Workflow Authoring Paths
|
||||||
{{wf_command_prefix}}
|
|
||||||
```
|
|
||||||
|
|
||||||
{{server_context}}
|
|
||||||
|
|
||||||
Use this repository's workflow product path. Start by checking the relevant
|
|
||||||
agent skills under `skills/`, especially the workflow/CLI skill references, and
|
|
||||||
use broad docs/code search only after those pointers are not enough. You should
|
|
||||||
use the `wf` CLI with the command prefix above, create or reuse a workflow
|
|
||||||
deployment, and run the deployment through the workflow API. Do not solve the
|
|
||||||
challenge with only a standalone Playwright/Python script.
|
|
||||||
|
|
||||||
Do not create a new helper script whose only job is to drive `WorkflowApi`
|
|
||||||
directly. The challenge is about whether the product-facing CLI/server workflow
|
|
||||||
can be discovered and used.
|
|
||||||
|
|
||||||
If you need to write a workflow definition, write a declarative JSON/YAML file
|
|
||||||
and then apply/run it through the product-facing workflow tools. Do not hide the
|
|
||||||
workflow construction inside a Python script.
|
|
||||||
|
|
||||||
Write all files you create for this attempt inside the designated workspace.
|
|
||||||
|
|
||||||
Two product-facing authoring paths are acceptable:
|
Two product-facing authoring paths are acceptable:
|
||||||
|
|
||||||
@@ -49,6 +30,8 @@ alias.
|
|||||||
Do not use a pre-existing generated patch or raw-plan answer file. If you find
|
Do not use a pre-existing generated patch or raw-plan answer file. If you find
|
||||||
one, ignore it and author your own workflow definition.
|
one, ignore it and author your own workflow definition.
|
||||||
|
|
||||||
|
## Disallowed Approaches
|
||||||
|
|
||||||
These do not satisfy the challenge, even if they produce the right output:
|
These do not satisfy the challenge, even if they produce the right output:
|
||||||
|
|
||||||
- importing `WorkflowApi`, `WorkflowServer`, or source functions directly;
|
- importing `WorkflowApi`, `WorkflowServer`, or source functions directly;
|
||||||
@@ -61,13 +44,7 @@ These do not satisfy the challenge, even if they produce the right output:
|
|||||||
- reusing artifacts, deployments, stores, workflow files, or run outputs created
|
- reusing artifacts, deployments, stores, workflow files, or run outputs created
|
||||||
by earlier trials.
|
by earlier trials.
|
||||||
|
|
||||||
The repository already includes a deterministic source example at:
|
## Evidence Requirements
|
||||||
|
|
||||||
```text
|
|
||||||
examples/browser_click_workflow/
|
|
||||||
```
|
|
||||||
|
|
||||||
You may inspect and use it.
|
|
||||||
|
|
||||||
Your final answer should include a short human-readable report with:
|
Your final answer should include a short human-readable report with:
|
||||||
|
|
||||||
@@ -79,6 +56,8 @@ Your final answer should include a short human-readable report with:
|
|||||||
- whether any server/browser process remains running,
|
- whether any server/browser process remains running,
|
||||||
- important failed attempts and how you fixed them.
|
- important failed attempts and how you fixed them.
|
||||||
|
|
||||||
|
## Required YAML Report
|
||||||
|
|
||||||
End your answer with exactly one fenced YAML block using this shape:
|
End your answer with exactly one fenced YAML block using this shape:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from examples.agent_challenges.workspace import ( # noqa: F401 - re-exported for backward compat
|
|
||||||
ChallengeDef,
|
|
||||||
TrialConfig,
|
|
||||||
TrialWorkspace,
|
|
||||||
render_prompt,
|
|
||||||
rpc_url_for_port,
|
|
||||||
server_command,
|
|
||||||
)
|
|
||||||
|
|
||||||
Classification = Literal[
|
|
||||||
"success",
|
|
||||||
"workflow_script",
|
|
||||||
"workflow_not_used",
|
|
||||||
"run_failed",
|
|
||||||
"timeout",
|
|
||||||
"parse_error",
|
|
||||||
"unknown",
|
|
||||||
]
|
|
||||||
|
|
||||||
CHALLENGE_REPORT_REQUIRED_FIELDS = {
|
|
||||||
"used_product_path",
|
|
||||||
"used_helper_script",
|
|
||||||
"workflow_file",
|
|
||||||
"deployment_id",
|
|
||||||
"run_id",
|
|
||||||
"before_clicked",
|
|
||||||
"after_clicked",
|
|
||||||
"run_failed",
|
|
||||||
"leftover_processes",
|
|
||||||
"read",
|
|
||||||
"attempts",
|
|
||||||
"missed_requirements",
|
|
||||||
"notes",
|
|
||||||
}
|
|
||||||
CHALLENGE_REPORT_READ_FIELDS = {
|
|
||||||
"skills",
|
|
||||||
"docs",
|
|
||||||
"product_code",
|
|
||||||
"adjacent_attempts",
|
|
||||||
"prior_store",
|
|
||||||
"existing_solution",
|
|
||||||
}
|
|
||||||
CHALLENGE_REPORT_ATTEMPT_FIELDS = {"total", "failed"}
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[3]
|
|
||||||
CHALLENGE_DIR = Path(__file__).resolve().parent
|
|
||||||
|
|
||||||
BROWSER_CLICK_DEF = ChallengeDef(
|
|
||||||
name="browser_click",
|
|
||||||
source_root=ROOT / "examples" / "browser_click_workflow",
|
|
||||||
source_id="local.browser_click",
|
|
||||||
source_module="ops",
|
|
||||||
source_registry="registry",
|
|
||||||
store_root=".wf_browser_click_store",
|
|
||||||
default_workspace_template=CHALLENGE_DIR / "workspace_template",
|
|
||||||
default_workspaces_dir=CHALLENGE_DIR / "workspaces",
|
|
||||||
default_results_dir=CHALLENGE_DIR / "results",
|
|
||||||
default_prompt=CHALLENGE_DIR / "workspace_template" / "prompt.md",
|
|
||||||
default_server_port=8772,
|
|
||||||
server_config_arg="examples/browser_click_workflow/wf.config.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
EXAMPLE_CONFIG_ARG = BROWSER_CLICK_DEF.server_config_arg
|
|
||||||
EXAMPLE_CONFIG = ROOT / EXAMPLE_CONFIG_ARG
|
|
||||||
EXAMPLE_SOURCE_ROOT = BROWSER_CLICK_DEF.source_root
|
|
||||||
LOCAL_WF_COMMAND_PREFIX = f"uv run wf --config {EXAMPLE_CONFIG_ARG} --local"
|
|
||||||
DEFAULT_PROMPT = BROWSER_CLICK_DEF.default_prompt
|
|
||||||
DEFAULT_RESULTS_DIR = BROWSER_CLICK_DEF.default_results_dir
|
|
||||||
DEFAULT_WORKSPACES_DIR = BROWSER_CLICK_DEF.default_workspaces_dir
|
|
||||||
DEFAULT_WORKSPACE_TEMPLATE = BROWSER_CLICK_DEF.default_workspace_template
|
|
||||||
DEFAULT_SERVER_PORT = BROWSER_CLICK_DEF.default_server_port
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
version: 1
|
||||||
|
id: browser_click
|
||||||
|
prompt: challenge-prompt.md
|
||||||
|
workspace_template: workspace_template
|
||||||
|
source:
|
||||||
|
id: local.browser_click
|
||||||
|
root: ../../browser_click_workflow
|
||||||
|
module: ops
|
||||||
|
registry: registry
|
||||||
|
store_root: .wf_browser_click_store
|
||||||
|
server:
|
||||||
|
config: ../../browser_click_workflow/wf.config.json
|
||||||
|
default_port: 8772
|
||||||
|
report:
|
||||||
|
required_fields:
|
||||||
|
- used_product_path
|
||||||
|
- used_helper_script
|
||||||
|
- workflow_file
|
||||||
|
- deployment_id
|
||||||
|
- run_id
|
||||||
|
- before_clicked
|
||||||
|
- after_clicked
|
||||||
|
- run_failed
|
||||||
|
- leftover_processes
|
||||||
|
- read
|
||||||
|
- attempts
|
||||||
|
- missed_requirements
|
||||||
|
- notes
|
||||||
|
success_assertions:
|
||||||
|
before_clicked: false
|
||||||
|
after_clicked: true
|
||||||
|
run_failed: false
|
||||||
|
leftover_processes: false
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from examples.agent_challenges.browser_click_challenge.challenge import (
|
|
||||||
CHALLENGE_REPORT_ATTEMPT_FIELDS,
|
|
||||||
CHALLENGE_REPORT_READ_FIELDS,
|
|
||||||
CHALLENGE_REPORT_REQUIRED_FIELDS,
|
|
||||||
Classification,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.classification import (
|
|
||||||
_contains_bool_marker,
|
|
||||||
extract_challenge_report,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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 ",
|
|
||||||
"wf-rpc-server",
|
|
||||||
]
|
|
||||||
workflow_evidence_markers = [
|
|
||||||
"deployment",
|
|
||||||
"run id",
|
|
||||||
"run_",
|
|
||||||
]
|
|
||||||
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
|
|
||||||
)
|
|
||||||
used_helper_script = (
|
|
||||||
"uv run python" in lowered
|
|
||||||
or "python examples/" in lowered
|
|
||||||
or "run_workflow.py" in lowered
|
|
||||||
)
|
|
||||||
failed = any(
|
|
||||||
marker in lowered
|
|
||||||
for marker in [
|
|
||||||
"error:",
|
|
||||||
"failed",
|
|
||||||
"traceback",
|
|
||||||
"exception",
|
|
||||||
"validation failed",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
before_false = _contains_bool_marker(lowered, "before.clicked", "false") or (
|
|
||||||
'"before"' in lowered and '"clicked": false' in lowered
|
|
||||||
)
|
|
||||||
after_true = _contains_bool_marker(lowered, "after.clicked", "true") or (
|
|
||||||
'"after"' in lowered and '"clicked": true' in lowered
|
|
||||||
)
|
|
||||||
|
|
||||||
if used_product_command and before_false and after_true and not failed:
|
|
||||||
return "success"
|
|
||||||
if has_workflow_evidence and used_helper_script and before_false and after_true:
|
|
||||||
return "workflow_script"
|
|
||||||
if (used_product_command or has_workflow_evidence) and failed:
|
|
||||||
return "run_failed"
|
|
||||||
if not has_workflow_evidence and (
|
|
||||||
before_false or after_true or "playwright" in lowered
|
|
||||||
):
|
|
||||||
return "workflow_not_used"
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
|
|
||||||
def classify_challenge_report(report: dict[str, Any]) -> Classification:
|
|
||||||
if challenge_report_schema_errors(report):
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
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 challenge_report_schema_errors(report: dict[str, Any]) -> list[str]:
|
|
||||||
errors: list[str] = []
|
|
||||||
missing = sorted(CHALLENGE_REPORT_REQUIRED_FIELDS.difference(report))
|
|
||||||
errors.extend(f"missing challenge_report.{field}" for field in missing)
|
|
||||||
|
|
||||||
for field in (
|
|
||||||
"used_product_path",
|
|
||||||
"used_helper_script",
|
|
||||||
"before_clicked",
|
|
||||||
"after_clicked",
|
|
||||||
"run_failed",
|
|
||||||
"leftover_processes",
|
|
||||||
):
|
|
||||||
if field in report and not isinstance(report[field], bool):
|
|
||||||
errors.append(f"challenge_report.{field} must be boolean")
|
|
||||||
for field in ("workflow_file", "deployment_id", "run_id", "notes"):
|
|
||||||
if field in report and not isinstance(report[field], str):
|
|
||||||
errors.append(f"challenge_report.{field} must be string")
|
|
||||||
|
|
||||||
read = report.get("read")
|
|
||||||
if isinstance(read, dict):
|
|
||||||
missing_read = sorted(CHALLENGE_REPORT_READ_FIELDS.difference(read))
|
|
||||||
errors.extend(
|
|
||||||
f"missing challenge_report.read.{field}" for field in missing_read
|
|
||||||
)
|
|
||||||
for field in CHALLENGE_REPORT_READ_FIELDS.intersection(read):
|
|
||||||
if not isinstance(read[field], bool):
|
|
||||||
errors.append(f"challenge_report.read.{field} must be boolean")
|
|
||||||
elif "read" in report:
|
|
||||||
errors.append("challenge_report.read must be object")
|
|
||||||
|
|
||||||
attempts = report.get("attempts")
|
|
||||||
if isinstance(attempts, dict):
|
|
||||||
missing_attempts = sorted(CHALLENGE_REPORT_ATTEMPT_FIELDS.difference(attempts))
|
|
||||||
errors.extend(
|
|
||||||
f"missing challenge_report.attempts.{field}" for field in missing_attempts
|
|
||||||
)
|
|
||||||
for field in CHALLENGE_REPORT_ATTEMPT_FIELDS.intersection(attempts):
|
|
||||||
value = attempts[field]
|
|
||||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
||||||
errors.append(f"challenge_report.attempts.{field} must be >= 0 integer")
|
|
||||||
elif "attempts" in report:
|
|
||||||
errors.append("challenge_report.attempts must be object")
|
|
||||||
|
|
||||||
missed = report.get("missed_requirements")
|
|
||||||
if "missed_requirements" in report and (
|
|
||||||
not isinstance(missed, list)
|
|
||||||
or any(not isinstance(item, str) for item in missed)
|
|
||||||
):
|
|
||||||
errors.append("challenge_report.missed_requirements must be list of strings")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from examples.agent_challenges.opencode_io import ( # noqa: F401
|
|
||||||
_event_text,
|
|
||||||
_parse_jsonl_tail,
|
|
||||||
build_opencode_command,
|
|
||||||
parse_opencode_output,
|
|
||||||
result_text,
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from examples.agent_challenges.reports import ( # noqa: F401
|
|
||||||
main,
|
|
||||||
report_from_result,
|
|
||||||
save_report,
|
|
||||||
save_report_from_result_payload,
|
|
||||||
)
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
# Support direct execution as `python examples/.../run_opencode_trials.py`.
|
|
||||||
# ruff: noqa: I001 - imports must stay after sys.path.insert
|
|
||||||
ROOT = Path(__file__).resolve().parents[3]
|
|
||||||
if str(ROOT) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT))
|
|
||||||
|
|
||||||
from examples.agent_challenges.browser_click_challenge.challenge import ( # noqa: E402
|
|
||||||
BROWSER_CLICK_DEF,
|
|
||||||
CHALLENGE_DIR,
|
|
||||||
CHALLENGE_REPORT_ATTEMPT_FIELDS,
|
|
||||||
CHALLENGE_REPORT_READ_FIELDS,
|
|
||||||
CHALLENGE_REPORT_REQUIRED_FIELDS,
|
|
||||||
Classification,
|
|
||||||
DEFAULT_PROMPT,
|
|
||||||
DEFAULT_RESULTS_DIR,
|
|
||||||
DEFAULT_SERVER_PORT,
|
|
||||||
DEFAULT_WORKSPACE_TEMPLATE,
|
|
||||||
DEFAULT_WORKSPACES_DIR,
|
|
||||||
EXAMPLE_CONFIG,
|
|
||||||
EXAMPLE_CONFIG_ARG,
|
|
||||||
EXAMPLE_SOURCE_ROOT,
|
|
||||||
LOCAL_WF_COMMAND_PREFIX,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.browser_click_challenge.classification import ( # noqa: E402
|
|
||||||
_contains_bool_marker,
|
|
||||||
challenge_report_schema_errors,
|
|
||||||
classify_challenge_report,
|
|
||||||
classify_output,
|
|
||||||
extract_challenge_report,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.opencode_io import ( # noqa: E402
|
|
||||||
_event_text,
|
|
||||||
_parse_jsonl_tail,
|
|
||||||
build_opencode_command,
|
|
||||||
parse_opencode_output,
|
|
||||||
result_text,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.reports import ( # noqa: E402
|
|
||||||
save_report_from_result_payload,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.runner import ( # noqa: E402
|
|
||||||
ManagedServer,
|
|
||||||
main as _generic_main,
|
|
||||||
run_trial as _generic_run_trial,
|
|
||||||
start_server as _generic_start_server,
|
|
||||||
stop_server as _generic_stop_server,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.workspace import ( # noqa: E402
|
|
||||||
TrialConfig,
|
|
||||||
TrialWorkspace,
|
|
||||||
prepare_trial_workspace as _generic_prepare,
|
|
||||||
render_prompt,
|
|
||||||
rpc_url_for_port,
|
|
||||||
server_command,
|
|
||||||
starting_trial_index as _generic_starting_index,
|
|
||||||
trial_output_path as _generic_trial_output_path,
|
|
||||||
wf_command_prefix_for_config as _generic_wf_prefix,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"BROWSER_CLICK_DEF",
|
|
||||||
"CHALLENGE_DIR",
|
|
||||||
"CHALLENGE_REPORT_ATTEMPT_FIELDS",
|
|
||||||
"CHALLENGE_REPORT_READ_FIELDS",
|
|
||||||
"CHALLENGE_REPORT_REQUIRED_FIELDS",
|
|
||||||
"Classification",
|
|
||||||
"DEFAULT_PROMPT",
|
|
||||||
"DEFAULT_RESULTS_DIR",
|
|
||||||
"DEFAULT_SERVER_PORT",
|
|
||||||
"DEFAULT_WORKSPACE_TEMPLATE",
|
|
||||||
"DEFAULT_WORKSPACES_DIR",
|
|
||||||
"EXAMPLE_CONFIG",
|
|
||||||
"EXAMPLE_CONFIG_ARG",
|
|
||||||
"EXAMPLE_SOURCE_ROOT",
|
|
||||||
"LOCAL_WF_COMMAND_PREFIX",
|
|
||||||
"ManagedServer",
|
|
||||||
"ROOT",
|
|
||||||
"TrialConfig",
|
|
||||||
"TrialWorkspace",
|
|
||||||
"_contains_bool_marker",
|
|
||||||
"_event_text",
|
|
||||||
"_parse_jsonl_tail",
|
|
||||||
"build_opencode_command",
|
|
||||||
"challenge_report_schema_errors",
|
|
||||||
"classify_challenge_report",
|
|
||||||
"classify_output",
|
|
||||||
"extract_challenge_report",
|
|
||||||
"main",
|
|
||||||
"parse_opencode_output",
|
|
||||||
"prepare_trial_workspace",
|
|
||||||
"render_prompt",
|
|
||||||
"result_text",
|
|
||||||
"rpc_url_for_port",
|
|
||||||
"run_trial",
|
|
||||||
"save_report_from_result_payload",
|
|
||||||
"server_command",
|
|
||||||
"start_server",
|
|
||||||
"starting_trial_index",
|
|
||||||
"stop_server",
|
|
||||||
"trial_output_path",
|
|
||||||
"wf_command_prefix_for_config",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
|
||||||
return _generic_main(BROWSER_CLICK_DEF, classify_output, argv)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_trial_workspace(
|
|
||||||
*,
|
|
||||||
model: str,
|
|
||||||
index: int,
|
|
||||||
workspaces_dir: Path = DEFAULT_WORKSPACES_DIR,
|
|
||||||
template_dir: Path = DEFAULT_WORKSPACE_TEMPLATE,
|
|
||||||
source_root: Path = EXAMPLE_SOURCE_ROOT,
|
|
||||||
) -> TrialWorkspace:
|
|
||||||
return _generic_prepare(
|
|
||||||
BROWSER_CLICK_DEF,
|
|
||||||
model=model,
|
|
||||||
index=index,
|
|
||||||
workspaces_dir=workspaces_dir,
|
|
||||||
template_dir=template_dir,
|
|
||||||
source_root=source_root,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_trial(
|
|
||||||
config: TrialConfig,
|
|
||||||
*,
|
|
||||||
index: int,
|
|
||||||
results_dir: Path,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
return _generic_run_trial(
|
|
||||||
config, index=index, results_dir=results_dir, classify_fn=classify_output
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def start_server(
|
|
||||||
*,
|
|
||||||
port: int,
|
|
||||||
timeout_seconds: int = 30,
|
|
||||||
) -> ManagedServer:
|
|
||||||
return _generic_start_server(
|
|
||||||
BROWSER_CLICK_DEF, port=port, timeout_seconds=timeout_seconds
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def stop_server(process: subprocess.Popen[str]) -> None:
|
|
||||||
return _generic_stop_server(process)
|
|
||||||
|
|
||||||
|
|
||||||
def starting_trial_index(
|
|
||||||
*,
|
|
||||||
model: str,
|
|
||||||
results_dir: Path,
|
|
||||||
workspaces_dir: Path,
|
|
||||||
) -> int:
|
|
||||||
return _generic_starting_index(
|
|
||||||
model=model, results_dir=results_dir, workspaces_dir=workspaces_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def trial_output_path(results_dir: Path, *, model: str, index: int) -> Path:
|
|
||||||
return _generic_trial_output_path(results_dir, model=model, index=index)
|
|
||||||
|
|
||||||
|
|
||||||
def wf_command_prefix_for_config(config_path: Path) -> str:
|
|
||||||
return _generic_wf_prefix(config_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Support direct execution as `python examples/.../save_manual_audit.py`.
|
|
||||||
ROOT = Path(__file__).resolve().parents[3]
|
|
||||||
if str(ROOT) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT))
|
|
||||||
|
|
||||||
from examples.agent_challenges.audit import ( # noqa: E402
|
|
||||||
audit_from_result,
|
|
||||||
main,
|
|
||||||
save_manual_audit,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ["audit_from_result", "main", "save_manual_audit"]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Support direct execution as `python examples/.../save_trial_report.py`.
|
|
||||||
ROOT = Path(__file__).resolve().parents[3]
|
|
||||||
if str(ROOT) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT))
|
|
||||||
|
|
||||||
from examples.agent_challenges.reports import ( # noqa: E402
|
|
||||||
main,
|
|
||||||
report_from_result,
|
|
||||||
save_report,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ["main", "report_from_result", "save_report"]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Report Workflow Challenge
|
||||||
|
|
||||||
|
This challenge tests whether an agent can discover and use the workflow product
|
||||||
|
path to build and run a three-node report pipeline. It is evidence tooling, not
|
||||||
|
product runtime code.
|
||||||
|
|
||||||
|
The deterministic workflow steps are: `read_notes -> extract_report ->
|
||||||
|
render_markdown_report`.
|
||||||
|
|
||||||
|
## Running Trials
|
||||||
|
|
||||||
|
Use the central runner from the repository root:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
|
--challenge examples/agent_challenges/report_workflow_challenge/challenge.yaml `
|
||||||
|
--instruction-profile skills `
|
||||||
|
--model opencode/mimo-v2.5-free `
|
||||||
|
--trials 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Profiles `none` and `all` are separate invocations:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
|
--challenge examples/agent_challenges/report_workflow_challenge/challenge.yaml `
|
||||||
|
--instruction-profile none `
|
||||||
|
--model opencode/mimo-v2.5-free `
|
||||||
|
--trials 1
|
||||||
|
|
||||||
|
uv run python examples/agent_challenges/run_trials.py `
|
||||||
|
--challenge examples/agent_challenges/report_workflow_challenge/challenge.yaml `
|
||||||
|
--instruction-profile all `
|
||||||
|
--model opencode/mimo-v2.5-free `
|
||||||
|
--trials 1
|
||||||
|
```
|
||||||
|
|
||||||
|
The hard timeout ceiling is 3,600 seconds per trial.
|
||||||
|
|
||||||
|
## Workspace Layout
|
||||||
|
|
||||||
|
- `workspace_template/` holds local store ignore rules (gitignored contents).
|
||||||
|
- `workspaces/` holds per-trial workspaces (gitignored).
|
||||||
|
- `results/` holds per-trial result JSON files (gitignored).
|
||||||
|
- `challenge.yaml` declares the manifest, source, server, and report schema.
|
||||||
|
- `challenge-prompt.md` contains the task-specific prompt.
|
||||||
|
|
||||||
|
## 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: "report_workflow.default"
|
||||||
|
run_id: "run_..."
|
||||||
|
title_matches: true
|
||||||
|
markdown_rendered: true
|
||||||
|
run_failed: false
|
||||||
|
read:
|
||||||
|
skills: true
|
||||||
|
docs: true
|
||||||
|
product_code: false
|
||||||
|
adjacent_attempts: false
|
||||||
|
prior_store: false
|
||||||
|
existing_solution: false
|
||||||
|
attempts:
|
||||||
|
total: 1
|
||||||
|
failed: 0
|
||||||
|
missed_requirements:
|
||||||
|
- "none"
|
||||||
|
notes: "short explanation"
|
||||||
|
```
|
||||||
|
|
||||||
|
Automatic success assertions are provisional until manual audit.
|
||||||
|
|
||||||
|
## Future Design Notes
|
||||||
|
|
||||||
|
> Ideas preserved from the original user-authored challenge notes.
|
||||||
|
|
||||||
|
The initial challenge is the deterministic three-node report pipeline so results
|
||||||
|
are auditable. Future expansions could include:
|
||||||
|
|
||||||
|
- docs collection -> foreach summarize by person/point
|
||||||
|
- render markdown -> send to email
|
||||||
|
- foreach (collect by person -> render markdown -> lookup name -> send to email)
|
||||||
|
- save somewhere
|
||||||
|
|
||||||
|
This exercises many workflow capabilities. To make it easier, the docs could be
|
||||||
|
very structured: each doc by topic, points by person, or some better system.
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Report Workflow Challenge
|
||||||
|
|
||||||
|
Build and successfully run a workflow that processes structured notes into a
|
||||||
|
rendered markdown report.
|
||||||
|
|
||||||
|
The workflow must execute these three steps in sequence:
|
||||||
|
|
||||||
|
1. `read_notes` -- read structured input notes
|
||||||
|
2. `extract_report` -- extract report content from the notes
|
||||||
|
3. `render_markdown_report` -- render the extracted content as a markdown report
|
||||||
|
|
||||||
|
## Workflow Authoring Paths
|
||||||
|
|
||||||
|
Two product-facing authoring paths are acceptable:
|
||||||
|
|
||||||
|
- draft path: create a draft from one capability, use focused draft edit
|
||||||
|
commands or your own RFC 6902 JSON Patch, then validate/save/deploy/run it;
|
||||||
|
- raw-plan path: write your own complete raw workflow plan file and use
|
||||||
|
`wf artifact create-from-plan` before deploy/run.
|
||||||
|
|
||||||
|
Do not mix the formats. Drafts use `steps`, `routes`, and step field `use`.
|
||||||
|
Raw plans use `nodes`, `edges`, and node field `node`. Do not pass draft JSON to
|
||||||
|
`wf artifact create-from-plan`.
|
||||||
|
|
||||||
|
The deployment command is `wf deploy save`; `wf deploy create` is accepted as an
|
||||||
|
alias.
|
||||||
|
|
||||||
|
Do not use a pre-existing generated patch or raw-plan answer file. If you find
|
||||||
|
one, ignore it and author your own workflow definition.
|
||||||
|
|
||||||
|
## Disallowed Approaches
|
||||||
|
|
||||||
|
These do not satisfy the challenge, even if they produce the right output:
|
||||||
|
|
||||||
|
- importing `WorkflowApi`, `WorkflowServer`, or source functions directly;
|
||||||
|
- writing a Python script that calls internal APIs to create artifacts,
|
||||||
|
deployments, or runs;
|
||||||
|
- calling the source functions directly instead of running a deployed workflow;
|
||||||
|
- solving it as a standalone script with no `wf artifact`, `wf deploy`, and
|
||||||
|
`wf run` lifecycle;
|
||||||
|
- reusing artifacts, deployments, stores, workflow files, or run outputs created
|
||||||
|
by earlier trials.
|
||||||
|
|
||||||
|
## Evidence Requirements
|
||||||
|
|
||||||
|
Your final answer should include a short human-readable report with:
|
||||||
|
|
||||||
|
- the commands you ran,
|
||||||
|
- the deployment id,
|
||||||
|
- the run id if one was produced,
|
||||||
|
- evidence that the report title equals `Weekly Project Update`,
|
||||||
|
- evidence that the rendered markdown begins with `# Weekly Project Update`,
|
||||||
|
- important failed attempts and how you fixed them.
|
||||||
|
|
||||||
|
## Required YAML Report
|
||||||
|
|
||||||
|
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: "report_workflow.default"
|
||||||
|
run_id: "run_..."
|
||||||
|
title_matches: true
|
||||||
|
markdown_rendered: true
|
||||||
|
run_failed: false
|
||||||
|
read:
|
||||||
|
skills: true
|
||||||
|
docs: true
|
||||||
|
product_code: false
|
||||||
|
adjacent_attempts: false
|
||||||
|
prior_store: false
|
||||||
|
existing_solution: false
|
||||||
|
attempts:
|
||||||
|
total: 1
|
||||||
|
failed: 0
|
||||||
|
missed_requirements:
|
||||||
|
- "none"
|
||||||
|
notes: "short explanation"
|
||||||
|
```
|
||||||
|
|
||||||
|
Reporting rules:
|
||||||
|
|
||||||
|
- The YAML block is a self-report only. It will be manually audited against your
|
||||||
|
commands, files, and run output.
|
||||||
|
- Set `used_product_path: true` only if you used `uv run wf ...` commands for
|
||||||
|
the artifact/deployment/run lifecycle, either in local same-process mode or
|
||||||
|
through `wf-rpc-server`.
|
||||||
|
- Set `used_helper_script: true` if you wrote or ran any script whose main
|
||||||
|
purpose was to drive the workflow API, JSON-RPC API, server internals, or
|
||||||
|
source functions outside `wf`.
|
||||||
|
- Set `title_matches: true` if the rendered report title equals
|
||||||
|
`Weekly Project Update`.
|
||||||
|
- Set `markdown_rendered: true` if the output is valid markdown beginning with
|
||||||
|
`# Weekly Project Update`.
|
||||||
|
- Set `read.product_code: true` if you or a spawned subagent grepped, searched,
|
||||||
|
or read source files under `src/`, `tests/`, or implementation examples to
|
||||||
|
determine plan shape or product behavior.
|
||||||
|
- Set `read.docs: true` if you read files under `docs/`.
|
||||||
|
- Set `read.skills: true` if you read files under `skills/`.
|
||||||
|
- Set `read.adjacent_attempts: true` if you read files under other trial
|
||||||
|
workspaces, prior result files, generated reports, or previous attempt
|
||||||
|
artifacts.
|
||||||
|
- Set `read.prior_store: true` if you inspected or reused `.wf_*` stores, saved
|
||||||
|
artifacts, deployments, or runs from outside your current trial workspace.
|
||||||
|
- Set `read.existing_solution: true` if you copied or inspected a ready-made
|
||||||
|
solution plan/workflow for this same challenge.
|
||||||
|
- `attempts.total` should count distinct product-lifecycle attempts, including
|
||||||
|
failed artifact creation, failed deployment validation, failed run starts, and
|
||||||
|
abandoned workflow plans.
|
||||||
|
- `attempts.failed` should count attempts that failed validation, failed to run,
|
||||||
|
produced wrong output, or were abandoned.
|
||||||
|
|
||||||
|
Spawned subagents count as you. If a subagent reads product code, set
|
||||||
|
`read.product_code: true`. If a subagent reads prior attempts, set
|
||||||
|
`read.adjacent_attempts: true`.
|
||||||
|
|
||||||
|
If something fails, report the exact command and error instead of hiding it.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
version: 1
|
||||||
|
id: report_workflow
|
||||||
|
prompt: challenge-prompt.md
|
||||||
|
workspace_template: workspace_template
|
||||||
|
source:
|
||||||
|
id: local.report
|
||||||
|
root: ../../report_workflow
|
||||||
|
module: ops
|
||||||
|
registry: registry
|
||||||
|
store_root: .wf_report_challenge_store
|
||||||
|
server:
|
||||||
|
config: ../../report_workflow/wf.config.json
|
||||||
|
default_port: 8773
|
||||||
|
report:
|
||||||
|
required_fields:
|
||||||
|
- used_product_path
|
||||||
|
- used_helper_script
|
||||||
|
- workflow_file
|
||||||
|
- deployment_id
|
||||||
|
- run_id
|
||||||
|
- title_matches
|
||||||
|
- markdown_rendered
|
||||||
|
- run_failed
|
||||||
|
- read
|
||||||
|
- attempts
|
||||||
|
- missed_requirements
|
||||||
|
- notes
|
||||||
|
success_assertions:
|
||||||
|
title_matches: true
|
||||||
|
markdown_rendered: true
|
||||||
|
run_failed: false
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
delete and replace file if youve done it.
|
|
||||||
|
|
||||||
THE OG. the GOAT. the latest and greatest.
|
|
||||||
|
|
||||||
examples\demo_workflow.py
|
|
||||||
|
|
||||||
the flow is docs -> foreach summarize -> email by flag.
|
|
||||||
|
|
||||||
also look at report_workflow, and other workflows in example.
|
|
||||||
|
|
||||||
im expanding it to the biggest and most challenging:
|
|
||||||
|
|
||||||
docs collection -> foreach summarize by person / point
|
|
||||||
|-> render markdown -> send to email
|
|
||||||
|-> foreach (collect by person -> render markdown -> lookup name -> send to email)
|
|
||||||
-> save somewhere
|
|
||||||
|
|
||||||
this exercises a lot of things.
|
|
||||||
|
|
||||||
to make shit easy lets have the docs VERY structured. Each doc is by topic(?), points by person(?), or some better system.
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -896,3 +896,101 @@ def test_v2_runner_preserves_report_parse_error_on_malformed_yaml(
|
|||||||
"YAMLError",
|
"YAMLError",
|
||||||
)
|
)
|
||||||
assert result.get("challenge_report") is None
|
assert result.get("challenge_report") is None
|
||||||
|
|
||||||
|
|
||||||
|
BROWSER_CHALLENGE = (
|
||||||
|
ROOT
|
||||||
|
/ "examples"
|
||||||
|
/ "agent_challenges"
|
||||||
|
/ "browser_click_challenge"
|
||||||
|
/ "challenge.yaml"
|
||||||
|
)
|
||||||
|
REPORT_CHALLENGE = (
|
||||||
|
ROOT
|
||||||
|
/ "examples"
|
||||||
|
/ "agent_challenges"
|
||||||
|
/ "report_workflow_challenge"
|
||||||
|
/ "challenge.yaml"
|
||||||
|
)
|
||||||
|
INSTRUCTION_BUNDLE = (
|
||||||
|
ROOT / "examples" / "agent_challenges" / "instruction_bundles" / "workflow_cli.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"manifest_path",
|
||||||
|
[BROWSER_CHALLENGE, REPORT_CHALLENGE],
|
||||||
|
ids=["browser_click", "report_workflow"],
|
||||||
|
)
|
||||||
|
def test_both_challenges_load_through_same_manifest(manifest_path: Path) -> None:
|
||||||
|
loaded = load_challenge_manifest(manifest_path)
|
||||||
|
|
||||||
|
assert loaded.manifest.version == 1
|
||||||
|
assert loaded.prompt_path.is_file()
|
||||||
|
assert loaded.workspace_template.is_dir()
|
||||||
|
assert loaded.manifest.report.required_fields
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"manifest_path, expected_id, expected_source_id",
|
||||||
|
[
|
||||||
|
(BROWSER_CHALLENGE, "browser_click", "local.browser_click"),
|
||||||
|
(REPORT_CHALLENGE, "report_workflow", "local.report"),
|
||||||
|
],
|
||||||
|
ids=["browser_click", "report_workflow"],
|
||||||
|
)
|
||||||
|
def test_both_challenges_prepare_workspaces_under_each_profile(
|
||||||
|
manifest_path: Path,
|
||||||
|
expected_id: str,
|
||||||
|
expected_source_id: str,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
loaded = load_challenge_manifest(manifest_path)
|
||||||
|
|
||||||
|
assert loaded.manifest.id == expected_id
|
||||||
|
assert loaded.manifest.source.id == expected_source_id
|
||||||
|
|
||||||
|
for profile in InstructionProfile:
|
||||||
|
workspace = prepare_v2_trial_workspace(
|
||||||
|
loaded,
|
||||||
|
profile=profile,
|
||||||
|
model="test-model",
|
||||||
|
index=1,
|
||||||
|
workspaces_dir=tmp_path / profile.value,
|
||||||
|
instruction_bundle=INSTRUCTION_BUNDLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert workspace.config_path.is_file()
|
||||||
|
config = json.loads(workspace.config_path.read_text(encoding="utf-8"))
|
||||||
|
assert config["client"]["target"] == {"kind": "local"}
|
||||||
|
assert config["server"]["store"]["root"] == loaded.manifest.store_root
|
||||||
|
|
||||||
|
if profile in (InstructionProfile.SKILLS, InstructionProfile.ALL):
|
||||||
|
assert (workspace.root / ".agent/skills/wf-cli/SKILL.md").is_file()
|
||||||
|
else:
|
||||||
|
assert not (workspace.root / ".agent").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_both_challenges_produce_different_challenge_hashes_but_same_base(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
from examples.agent_challenges.prompts import compose_trial_prompt
|
||||||
|
|
||||||
|
browser = load_challenge_manifest(BROWSER_CHALLENGE)
|
||||||
|
report = load_challenge_manifest(REPORT_CHALLENGE)
|
||||||
|
|
||||||
|
hashes: dict[str, str] = {}
|
||||||
|
for name, challenge in [("browser", browser), ("report", report)]:
|
||||||
|
for profile in InstructionProfile:
|
||||||
|
rendered = compose_trial_prompt(
|
||||||
|
challenge,
|
||||||
|
profile=profile,
|
||||||
|
wf_command_prefix="uv run wf --config wf.config.json --local",
|
||||||
|
server_context="Local mode.",
|
||||||
|
workspace_path=tmp_path / f"{name}_{profile.value}",
|
||||||
|
)
|
||||||
|
hashes[f"{name}_{profile.value}"] = rendered.challenge_sha256
|
||||||
|
|
||||||
|
assert hashes["browser_none"] != hashes["report_none"]
|
||||||
|
for profile in InstructionProfile:
|
||||||
|
assert hashes[f"browser_{profile.value}"] != hashes[f"report_{profile.value}"]
|
||||||
|
|||||||
@@ -2,82 +2,79 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from examples.agent_challenges import reports as generic_reports
|
from examples.agent_challenges import reports as generic_reports
|
||||||
from examples.agent_challenges.browser_click_challenge import (
|
from examples.agent_challenges.audit import (
|
||||||
run_opencode_trials,
|
main as save_manual_audit_main,
|
||||||
)
|
)
|
||||||
from examples.agent_challenges.browser_click_challenge.challenge import (
|
from examples.agent_challenges.manifests import load_challenge_manifest
|
||||||
BROWSER_CLICK_DEF,
|
from examples.agent_challenges.models import InstructionProfile
|
||||||
LOCAL_WF_COMMAND_PREFIX,
|
from examples.agent_challenges.opencode_io import (
|
||||||
render_prompt,
|
|
||||||
server_command,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.browser_click_challenge.classification import (
|
|
||||||
challenge_report_schema_errors,
|
|
||||||
classify_challenge_report,
|
|
||||||
classify_output,
|
|
||||||
extract_challenge_report,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.browser_click_challenge.opencode_io import (
|
|
||||||
build_opencode_command,
|
build_opencode_command,
|
||||||
parse_opencode_output,
|
parse_opencode_output,
|
||||||
)
|
)
|
||||||
from examples.agent_challenges.browser_click_challenge.reports import (
|
from examples.agent_challenges.reports import (
|
||||||
|
main as save_trial_report_main,
|
||||||
|
)
|
||||||
|
from examples.agent_challenges.reports import (
|
||||||
report_from_result,
|
report_from_result,
|
||||||
save_report,
|
save_report,
|
||||||
)
|
)
|
||||||
from examples.agent_challenges.browser_click_challenge.run_opencode_trials import (
|
from examples.agent_challenges.runner import main as generic_runner_main
|
||||||
prepare_trial_workspace,
|
from examples.agent_challenges.runner import run_trial
|
||||||
run_trial,
|
|
||||||
starting_trial_index,
|
|
||||||
trial_output_path,
|
|
||||||
wf_command_prefix_for_config,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.browser_click_challenge.save_manual_audit import (
|
|
||||||
main as save_manual_audit_main,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.browser_click_challenge.save_trial_report import (
|
|
||||||
main as save_trial_report_main,
|
|
||||||
)
|
|
||||||
from examples.agent_challenges.workspace import (
|
from examples.agent_challenges.workspace import (
|
||||||
ChallengeDef,
|
ChallengeDef,
|
||||||
TrialConfig,
|
TrialConfig,
|
||||||
|
prepare_trial_workspace,
|
||||||
|
prepare_v2_trial_workspace,
|
||||||
|
render_prompt,
|
||||||
|
server_command,
|
||||||
|
starting_trial_index,
|
||||||
|
trial_output_path,
|
||||||
|
wf_command_prefix_for_config,
|
||||||
write_trial_config,
|
write_trial_config,
|
||||||
)
|
)
|
||||||
from examples.agent_challenges.workspace import (
|
|
||||||
prepare_trial_workspace as generic_prepare_trial_workspace,
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
LOCAL_WF_COMMAND_PREFIX = (
|
||||||
|
"uv run wf --config examples/browser_click_workflow/wf.config.json --local"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
BROWSER_CLICK_DEF = ChallengeDef(
|
||||||
def _valid_challenge_report(**overrides: object) -> dict[str, object]:
|
name="browser_click",
|
||||||
report: dict[str, object] = {
|
source_root=ROOT / "examples" / "browser_click_workflow",
|
||||||
"used_product_path": True,
|
source_id="local.browser_click",
|
||||||
"used_helper_script": False,
|
source_module="ops",
|
||||||
"workflow_file": "browser-click.workflow.yaml",
|
source_registry="registry",
|
||||||
"deployment_id": "browser_click_case_study.default",
|
store_root=".wf_browser_click_store",
|
||||||
"run_id": "run_123",
|
default_workspace_template=ROOT
|
||||||
"before_clicked": False,
|
/ "examples"
|
||||||
"after_clicked": True,
|
/ "agent_challenges"
|
||||||
"run_failed": False,
|
/ "browser_click_challenge"
|
||||||
"leftover_processes": False,
|
/ "workspace_template",
|
||||||
"read": {
|
default_workspaces_dir=ROOT
|
||||||
"skills": True,
|
/ "examples"
|
||||||
"docs": True,
|
/ "agent_challenges"
|
||||||
"product_code": False,
|
/ "browser_click_challenge"
|
||||||
"adjacent_attempts": False,
|
/ "workspaces",
|
||||||
"prior_store": False,
|
default_results_dir=ROOT
|
||||||
"existing_solution": False,
|
/ "examples"
|
||||||
},
|
/ "agent_challenges"
|
||||||
"attempts": {"total": 1, "failed": 0},
|
/ "browser_click_challenge"
|
||||||
"missed_requirements": ["none"],
|
/ "results",
|
||||||
"notes": "ok",
|
default_prompt=ROOT
|
||||||
}
|
/ "examples"
|
||||||
report.update(overrides)
|
/ "agent_challenges"
|
||||||
return report
|
/ "browser_click_challenge"
|
||||||
|
/ "challenge-prompt.md",
|
||||||
|
default_server_port=8772,
|
||||||
|
server_config_arg="examples/browser_click_workflow/wf.config.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_build_opencode_command_without_attach(tmp_path: Path) -> None:
|
def test_build_opencode_command_without_attach(tmp_path: Path) -> None:
|
||||||
@@ -123,24 +120,6 @@ def test_build_opencode_command_with_attach(tmp_path: Path) -> None:
|
|||||||
assert "http://127.0.0.1:4096" in command
|
assert "http://127.0.0.1:4096" in command
|
||||||
|
|
||||||
|
|
||||||
def test_run_opencode_trials_script_supports_direct_execution() -> None:
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"uv",
|
|
||||||
"run",
|
|
||||||
"python",
|
|
||||||
"examples/agent_challenges/browser_click_challenge/run_opencode_trials.py",
|
|
||||||
"--help",
|
|
||||||
],
|
|
||||||
text=True,
|
|
||||||
capture_output=True,
|
|
||||||
check=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.returncode == 0
|
|
||||||
assert "--model" in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_trial_saves_final_report_from_successful_result(
|
def test_run_trial_saves_final_report_from_successful_result(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
@@ -204,7 +183,12 @@ def test_run_trial_saves_final_report_from_successful_result(
|
|||||||
server_context="Use local CLI mode.",
|
server_context="Use local CLI mode.",
|
||||||
)
|
)
|
||||||
|
|
||||||
result = run_trial(config, index=1, results_dir=tmp_path / "results")
|
result = run_trial(
|
||||||
|
config,
|
||||||
|
index=1,
|
||||||
|
results_dir=tmp_path / "results",
|
||||||
|
classify_fn=lambda _text: "success",
|
||||||
|
)
|
||||||
|
|
||||||
assert result["classification"] == "success"
|
assert result["classification"] == "success"
|
||||||
assert result["report_path"] == (workspace / "final-report.md").as_posix()
|
assert result["report_path"] == (workspace / "final-report.md").as_posix()
|
||||||
@@ -242,7 +226,12 @@ def test_run_trial_records_report_save_error_for_timeout(
|
|||||||
server_context="Use local CLI mode.",
|
server_context="Use local CLI mode.",
|
||||||
)
|
)
|
||||||
|
|
||||||
result = run_trial(config, index=1, results_dir=tmp_path / "results")
|
result = run_trial(
|
||||||
|
config,
|
||||||
|
index=1,
|
||||||
|
results_dir=tmp_path / "results",
|
||||||
|
classify_fn=lambda _text: "unknown",
|
||||||
|
)
|
||||||
|
|
||||||
assert result["classification"] == "timeout"
|
assert result["classification"] == "timeout"
|
||||||
assert result["report_save_error"] == "result file is missing parsed output"
|
assert result["report_save_error"] == "result file is missing parsed output"
|
||||||
@@ -282,7 +271,12 @@ def test_run_trial_records_parse_error_details(
|
|||||||
server_context="Use local CLI mode.",
|
server_context="Use local CLI mode.",
|
||||||
)
|
)
|
||||||
|
|
||||||
result = run_trial(config, index=1, results_dir=tmp_path / "results")
|
result = run_trial(
|
||||||
|
config,
|
||||||
|
index=1,
|
||||||
|
results_dir=tmp_path / "results",
|
||||||
|
classify_fn=lambda _text: "unknown",
|
||||||
|
)
|
||||||
|
|
||||||
assert result["classification"] == "parse_error"
|
assert result["classification"] == "parse_error"
|
||||||
assert result["parse_error"]["type"] == "JSONDecodeError"
|
assert result["parse_error"]["type"] == "JSONDecodeError"
|
||||||
@@ -339,159 +333,6 @@ def test_parse_opencode_output_prefers_text_event_before_step_finish() -> None:
|
|||||||
assert parsed["text"] == "deployment id: demo.default\nbefore.clicked is false"
|
assert parsed["text"] == "deployment id: demo.default\nbefore.clicked is false"
|
||||||
|
|
||||||
|
|
||||||
def test_classify_output_success() -> None:
|
|
||||||
result = classify_output(
|
|
||||||
"""
|
|
||||||
uv run wf-rpc-server --config examples/browser_click_workflow/wf.config.json
|
|
||||||
uv run wf run start browser_click_case_study.default
|
|
||||||
deployment id: browser_click_case_study.default
|
|
||||||
run id: run_123
|
|
||||||
before.clicked is false
|
|
||||||
after.clicked is true
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "success"
|
|
||||||
|
|
||||||
|
|
||||||
def test_extract_challenge_report_from_yaml_block() -> None:
|
|
||||||
text = """
|
|
||||||
The run worked.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
challenge_report:
|
|
||||||
used_product_path: true
|
|
||||||
used_helper_script: false
|
|
||||||
workflow_file: "browser-click.workflow.yaml"
|
|
||||||
deployment_id: "browser_click_case_study.default"
|
|
||||||
run_id: "run_123"
|
|
||||||
before_clicked: false
|
|
||||||
after_clicked: true
|
|
||||||
run_failed: false
|
|
||||||
leftover_processes: false
|
|
||||||
read:
|
|
||||||
skills: true
|
|
||||||
docs: true
|
|
||||||
product_code: false
|
|
||||||
adjacent_attempts: false
|
|
||||||
prior_store: false
|
|
||||||
existing_solution: false
|
|
||||||
attempts:
|
|
||||||
total: 1
|
|
||||||
failed: 0
|
|
||||||
missed_requirements:
|
|
||||||
- "none"
|
|
||||||
notes: "ok"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
report = extract_challenge_report(text)
|
|
||||||
|
|
||||||
assert report is not None
|
|
||||||
assert report["used_product_path"] is True
|
|
||||||
assert report["before_clicked"] is False
|
|
||||||
assert report["after_clicked"] is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_challenge_report_success() -> None:
|
|
||||||
result = classify_challenge_report(_valid_challenge_report())
|
|
||||||
|
|
||||||
assert result == "success"
|
|
||||||
|
|
||||||
|
|
||||||
def test_challenge_report_schema_errors_reject_missing_read_block() -> None:
|
|
||||||
report = _valid_challenge_report()
|
|
||||||
report.pop("read")
|
|
||||||
|
|
||||||
assert challenge_report_schema_errors(report) == ["missing challenge_report.read"]
|
|
||||||
assert classify_challenge_report(report) == "unknown"
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_output_prefers_yaml_report() -> None:
|
|
||||||
result = classify_output(
|
|
||||||
"""
|
|
||||||
Some prose that would otherwise be ambiguous.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
challenge_report:
|
|
||||||
used_product_path: true
|
|
||||||
used_helper_script: false
|
|
||||||
workflow_file: "browser-click.workflow.yaml"
|
|
||||||
deployment_id: "browser_click_case_study.default"
|
|
||||||
run_id: "run_123"
|
|
||||||
before_clicked: false
|
|
||||||
after_clicked: true
|
|
||||||
run_failed: false
|
|
||||||
leftover_processes: false
|
|
||||||
read:
|
|
||||||
skills: true
|
|
||||||
docs: true
|
|
||||||
product_code: false
|
|
||||||
adjacent_attempts: false
|
|
||||||
prior_store: false
|
|
||||||
existing_solution: false
|
|
||||||
attempts:
|
|
||||||
total: 1
|
|
||||||
failed: 0
|
|
||||||
missed_requirements:
|
|
||||||
- "none"
|
|
||||||
notes: "ok"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "success"
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_challenge_report_detects_helper_script() -> None:
|
|
||||||
result = classify_challenge_report(
|
|
||||||
_valid_challenge_report(
|
|
||||||
used_product_path=False,
|
|
||||||
used_helper_script=True,
|
|
||||||
workflow_file="",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "workflow_script"
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_output_workflow_script() -> None:
|
|
||||||
result = classify_output(
|
|
||||||
"""
|
|
||||||
uv run python examples/browser_click_workflow/run_workflow.py
|
|
||||||
Deployment id: browser_click_case_study.default
|
|
||||||
Run id: run_123
|
|
||||||
`before.clicked`: `False`
|
|
||||||
`after.clicked`: `True`
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "workflow_script"
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_output_workflow_not_used() -> None:
|
|
||||||
result = classify_output(
|
|
||||||
"""
|
|
||||||
I wrote a Playwright script.
|
|
||||||
before clicked false
|
|
||||||
after clicked true
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "workflow_not_used"
|
|
||||||
|
|
||||||
|
|
||||||
def test_classify_output_run_failed() -> None:
|
|
||||||
result = classify_output(
|
|
||||||
"""
|
|
||||||
wf run start browser_click_case_study.default
|
|
||||||
error: deployment validation failed
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == "run_failed"
|
|
||||||
|
|
||||||
|
|
||||||
def test_trial_output_path_is_zero_padded(tmp_path: Path) -> None:
|
def test_trial_output_path_is_zero_padded(tmp_path: Path) -> None:
|
||||||
path = trial_output_path(tmp_path, model="opencode/mimo-v2.5-free", index=3)
|
path = trial_output_path(tmp_path, model="opencode/mimo-v2.5-free", index=3)
|
||||||
|
|
||||||
@@ -509,7 +350,23 @@ def test_prepare_trial_workspace_copies_template_to_model_trial_dir(
|
|||||||
source_root = tmp_path / "browser_click_workflow"
|
source_root = tmp_path / "browser_click_workflow"
|
||||||
source_root.mkdir()
|
source_root.mkdir()
|
||||||
|
|
||||||
|
defn = ChallengeDef(
|
||||||
|
name="browser_click",
|
||||||
|
source_root=source_root,
|
||||||
|
source_id="local.browser_click",
|
||||||
|
source_module="ops",
|
||||||
|
source_registry="registry",
|
||||||
|
store_root=".wf_browser_click_store",
|
||||||
|
default_workspace_template=template,
|
||||||
|
default_workspaces_dir=workspaces,
|
||||||
|
default_results_dir=tmp_path / "results",
|
||||||
|
default_prompt=template / "prompt.md",
|
||||||
|
default_server_port=8772,
|
||||||
|
server_config_arg="examples/browser_click_workflow/wf.config.json",
|
||||||
|
)
|
||||||
|
|
||||||
prepared = prepare_trial_workspace(
|
prepared = prepare_trial_workspace(
|
||||||
|
defn,
|
||||||
model="opencode/mimo-v2.5-free",
|
model="opencode/mimo-v2.5-free",
|
||||||
index=7,
|
index=7,
|
||||||
workspaces_dir=workspaces,
|
workspaces_dir=workspaces,
|
||||||
@@ -864,12 +721,28 @@ def test_prepare_trial_workspace_uses_next_available_directory(
|
|||||||
first.mkdir(parents=True)
|
first.mkdir(parents=True)
|
||||||
stale.write_text("stale", encoding="utf-8")
|
stale.write_text("stale", encoding="utf-8")
|
||||||
|
|
||||||
|
defn = ChallengeDef(
|
||||||
|
name="browser_click",
|
||||||
|
source_root=source_root,
|
||||||
|
source_id="local.browser_click",
|
||||||
|
source_module="ops",
|
||||||
|
source_registry="registry",
|
||||||
|
store_root=".wf_browser_click_store",
|
||||||
|
default_workspace_template=template,
|
||||||
|
default_workspaces_dir=workspaces,
|
||||||
|
default_results_dir=tmp_path / "results",
|
||||||
|
default_prompt=template / "prompt.md",
|
||||||
|
default_server_port=8772,
|
||||||
|
server_config_arg="examples/browser_click_workflow/wf.config.json",
|
||||||
|
)
|
||||||
|
|
||||||
next_index = starting_trial_index(
|
next_index = starting_trial_index(
|
||||||
model="opencode/mimo-v2.5-free",
|
model="opencode/mimo-v2.5-free",
|
||||||
results_dir=tmp_path / "results",
|
results_dir=tmp_path / "results",
|
||||||
workspaces_dir=workspaces,
|
workspaces_dir=workspaces,
|
||||||
)
|
)
|
||||||
prepared = prepare_trial_workspace(
|
prepared = prepare_trial_workspace(
|
||||||
|
defn,
|
||||||
model="opencode/mimo-v2.5-free",
|
model="opencode/mimo-v2.5-free",
|
||||||
index=next_index,
|
index=next_index,
|
||||||
workspaces_dir=workspaces,
|
workspaces_dir=workspaces,
|
||||||
@@ -915,7 +788,9 @@ def test_main_uses_custom_workspace_template_and_source_root(
|
|||||||
monkeypatch.setattr(generic_runner, "run_trial", fake_run_trial)
|
monkeypatch.setattr(generic_runner, "run_trial", fake_run_trial)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
run_opencode_trials.main(
|
generic_runner_main(
|
||||||
|
BROWSER_CLICK_DEF,
|
||||||
|
lambda _text: "success",
|
||||||
[
|
[
|
||||||
"--model",
|
"--model",
|
||||||
"check/model",
|
"check/model",
|
||||||
@@ -929,7 +804,7 @@ def test_main_uses_custom_workspace_template_and_source_root(
|
|||||||
str(workspaces),
|
str(workspaces),
|
||||||
"--results-dir",
|
"--results-dir",
|
||||||
str(results),
|
str(results),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
@@ -1044,7 +919,7 @@ def test_generic_workspace_preparation_writes_config_for_arbitrary_challenge_def
|
|||||||
server_config_arg="examples/custom/wf.config.json",
|
server_config_arg="examples/custom/wf.config.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
ws = generic_prepare_trial_workspace(
|
ws = prepare_trial_workspace(
|
||||||
defn,
|
defn,
|
||||||
model="test-model",
|
model="test-model",
|
||||||
index=1,
|
index=1,
|
||||||
@@ -1073,8 +948,8 @@ def test_browser_click_wrapper_produces_expected_paths_and_command_prefix() -> N
|
|||||||
assert LOCAL_WF_COMMAND_PREFIX == (
|
assert LOCAL_WF_COMMAND_PREFIX == (
|
||||||
"uv run wf --config examples/browser_click_workflow/wf.config.json --local"
|
"uv run wf --config examples/browser_click_workflow/wf.config.json --local"
|
||||||
)
|
)
|
||||||
assert BROWSER_CLICK_DEF.default_prompt.name == "prompt.md"
|
assert BROWSER_CLICK_DEF.default_prompt.name == "challenge-prompt.md"
|
||||||
assert BROWSER_CLICK_DEF.default_prompt.parent.name == "workspace_template"
|
assert BROWSER_CLICK_DEF.default_prompt.parent.name == "browser_click_challenge"
|
||||||
|
|
||||||
|
|
||||||
def test_generic_runner_can_be_configured_with_fake_challenge_and_fake_opencode(
|
def test_generic_runner_can_be_configured_with_fake_challenge_and_fake_opencode(
|
||||||
@@ -1173,3 +1048,60 @@ def test_generic_write_trial_config_with_custom_source_root(
|
|||||||
"module": "test_mod",
|
"module": "test_mod",
|
||||||
"registry": "test_reg",
|
"registry": "test_reg",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BROWSER_CHALLENGE = (
|
||||||
|
ROOT
|
||||||
|
/ "examples"
|
||||||
|
/ "agent_challenges"
|
||||||
|
/ "browser_click_challenge"
|
||||||
|
/ "challenge.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_browser_click_manifest_declares_task_success_contract() -> None:
|
||||||
|
loaded = load_challenge_manifest(BROWSER_CHALLENGE)
|
||||||
|
|
||||||
|
assert loaded.manifest.id == "browser_click"
|
||||||
|
assert loaded.manifest.source.id == "local.browser_click"
|
||||||
|
assert loaded.manifest.report.success_assertions == {
|
||||||
|
"before_clicked": False,
|
||||||
|
"after_clicked": True,
|
||||||
|
"run_failed": False,
|
||||||
|
"leftover_processes": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_browser_click_workspace_uses_generic_profile_copy(tmp_path: Path) -> None:
|
||||||
|
loaded = load_challenge_manifest(BROWSER_CHALLENGE)
|
||||||
|
bundle = ROOT / "examples/agent_challenges/instruction_bundles/workflow_cli.yaml"
|
||||||
|
|
||||||
|
workspace = prepare_v2_trial_workspace(
|
||||||
|
loaded,
|
||||||
|
profile=InstructionProfile.SKILLS,
|
||||||
|
model="opencode/test",
|
||||||
|
index=1,
|
||||||
|
workspaces_dir=tmp_path,
|
||||||
|
instruction_bundle=bundle,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert workspace.config_path.is_file()
|
||||||
|
assert (workspace.root / ".agent/skills/wf-cli/SKILL.md").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
def test_central_runner_accepts_browser_challenge() -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"examples/agent_challenges/run_trials.py",
|
||||||
|
"--help",
|
||||||
|
],
|
||||||
|
cwd=ROOT,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
assert "--challenge" in result.stdout
|
||||||
|
assert "--instruction-profile" in result.stdout
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from examples.agent_challenges.manifests import load_challenge_manifest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
REPORT_CHALLENGE = (
|
||||||
|
ROOT
|
||||||
|
/ "examples"
|
||||||
|
/ "agent_challenges"
|
||||||
|
/ "report_workflow_challenge"
|
||||||
|
/ "challenge.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_workflow_manifest_uses_report_source() -> None:
|
||||||
|
loaded = load_challenge_manifest(REPORT_CHALLENGE)
|
||||||
|
|
||||||
|
assert loaded.manifest.id == "report_workflow"
|
||||||
|
assert loaded.manifest.source.id == "local.report"
|
||||||
|
assert loaded.manifest.report.success_assertions == {
|
||||||
|
"title_matches": True,
|
||||||
|
"markdown_rendered": True,
|
||||||
|
"run_failed": False,
|
||||||
|
}
|
||||||
|
assert loaded.prompt_path.is_file()
|
||||||
|
assert loaded.workspace_template.is_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_challenge_prompt_requires_full_product_lifecycle() -> None:
|
||||||
|
loaded = load_challenge_manifest(REPORT_CHALLENGE)
|
||||||
|
prompt = loaded.prompt_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "read_notes" in prompt
|
||||||
|
assert "extract_report" in prompt
|
||||||
|
assert "render_markdown_report" in prompt
|
||||||
|
assert "deployment" in prompt.lower()
|
||||||
|
assert "run_id" in prompt
|
||||||
Reference in New Issue
Block a user