Skip to content

Commit 96eb2e0

Browse files
authored
Merge pull request #160 from slowdini/dev
Release v0.5.1
2 parents 5d68be1 + 8443f4a commit 96eb2e0

48 files changed

Lines changed: 3498 additions & 441 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/PULL_REQUEST_TEMPLATE/harness-descriptor.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Title: feat(harness): add <label> descriptor
1919
- [ ] This PR adds/changes descriptor **data only** — no parser, slug, shadow,
2020
or guard code (those are separate one-capability-per-PR contributions)
2121
- [ ] `[transcript]`/`[shadow]`/`[staging]` (if declared) reuse an existing named
22-
capability (`claude-stream-json` / `codex-items` / `opencode` / `claude-plugins`)
22+
capability (`claude-stream-json` / `codex-items` / `opencode-events` / `opencode` /
23+
`claude-plugins` / `codex-skills` / `opencode-skills`)
2324
or ingest through a `[transcript.extract]` block (declarative data)
2425
- [ ] No `[guard]` table and no `run.supports_guard = true` in this PR
2526

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
`eval-magic` is a Rust CLI crate. The binary entry point is `src/main.rs`; reusable logic lives in
66
`src/lib.rs` and submodules such as `cli/`, `pipeline/`, `sandbox/`, `validation/`, and
7-
`workspace/`. JSON schemas are tracked in `schema/`, harness descriptors in `harnesses/`, shared
7+
`workspace/`. JSON schemas are tracked in `schema/`, harness descriptors (plus embedded harness
8+
assets such as the OpenCode write-guard plugin template) in `harnesses/`, shared
89
prompt profiles in `profiles/`, and development docs (the harness enhancement contract,
910
per-harness notes) in `docs/`. Integration
1011
tests are split by surface area under `tests/cli/` and `tests/run/`; unit tests usually live

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "eval-magic"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2024"
55
description = "One-stop CLI for running skill evals — measure whether an agent skill actually shifts behavior."
66
license = "MIT"

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
An eval dispatches a fresh subagent twice per test case — once with the skill loaded, once without (or old version vs. new) — and grades both outputs against assertions. The pass-rate delta tells you whether the skill is worth shipping or the change is worth landing. The runner builds the workspace, stages skills for discovery, generates dispatch prompts, assembles run records from transcripts, grades, and aggregates; your agent harness supplies the one thing the runner never does itself: dispatching the subagents.
66

