Skip to main content

Metrifid

PyPI version Python versions License: Apache-2.0 CI GitHub Marketplace

Know whether your MuJoCo model actually changed — before you argue about whether the simulation did.

A source diff cannot tell you. Reordered attributes, reformatted XML and moved includes change the bytes without changing the model. Running the simulation cannot tell you either: it only reports on the trajectories you happened to run.

metrifid certify compiles both versions and compares every byte MuJoCo emits for the compiled model.

Project links: repository · documentation · issues · changelog · security reporting

Start with the change you made

You do not need to pick a command from a list to get an answer. Two things change in practice — what you model, and what you run it on — and each has one place to start.

Your model or asset changed

Someone edited an MJCF file, regenerated a mesh, bumped an importer, or merged a pull request, and you need to know whether the model MuJoCo actually compiles is still the same model.

metrifid certify old/robot.xml new/robot.xml --output out/
exit 0   CERTIFIED_COMPILED_EQUIVALENCE   every serialized byte matched
exit 40  NOT_CERTIFIED_COMPILED_DIFFERS   at least one byte differed

Certify compiles both closures and compares every byte MuJoCo emits. It runs no workload, needs no initial state, actions, or tolerances, and never steps the simulation. Certify makes no behavioral-equivalence claim: it tells you the compiled artifact differs, not that the robot will behave differently.

metrifid diff old/robot.xml new/robot.xml answers the same compiled-identity question with no --output and no policy file, and retains a receipt, a Markdown summary and an offline HTML report under ~/.metrifid/runs/. It is the shortest way in; First use below starts there.

Exit 40 is not a crash. A completed exit 20, 30, or 40 can be a referee decision — Metrifid finished its work and is reporting what it found.

Your MuJoCo runtime changed

You are moving to a new MuJoCo native version and need to know whether it may replace the one your evidence was built on, for your closure and your workload.

metrifid run-runtime-review runtime_review_run.json

This creates the fixed twelve native evidence cells through two explicit, already-prepared Python profiles and returns the full-horizon Runtime Review decision. Metrifid does not discover, install, or repair MuJoCo environments — you prepare the two profiles, and Metrifid measures what they do.

Follow the evidence

Both routes hand you a receipt that names the next question. Follow the one you actually have:

You now know You still want Use
the compiled artifacts differ which fields changed, and whether your policy allows them review-model
a change is allowed statically whether it moves the trajectories you care about compare
your tolerances are set whether your workloads would notice a perturbation at all qualify-workload
the model is settled which timestep still holds within tolerance audit-timestep
twelve evidence cells already exist whether one native profile may replace another review-runtime

Every one of these is a completed decision with a receipt you can revalidate independently. None of them establishes physical correctness or safety.

See docs/public_cases.md for six complaint-backed cases that walk the whole journey end to end, and docs/getting_started.md for the guided tour.

First use

From an empty directory, with nothing but the installed wheel:

python -m pip install metrifid
python -m metrifid.demo
metrifid --help

python -m metrifid.demo needs no checkout, no arguments, and no network. It writes three tiny models into a temporary directory, compares two pairs, retains the evidence for each, prints where that evidence went, and ends with Metrifid demo passed:

different source, same compiled model : exit 0 (identical)
  receipt                             : ~/.metrifid/runs/<run>/model_release.json
  report                              : ~/.metrifid/runs/<run>/report.html
one changed mass                      : exit 40 (different)
  receipt                             : ~/.metrifid/runs/<run>/model_release.json
  report                              : ~/.metrifid/runs/<run>/report.html
Metrifid demo passed

The demo models are temporary. The results are not: those printed paths stay on disk after the command exits, and stay readable.

Then compare two of your own model versions:

metrifid diff old/robot.xml new/robot.xml --output out/

diff compiles both versions with one recorded MuJoCo runtime, compares the compiled artifacts byte for byte, and retains three files: model_release.json, model_release.md and report.html. With no --output it retains the run under ~/.metrifid/runs/<run>/ instead. --output DIR needs a directory that is absent or empty and that lies outside both model roots. Its parent must already exist: metrifid creates the final directory, not the path leading to it.

Exit Meaning
0 the compiled artifacts are byte-identical
40 the compiled artifacts differ
64 the request was refused: a bad path, an unusable output directory, an unreadable receipt
70 internal failure

Exit 0 and exit 40 are both completed comparisons. Only 64 and 70 mean no comparison was made, so a script should reject those two rather than every nonzero exit:

