Skip to main content
This is advanced content, for users who want to understand how eval scores, how metrics are computed, and how automatic multi-turn evaluation runs. For day-to-day evaluation, Quickstart is enough.
Comet’s eval doesn’t just give “pass/fail” — it’s metric-driven evaluation: it uses rubric multi-dimension scoring to quantify a Skill’s performance across quality dimensions, and uses pass@k / pass^k to distinguish the “capability ceiling” from the “reliability floor”. For multi-stage workflow-type Skills, the evaluation is completed by two Agents interacting automatically — one runs the Skill under test, the other simulates the user replying at decision points.

Metric System at a Glance

MetricQuestion it answersTypeIs it a gate?
rubric per-dimension score (0.0–1.0)How well did this dimension doDiagnosticNo (informational)
weighted_score (weighted total)How is the overall qualityDiagnosticNo
RubricAvg (report column)Mean dimension score for that runDiagnosticNo
pass@kProbability of at least one success out of k (capability ceiling)DistributionalNo
pass^kProbability of all k succeeding (reliability floor)DistributionalNo
Workflow contract checksWhether the tested Skill actually triggered as contractedHard judgmentYes (missing required Skills fails)
Task validator pass/failWhether this implementation is actually correctHard judgmentYes (decides pass/fail)
Key distinction: rubric dimension scores and pass@k/pass^k are informational metrics, for diagnosis and comparison; the real pass/fail is checks_failed == []. Most failures come from task validators (target_artifacts existence + test_scripts), but profiles can also write workflow-contract failures, such as missing required Skills, into checks_failed.

Where the Evidence Comes From

Comet eval evidence comes from stream-json emitted directly by the Claude CLI. It is not guessed from terminal text, and it is not reconstructed from another model’s summary. Single-turn evaluation runs the subject Agent directly inside the task directory in Docker:
claude -p "$prompt" \
  --dangerously-skip-permissions \
  --output-format stream-json \
  --verbose
The eval harness saves raw stdout / stderr, then parses JSONL events from stdout line by line. The parser extracts:
  • duration_ms, num_turns, tokens, and cost from result events
  • Tool calls from tool_use events
  • Bash commands as commands_run
  • Write / Edit paths as files_created / files_modified
  • Skill calls as skills_invoked
  • Matching tool_result output attached back to the original tool call
So report fields such as Skills invoked, commands_run, tool_calls, files_created, token/cost, and duration are organized from Claude CLI’s structured event stream.
stream-json is still an observable behavior log. It is good for counting tool calls, commands, Skill invocations, file changes, and cost; it does not contain the model’s private reasoning, and it cannot prove the causal chain behind an internal decision. Schema changes, malformed JSON lines, or CLI version changes can affect parsing, so raw stdout/stderr are retained for audit.

Trajectory and Event Stream Are Different

The Comet Classic runtime also writes .comet/trajectory*.jsonl, recording state progression such as state_transitioned. That trajectory is one piece of recovery evidence, and the rubric checks for it under recovery_resilience. But pass@k / pass^k are not computed from trajectory, and most rubric dimensions do not rely only on trajectory. The main evidence sources are Claude CLI stream-json, artifacts in the task directory, task validator results, and state files left by the Comet runtime.

rubric Scoring

The rubric breaks a Skill’s quality down into multiple dimensions, each dimension composed of several binary (pass/fail) check items. The dimension score = passed items / total items (0.0–1.0). Each dimension outputs a line [RUBRIC] <dimension>: <score> - <reason>. Scoring methodology (aligned with industry practices like Galileo, Hebbia, τ-bench):
  • Each dimension contains N binary check items
  • Dimension score = passed / total (0.0–1.0)
  • Weighted total = Σ(dimension score × weight) / Σ(weights)
  • Weights reflect the dimension’s importance to workflow quality

Three Rubrics

eval has three built-in rubrics, corresponding to three profiles:
ProfileDimensionsWeight sumInteraction modeSuitable for
generic77.7none (single-turn)Generic Skill smoke testing
comet-workflow911.0auto_user (multi-turn)Classic /comet five-phase
authoring-skill1113.8auto_user (multi-turn)/comet-any artifacts