7-
`eval-magic` ships as a dependency-less prebuilt binary under the command name `eval-magic`. Every artifact follows a documented JSON Schema, so records grade the same way regardless of where they were authored. **Claude Code and Codex CLI are fully wired harnesses today**; OpenCode has native staging support; see [Harnesses](#harnesses) for per-harness enhancement support. From inside an agent session, running an eval is as simple as: *"Install eval-magic and help me run an eval on my-skill."*
7+
`eval-magic` ships as a dependency-less prebuilt binary under the command name `eval-magic`. Every artifact follows a documented JSON Schema, so records grade the same way regardless of where they were authored. **Claude Code and Codex CLI are fully wired harnesses today**; OpenCode has native staging and transcript-ingest support; see [Harnesses](#harnesses) for per-harness enhancement support. From inside an agent session, running an eval is as simple as: *"Install eval-magic and help me run an eval on my-skill."*
88

99
This README is the complete operating guide: install, author cases, run the loop, read results, and keep a baseline. For the full flag-by-flag reference, run `eval-magic --help` (and `eval-magic <subcommand> --help`). For *when and why* to write an eval at all — the methodology, the decision to test, designing cases under pressure — see the [`slow-powers`](https://github.com/slowdini/slow-powers) plugin's `evaluating-skills` skill, which owns that craft.
1010

@@ -250,7 +250,7 @@ A subagent that runs an eval should start in an environment that mirrors a real
250250

251251
For the `without_skill` / baseline condition, the dispatch reflects "this skill is unavailable, others remain" when siblings were opted in with `--skill-dir`; otherwise it measures the skill against a clean no-skill baseline. `--bootstrap` is separate from parity: it injects product-specific framing inside the `<session-start-context>` block and does not enumerate skills.
252252

253-
**Parity is only as clean as your session.** Staging controls what the runner *adds*, not what your session already *loaded*. Subagents dispatched in-process share the parent session's plugins, so an installed plugin exposing a same-named skill is still discoverable and contaminates both arms — the staging slug stops an on-disk collision, not runtime discovery. The runner can't unload a live plugin; on Claude Code it emits a build-time *plugin-shadow* warning (also surfaced in `benchmark.json`'s `validity_warnings`) that lists the per-dispatch isolation options inline. Closing it is a launch-time step for whoever dispatches.
253+
**Parity is only as clean as each dispatch's live environment.** Staging controls what the runner *adds*, not the user, repository, admin, or plugin skills the harness's one-shot CLI also discovers. A live copy of the logical eval skill can therefore contaminate both arms — the staging slug stops an on-disk collision, not runtime discovery. The runner can't unload a live skill; Claude Code, Codex, and OpenCode emit a build-time *skill-shadow* warning (recorded in the legacy `plugin-shadow.json` artifact and surfaced in `benchmark.json`'s `validity_warnings`) with harness-specific isolation guidance. Closing it is a launch-time step for whoever dispatches.
254254

255255
## Harnesses
256256

@@ -266,15 +266,13 @@ Every eval test and judge is dispatched the same way: through the harness's one-
266266

267267
This table is the source of truth for per-harness enhancement support:
268268

269-
| Harness | Native staging | Dispatch recipes | Transcript ingest | Model flag | Write guard |
270-
|---------|:--------------:|:----------------:|:-----------------:|:----------:|:-----------:|
271-
| **Claude Code** ||||||
272-
| **Codex** ||||||
273-
| **OpenCode** || ❌¹ | | | |
269+
| Harness | Native staging | Dispatch recipes | Transcript ingest | Model flag | Write guard | Shadow preflight |
270+
|---------|:--------------:|:----------------:|:-----------------:|:----------:|:-----------:|:----------------:|
271+
| **Claude Code** |||||||
272+
| **Codex** |||||||
273+
| **OpenCode** || | | | | |
274274

275-
¹ `run --harness opencode` stages skills and emits native dispatch prompts, but prints manual `opencode run` guidance instead of a copy-pasteable recipe.
276-
277-
A missing enhancement degrades fidelity, never correctness — every column has a fallback, and the `run` preflight warns naming it: without native staging, `--no-stage` inlines each `SKILL.md` into its dispatch prompt; without transcript ingest, `transcript_check` assertions grade as unverifiable and `llm_judge` carries the grading (tokens and duration go unrecorded); without a model flag, `--agent-model` / `--judge-model` are recorded as provenance only; without a write guard, the run continues unguarded (auto-arm quietly stays off; an explicit `--guard` warns) and `detect-stray-writes` audits after the fact; without dispatch recipes, `RUNBOOK.md` / `dispatch-manifest.md` carry handoff guidance without a copy-pasteable per-task command. Supported enhancements are provided automatically — the write guard arms on every staged run of a guard-capable harness unless `--no-guard` opts out.
275+
A missing enhancement degrades fidelity, never correctness — every column has a fallback: without native staging, `--no-stage` inlines each `SKILL.md` into its dispatch prompt; without transcript ingest, `transcript_check` assertions grade as unverifiable and `llm_judge` carries the grading (tokens and duration go unrecorded); without a model flag, `--agent-model` / `--judge-model` are recorded as provenance only; without a write guard, the run continues unguarded (auto-arm quietly stays off; an explicit `--guard` warns) and `detect-stray-writes` audits after the fact; without shadow preflight, no automatic live-skill collision scan runs; without dispatch recipes, `RUNBOOK.md` / `dispatch-manifest.md` carry handoff guidance without a copy-pasteable per-task command. Supported enhancements are provided automatically — the write guard arms on every staged run of a guard-capable harness unless `--no-guard` opts out, and the `run` preflight names actionable fallbacks where it can.
278276

279277
Per-harness implementation notes for developers wiring features live in [docs/claude-notes.md](docs/claude-notes.md), [docs/codex-notes.md](docs/codex-notes.md), and [docs/opencode-notes.md](docs/opencode-notes.md).
280278

docs/byoh.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,11 @@ you get the full feature from configuration alone:
135135

136136
- `transcript.parser = "claude-stream-json"` — Claude Code `-p --output-format stream-json` events.
137137
- `transcript.parser = "codex-items"` — Codex `item.started`/`item.completed` JSONL.
138+
- `transcript.parser = "opencode-events"` — OpenCode `run --format json` `tool_use`/`text`/`step_finish` events.
138139
- `staging.slug_capability = "opencode"` — OpenCode's sanitizing slug rules.
139140
- `shadow.preflight = "claude-plugins"` — the Claude plugin/global-skills shadow scan.
141+
- `shadow.preflight = "codex-skills"` — the Codex repo/user/admin/plugin skill scan.
142+
- `shadow.preflight = "opencode-skills"` — the OpenCode project/global `.opencode`/`.claude`/`.agents` skill scan.
140143

141144
For example, a harness that logs Codex-compatible item JSONL gets full transcript ingest — parsed
142145
tool invocations, `transcript_check` grading, the works — with:
@@ -211,8 +214,10 @@ Dotted paths (`"usage.input_tokens"`, `"item.text"`) descend nested objects only
211214
array indexing, and keys containing literal dots are unaddressable. Malformed JSONL lines are
212215
silently skipped, as with every parser. `[transcript]` requires the `[tools]` write/shell
213216
vocabulary alongside it (the stray-writes audit classifies by it). Leave
214-
`surfaces_skill_invocation` false unless the mapping verifiably yields invocations named `Skill`
215-
carrying an `args.skill` field.
217+
`surfaces_skill_invocation` false unless the mapping verifiably yields a deterministic
218+
skill-invocation event; when true, the `__skill_invoked` meta-check matches the tool named by
219+
`skill_tool` whose `skill_arg` argument equals the staged slug (defaults `"Skill"` / `"skill"`
220+
Claude Code's spellings; OpenCode declares `"skill"` / `"name"`).
216221

217222
**If a stream needs more than these primitives, it's a code capability, not a bigger DSL.** The
218223
line is cross-event state: a stream whose tool results arrive in *separate* records joined by id
@@ -267,7 +272,8 @@ beyond a mechanical registration. What counts as data vs code:
267272
`config_dirs`, the `[dispatch]` templates, `[model]`, `[staging]` + `[skills_block]`,
268273
`[tools]`, the `[run]` booleans, a `[transcript.extract]` block (the declarative tier is pure
269274
data), and `[transcript]` / `[shadow]` **when they reuse an existing named capability**
270-
(`claude-stream-json`, `codex-items`, `opencode`, `claude-plugins`).
275+
(`claude-stream-json`, `codex-items`, `opencode-events`, `opencode`, `claude-plugins`,
276+
`codex-skills`, `opencode-skills`).
271277
- **Code — one capability per PR, separate from the descriptor PR:** a new transcript parser,
272278
slug capability, or shadow preflight (each is `src/adapters/capabilities.rs` + a
273279
`src/adapters/<harness>/` module + a schema enum entry), and guard support (`[guard]` data is

docs/codex-notes.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ references:
1414
|------|--------------|
1515
| `harnesses/codex.toml` | the descriptor — every declarative value + capability references |
1616
| `transcript.rs` | `item.completed` event-stream parsing (`codex-items`) |
17+
| `skill_shadow.rs` | repo/user/admin/plugin skill collision scan (`codex-skills`) + reporting |
1718

1819
The write guard has no per-harness code: the descriptor's `[guard]` block (hook file, matcher,
1920
hook-entry and `{"decision": "block"}` verdict templates) is rendered by the generic engine in
@@ -46,6 +47,37 @@ so the staged skill-under-test's frontmatter is rewritten to the eval slug
4647
(`rewrites_frontmatter_name` true) and the available-skills block advertises the slug
4748
(`advertises_staged_slug_name` true).
4849

50+
Codex can also inject `--bootstrap` content into a no-stage dispatch: the bootstrap remains in
51+
`<session-start-context>`, while the skill-under-test is inlined separately for the treatment arm.
52+
`--stage-name` still requires staging because it names an on-disk staged copy.
53+
54+
## Isolating from live skills and plugins
55+
56+
Every `codex exec` can discover skills beyond the eval env. Before dispatch, the `codex-skills`
57+
preflight compares each logical eval skill name with:
58+
59+
- `.agents/skills` at each repository ancestor of the dispatch cwd;
60+
- `$HOME/.agents/skills`;
61+
- `/etc/codex/skills`; and
62+
- skills in enabled installed plugins reported by `codex plugin list --json`, under
63+
`$CODEX_HOME/plugins/cache/<marketplace>/<plugin>/<version>/skills`.
64+
65+
Direct skill directories are matched by the `name:` in `SKILL.md` frontmatter, not the folder
66+
name. Missing directories, malformed skills, and unavailable or invalid plugin-list output are
67+
ignored; a plugin-list failure does not suppress findings from the direct directories. Findings
68+
produce a build-time Codex banner and the backward-compatible `plugin-shadow.json` artifact;
69+
`aggregate` turns the same report into Codex-specific `benchmark.json` validity warnings.
70+
71+
eval-magic detects but cannot unload these sources. Disable a conflicting installed plugin from
72+
Codex's `/plugins` UI, or move/rename a conflicting repo, user, or admin skill before dispatch.
73+
For a user skill only, a clean `HOME` can isolate `$HOME/.agents/skills`; preserve `CODEX_HOME` if
74+
the dispatch still needs the existing Codex configuration. That does not isolate plugins stored
75+
under `CODEX_HOME` or repository/admin skills.
76+
77+
**Known limit:** Codex also ships bundled system skills, but currently exposes no stable
78+
enumeration mechanism for them. The preflight therefore cannot detect a collision with a bundled
79+
system skill; verify that case manually when relevant.
80+
4981
## Transcript (`item.completed`)
5082

5183
`item.completed` events whose item type is not an agent message / reasoning / plan update become

0 commit comments

Comments
 (0)