metrifid diff old/robot.xml new/robot.xml --output out/
exit_code=$?
if [ "$exit_code" -ne 0 ] && [ "$exit_code" -ne 40 ]; then
  echo "metrifid did not complete the comparison (exit $exit_code)" >&2
  exit "$exit_code"
fi
metrifid show out/model_release.json   # or open out/report.html

Each model root is measured whole. A root defaults to the directory holding the entrypoint, so every file beside the model is admitted, not only the file you named. When both versions draw on assets in a directory above them, name the wider root yourself:

metrifid diff old/models/robot.xml new/models/robot.xml \
  --baseline-root old/ --candidate-root new/ --output out/

The first lines of a real run on a differing pair:

Compiled model changed.
71 serialized byte(s) differ, first at offset 1692.
Recorded evaluation: REVIEW_REQUIRED. The run that produced it exited 40.

Compared
  baseline   .../examples/certify/equivalent/baseline.xml
             root .../examples/certify/equivalent  2 file(s) measured
  candidate  .../examples/certify/changed.xml
             root .../examples/certify  5 file(s) measured
  every file under each root is measured, not only the entrypoint
  the two roots differ: the measured file counts are not a change list

Named changes (2)
  body link mass: 1.5 -> 2.0

metrifid show RECEIPT reads a retained result back afterwards, including a certification.json written by certify. It imports no MuJoCo and no NumPy, so a retained result stays readable on a machine that has neither. Its exit 0 means the receipt was read, not that the two models matched: the recorded status is reported inside the result.

Three limits travel with every comparison, and the result states them. It is bound to one recorded MuJoCo version, Python build and platform, and says nothing about any other. Its coverage can be unknown, because the field producer omits some compiled members and a compiled object carrying no name cannot be attributed to a named object; unknown coverage is neither zero coverage nor full coverage. And it is not approval, sign-off, or a safety or correctness claim.

Working from a source checkout instead? python -m pip install . installs the same package; integration, security, receipt, and release evidence must run from a noneditable installation.

Use Metrifid in GitHub Actions

The repository ships a composite action that certifies two model revisions inside a workflow. Check out the models first, then run the action:

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: certify
  uses: Steady-Hand-AI/Steady-Hand-Metrifid@0.8.0
  with:
    baseline_mjcf: old/robot.xml
    candidate_mjcf: new/robot.xml
    strict: "false"
- run: |
    echo "exit code: ${{ steps.certify.outputs.exit_code }}"
    echo "status:    ${{ steps.certify.outputs.status }}"

strict defaults to "true", which fails the step when the compiled models differ. Setting it to "false" softens only that one completed outcome, exit 40: the step succeeds and the caller decides what to do from exit_code and status. Everything else still fails the step in both modes — an invalid strict value, an invocation refusal (exit 64), an operational error (exit 70), and any unexpected exit. Non-strict mode is not a way to ignore failures; it hands one specific, completed answer back to the workflow.

Try the source-checkout example

From a clone of the Metrifid repository:

python examples/certify/run_example.py
different source, same compiled model : CERTIFIED_COMPILED_EQUIVALENCE (exit 0)
one changed mass                      : NOT_CERTIFIED_COMPILED_DIFFERS (exit 40)

all 6 checks passed

Two models written differently that compile identically, and one model with a single changed mass. See examples/certify/.

Why complete source closure and compiled identity matter

Closure, because a model is not one file. The source closure contains every admitted regular file under the declared model root, measured by exact bytes. Dependency discovery separately identifies the files MuJoCo reaches while compiling the entrypoint. An unused regular file therefore changes the source-closure identity, but does not change the compiled MJB unless MuJoCo resolves it. Your original files are never modified: the measured members are copied into a private immutable snapshot, compiled from there, and the source is reverified before anything is published.

Compiled identity, because the compiled model is what the engine simulates. The identity is SHA-256 over every byte mj_saveModel emits — no projection, no normalization, no rounding, no tolerance. If the certificate says the artifacts are identical, nothing in the compiled model differs, including fields nobody thought to check.

When artifacts differ, the receipt locates the difference: the first differing byte offset, the count, and a bounded field report naming the changed public fields with sorted index and value witnesses.

Review a model release

metrifid review-model — what changed between two compiled robot models, was each change allowed, required, forbidden or undeclared by your policy, and where is the first unexpected one? It reads two model sources and one bounded maintainer policy, and links its decision to a Certify receipt built from the exact same compiled artifacts.

metrifid review-model baseline/model.xml candidate/model.xml \
  --policy model_release_policy.json \
  --output review-output
