Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,23 @@ binary, `cargo test --lib` alone does not build it — run `cargo test`, or `car
compilation and clippy on the other host and hides the coverage gap. Instead, probe for what the
test actually needs and call `report_skip` (`src/core/runtime.rs`), which prints the reason and
returns `true`. Setting `EVAL_MAGIC_REQUIRE_POSIX_TOOLS=1` turns every skip into a failure; CI sets
it on both runners, so neither can quietly stop covering something. Three capabilities are gated
today: the recipe tools beyond the shell itself (`require_posix_toolchain` — in practice `jq`),
symlink creation, which Windows allows only under Developer Mode, and creating a path past
it on both runners, so neither can quietly stop covering something. Two capabilities are gated
today: symlink creation, which Windows allows only under Developer Mode, and creating a path past
Windows' 259-character limit (`deep_task_root`, `src/cli/run/orchestrate/git.rs`). The Windows
runner is provisioned for those rather than exempted from them, so a skip there is a red build. The
shell is not one of them; it is a hard requirement, per the section below.
`require_posix_toolchain` is not test-only either — the `run` preflight uses it to warn about the
same gap. Where a genuine per-OS difference is the behavior under test — signals, path separators —
branch on `cfg!(windows)` at runtime so both arms still compile everywhere.
runner is provisioned for both rather than exempted from them, so a skip there is a red build. The
shell is not one of them; it is a hard requirement, per the section below. Where a genuine per-OS
difference is the behavior under test — signals, path separators — branch on `cfg!(windows)` at
runtime so both arms still compile everywhere.

**A POSIX shell is required, for use and for development.** Harness `exec_template`s are POSIX
command lines, so the dispatch and probe paths spawn `sh` via `posix_shell()`
(`src/core/runtime.rs`) rather than a hardcoded `/bin/sh`: it searches `PATH`, then a Git for
Windows install. Set `EVAL_MAGIC_SH` to override it. `cargo test` inherits the requirement — the
scripted-turn tests spawn a `#!/bin/sh` harness stub through the resolved shell and do not skip —
so a host without `sh` fails the suite instead of quietly covering less. `jq` is required alongside
it for the parallel-dispatch and judge recipes; Git for Windows supplies the shell, `xargs`, `tr`,
and `wc`, but not `jq`. `POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is the one wording the
command lines, so the dispatch and probe paths spawn `sh` through `run_in_posix_shell` /
`posix_shell()` (`src/core/runtime.rs`) rather than a hardcoded `/bin/sh`: it searches `PATH`, then
a Git for Windows install. Set `EVAL_MAGIC_SH` to override it. `cargo test` inherits the
requirement — the dispatch tests spawn a `#!/bin/sh` harness stub through the resolved shell and do
not skip — so a host without `sh` fails the suite instead of quietly covering less. The shell is
the whole requirement: `jq` was needed only while operators pasted the generated dispatch and judge
recipes, and `eval-magic dispatch` drives both itself.
`POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is the one wording the
Markdown-carrying surfaces reuse: the shell-discovery errors, the `run` preflight warnings,
`RUNBOOK.md`, and `dispatch-manifest.md`. State the requirement from there rather than rephrasing
it. `--help` is the one deliberate restatement (`AFTER_HELP` in `src/cli/help.rs`), hard-wrapped and
Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,19 @@

eval-magic runs the same task in two controlled conditions—such as a new skill versus no skill, or
an edited skill versus its previous version—and grades both results against shared assertions. It
builds isolated task workspaces, stages skills, generates harness-specific dispatch instructions,
ingests transcripts and final state, and produces comparison artifacts. You dispatch the agent
sessions with Claude Code, Cline, Codex, OpenCode, or a descriptor-backed harness of your own.
builds isolated task workspaces, stages skills, dispatches the agent sessions itself, ingests
transcripts and final state, and produces comparison artifacts. It drives Claude Code, Cline,
Codex, OpenCode, or a descriptor-backed harness of your own.

The installed CLI is the primary manual. Start with `eval-magic --help`, and use
`eval-magic <command> --help` whenever you reach a new phase.

## Install

Git is required at runtime, plus a POSIX shell with `jq`: the dispatch and judge recipes eval-magic
generates are POSIX command lines built on `jq`, `xargs`, `tr`, and `wc`. The shell that runs them
has to resolve the same paths the workspace was prepared with. On Windows that is Git Bash (Git for
Windows), with `jq` installed separately — Git for Windows does not bundle it. WSL resolves a
different filesystem namespace, so run eval-magic inside WSL rather than dispatching into it.
Git is required at runtime, plus a POSIX shell: harness dispatch commands are POSIX command lines,
and `eval-magic dispatch` runs them itself, so the host it runs on needs a shell that resolves the
workspace's own paths. On Windows that is Git Bash (Git for Windows). WSL resolves a different
filesystem namespace, so run eval-magic inside WSL rather than dispatching into it.
Set `EVAL_MAGIC_SH` to select a specific `sh`.

Windows support runs through Git Bash and is deprecated: a future release will require WSL.
Expand Down Expand Up @@ -101,10 +100,10 @@ eval-magic run --harness codex
eval-magic run --harness opencode
```

