Skip to main content

FarmNotary

CI

Notary for AgentFarm runs. Simulation stays off-chain.

FarmNotary writes a manifest.json (config, code identity, artifact hashes), optionally pins the run directory to IPFS, and can anchor the manifest hash via OpenTimestamps — free calendar servers that batch digests into Bitcoin.

Immutability is not correctness. Re-run from the committed seed to check the science. The anchor only makes “this is the file we published” hard to walk back.

This release is 0.2.0. Install from PyPI (see Install).

Why FarmNotary

Published runs need a record that does not depend on the author's laptop. Reviewers should be able to fetch the official artifacts, rehash them, and see when that exact file was published — without trusting a zip, a Drive folder, or a local IPFS pin.

That is a narrower job than "put the simulation on a chain" and a broader one than sha256sum. The simulation stays off-chain. FarmNotary hashes all files into the withheld commitment (withheld_root) but excludes paths containing ballot, vote, voter, individual_choice, or private from published artifacts and uploads. What gets notarized is the official record: code identity, config, aggregate metrics, winner allocations. Anchoring a hash is already solved (OpenTimestamps into Bitcoin); FarmNotary's work is the domain part — allowlists, publication scope, and honest claims.

A hash tool will tell you the bytes match. A research notary also tells you what you may claim, and what you may not.

In and out of scope

In

  • Canonical manifest.json (farmnotary.manifest.v1): recursive discovery, SHA-256 map, publish allowlist
  • Campaign / sweep parent manifests (farmnotary.campaign.v1)
  • Derivation claims (notary.derived_from) — executed only with --verify-derived
  • Environment fingerprint (OS, arch, Python, system / machine, optional numpy/BLAS)
  • Optional minisign / SSH identity on the content hash (no protocol token)
  • Paper-pack appendix snippet and a static public index (no scores)
  • Reusable GitHub Action in this repo (dooders/FarmNotary)
  • Optional IPFS upload via the Kubo HTTP API (stdlib only)
  • Anchoring via OpenTimestamps: no contract, no keys, no gas
  • Verify: claim card + local rehash; OTS proof commits to the content hash

Out

  • Running anchoring infrastructure (contracts, chains, servers)
  • Running simulations inside the EVM
  • Publishing individual voter or agent ballots
  • Social scores, rankings, or reputation
  • A public index that is anything other than a directory of published manifests

Official record vs private choice

For a consensus / selection experiment:

  • Voter (or agent) choice stays private
  • What gets notarized is the official record: config, code hash, aggregate metrics, winner allocations

Nothing is hashed, listed, or uploaded by default. Prefer a named profile so labs do not invent globs (and forget REPORT.md or include a path they should not):

farm-notary manifest --run-dir path/to/run --profile consensus

Checked-in profiles: consensus, rl-sweep, evolution-run. Extra --publish globs append. The resolved allowlist is recorded as publish_patterns (and publish_profile) so the policy is part of the claim.

The denylist still applies: any path containing ballot, vote, voter, individual_choice, or private is excluded even if a pattern would admit it. Files left out of the official record increment unmatched_count (names are never printed) and, when any exist, a salted Merkle commitment (withheld_root, withheld_classes) records the publication scope: published X, withheld N in class C, commitment C_root.

Install

Python ≥ 3.9. Core (manifest, verify, IPFS, dry-run) is stdlib-only.

pip install "farm-notary[ots]"

Extras:

Extra Adds Needed for
(none) — manifest, verify (local rehash), IPFS pin
[ots] opentimestamps anchor --backend ots, upgrade, OTS proof checks
[chain] web3 experimental EAS backend
[dev] pytest + extras + ruff + mypy development
[lint] ruff, mypy ruff check farm_notary / mypy farm_notary