comet-workflow rubric (9 dimensions)

Evaluates the classic five-phase workflow. The nine [RUBRIC] dimension scores are diagnostic, but the profile still checks the workflow contract: if comet, nested Comet stage Skills, OpenSpec dependency Skills, or Superpowers dependency Skills were not actually triggered, the run gets a hard failure.
DimensionWeightWhat it checks
completion2.0 (highest)Baseline validator pass ratio (did the task actually get done right)
main_flow1.5How many of the five phases (open→design→build→verify→archive) left evidence
gate_guard1.5Whether comet-guard / transition / --apply were used
artifact_quality1.2Whether proposal/design/tasks/test have substantive content (proposal≥10 lines, design contains tradeoff keywords, tasks≥3 checkable items, has assertion tests)
skill_invocation1.0Whether the comet entry, nested Comet stage Skills, OpenSpec dependency Skills, and Superpowers dependency Skills were actually observed, with the entry before stage Skills
spec_drift1.0Whether the delta spec changed during build is reconciled before archiving (neutral 1.0 if spec untouched)
decision_point_compliance1.0Whether it pauses at decision points to ask the user rather than deciding automatically (ask calls ≥ half of mutation count)
recovery_resilience1.0Whether state is correctly preserved and recovered after interruption (five items: checkpoint/trajectory/context/pending/.comet.yaml)
efficiency0.8 (lowest)Cost normalization (three items: turns≤80, tools≤150, duration≤600s)

generic rubric (7 dimensions)

Evaluates generic Skills. Unlike comet-workflow, it produces a hard failure for “required Skill not invoked” (when require_skill_invocation: true); the other dimensions are informational.
DimensionWeightWhat it checks
completion2.0Baseline validator pass ratio
safety_boundary1.2Whether there are dangerous commands (rm -rf, git reset --hard, `curl …sh`)
skill_invocation1.0Whether all required skills were invoked (neutral 1.0 if not configured)
artifact_presence1.0Whether expected_artifacts exist (supports glob)
instruction_following1.0Whether any failed check mentions “constraint”
interaction_compliance0.8Whether outer round-trips under auto_user exceed max_turns
efficiency0.7Same thresholds as comet’s efficiency

authoring-skill rubric (11 dimensions)

Evaluates /comet-any generated Skill bundles. It first inherits four shared dimensions from generic, then adds seven bundle-specific checks. Missing SKILL.md, missing resolved-skills.json, missing workflow-protocol.json, missing Engine files, missing authoring-lanes.json, and missing skill-review.md all produce hard failures.
DimensionWeightWhat it checks
workflow_route_conformance1.5 (tied for highest with completion)Whether the protocol route order matches the expected node sequence, whether internal Node Skill files are complete, whether comet/eval.yaml lists the route
completion1.5Inherited from generic
generated_package1.4Bundle exists + SKILL.md contains route/pause-points/workflow and resolved-skill references/recovery guidance
resolved_skill_evidence1.2reference/resolved-skills.json exists and sourceSummaries is non-empty
engine_contract1.2comet/ contains skill.yaml/guardrails.yaml/checks.yaml/eval.yaml (neutral 1.0 for lightweight bundles without Engine)
authoring_lanes1.2reference/authoring-lanes.json contains the 6 required lanes (skill-core/script-contract/reference/pause-points/eval/skill-review) and review passes
safety_boundary1.2Inherited from generic
skill_invocation1.0Inherited from generic
artifact_presence1.0Inherited from generic
review_gate0.8reference/skill-review.md report passes, authoring-lanes review passes, no blocking findings
review_readiness0.8 (lowest)1.0 if the generic layer has no hard failure

Weighted Total Formula

weighted_score = Σ(dimension score × weight) / Σ(weights)
Each dimension outputs a line [RUBRIC] <dim>: <score> - <reason>, and finally a line [RUBRIC] weighted_score: <score>.