`run` prepares the campaign; it does not dispatch agents. Review the printed task and model-usage
summary before continuing. Then read the generated `RUNBOOK.md` from beginning to end. It contains
the exact dispatch, ingest, judge, finalize, and `eval-magic teardown` commands for that campaign
and harness.
`run` prepares the campaign; `eval-magic dispatch` runs it. Review the printed task and model-usage
summary before continuing — dispatch is where model usage is spent. Then read the generated
`RUNBOOK.md` from beginning to end. It contains the exact dispatch, ingest, judge, finalize, and
`eval-magic teardown` commands for that campaign and harness.

After finalization, open the generated `benchmark.json` to compare pass rates, token and duration
measurements, and validity warnings. Use `eval-magic aggregate --help` when you need to combine
Expand Down Expand Up @@ -152,9 +151,9 @@ Issues and planned work are tracked in the

## Development

Development carries the same host requirement as use: a POSIX shell with `jq`. The scripted-turn
tests spawn `#!/bin/sh` harness stubs through the resolved shell and do not skip, so the suite
cannot pass without one. Tests that need `jq` or symlink creation report a skip instead.
Development carries the same host requirement as use: a POSIX shell. The dispatch tests spawn
`#!/bin/sh` harness stubs through the resolved shell and do not skip, so the suite cannot pass
without one. Tests that need symlink creation report a skip instead.