Status Exit Meaning
NO_COMPILED_CHANGE 0 The complete MJBs matched; there is no compiled change row.
WITHIN_DECLARED_POLICY 0 Every observed compiled change is declared by the policy.
REVIEW_REQUIRED 40 At least one observed change is undeclared, or coverage is incomplete.
OUTSIDE_DECLARED_POLICY 40 A change is forbidden, or a required change is missing.

This is a static policy decision. It steps no model and does not establish dynamic equivalence: a statically permitted compiled change may still change behavior, which is what compare measures.

See docs/model_release.md for the complete schema and semantics, and examples/model_release/README.md for a runnable two-pass example.

The remaining decisions

  • metrifid run-runtime-review — create the fixed twelve native evidence cells through two explicit, already-prepared Python profiles and immediately receive the same full-horizon Runtime Review decision. Metrifid measures but does not discover, install, or repair those environments. See the copyable examples/runtime_review_run journey.

  • metrifid review-runtime — whether one exact candidate MuJoCo native profile may replace one exact baseline profile for a declared source closure and workload over the complete retained horizon. It reads twelve already-produced three-grid/two-repeat evidence cells; it does not run a worker or select a method. Both paths use the same referee. See docs/runtime_review.md.

  • metrifid qualify-workload — whether three of your declared workloads would actually notice a supplied probe model at or above the magnitude you care about, and which probes stay invisible. Every answer is built from ordinary compare runs against probe models you supply, so it reports detection under your tolerances rather than a sensitivity estimate. The parameter name, direction, magnitude and magnitude_semantics you write down are preserved declarations about your probe closures: Metrifid binds the exact bytes it compared and reports what was detected, and does not independently establish that those labels describe the source edits or that no other source change exists.

  • metrifid audit-timestep — which of your declared candidate timesteps still produces no material difference against the reference, for a declared workload and tolerances? Every candidate is attempted independently; the recommendation is the largest one backed by an unbroken within-tolerance prefix. See docs/timestep_audit.md.

  • metrifid compare — under one declared deterministic open-loop workload, did monitored hinge or slide traces move beyond tolerances you set in physical units? See docs/comparison.md.

compare is the workload-bounded decision. certify is an artifact statement and makes no behavioral claim.

Documentation

Document What it covers
docs/getting_started.md a human-friendly first tour, installation, and documentation map
docs/capabilities_and_use_cases.md current capabilities, practical uses, limits, and non-goals
docs/public_cases.md six complaint-backed public cases and the external Robosuite Jaco study
docs/reference.md commands, exits, statuses, schemas, Python API
docs/compiled_certification.md the certify contract and claim boundary
docs/model_closure.md how the model closure is measured and confined
docs/workloads.md state, action and exact time-grid declarations
docs/comparison.md the compare decision
docs/timestep_audit.md the audit-timestep decision
docs/model_release.md the review-model static Model Change Gate
docs/workload_qualification.md the qualify-workload detection decision
docs/runtime_review.md the run-runtime-review execution journey and review-runtime retained-evidence decision
docs/sdk.md the supported programmatic execution surfaces, including review_runtime_configuration_file
docs/canonicalization.md canonical JSON, exact numbers, self-hashing
docs/menagerie_formatter_case.md a real public case study
skills/metrifid/SKILL.md the consumer skill for a coding agent — source only, not in the Python package

Requirements

Python 3.11 or newer, with no upper bound and no runtime rejection based on the interpreter name. Release evidence currently uses CPython because the MuJoCo binary wheels exercised by the release matrix target CPython. Other Python implementations are not claimed as validated without native evidence. Runtime admission requires a stable MuJoCo 3.9 or newer and is capability-based, with no minor-version ceiling on any platform. Package resolution carries one platform exception: because upstream publishes no Intel macOS wheel for MuJoCo 3.11 or newer, the dependency is mujoco>=3.9,<3.11 on Darwin x86_64 and mujoco>=3.9 with no minor ceiling everywhere else. That bound is a packaging-resolution fact about upstream wheel availability, not a narrowing of what the runtime admits. Exact stable MuJoCo 3.9.0, 3.10.0, 3.11.0, and 3.12.0 profiles are retained-validated. A later stable release is admitted only when its Python/native identities agree and the requested operation's measured capabilities pass; it is reported as capability-compatible, never falsely as validated. On platforms without that packaging exception the newest stable release is the primary development and release profile—3.12.0 for the frozen 2026-08-22 snapshot—while the exact retained older profiles remain backward-compatibility evidence. NumPy is >=1.26, with no runtime ceiling and no runtime version gate.