RubricAvg (report column)

The RubricAvg column in the report is the simple mean (sum/len) of all dimension scores for that run (including the weighted_score row). Across multiple runs it’s the mean of each run’s mean. It’s a summary for quick horizontal comparison, computed differently from a single rubric’s weighted_score (which uses its own weights).

LLM-as-judge Override (Optional)

Enable by setting BENCH_LLM_JUDGE=1. By default, Comet’s final weighted_score is a rule score, not LLM-as-judge. Rule-based rubrics can catch structural signals (file existence, command execution) but can’t catch the substantive depth of artifacts — did the agent actually produce meaningful output, or just generate a stub? The LLM judge lets a judge model read workspace artifacts and re-score, outputting [RUBRIC-JUDGE] lines (distinct from the rule score’s [RUBRIC]). Best-effort: on failure it falls back to the rule score without affecting the run. The judge runs on the host (not inside Docker) by reusing the claude CLI, introducing no new dependencies. Judge configuration is intentionally isolated from the subject Agent configuration: when BENCH_LLM_JUDGE=1 is enabled, BENCH_JUDGE_MODEL must be set explicitly, and the judge does not fall back to the subject model from ANTHROPIC_MODEL. When BENCH_JUDGE_BASE_URL and BENCH_JUDGE_AUTH_TOKEN / BENCH_JUDGE_API_KEY are configured, the judge first calls Anthropic Messages HTTP directly (/v1/messages). Without a dedicated judge endpoint, it falls back to the host claude CLI, clears inherited subject ANTHROPIC_* provider settings, then maps the dedicated judge settings into its own CLI invocation:
Environment variablePurpose
BENCH_JUDGE_MODELRequired. Judge model; used as the request model for direct HTTP, or passed to claude --model and subprocess ANTHROPIC_MODEL for CLI fallback
BENCH_JUDGE_AUTH_TOKENOptional. Judge-only Anthropic-compatible proxy token; takes precedence over BENCH_JUDGE_API_KEY
BENCH_JUDGE_BASE_URLOptional. Judge-only Anthropic-compatible proxy URL
BENCH_JUDGE_API_KEYOptional. Judge-only Anthropic API key; not passed to the subprocess when an auth token is set
This avoids the “same model as both contestant and judge” problem: the subject Agent can keep using its own ANTHROPIC_MODEL, ANTHROPIC_BASE_URL, and token, while the judge must declare its model and provider separately. The direct HTTP path also avoids strict Anthropic-compatible proxies rejecting extra request parameters emitted by Claude CLI. If BENCH_LLM_JUDGE=1 is enabled without BENCH_JUDGE_MODEL, the report records:
[RUBRIC-JUDGE] status: skipped - BENCH_JUDGE_MODEL is required when BENCH_LLM_JUDGE=1
That skipped state is not marked as enabled_and_successful, and the judge model is not invoked. Different profiles cover different dimensions:
ProfileCovered dimensionsDescription
comet-workflowartifact_quality, spec_drift, main_flowRe-scores the three qualitative dimensions where rules are weak (llm_judge.py)
generic / authoring-skilltask_completion, output_quality, instruction_adherenceThree generic dimensions, plus the task’s rubric_criteria declared in task.toml as custom_N dimensions (generic_llm_judge.py)
generic/authoring three-dimension scoring criteria:
Dimension1.0 (full)0.50.0
task_completionAll requirements met, artifacts exist and are correct, baseline checks passPartially completeCritical artifacts missing or empty
output_qualityWell-structured, complete, non-trivial, shows thoughtExists but shallow, barely meets the barMissing, empty, obvious stub
instruction_adherenceFully follows instructions and constraints, no forbidden patterns, sound tool usageMinor deviation but core intent preservedSevere violation, dangerous commands, constraint failure
When collecting workspace files, the judge skips .git, node_modules, .comet and similar directories, with a 3000-character per-file cap and a 20000-character total budget; large files (>50KB) and binary files are skipped to keep the prompt under control. The judge must output line by line in [RUBRIC-JUDGE] <dim>: <score> - <reason> format, with each reason ≤25 words and citing specific content.