```bash
cargo fmt --check
Expand Down
8 changes: 4 additions & 4 deletions docs/claude-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ hook-entry and `hookSpecificOutput` verdict templates) is rendered by the generi

## Permission mode

Every dispatch and judge recipe carries `--permission-mode bypassPermissions`. The obvious
alternative, `acceptEdits`, is wrong here: it auto-approves *file edits* but **not Bash**, and
because the recipe detaches stdin (`</dev/null`) there is nobody to approve, so anything not
Every dispatch — eval agent and judge alike — carries `--permission-mode bypassPermissions`. The
obvious alternative, `acceptEdits`, is wrong here: it auto-approves *file edits* but **not Bash**,
and because the command detaches stdin (`</dev/null`) there is nobody to approve, so anything not
trivially safe is auto-denied. Measured on a real dispatch, `ls`/`grep`/`find` ran while
`bun run repro.ts`, `node -e '…'` and even `bun --version` came back "This command requires
approval".
Expand Down Expand Up @@ -145,7 +145,7 @@ topic ([isolation guide](guides/isolation.md)). The per-source strings the banne

`--setting-sources project,local` drops **all** user-scope discovery, not just `enabledPlugins`:
skills under `<config_dir>/skills` are unloaded too. Verified 2026-08-06 by A/B within one campaign —
the judge recipe carries no `--setting-sources` and its capture lists both `~/.claude/skills`
the judge dispatch carries no `--setting-sources` and its capture lists both `~/.claude/skills`
entries and every `<plugin>:<skill>` id, while all 48 isolated eval dispatches list neither.

Project-local staged skills are independent of installed plugins, so they still load and the
Expand Down
3 changes: 1 addition & 2 deletions docs/cline-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ the descriptor references. "Probe capture" refers to the observed dispatches des
| Plugin hook contract | `beforeTool({snapshot, tool, toolCall, input})`; block with `{skip: true, reason}`; 3000ms default hook budget (plugin spawns with a 2s timeout so a hung arbiter fails open); `spawnSync` works from the plugin sandbox | 3.0.53 spike capture + the binary's runtime hook loop; the docs' `tool_call_before`/`fail_closed` vocabulary lags the binary |
| `shadow.preflight` | `cline-skills` | 3.0.53 root probe (one uniquely-named skill per candidate root): dispatch cwd's `.cline/skills` read, ancestor's NOT (no project walk), `~/.agents/skills` IS read (and receives `cline skill install` global installs); `$CLINE_DIR` overrides the `~/.cline` default (3.0.53 binary) |
| `dispatch.capture_prefix` | `cline` | chosen name (judge capture files `$response_base.cline-events.jsonl`) |
| `dispatch.exec_template` / `parallel_command_template` | see descriptor | flags from `cline --help` (`--act` from the 3.0.52 binary’s hidden option registration + behavioral write test); `--json` NDJSON stdout and `</dev/null` stdin detach from the docs CLI overview (piped stdin becomes prompt context); final-message jq recovery verified by the live probe |
| `dispatch.judge_command_template` | `cline --cwd "{cwd}" --act --json --auto-approve true $model_arg \` | same flag sources; render-checked by the live probe |
| `dispatch.exec_template` | see descriptor | flags from `cline --help` (`--act` from the 3.0.52 binary’s hidden option registration + behavioral write test); `--json` NDJSON stdout and `</dev/null` stdin detach from the docs CLI overview (piped stdin becomes prompt context); final-message jq recovery verified by the live probe. `eval-magic dispatch --judges` reuses this same template, bound to the iteration directory and the judge prompt |
| `dispatch.next_steps_template` / `manifest_template` | see descriptor | prose authored for eval-magic artifacts (same structure as the other built-ins) |

## Dispatch quirks
Expand Down
41 changes: 21 additions & 20 deletions docs/developer_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ focused internal notes instead of duplicating their details.
artifacts. The iteration lives in the eval home, which defaults outside the skill's own
repository (`workspace_root_from`, `src/core/context.rs`). The generated runbook—not a
checked-in recipe—is the authority for dispatching that particular campaign.
3. An operator or automation dispatches each task with the selected harness. One-shot tasks invoke
the harness once; scripted conversations use `eval-magic dispatch-task` to preserve one native
harness session across turns.
3. `eval-magic dispatch` runs every task through the selected harness, `--jobs` at a time, each in
its own private environment. A scripted conversation resumes one native harness session across
its turns. Each task ends with a `conversation.json`, which is also what a rerun skips on.
4. `eval-magic ingest` reads the harness outputs, transcript evidence, guard denials, and final
task state. Runner-owned deterministic checks and diff-scope evidence are collected here.
5. `eval-magic grade` evaluates runner-owned assertions and emits tasks for assertions that require
an LLM. The generated recipes dispatch those judge tasks through the selected harness.
an LLM. `eval-magic dispatch --judges` runs those judge tasks through the selected harness.
6. `eval-magic finalize` checks that required work is complete and writes the final per-run and
benchmark artifacts. `eval-magic aggregate` combines campaigns when a larger comparison is
needed.
Expand Down Expand Up @@ -79,18 +79,19 @@ following authorities:
| Deprecated | Windows, through Git Bash (Git for Windows) | the `windows-latest` CI job |
| Unsupported | preparing a workspace on Windows and dispatching it from WSL | — |

Windows support is deprecated in favor of WSL, and its removal is gated on #256, which replaces
the generated POSIX recipes with a runner-driven `eval-magic dispatch`. Until that lands, the
Windows runner stays green and Windows-native behavior is held to the same bar as any other
platform: a Windows failure is a real failure, not an accepted gap. Do not add new Windows-native
accommodation in the meantime.
Windows support is deprecated in favor of WSL. #256 has landed, so the recipe surface that carried
the largest Windows accommodation is gone; the remaining removal — the `cfg(windows)` sites, the CI
leg, and the msvc target — is #275. Until that lands, the Windows runner stays green and
Windows-native behavior is held to the same bar as any other platform: a Windows failure is a real
failure, not an accepted gap. Do not add new Windows-native accommodation in the meantime.

The unsupported row is a correctness boundary rather than a preference. A generated recipe carries
the absolute paths of the host that prepared the workspace. Git Bash shares the Windows filesystem,
so those paths resolve; WSL resolves its own namespace, where a `C:\…` path names nothing. Nothing
in the tree translates between the two, so the split fails quietly instead of loudly.
`POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is the single wording every user-facing surface
reuses to state this; `src/cli/help.rs` restates it for clap by hand.
The unsupported row is a correctness boundary rather than a preference. `dispatch` spawns each
harness command line with the workspace's own absolute paths, so the shell it resolves has to
resolve those. Git Bash shares the Windows filesystem, so those paths resolve; WSL resolves its own
namespace, where a `C:\…` path names nothing. Nothing in the tree translates between the two, so
the split fails quietly instead of loudly. `POSIX_TOOLING_REQUIREMENT` (`src/core/runtime.rs`) is
the single wording every user-facing surface reuses to state this; `src/cli/help.rs` restates it
for clap by hand.