IPFS upload needs a reachable Kubo daemon (FARM_NOTARY_IPFS_API, default http://127.0.0.1:5001), not a Python extra.

Quick start

# 1. Hash the run directory (profile or --publish is required)
farm-notary manifest --run-dir path/to/run --profile consensus --config config.json

# 2. Claim card: rehash artifacts. Derivation commands are not run.
farm-notary verify --run-dir path/to/run

# 3. Optional: stamp the content hash on public calendars
farm-notary anchor --run-dir path/to/run --backend ots

anchor and precommit default to --backend dry-run (print the payload, contact nothing). Pass --backend ots to submit. The GitHub Action defaults to ots.

Published path (durable pin + calendar stamp):

ipfs pin remote service add pinata https://api.pinata.cloud/psa <jwt-token>

farm-notary anchor --run-dir path/to/run --pin-remote pinata --backend ots
# hours later, once a calendar has batched the digest into Bitcoin:
farm-notary upgrade --run-dir path/to/run
farm-notary verify --run-dir path/to/run

precommit and anchor refuse a dirty git tree: the recorded SHA does not identify the code. git_dirty is still recorded. A supplied --git-sha is not a bypass — the working tree is inspected. Pass --allow-dirty (or allow_dirty=True) for an explicit exception.

Beacon-derived seeds (L2): commit how many seeds and a min_round on the plan, stamp it (--backend ots), then derive-seeds. The used round must be exactly min_round. A 3s drand chain plus a delayed stamp is a race; stamp immediately after precommit, then derive. Default verify does not fetch drand; pass --live-beacon (or --beacon-fixture in tests) so L2 can compare randomness over TLS. Threshold signatures are not checked. L2 is not scientific correctness.

farm-notary precommit --config plan.json --command "python run.py --seed {seed} --out {run_dir}" \
  --seed-count 8 --inclusion all_in_campaign --backend ots
farm-notary derive-seeds --precommit precommit.json --wait --live-beacon
farm-notary manifest --run-dir runs/i-0 --precommit precommit.json --seed-index 0 --profile consensus
farm-notary verify --run-dir runs/i-0 --live-beacon

--beacon-fixture (or FARM_NOTARY_BEACON_FIXTURE) is for tests and offline checks. A fetch failure leaves L2 unearned; it does not fail verify.

Commands

Command What it does
manifest Write manifest.json (requires --profile and/or --publish)
check Zero-install reviewer check: claim level + anchor status from manifest.json only; no artifact rehash (uvx farm-notary check --manifest …)
verify Print a CLAIMS.md claim card (ladder + rows); rehash; check proofs if present
verify --verify-derived Also run derived_from commands (trusted manifests only)
verify --campaign Check child hashes and the shared config hash (not a claim card)
precommit Write precommit.json before the run (dry-run or ots); --seed-count adds a beacon seed plan
anchor Optional pin + stamp (dry-run default; ots; eas experimental)
upgrade Complete a pending manifest.ots with a Bitcoin attestation
reproduce Re-run the recorded command; write reproduction.json (executes the recorded shell command; --sign is Sigstore on the receipt, not lab SSH)
sign Attach a minisign or SSH signature of the content hash (not Sigstore)
campaign Build a parent campaign.json from child run directories
paper-pack Write appendix.md for a PDF
index Append a run or campaign to a static registry (no scores)
reveal-withheld Open a named subset of withheld files against withheld_root
derive-seeds After the plan is stamped, fetch min_round and write seeds.json
register-schema One-time EAS schema registration
emit-interop Unsigned JSON summaries (SLSA/in-toto vocabulary, RO-Crate, C2PA-style). Not verifiable provenance
archive Optional Zenodo deposit / Software Heritage lookup. IDs are not claim-card rows
chain Manifest-hash lineage (provenance-chain.json). Not input/output data flow

What you may claim

farm-notary verify prints a card. Reviewers read the card, not the exit code. Missing is not failure. Exit 0 means the artifact hashes match and every attempted proof check passed (including that an OTS proof, if present, commits to the content hash) — not that every claim was earned, not that existed by time T reached Bitcoin, and not that the science is right.

claim card
level: none — no earned ladder level
next:  L0 — these bytes existed by time T; Bitcoin headers not verified by this tool (missing: Bitcoin attestation)
•  tamper-evident record           — pass
•  existed by time T               — pending on public OpenTimestamps calendar: https://a.pool.opentimestamps.org (not yet Bitcoin-attested)
•  pre-specified design            — missing
•  bitwise reproducible (scoped)   — 6/6, ignored: *.mp4; byte-identical on x86-64 Linux in a pinned environment
•  not claimed: scientific correctness

The level: line is the highest earned reader-ladder step (L0–L3). Pending OTS is not L0. The existed by time T row distinguishes Bitcoin-attested proofs from pending public-calendar submissions and user-supplied calendars that remain untrusted until Bitcoin. L0 means the proof commits to the content hash and carries a Bitcoin-height attestation; this tool does not check Bitcoin headers (ots verify does). L1 needs a recorded command, git SHA, and environment fingerprint — not a completed re-run. L2 requires a beacon-derived seed after the plan is anchored (not scientific correctness). L3 needs a Sigstore keyless signature on the reproduction receipt (farm-notary reproduce --sign). That is not independently reproduced: identity is recorded when it can be parsed, not proven distinct from the publisher. A receipt count is not credibility. Prefer COSIGN_IDENTITY_TOKEN / SIGSTORE_ID_TOKEN or --identity-token @PATH (never a raw JWT on argv). Cosign pin: v2.5.3. See docs/CLAIMS.md.

The only bitwise sentence the tool may emit today is byte-identical on x86-64 Linux in a pinned environment. Other hardware still reports N/M and refuses a cross-hardware claim. See docs/CLAIMS.md.

Derivation is a separate, opt-in check:

farm-notary verify --run-dir path/to/run --verify-derived

Without the flag, rules stay on the manifest and the card still exits 0. The CLI prints a note; it does not execute commands from a downloaded manifest. Only pass --verify-derived for manifests you trust.

Pinning

--pin-remote is the published path. Local Kubo (--pin) is a lab convenience and always warns.

The CID is content-addressed, but the bytes are only reachable while some node holds them. Do not cite a local-only pin. --pin-remote <service> uploads via Kubo, then calls a registered IPFS Pinning Service (Pinata, web3.storage, …). --pin-remote implies --pin. The manifest records pin_service ("local" or the service name).

After each pin, FarmNotary checks https://ipfs.io/ipfs/<cid> and records cid_reachable plus a UTC timestamp. Pass --no-check-gateway in CI (the Action does this by default).

Anchoring

  • CLI default: dry-run — no network.
  • Recommended live backend: --backend ots — keyless, no gas, Bitcoin-backed.
  • Experimental: --backend eas — funded key, gas on Base, attester-address trust. See docs/EAS.md.

--backend ots submits the content hash (SHA-256 of the canonical body, excluding stamp fields cid, cid_reachable, pin_service, anchor, identity) to calendars (--calendar or FARM_NOTARY_CALENDARS; default: public pools). The proof is manifest.ots. verify reports whether that proof is Bitcoin-attested, pending on known public pools, or pending only on user-supplied calendars. Stamping the manifest afterwards does not invalidate it.

upgrade asks calendars for a Bitcoin attestation (typically hours). Exit 1 means still pending. Full header checks against your own node are left to standard ots verify tooling.

Reproducing a run

Record the command with {run_dir} marking the output directory:

farm-notary manifest --run-dir path/to/run --profile consensus \
  --command "python run_experiment.py --seed 0 --out {run_dir}" \
  --lockfile requirements.lock
farm-notary reproduce --run-dir path/to/run --cwd path/to/experiment --ignore '*.mp4' --anchor

reproduce executes the manifest's recorded command via the shell. Treat a downloaded manifest as untrusted code execution — remote code execution by design — not as independent verification.

By default, FarmNotary refuses to run that command unless the manifest matches the same local checkout (git_sha) or the same GitHub Actions repo/SHA the operator is already trusting. To override, pass --i-accept-untrusted-command and do the run inside your own sandbox (container or VM, no network).

reproduce re-runs into a fresh directory, compares every listed artifact, and writes reproduction.json. A byte-diff is classified (embedded_absolute_path, timestamp, float_print_format, video_encoder) and labeled not a science failure when that is what it is. --cwd is the experiment repo when it is not the run directory. --anchor timestamps the receipt (reproduction.ots).

verify then reports bitwise reproducible (scoped) — N/M of compared artifacts, any --ignore globs, and the scoped sentence above.

Campaigns, paper pack, and the public index

farm-notary campaign --name consensus-sweep \
  --run-dir runs/seed-0 --run-dir runs/seed-1 \
  --out sweep/
farm-notary verify --campaign sweep/
farm-notary paper-pack --campaign sweep/ --out sweep/appendix.md
farm-notary index --registry docs/registry.md --campaign sweep/

paper-pack writes the appendix snippet: CID, content hash, Bitcoin attestation (or pending), allowlist, unmatched count, precommit hash, artifact label, reader-ladder placeholder (—), environment, scoped sentence. It does not cite Ln. Campaigns also list child seeds and CIDs.

index maintains a static directory (Markdown + registry.json sidecar): experiment, seed, CID, claim level, date. Not a scoreboard. Running index rewrites the Markdown table; how-to text in that file is not preserved. See docs/registry.md.

Optional lab identity on the manifest content hash (still no protocol token). This is not reproduce --sign (Sigstore on the receipt):

farm-notary sign --run-dir path/to/run --scheme ssh --key ~/.ssh/id_ed25519

Derivation rules live in the experiment profile so statistics can recompute when a PNG is renderer-dependent:

{
  "notary": {
    "profile": "consensus",
    "publish": ["extra_table.csv"],
    "derived_from": [
      {
        "outputs": ["summary.csv", "allocation_means.csv", "REPORT.md"],
        "sources": ["trials.csv"],
        "command": "python run_experiment.py verify-report --results {run_dir}",
        "mode": "verify"
      }
    ]
  }
}

mode: recompute (default) copies sources to a temp directory, runs command, and byte-compares outputs. mode: verify runs a check that exits 0 when derived artifacts match.

GitHub Action

The action lives at the repo root (action.yml). Pin v0.2.0 (or a commit SHA). @dev is the unreleased line.

- uses: dooders/FarmNotary@v0.2.0
  with:
    phase: notarize
    run-dir: results
    profile: consensus
    pin-remote: pinata
    backend: ots

phase: precommit at job start, phase: notarize after success (binds precommit.json when present, anchors, runs verify, uploads manifest.json + manifest.ots). phase: all is notarize-only for an already-finished run. Verify failure fails the job; artifacts are still uploaded. The Action does not pass --verify-derived. Full contract: docs/ACTION.md.

CI provenance

When farm-notary manifest runs inside GitHub Actions, the tool automatically reads GITHUB_SHA, GITHUB_REPOSITORY, GITHUB_REF, GITHUB_WORKFLOW, and GITHUB_RUN_ID and records them in a ci_provenance block in the manifest:

"ci_provenance": {
  "kind": "github_actions",
  "sha": "<GITHUB_SHA>",
  "repository": "Dooders/FarmNotary",
  "ref": "refs/heads/main",
  "workflow": "CI",
  "run_id": "123456",
  "run_url": "https://github.com/Dooders/FarmNotary/actions/runs/123456"
}

ci_provenance is included in the content hash so the anchor commits to both the artifacts and the attested CI context. verify checks that git_sha agrees with ci_provenance.sha; a disagreement is a hard failure. Local / developer runs that have no ci_provenance remain valid at a lower claim level — the field is optional and additive.

Documented path (checkout → run → notarize with provenance attached):

jobs:
  notarize:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4        # sets GITHUB_SHA
      - run: python run.py --out results  # produce artifacts
      - uses: dooders/FarmNotary@v0.2.0
        with:
          phase: notarize
          run-dir: results
          profile: consensus
          backend: ots
          # No git-sha override needed: farm-notary reads GITHUB_SHA
          # automatically and cross-checks it against git rev-parse HEAD.

The identity field (minisign / SSH optional signature) labels local signatures as "holder of key K asserts this" — it is never interpreted as CI provenance.

Python API

AgentFarm should depend on a 0.2 pin (farm-notary>=0.2,<0.3) via an extra such as farm[notary]:

from farm_notary import notarize_run
from farm_notary.ots import OpenTimestampsBackend

manifest, receipt = notarize_run(
    run_dir,
    git_sha=sha,
    runner="consensus_paradigms",
    config=config,
    publish_profile="consensus",
    backend=OpenTimestampsBackend(),
    pin_remote="pinata",
)

notarize_run is dry-run until a backend is passed. pin_remote= is the published pin path; pin=True is local Kubo only. Do not submodule unless the API is still thrashing.

Schema stability

Every manifest records farm_notary_version (currently 0.2.0) and schema (farmnotary.manifest.v1).

Promise: within the 0.x line, schema changes are minor-version bumps. verify stays backward-compatible with older manifests: new fields are ignored when reading an older body. A newer schema emits a warning and still attempts verification. Optional content-hashed fields (derived_from, ci_provenance, publish_profile, withheld_root, …) are omitted when empty so a v1 body keeps a stable content hash.

Required on every v1 body: schema, created_utc, git_sha, config, artifacts, artifact_hashes, publish_patterns, unmatched_count.

identity is a stamp field (with cid and anchor): written after content_hash and excluded from it. JSON Schema files are in schemas/ (source/GitHub repo only; not included in the PyPI wheel). Migration notes: docs/MIGRATION.md.

Breaking changes (new required fields, renamed keys, removed fields) are reserved for 1.0 and will land in the changelog with a migration guide.

Verifying someone else's claim

Quick check (zero-install): No venv, no extras, no account. You only need manifest.json (and manifest.ots if you want the anchor checked).

uvx farm-notary check --manifest path/to/manifest.json
# or: pipx run farm-notary check --manifest path/to/manifest.json

Output:

content_hash: a1b2c3…
cid:          bafybeig…
claim_level:  bytes
anchor:       Bitcoin height 840000

Add [ots] to verify the OTS proof commits to the content hash:

uvx "farm-notary[ots]" check --manifest path/to/manifest.json

See docs/VERIFIER.md for the full reviewer guide.

Full verify (artifact rehash):

  1. Fetch the CID (ipfs get <cid>), or obtain the run directory another way. If ipfs get hangs, check pin_service (local-only is not archival), cid_reachable, and try https://ipfs.io/ipfs/<cid>.
  2. uvx "farm-notary[ots]" verify --run-dir <dir>
  3. Read the claim card. Missing is not failure. The card always ends with not claimed: scientific correctness.
  4. Only if you trust the recorded commands: farm-notary verify --run-dir <dir> --verify-derived

verify distinguishes:

  • artifact unreachable: <name> — file absent; CID hint appended when known
  • artifact hash mismatch: <name> — present but different bytes

Development

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest

CI runs pytest on Python 3.9–3.12.

Documentation

Doc Contents
docs/PRINCIPLES.md Constraints that settle design arguments
docs/CLAIMS.md What each claim means, what backs it, hardware scope
docs/DESIGN.md Schema, backends, publication scope, provenance flow
docs/MIGRATION.md 0.1 → 0.2 and withheld-commitment notes
schemas/ JSON Schema for manifest, campaign, and registry
docs/ACTION.md GitHub Action inputs, outputs, phases
docs/EAS.md Experimental EAS backend
docs/VERIFIER.md Reviewer quick-check: zero-install uvx/pipx run path
docs/registry.md Generated public index (no scores)
docs/demo/ Live notebook: claim card, allowlist, scoped re-run
docs/slides/ Intro deck (PDF) and consensus walkthrough (PDF)
CHANGELOG.md Version history
integration/agentfarm/README.md AgentFarm provenance patch

Status

0.2.0 is on PyPI. Manifests, campaigns, derivation claims, environment fingerprints, optional identity, paper pack, public index, hashing, IPFS pinning, OpenTimestamps (CLI dry-run default; Action ots), proof upgrade, claim-card verify, reusable GitHub Action, and experimental EAS on Base / Base Sepolia are implemented and tested.

Later-wave helpers from #40 (emit-interop, archive, tracker plugins, chain) are experimental first-cuts. They are not claim-ladder steps: SLSA/C2PA files are unsigned summaries, plugins require an explicit allowlist, and a provenance chain is hash lineage only.

The anchoring layer is outsourced — earlier revisions carried a custom SimulationRegistry contract, which was removed. Breaking schema changes are reserved for 1.0.

Metadata

Release files for farm-notary 0.2.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 farm-notary 0.2.0
File Size Uploaded
farm_notary-0.2.0.tar.gz 168.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for farm-notary 0.2.0
File Interpreter ABI Platform
farm_notary-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 287.4 kB

Release files / farm_notary-0.2.0.tar.gz

Download URL farm_notary-0.2.0.tar.gz
Size 168.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f0cf8f1201a2ddceccf1922a304567bf4270afc52ac64bfef775dec08733a70f
BLAKE2b-256 checksum
How to use checksums
4d90ed9e496a671b2fe5ea3c00d560e96e5a43bbb259af301e91f22e8bd79623
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 1, 2026.

Transparency log

Release files / farm_notary-0.2.0-py3-none-any.whl

Download URL farm_notary-0.2.0-py3-none-any.whl
Size 119.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5e55137916ba7b5a2985153e0b830bb5775e1737ce5f1cdb1e9af53d9b8c4a49
BLAKE2b-256 checksum
How to use checksums
2138082907c82aa15b9383e47893e360f90e5c83fcd18e89bfc92d8e590e661e
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 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

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