pass@k and pass^k

These two metrics measure capability vs reliability and are key to evaluating whether a Skill can run stably and repeatedly. They are computed from the pass/fail sequence of multiple repeated runs (produced by --count N).

Definitions

MetricFormulaMeaning
pass@k (capability ceiling)1 - C(n-c, k) / C(n, k)Probability of at least one success in k independent attempts (HumanEval unbiased estimate)
pass^k (reliability floor)1.0 if and only if c == n, else 0.0Probability that all k attempts succeed
Where n = total runs, c = successful runs. “Success” = that run’s task validator had zero failures.

Why Both Are Needed

Little fish comparing pass@k's at least one success with pass^k's stable success every time

pass@k looks at the capability ceiling, pass^k looks at the reliability floor — the larger the gap, the more unstable

  • High pass@k, low pass^k: capability is sufficient, but unstable — “can do it, but can’t guarantee doing it right every time”. For a Skill that users run repeatedly, this is a danger signal.
  • Both high: can do it and gets it right every time — trustworthy.
The gap (pass@k − pass^k) quantifies instability. The report explicitly highlights this gap.

How to Get Multiple Runs

--count N (a pytest option) repeats each (task, treatment) combination N times, producing N independent pass/fail results. The comparison report first filters to the analysis set: clear environment or runner noise is excluded, while flagged runs still participate in the main statistics and are called out. Those N booleans in the analysis set are the input to pass@k/pass^k.
# Repeat 5 times to make pass@k/pass^k meaningful
# (actually driven indirectly via comet eval; equivalent to --count 5 under the hood)
The report warns when there aren’t enough runs: “Only N run per treatment — pass@k/pass^k for k>1 need ≥2 runs to be meaningful. Use —count 5.” A single run can only compute pass@1/pass^1; k>1 metrics require multiple repeats.

How to Choose k

The report takes k values from {1, 2, 5} that don’t exceed the actual number of runs n (k is clamped to n), falling back to [1] when insufficient. Report columns: pass@1 [pass@2 pass@5] and pass^1 [pass^2 pass^5].

Where They Appear

pass@k/pass^k are not in summary.md but in the comparison report (produced by compare_baselines.py as comparison_report.md) in the ## pass@k / pass^k — capability vs reliability section. They are informational, not gates.

Dual-Agent Automatic Interaction Evaluation

For multi-stage workflow-type Skills (comet-workflow and authoring-skill profiles), the evaluation is completed by two Agents interacting automatically, with no human involvement needed.

Two Roles

RoleWhat it doesSession
Subject Agent (subject)Runs the Skill under test (e.g. the /comet five-phase)A single continuous session, resumed with --resume each turn
User simulator Agent (simulator)Simulates user replies at decision points (approves reasonable proposals, picks reasonable defaults, clarifies only when truly ambiguous)A one-shot claude -p call per decision point, stateless

Interaction Loop

The loop runs up to max_turns outer round-trips (comet-workflow typically 12, authoring-skill typically 8), ending early when “complete” is hit (archive complete / workflow complete / all 5 phases, etc.). Every subject Agent turn also runs with --output-format stream-json --verbose. The loop driver only concatenates the subject Agent’s stream-json stdout for the harness to parse; the user simulator Agent’s one-shot replies do not enter the main event stream. As a result, event statistics reflect the subject Agent’s observable behavior, not the simulator’s behavior.
max_turns is not the number of real internal working turns of the subject Agent, nor the number of tool calls. One outer round-trip means: the subject Agent runs to a decision point, the user simulator Agent replies, then the subject Agent continues with —resume. Within the same round-trip there may still be multiple assistant messages, tool calls, and file operations.

Decision Point Detection

The subject Agent’s output text is judged as a decision point when it matches any of these signals (case-insensitive):
? | confirm | choose | proceed | continue | approve | select an option |
which (option|approach|name) | enter the | provide | would you | shall we |
do you want | preferred
Custom --decision-pattern is also supported.