## Make and verify a change

Expand All @@ -99,11 +100,11 @@ editing. Add a focused failing test at the narrowest useful boundary, implement
run the focused test again. Cross-harness changes belong at shared descriptor, runner, or adapter
boundaries unless the evidence requires a named harness capability.

Development carries the host requirement the tool itself declares: a POSIX shell with `jq`. The
scripted-turn tests spawn `#!/bin/sh` harness stubs through the resolved shell and do not skip, so
the suite cannot pass without one. Tests needing `jq`, symlink creation, or a path past Windows'
259-character limit report a skip instead; `EVAL_MAGIC_REQUIRE_POSIX_TOOLS=1` turns those skips into
failures, as CI sets it to do on both its Ubuntu and its Windows runner.
Development carries the host requirement the tool itself declares: a POSIX shell. The dispatch
tests spawn `#!/bin/sh` harness stubs through the resolved shell and do not skip, so the suite
cannot pass without one. Tests needing symlink creation or a path past Windows' 259-character limit
report a skip instead; `EVAL_MAGIC_REQUIRE_POSIX_TOOLS=1` turns those skips into failures, as CI
sets it to do on both its Ubuntu and its Windows runner.

Before handing work off, run:

Expand Down
6 changes: 3 additions & 3 deletions docs/guides/byoh.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ eval-magic run --harness-file ./cool-custom-harness.toml

Its `label` becomes the default harness for that invocation.

## Add a dispatch recipe first
## Add a dispatch command first

The highest-leverage field is `[dispatch].exec_template`. It lets the generated `RUNBOOK.md` and
`dispatch-manifest.md` carry a copy-pasteable command for every task.
The highest-leverage field is `[dispatch].exec_template`. It is the command `eval-magic dispatch`
spawns for every task, so without it there is nothing for the runner to run.

```toml
label = "cool-custom-harness"
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ ID rather than the total list length because staged and bundled skills remain pr
produce an init record under each `turn-N/`; inspect a resumed turn as well.

Codex and OpenCode captures do not provide the equivalent roster used by eval-magic. Verify those
harnesses by checking every rendered eval-agent command in `RUNBOOK.md` and
`dispatch-manifest.md`, then use `isolates_live_sources` to record the operator assertion.
harnesses by checking the eval-agent command `dispatch-manifest.md` says the runner will spawn,
then use `isolates_live_sources` to record the operator assertion.

### `claude plugin list` does not prove isolation

Expand Down
2 changes: 1 addition & 1 deletion docs/opencode-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ failed or unparseable probe remains `unknown` rather than guessing. Unique runti
probe and are `selected`. The shared banner and `aggregate` validity warnings render this same
report; historical unversioned artifacts remain readable.

eval-magic detects but cannot unload these sources, and the generated dispatch recipes never
eval-magic detects but cannot unload these sources, and the dispatch commands the runner spawns never
set the `OPENCODE_DISABLE_*` kill switches on the operator's behalf — parity with a real user
session matters. The operator-facing recipes — both switches with their exact scopes, and
move-or-rename as the only remedy for an `.opencode` root — are in the shipped
Expand Down
Loading
Loading