onejudge
A Rust library that drives a simulated interaction and evaluation loop on top
of oneharness: take a skill or
agent, drive it through a multi-turn conversation with a simulated user, and score
the resulting transcript with natural-language (judge) verdicts and tool-event
queries.
It is the engine extracted from
skilltest (see
nickderobertis/skilltest#31).
The layering:
oneharness → one harness invocation, one JSON report (pure substrate)
onejudge → simulated interaction + judging loop (this crate)
skilltest → test-framework surface: cases, evals-as-assertions, SDKs
Reach for onejudge when you want to "drive a harness through a simulated conversation and score the transcript" without skilltest's YAML / case framing.
Install
cargo add onejudge
Minimum supported Rust version: 1.82.
The onejudge CLI
The same engine that tests a skill can drive real work. onejudge run points a
harness at a task and lets an LLM-driven simulated user supervise it — pushing
back, asking for verification, re-prompting — until a done_when condition holds
or max_turns is hit. Configured by YAML; the library API is unchanged and CLI
deps (clap, a YAML parser) are opt-in behind the non-default cli feature.
Spin up a run in three steps:
cargo install onejudge --features cli # or: install.sh (prebuilt archives)
onejudge init # scaffold onejudge.yaml + oneharness configs
onejudge run # reads ./onejudge.yaml, drives to completion
init shells out to oneharness init (needs oneharness 0.12.0+) to scaffold
oneharness.toml (the agent side) and oneharness.judge.toml (the judge side),
then writes a fully-commented loop-only onejudge.yaml. The fields that make a run
yours are task (what to do), the system framing — a skill (a SKILL.md
directory) and/or a system_prompt, both optional — and the user block
(persona / done_when / max_turns — omit it for a single-turn run). After
each nonterminal agent turn, one unified supervisor call either completes with a
reason or supplies the exact next user message. It sees compact normalized tool
summaries by default, never raw dumps; when needed it may inspect the agent-side
recording with oneharness history show <session>-skill --project <worktree> --format text. Agent and judge harnesses run in that worktree, but only agent
runs are automatically history-recorded. Harness
and model selection lives in those oneharness.toml
files, not onejudge.yaml. onejudge schema prints the annotated config, the
single source of truth for every field.
Flags override the file (flags > file > defaults), so one config serves many tasks:
onejudge run --task - < task.txt, --max-turns 8, --format json -o result.json.
Config
A run is a YAML file carrying only the loop's own concerns. The fields that make it
yours — task, the system framing (skill and/or system_prompt), and the user
block. Everything else has a default; omit user for a single-turn run. A minimal
config:
system_prompt: You are a senior engineer. Complete the task and keep tests green.
# skill: ./skills/my-skill # optional: a SKILL.md dir; its body is appended
task: Add a --version flag to the CLI.
user: # the simulated supervisor that drives the loop
persona: A demanding tech lead. Do not accept "done" until you have verified it.
done_when: the task is complete and all tests pass
max_turns: 8
evals: # optional: score the finished transcript
- criterion: the change is well-scoped and readable
kind: numeric
scale: [1, 5]
assessment: Identify useful follow-up work left out of scope.
The harness and model come from oneharness's own config (oneharness.toml for the
agent, oneharness.judge.toml for the judge side) — onejudge init scaffolds
them. More keys — provider (oneharness / command / split, with the
oneharness judge_config path, and under split the judge side as a judges:
list — an llmlint entry there makes a judge of one llmlint run over the
worker's tree), session, boolean evals. onejudge init writes a
fully-commented starter and onejudge schema prints the annotated field reference
(the single source of truth); it is validated strictly (deny_unknown_fields) so a
typo is a loud error.
Human output is the conversation + tool actions + completion status + eval
verdicts; --format json emits the versioned Report. The
exit code is 0 only when the task completed and every boolean eval passed, 1
if it hit max_turns or a boolean eval failed, 2 on a bad config. Add --stream
to publish tool events on stdout as they occur, ahead of that same report
(docs/streaming.md). Full docs: docs/cli.md.
Concepts
Provideris the boundary — onejudge never talks to a model directly. Every model call goes throughoneharness, and harness/model selection lives in oneharness's config files, not onejudge.OneharnessProvider(default) shells out to theoneharnessCLI (v0.12.0+): the agent side uses the discoveredoneharness.toml, and the judge side uses a separate--configfile (defaultoneharness.judge.toml). Withstream: trueit consumes the agent turn as it happens — NDJSON tool events, then a terminal report — instead of only when it ends (docs/streaming.md).CommandProviderspeaks a small JSON-lines protocol, for a custom backend or a deterministic test double.LlmlintProvideris a judge whose whole verdict is onellmlintrun over the worker's tree, met at the process boundary (nothing linked;llmlintinstalled on the host): failing rules send the worker llmlint's own report, a clean run passes it, and a run that could not complete is an error, never a verdict (docs/judges.md).JudgePanelmakes the judge side a list: every judge runs against the same worker turn at the same time, the panel waits for all of them, and the worker gets one combined, attributed message when any says the work is not done; each judge's decision is recorded on the report. A panel of one is byte-identical to a bare provider. See docs/judges.md.SplitProvidercomposes two providers — one that runs the skill, one that judges and role-plays the user (a single provider or aJudgePanel; e.g. run the skill on one harness, judge on another).
Engineruns aConversation(aSkill, an initial input, and an optionalSimulatedUser) into aTranscript, bounded bymax_turns/done_when/ the skill declaring itself done.Transcriptcarries each turn plus the normalized **ToolEvent**s the skill took, so the judge — and aToolQuery— can reason over what the skill did, not just what it said.Reportis onejudge's own versioned contract (SCHEMA_VERSION): a serializable bundle of the transcript, verdicts, optional free-textassessment, and usage that higher-level frameworks compose over and re-export. See docs/contract.md.SpawnHookis the seam for an in-process embedder: onejudge offers every process it is about to create so the embedder can place it in a group it owns — a POSIX process group, a Windows job object — and still reap the whole harness tree when a run is cancelled. The embedder owns the group; onejudge reports what a hook claimed onReport::processes. Install it on each provider, or on aPlan(Plan::with_spawn_hook) when you drive the CLI's run driver — the plan reaches both sides of a two-party worker + judge run. See docs/spawn-hook.md.Notesis the note delivery seam: a role-addressed correction sent into a running conversation reaches whichever party is live, and the other party receives it with that party's response. A note can bind aCriterion, which enters the acceptance criteria the judge evaluates against; one that arrives after the conversation completed raisesUndeliveredrather than being silently accepted. Open it withNotes::channel()and install the inbox on anEngine(with_notes) or aPlan. See docs/notes.md.
Two things it improves over the in-skilltest engine:
- The judge sees tool events. Verdicts render the transcript with a compact,
token-budget-aware summary of each turn's tool calls, so a criterion like
"the change was committed" can be decided from the
git committhe skill actually ran — not only from what it said.Transcriptalso exposes aToolQueryprimitive for events-backed assertions with no judge call. - One caller-owned session name. The engine always threads a single
--session <name>across turns instead of extracting and re-passing a native id; if a harness cannot bind a session, the provider gracefully retries the call without it, re-prompting the inlined transcript.
Example
use onejudge::{Conversation, Engine, OneharnessProvider, Settings, SimulatedUser, Skill};
let provider = OneharnessProvider::new();
// Harness/model selection lives in oneharness's config files, not here; Settings
// carries only the loop's own concerns (turn cap, session name).
let settings = Settings::new();
let engine = Engine::new(&provider, settings);
let skill = Skill::new("greeter", "./skills/greeter", "Greet the user warmly.");
let user = SimulatedUser::new("A curious first-time visitor.")
.done_when("the assistant has answered the visitor's question")
.max_turns(6);
let outcome = engine.run(&Conversation::multi_turn(skill, "hi", user))?;
let verdict = engine.judge_boolean("the reply was welcoming", &outcome.transcript)?;
println!("{:?}: {}", verdict.value, verdict.reason);
# Ok::<(), onejudge::Error>(())
Drive a deterministic backend instead of a live harness by pointing a
CommandProvider at any command that speaks the protocol.
Development
The command surface is a just recipe set; just --list is the index.
just bootstrap # clean-clone setup: toolchain + cargo tools + fetch
just check # the full gate: format, lint, doc, coverage-enforced tests, audit
just test # fast unit + integration + e2e
The gate is deterministic and offline — the model is faked by real subprocess
test doubles, never mocked. The one path that needs a real external service is
proven in an opt-in tier, kept out of check:
just test-live— theOneharnessProviderpath against a real harness (see docs/live-tier.md).
See AGENTS.md for the durable contributor guide.
License
MIT.
Metadata
Release files for onejudge-cli 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| onejudge_cli-0.11.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| onejudge_cli-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| onejudge_cli-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| onejudge_cli-0.11.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| onejudge_cli-0.11.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 15.6 MB
Release files / onejudge_cli-0.11.0-py3-none-win_amd64.whl
| Download URL | onejudge_cli-0.11.0-py3-none-win_amd64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
a733ff4e630c97fa2e241dc4a448dfc167b005956679f76e4a16327de0a00d42
|
|
BLAKE2b-256 checksum How to use checksums |
b39a0d14641c315c87ecacfe473cc5c995fa35df7d981caf9939cd8a593f99ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / onejudge_cli-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | onejudge_cli-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e4dd8ec5bbeca73e0584d1d954bc08a1d18bdc6289be9c34488169d5fdbeae33
|
|
BLAKE2b-256 checksum How to use checksums |
ffaf88259572fdff1dae10b7d23dedf75474bdceb62ddf1ed25e60057d2713e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / onejudge_cli-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | onejudge_cli-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
21eaf0f4d81624ab44ef4ec5ed2ddef6dc2a834a001ef37507bab4a8ef216f5b
|
|
BLAKE2b-256 checksum How to use checksums |
7c747cc1a44c617ddcd29f318b9dcaa638d7fff861ae015b2f65c0c6551fae75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / onejudge_cli-0.11.0-py3-none-macosx_11_0_arm64.whl
| Download URL | onejudge_cli-0.11.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
52604dd5febd09dbc4a79d3697e545ecee8b344d3618fcae8dc9d94524f50b4e
|
|
BLAKE2b-256 checksum How to use checksums |
682fdb66249f5867ea22ebda1638ab9685a2ac87028b385c92b7ef64160bea02
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / onejudge_cli-0.11.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | onejudge_cli-0.11.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
94ed4e5ab95a3dd46df8bc11925f073d6753b6bdbf9cd4944c7ff7b4c3942c85
|
|
BLAKE2b-256 checksum How to use checksums |
9a5bca08645a110d60d3f3a9dc5b76101bcae948e2e056825771a85e25d7eb55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logSecurity for onejudge-cli 0.11.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for onejudge_cli-0.11.0-py3-none-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.