User Simulator Agent Instructions

The simulator Agent receives the simulator prompt + the subject Agent’s last message, and is asked to:
  • When asked to confirm, approve the proposed plan/name/approach
  • When asked to choose, pick the most reasonable default
  • Ask for clarification only when the question is genuinely ambiguous about “what to do”
  • Never refuse; always let the workflow move forward
  • Don’t write code or files
comet-workflow uses COMET_SIMULATOR_PROMPT; generic/authoring uses GENERIC_SIMULATOR_PROMPT (slightly simpler wording). When the simulated reply is empty, it falls back to "Yes, please proceed with the recommended option.".

Customizing the Simulator Prompt

The simulator instructions for auto_user evaluation can be overridden. By default it reads from eval/simulator-instruction.md, whose content is the standard template of the principles above. Two override methods (from highest to lowest priority):
MethodHow to setScope
--simulator-prompt "..."pytest command-line argumentSingle run
BENCH_SIMULATOR_PROMPT_FILE=<path>Environment variable (can be written into eval/.env)All auto_user runs
Relative paths for BENCH_SIMULATOR_PROMPT_FILE are resolved from eval/, with a default value of simulator-instruction.md; the file is read only if it exists. This lets you swap in a different user behavior profile (e.g. more demanding, asks for more clarification) to stress-test how the workflow behaves under a “demanding user” without changing harness code.

Why It’s Designed This Way

Workflow-type Skills pause at decision points waiting for user confirmation. If the evaluation only ran a single turn, the Skill would get stuck at the first decision point. The dual-agent loop lets the evaluation automatically run the entire workflow (open→…→archive), while ensuring the user input at decision points is “reasonable and forward-moving” rather than hardcoded replies. This way the rubric scores and pass/fail measured reflect real usage scenarios.

Single-turn vs Multi-turn

  • generic profile (interaction.mode: none): single-turn; the subject Agent runs to completion in one go (suitable for smoke tasks that don’t need interaction).
  • comet-workflow / authoring-skill profile (interaction.mode: auto_user): multi-turn; enables the dual-agent loop.
Tasks starting with comet-* or with category: comet are automatically inferred as the comet-workflow profile and enable auto_user.

The Three Axes of Evaluation: treatment × task × reps

An evaluation is the Cartesian product of three axes:
AxisMeaningExample
taskAn evaluation task (directory, with instruction/environment/validation)comet-fix-median (the bug-fix dimension)
treatmentExperimental condition (which Skills / CLAUDE.md to inject)CONTROL (no-Skill baseline), COMET_FULL (inject comet)
repsNumber of repeats (--count N)--count 5 runs each (task,treatment) 5 times

treatment Enables A/B Comparison

Running multiple treatments on the same task lets you measure the marginal effect of the Comet Skill:
TreatmentInjectsPurpose
CONTROLNo-Skill baselineControl
COMET_FULLcomet main + 7 phase sub-Skills + brainstorming (v3, .mjs runtime)Current version
COMET_FULL_0390.3.9 frozen baseline (.sh runtime)Regression comparison
The comparison report (compare_baselines.py) compares COMET_FULL (WORKFLOW) and COMET_FULL_039 (BASELINE) across rubric dimensions; CONTROL is only context.

How Metrics Get Into the Report

Report locationContent
summary.md Results tableOne row per treatment: Checks + each rubric dimension column + RubricAvg + Turns/Duration/Tools/Tokens/Cost
summary.md Aggregated by Treatment (when reps>1)Reps Passed, Checks, Avg Turns/Duration, Tokens, Cost, Skills, Scripts
Per-run report.jsonpassed, checks_passed[], checks_failed[], events_summary (including failure_attribution), rubric scores
comparison_report.mdpass@k/pass^k table (capability vs reliability) + rubric dimension cross-treatment comparison
See Reading Evaluation Reports.

Next steps

Last modified on July 3, 2026