English | Русский | Deutsch | 中文
Animated demo. Prefer a static image? Open assets/readme/hero.en.svg.
AI coding agents work fast and report their own results. PolyForge adds a notebook to your repository that cannot quietly rewrite history. The agent writes down what it claims to have done. A real tool run, such as tests, type checks, or a build, must confirm each claim before it counts as verified. When the proof is missing, the gate stays red and the work does not merge.
Under the hood that notebook is an append-only Merkle chain: every entry commits to the hash of the previous one, so editing a single byte breaks the chain and every later check fails.
Everything described here is covered by 414 tests across the six workspace crates, plus CLI/MCP smoke and end-to-end harnesses. Run the suite yourself: cargo build --workspace && cargo test --workspace.
Three more reasons to trust the numbers:
- This repository gates its own CI with tensorov/polyforge-action@v1 on every push and pull request.
- Gate bundles are reproducible: a second passing run produces a byte-identical bundle and the same SHA-256.
- A runnable end-to-end walkthrough of the evidence lifecycle ships in crates/polyforge-core/examples/ledger_flow.rs:
cargo run -p polyforge-core --example ledger_flow.
Install from crates.io. All six crates are published at v0.4.0; polyforge-tui and polyforge-attest ship with this release. You need a Rust toolchain (1.85 or newer, 1.88+ for the TUI):
cargo install polyforge-cli polyforge-mcp polyforge-tui polyforge-attestRecord a claim, prove it with a tool attestation, validate it, and pass two gates:
export PF_LEDGER=/tmp/pf-demo/ledger.jsonl
export PF_EVIDENCE_DIR=/tmp/pf-demo/evidence/
polyforge-cli init
polyforge-cli append model_claim "claim datum" --task demo --commit abc123 --diff d1
polyforge-cli append tool_attestation "ran" --task demo
polyforge-cli ledger tail
polyforge-cli gate demo --required verified
polyforge-cli append validation "operator check" --task demo
polyforge-cli gate demo --required validatedWhat happened: init created the ledger, the model claim created a ModelClaimed entry, the tool attestation promoted it to Verified, the validation promoted it further to Validated, and both gates passed with exit code 0. ledger tail printed the 64-hex SHA-256 tail hash of the chain.
Evidence is tri-state and only ever moves forward:
model_claim: the model records a claim about its own work. Creates aModelClaimedentry.tool_attestation: an allowlisted tool run promotes the task's claim toVerified.eval_attestation: an operator records an evaluation outcome (experiment, run, model fingerprint, budget) that also promotes a claim toVerified.discrepancy: an operator, or the toolrunner on a failed verifier run, records a refutation trace that moves the claim toRefuted.validation: an operator validation promotes aVerifiedentry toValidated.
A gate can require verified, validated, or both. Refuted entries are recorded but never satisfy a gate. Tool attestations carry a wall-clock timestamp, and when a verifier runs inside a git checkout the recorded payload reflects the repository state (commit and diff) rather than a bare command string.
- A model's word alone is never enough.
model_claimcan only create a newModelClaimedentry, nothing more. - Reaching
Verifiedrequires an attestation from an allowlisted tool:cargo,rustc, andgccfor Rust and C;pytest,ruff,mypy, andpyrightfor Python;vitest,tsc,eslint, andbiomefor JavaScript and TypeScript. - Over MCP the lock is tighter: the
evidence_appendtool acceptskind=ModelClaimonly. Attestations, validations, and discrepancies are rejected at the server, so a connected model cannot createVerified,Refuted, orValidatedentries at all.
The published action gates a task against the ledger and verifies the committed Merkle-chain anchor. This repository runs it on every push and pull request.
- uses: tensorov/polyforge-action@v1
with:
task-id: my-task
required: verified,validated
ledger-path: .pf/ledger.jsonl
evidence-dir: .pf/evidence/| Input | Required | Default | Description |
|---|---|---|---|
task-id |
yes | Task id to gate against the ledger. | |
required |
no | verified,validated |
Comma-list of required evidence states. |
ledger-path |
no | .pf/ledger.jsonl |
Ledger path relative to the workspace root. |
evidence-dir |
no | .pf/evidence/ |
Evidence directory relative to the workspace root. |
The action fails closed: a corrupted chain, a missing anchor, or a gate that never reaches the required state fails the job. Add the job as a required status check in a branch ruleset and a PR cannot merge while the gate is red.
A passing gate writes gate-<task_id>.jsonl plus gate-<task_id>.manifest.json containing task_id, tail_hash, passed, bundle_sha256, and tool_versions. A failing gate exits non-zero and writes no bundle. By default the gate evaluates the task's latest claim; pass --commit <sha> --diff <hash> to pin it to a specific claim, and a pinned gate that no longer matches the latest claim is rejected as stale instead of silently passing against older evidence. Running a foreign repo in CI: add a Rust toolchain step (for example dtolnay/rust-toolchain) before the action, which runs cargo install polyforge-cli --locked.
Every agent registers the same polyforge-mcp server over stdio. Make sure polyforge-mcp is on PATH, or point command at the full path to your built binary.
In opencode.json (project root or ~/.config/opencode/):
{
"mcp": {
"polyforge": {
"type": "local",
"command": ["polyforge-mcp"],
"env": {
"PF_MCP_TRANSPORT": "stdio"
}
}
}
}claude mcp add polyforge -- polyforge-mcpIn ~/.codex/config.toml:
[mcp_servers.polyforge]
command = "polyforge-mcp"
env = { PF_MCP_TRANSPORT = "stdio" }In ~/.openclaw/config.json (example, adjust path):
{
"mcpServers": {
"polyforge": {
"command": "polyforge-mcp",
"args": [],
"env": {
"PF_MCP_TRANSPORT": "stdio"
}
}
}
}The server exposes four tools: evidence_append (accepts ModelClaim only, so models can never append attestations or validations), evidence_verify (runs an allowlisted tool to verify a claim; arbitrary binaries are never executed), and the read-only pair gate_evaluate and gate_report.
Transport options: PF_MCP_TRANSPORT=stdio (default) or tcp with PF_MCP_ADDR (default 127.0.0.1:18888). The TCP listener is loopback-only and requires PF_MCP_TOKEN; every request must carry it as _pf_token, and a missing or invalid token is rejected with JSON-RPC error -32001. PF_MCP_LEDGER selects the ledger path (default .pf/ledger.jsonl).
LazyForge is a terminal UI for browsing tasks, validating entries, and bulk-validating over the evidence ledger. Install it with cargo install polyforge-tui (binary: lazyforge) and read the LazyForge user guide. Verified integration guides for OpenCode, Claude Code, and Codex live in docs/integrations/, and the MCP servers-directory submission kit is in docs/mcp-servers-pr-kit/.
Architecture: six crates
Workspace of six crates (edition 2021, rust-version 1.85, developed against toolchain 1.95.0):
| Crate | Responsibility |
|---|---|
polyforge-core |
Evidence model: tri-state entries, promotion rules, the append-only Merkle ledger, and deterministic gate evaluation. |
polyforge-toolrunner |
Allowlisted tool runner: only allowlisted binaries, typed arguments, no shell, per-command environment fingerprint. |
polyforge-mcp |
Model Context Protocol server (rmcp): the interface models use to append claims and query gates. |
polyforge-cli |
Operator CLI: init, append, ledger inspection, and gate execution over a local ledger. |
polyforge-tui |
LazyForge terminal operator console: browse tasks, validate, bulk-validate over the evidence ledger. |
polyforge-attest |
Canonical JSON writer plus in-toto Statement v1 and DSSE envelope types for exporting attestations. |
The CLI binary is named polyforge-cli (the crate name); alias pf=polyforge-cli if you prefer the short name.
Environment fingerprints are per-command: a Nix store-path digest and devbox.lock sha256 are folded in when present, Cargo.lock sha256 always, plus the values of key build env vars such as CFLAGS/CXXFLAGS/LDFLAGS/RUSTDOCFLAGS when set. For Python and JS/TS repos the runner folds uv.lock, pnpm-lock.yaml, package-lock.json, and yarn.lock when present, discovered from the git root or cwd ancestors, so no Cargo.toml is required.
Mutating or code-loading flags are denied at validation: --fix / --unsafe-fixes (ruff check), --fix / --rulesdir / --resolve-plugins-relative-to and non-builtin --format values (eslint), --apply / --apply-unsafe / --write (biome check), -u / --update (vitest run), -p (except -p no:*) and --pdb (pytest). gcc -v accepts no extra args. Package runners (uv run, npx, npm exec) are excluded entirely because their argv resolves an unbounded binary set. Tools resolve from the PATH of the polyforge process; activating your project's venv before running attestations is operator duty.
Other CLI surface: ledger summary prints per-task state counts as one grep-able line (tasks_verified=… tasks_validated=… tasks_failed=…); coverage-check --report <llvm-cov.json> evaluates a cargo llvm-cov --json report against the coverage floor (default 80% aggregate / 80% per file); any append kind accepts optional record-only identity flags --experiment, --model, --run, --budget, and --metadata, carried through promotion. A global --executor <process|sandbox> flag placed before any subcommand selects the execution backend used for tool attestations; it is validated before anything runs, the default process keeps behavior byte-identical, and sandbox takes an explicit --sandbox-backend <container|gvisor|firecracker> tier: each backend ships behind its own cargo feature, is probed fail-closed before any run, and the attestation records its tier-prefixed executor digest (container: / gvisor: / fc:). Environment variables: PF_LEDGER (ledger path, default .pf/ledger.jsonl), PF_EVIDENCE_DIR (gate bundles, default .pf/evidence/), PF_ENV_FINGERPRINT (operator-supplied fingerprint recorded on attestations, default cli).
Build from source:
cargo build --release # binary lands in target/release/polyforge-cli
cargo build --workspace && cargo test --workspaceTamper and rewind guarantee
The ledger is an append-only Merkle chain: every entry commits to the hash of the previous entry. Rewinding the file or tampering with one byte of any entry breaks the chain, and any subsequent polyforge-cli gate or evidence_verify fails with LedgerIntegrity. Failed integrity checks never fabricate a bundle or manifest: the failure is surfaced and nothing is written. This is exercised end-to-end by the harness (byte-flip in the tail entry means the gate exits non-zero with ledger integrity broken at seq …, no bundle, no manifest).
The chain is hardened against reordering and concurrent writers: entries use a length-prefixed canonical encoding (hash version 2), and a committed anchor sidecar records the chain tail so a rewind past the anchor is detected. Writers take an exclusive file lock (fs2) around each append, so concurrent processes cannot interleave entries.
Scope note: tamper evidence holds within a trusted checkout. The chain proves the ledger was not rewritten, not that the checkout itself is authentic. Cryptographic external anchoring is on the roadmap (Phase 3).
Comparison matrix (survey dated 2026-08-09)
PolyForge is a single-format evidence ledger: one append-only Merkle chain, one tri-state entry model, one deterministic gate. The matrix below surveys the direct evidence-ledger and MCP tools found on 2026-08-09, plus three adjacent categories for context. Among the direct evidence-ledger tools surveyed, PolyForge is the only Rust crate-workspace observed; the rest are single-language or proprietary. Facts come from each project's public page; where a source is silent the cell reads ?.
| Feature | PolyForge | agent-gate | AttestMCP | AGA MCP | audit-ledger-mcp | Xiid | Zyvra | Omega | Observability | Provenance | Sandboxes |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Tamper-evident ledger | ✅ | ✅ | ✅ | ✅ | ✅ | ? | ? | ? | ? | ? | ? |
| Deterministic gate | ✅ | ✅ | ? | ✅ | ? | ? | ? | ? | ? | ? | ? |
| MCP interface | ✅ | ? | ✅ | ✅ | ✅ | ? | ? | ? | ? | ? | ? |
| CLI | ✅ | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| Open source | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ? | ? | ? | ? | ? |
| Rust | ✅ | ❌ | ❌ | ❌ | ? | ? | ? | ? | ? | ? | ? |
| Tri-state evidence model | ✅ | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| Fail-closed on integrity break | ✅ | ✅ | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| Evidence bundle output | ✅ | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| Hardware attestation | ❌ | ? | ? | ? | ? | ? | ? | ✅ | ? | ? | ? |
| SaaS / hosted | ❌ | ? | ? | ? | ? | ? | ✅ | ? | ? | ? | ? |
Legend: ✅ = feature confirmed by the cited source · ❌ = source explicitly states the feature is absent · ? = source is silent (unknown).
Sources (accessed 2026-08-09):
agent-gate— Jott2121/agent-gate — https://github.com/Jott2121/agent-gateAttestMCP— attestmcp/attestmcp — https://github.com/attestmcp/attestmcpAGA MCP— attestedintelligence/aga-mcp-server — https://github.com/attestedintelligence/aga-mcp-serveraudit-ledger-mcp— shahidh68/audit-ledger-mcp — https://github.com/shahidh68/audit-ledger-mcpXiid— https://xiid.comZyvra— https://zyvra.techOmega— arXiv 2512.05951 — https://arxiv.org/abs/2512.05951Observability— LangSmith / Langfuse / AgentOps / Arize Phoenix / Helicone — https://docs.smith.langchain.com, https://helicone.aiProvenance— in-toto / Sigstore/cosign / Witness / SLSA — https://in-toto.ioSandboxes— e2b / Modal / Daytona — https://e2b.dev
Taken together: among the direct evidence-ledger tools surveyed on 2026-08-09, PolyForge is the only Rust crate-workspace combining a single-format Merkle ledger, a deterministic gate, and an MCP interface in one workspace.
Performance
Measured on the development machine with the release binary and a fresh ledger in /tmp/pf-bench; the rebuilt binary is byte-identical to the one measured:
| Scenario | Value | How to reproduce |
|---|---|---|
| 100-task full chain (300 ledger appends) | 0.510 s | time ( for i in $(seq 1 100); do polyforge-cli append model_claim "bench claim $i" --task task$i --commit c$i --diff d$i >/dev/null; polyforge-cli append tool_attestation "ran" --task task$i >/dev/null; polyforge-cli append validation "op" --task task$i >/dev/null; done ) |
| 100 gate checks over a 300-entry ledger | 0.500 s | time ( for i in $(seq 1 100); do polyforge-cli gate task$i --required validated >/dev/null; done ) |
| Release binary size | 774664 B | stat -c%s target/release/polyforge-cli |
Full clean rebuild (cargo clean + cargo build --release) |
34.15 s | cargo clean && time cargo build --release |
Roadmap
Production-readiness path, full detail in docs/ROADMAP.md. Priorities follow two constraints: attestations must be ungameable and reproducible (trust first), and PolyForge gates its own development (dogfooding).
| Phase | Status | Key items |
|---|---|---|
| Phase 0: Trust hardening + self-gating | Shipped | Mutation testing (cargo-mutants, Stryker), Nix/Devbox fingerprints, polyforge-action self-gating on this repo |
| Phase 1: Adoption | Shipped | tensorov/polyforge-action v1 published, Python/TS toolrunner, LazyForge TUI. Cline/Aider/Cursor prompts intentionally cut. |
| Phase 2: Scale & observability | Started | OpenTelemetry/OTLP exporter subcommand exists. Next: remote backend (PostgreSQL + S3/DynamoDB), web dashboard + REST/gRPC API, LangGraph/CrewAI/AutoGen middleware |
| Phase 3: Enterprise & ecosystem | Future | SLSA/in-toto/Sigstore, deep plugins (Cursor/Windsurf/Continue.dev), web human-in-the-loop, Policy-as-Code |
| Moonshot backlog | Future | Verification marketplace, TEE / hardware attestations |
PolyForge is licensed under the Apache License, Version 2.0. See the NOTICE file for attribution requirements.
Before opening an issue, please read SECURITY.md for how to report vulnerabilities. Feature and bug reports use the templates under .github/ISSUE_TEMPLATE/.