Every native command — certify, review-model, compare, audit-timestep, and qualify-workload — passes through one shared runtime gate. That gate admits Linux and macOS when the required POSIX capabilities are present, and it never inspects the machine architecture: architecture is receipt evidence only, so there is no allowlist. The Intel macOS packaging bound above is a dependency marker read by the installer, not a runtime check; the gate itself remains architecture-neutral. Native Windows is unsupported because those capabilities are absent; WSL is the documented route. A coherent runtime can still refuse one claim: missing call-graph capabilities use MUJOCO_RUNTIME_CAPABILITY_MISSING, and an unrepresentable feature uses MUJOCO_FEATURE_COVERAGE_INCOMPLETE. In particular, review-model requires an exact characterized public-field surface, while certify may still identify complete MJB bytes on a newer admitted runtime. Receipts continue binding exact package, native-library, and installed-distribution hashes.

run-runtime-review instead measures and admits both explicitly declared external native profiles through its fixed preflight collector before invoking the frozen worker in either profile. It does not search for or repair an environment, and it does not substitute the installed Metrifid process's runtime gate for those profile-specific measurements.

The package metadata has no artificial Python upper bound. Release CI runs two complete installed-wheel suites — one on the primary resolver-latest profile and one on the declared dependency-floor profile — and bounded smokes on the other default interpreter and platform profiles. The exact matrix lives in the workflow and changes as upstream support does. A tuple is described as validated only after that exact lane passes; a future Python minor or another implementation is not rejected solely by its name or version, but is not described as validated without native evidence.

Pure helpers — the workload artifact writers, canonical JSON and exact-number helpers, and receipt parsing and validation — never require the native gate and work without MuJoCo admission.

What a certificate claims

The measured baseline and candidate source closures, compiled under the recorded runtime identity, produced byte-identical complete MJB artifacts.

That claim is workload-free but runtime-bound: it holds for the runtime identity recorded in the receipt, which is why that identity is part of the receipt. It does not claim source-text equality, licensing, visual intent, task suitability, hardware safety, or that the same digest would appear under another operating system, MuJoCo version or build, and it does not survive a caller mutating mjModel afterwards. Every receipt carries those limitations explicitly and can be revalidated with metrifid.certify.validate_receipt.

Using Metrifid from Python

Each CLI command is a thin wrapper over one supported function:

from metrifid.certify import certify_models
from metrifid.compare import compare_configuration_file
from metrifid.model_release import review_model_release
from metrifid.timestep_audit import audit_configuration_file
from metrifid.workload_qualification import qualify_configuration_file
from metrifid.runtime_review import (
    review_runtime_configuration_file,
    run_runtime_review_configuration_file,
)

See docs/sdk.md for signatures, return types, the completed-decision versus refusal distinction, output ownership, the native runtime requirement, and the concurrency statement. Runnable SDK scripts for certify, compare, audit-timestep, qualify-workload, and the Runtime Review execution journey live in examples/sdk/. The Model Change Gate example lives in examples/model_release/.

License, ownership, and contributions

Copyright 2026 Volodymyr Barylyak. Metrifid is licensed under Apache License 2.0.

  • LICENSE contains the controlling software license.
  • NOTICE contains project attribution.
  • DCO contains the Developer Certificate of Origin 1.1 used for contributed commits.
  • docs/licensing_and_contributions.md explains the relationship among those files.

Contributions require a DCO sign-off as described in CONTRIBUTING.md. The DCO is not a copyright assignment; contributors retain copyright in their contributions.

Metadata

Release files for metrifid 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for metrifid 0.8.0
File Size Uploaded
metrifid-0.8.0.tar.gz 1.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for metrifid 0.8.0
File Interpreter ABI Platform
metrifid-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.5 MB

Release files / metrifid-0.8.0.tar.gz

Download URL metrifid-0.8.0.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
8752fcf4767fbb579cb1d96b2c07a0763d9192e92b4e8bbc0cd7f211aeb2ca43
BLAKE2b-256 checksum
How to use checksums
00c601a7b62cd1b51e7cd0bad250a80fd86acc768edf2c149319def0dafd998e
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 8, 2026.

Transparency log

Release files / metrifid-0.8.0-py3-none-any.whl

Download URL metrifid-0.8.0-py3-none-any.whl
Size 519.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5ec33dbb04b42229acc453addb63e11f62fec04bbd5fa11e67bfa46b9254013b
BLAKE2b-256 checksum
How to use checksums
aa4668512ac3cdac7529e608c1b7e27c9c2f9d414d93601e5d80ba337fa996d3
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 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page