Skip to content
BitExactdocs
Documentation
On this page

BitExact guide

Self-hosted evidence instrument for AI agents. Reconstruct a decision, replay it deterministically, and let anyone verify it without trusting you.

See it in action: watch a lending decision get proven, then improved: one real run recorded, replayed bit-for-bit when the decision came under review, then forked to test a corrected policy. Anyone can re-derive the whole record offline with the open verifier.

Overview

BitExact records every model call, tool call, and captured source of nondeterminism that runs through the proxy or a wrapped client into a hash-chained manifest, then replays every value the record kept bit-exactly with zero network and zero side effects (a value the capture policy scrubbed or a redaction removed is refused, never guessed). Any divergence between the record and a re-execution halts with the exact step and a diff. Runs export as signed evidence bundles that anyone can check with the open-source verifier — no BitExact license required. Everything runs inside your VPC; nothing phones home. BitExact proves the integrity of what was captured, not that the capture is complete: a call made entirely out of band is outside the record. The determinism linter flags code that would bypass capture - direct HTTP calls, unwrapped model clients - before it runs; a call made entirely out of band at run time leaves no gap to detect.

Install

# from your platform's release bundle (Python 3.15+), no index needed:
pip install --no-index --find-links wheels --require-hashes -r requirements.lock
pip install --no-index --no-deps bitexact_sdk-<version>-*.whl
# or load the image we deliver and run the recording proxy:
IMAGE=$(docker load -q -i bitexact-<version>-amd64.tar | sed -n 's/^Loaded image: //p')
docker run -p 8787:8787 -v bitexact-data:/data -v $PWD/secrets:/secrets:ro \
  -e BITEXACT_LICENSE=/secrets/bitexact.license \
  -e BITEXACT_SIGNING_KEY=/secrets/recorder.key \
  $(docker image inspect --format '{{.Id}}' "$IMAGE")

Both are delivered to you with each release, with a SHA256SUMS file and the image's cosign signature. The vendor key licenses are checked against is built into both.

Record a run

import bitexact
from bitexact.adapters import wrap_openai   # or wrap_anthropic

@bitexact.record_tool
def fetch_balance(account_id): ...

with bitexact.run("run-42") as r:
    client = wrap_openai(openai.OpenAI())
    balance = fetch_balance("acct-104")
    answer = client.chat.completions.create(...)

Model traffic flows through a loopback recording proxy bound to the run; tool results and time.time / random.random / random.randint / uuid.uuid4 values are captured into the same manifest. API keys are forwarded upstream and never recorded. The proxy speaks OpenAI, Anthropic, Gemini, Vertex AI, Azure OpenAI and Bedrock, streaming included (providers).

Replay — bit-exactly, offline

with bitexact.run("run-42", mode="replay"):
    ...  # identical code; zero network, zero tool side effects

Every call is matched by identity and content against the recording: a changed prompt, different tool arguments, an extra call, an early exit, or any recorded step left unserved raises DivergenceError naming the exact step with a recorded-vs-actual diff (unserved steps are listed by identity). Pure thread-scheduling reorder is not a divergence — replay verifies calls and content, not the scheduler. A tampered manifest refuses to replay at all. Set the mode without code changes via BITEXACT_MODE.

Counterfactual fork & fork science

with bitexact.fork("run-42", at_step="http_call:2") as f:
    ...  # everything before the 2nd model call served, the rest runs live

stats = bitexact.fork_matrix("run-42", 7, 20, agent_fn)
#  -> outcome counts across 20 forks: is this outcome typical, or a fluke?
#     agent_fn may take the run or take nothing and use bitexact.current()

mutated = {"status": 200, "media_type": "application/json",
           "text": '{"choices": [{"message": {"content": "APPROVE"}}]}'}
bitexact.fork("run-42", at_step=9, inject={4: mutated})
#  -> "what if the model had said X at step 4?" - recorded as `injected`;
#     a replacement is the recorded-response shape: status, media_type, text

The fork writes its own manifest, opened with a provenance entry hash-linked to the source prefix — a counterfactual is itself verifiable evidence, and bitexact verify RUN --provenance walks the entire fork ancestry, hash-checking every hop against its verified source and surfacing synthetic responses anywhere in the chain. Fork points address by step or by kind ("tool_call:2"). A fork matrix runs N forks from one step and aggregates outcomes, so "was this failure statistically typical?" is answerable from counts. A response-mutation fork replaces a recorded response and continues — recorded as an explicitly synthetic injected entry, so an experiment can never be laundered into run evidence. bitexact forks prints the provenance tree.

Determinism linter

bitexact lint src/ --format sarif   # stable BX rule ids, JSON/SARIF, inline suppressions

The capture matrix and the linter derive from one table, so they can never disagree about what replays. Captured during a run: time.time/time_ns/monotonic, random.random/randint/ choice/uniform/shuffle/getrandbits/randbytes, uuid.uuid4, os.urandom, and the secrets token functions. The linter flags the rest — datetime.now, perf_counter, uuid1, unknown random.*/secrets.* calls — and from time import time-style imports that bind functions directly and defeat interception. Exit code 1 when findings exist.

Frameworks & concurrency

@bitexact.record_tool          # sync or async: both record and replay
async def fetch(...): ...

pool.submit(bitexact.propagate(tool), args)   # thread pools, explicitly

from bitexact.adapters import temporal_run
with temporal_run():                          # inside a Temporal activity
    ...                        # one evidence trail per execution, per retry

Replay matches each call by identity and content, so parallel graphs (a LangGraph fan-out with an async tool and an async model client is in the test suite) replay correctly even when scheduling differs from the recording. Capture follows the run context: asyncio tasks, executor calls and propagated pool tasks are captured. An unpropagated pool tool call, or one through a propagate() wrapper created outside a run, still runs but is not recorded, and every run recording at that moment declares a run_context_lost gap naming the tool, so verify exits 3 rather than calling the capture whole; a task that outlives its run is noted beside that run and never touches its sealed chain. Strict mode raises instead. Agent code that itself lives under site-packages must be named in BITEXACT_AGENT_PACKAGES to be captured. Runs are isolated per context: one run per thread or task, and any number of them at once, so a Temporal worker records concurrent activities with no limit on max_concurrent_activities; each activity execution and each retry is its own evidence trail. bitexact otel-export renders any verified run (with the otel license entitlement) as OTel GenAI span events — zero-duration markers derived from the evidence, one shared trace per run — for the observability stack you already have.

Recording secrets.* or os.urandom writes the generated values into the manifest — that is what makes them replayable, and it means evidence can contain live key material. The recorder warns once per run; redact before sharing (bitexact redact --fields STEP:value) and rotate any secret that replay or fork feeds into live traffic.

Determinism qualification

bitexact qualify --model gpt-4o --prompt "..." -n 20
bitexact qualify --wire anthropic --model claude-sonnet-5 --prompt "..." -n 20

Sends the identical request N times and compares the model-controlled bytes exactly: exact-match rate, distinct outputs, first divergence with context. The report is self-hashed.

Regression across a run corpus

bitexact regress run-1 run-2 --entry myagent:main \
    --intended http_call:request.text.messages \
    --git-sha $GIT_SHA --sign --key signing.key
# nonzero exit on proven unintended divergence — a drop-in CI gate

Regress replays recorded runs against your changed prompt or code, offline and with zero model spend. Every place the new code behaves differently becomes a finding: flipped prompts and tool decisions (with recorded-vs-actual diffs), brand-new calls (a typed UnrecordedCallError, never a silent None), and recorded steps no longer taken, reported by identity. Declare the change you meant to make with --intended, and first_divergence lands on the first unintended one. Mind the grain: an intended path marks everything beneath it, list contents included, so declare the narrowest path that covers your change and review the flip diffs the report keeps. Reports carry source head hashes, SDK version, timestamps, the git sha and label as supplied by the caller, and an optional signature a recipient checks with bitexact verify-report; gate is the machine-readable CI verdict. Reports never overwrite history: they accumulate beside the manifests, so prune on your own schedule. A sweep is crash-honest: an aborted run reports the exception and the step it reached, and one crashing run never stops the sweep. Offline regress shows the calls your change makes, not how the model would answer them. That ceiling is by design; confirm leads with a live fork at the divergence step.

Local viewer

bitexact view   # http://127.0.0.1:8788 — the inspector

Optionally token-gated (BITEXACT_VIEWER_TOKENS), paginated, with on-demand payloads, head-hash-cached verification, fork provenance, and regress reports surfaced. Browse recorded runs, per-step detail, and chain-verification status in the browser. The viewer never edits or deletes a recorded entry. It does act, and each act is recorded as evidence of its own: a legal hold, a fork, a two-person redaction (which destroys the named values in place and keeps their commitments), a regress, a suite run, a case. Every such route needs the X-BitExact-Action header and a role that may perform it; the route list is in the API reference.

For the people who read the evidence there: the inspector guide for compliance and audit staff. Signing in, roles, SSO, the read-only token compliance staff should be given, and the sign-in throttle: identity and access. How an auditor checks a bundle independently: verifying a bundle.

Concurrent agents

with bitexact.run("run-42", mode="replay"):
    ...  # asyncio.gather-style calls match by identity and content

Identical concurrent calls to one source consume their recorded values first-to-last — which recorded value lands in which racing task is scheduler-dependent, the documented ceiling for exact duplicates. A tool served from the record absorbs the nondeterminism it recorded internally, so tools with internal clocks or jitter replay clean. http_matching="unordered" remains accepted and now describes regress mode's content matching.

PII redaction

bitexact redact run-42 --fields 3:request.text     # the stored manifest
bitexact export run-42 --redact-steps 7 --sign --key signing.key

Every committed field carries its own salted commitment, so redaction is per-field: remove the prompt, keep the proven status, model, and usage. Either way the chain and any signature still verify, and the destroyed salt defeats dictionary attacks. bitexact redact rewrites the stored manifest itself, write-ahead: a chained audit entry naming the fields and the operator lands on the chain before any value is destroyed, and the CLI warns if quarantine or stream-journal files may still hold copies so the operator can complete the erasure. Export-time redaction (--redact-steps) instead produces a redacted copy for a recipient and leaves the store untouched. A redacted bundle proves what it omits was committed to, not what it said.

Evidence bundles & the verifier

bitexact keygen -o signing.key
bitexact export run-42 --sign --key signing.key -o run-42.bundle.json
bitexact-verifier run-42.bundle.json --expect-key <public key>

Bundles follow the bundle spec (aer-bundle): a BLAKE2b-256 hash chain (SHA-256 in FIPS mode) over every step, recorder-signed checkpoints when a recorder key is configured, plus an optional detached signature at export. Signatures are hybrid Ed25519 + ML-DSA (FIPS 204), the key bitexact keygen makes: both halves sign the same bytes, so a relying party who pins the ML-DSA key (--expect-pq-key) refuses a forger who recovers the Ed25519 key with a quantum computer, and every verdict names the suite it checked, Ed25519 only included. A recorder key held in an HSM keeps both halves in the token; the ML-DSA half is exercised against a real kryoptic PKCS#11 3.2 token (key custody). The verifier is a standalone Apache-2.0 tool an auditor runs independently: github.com/bitexact/bitexact-verifier, the standard library plus cryptography and asn1crypto, no product code. Its real verdict on a signed four-step run:

$ python bitexact_verifier.py run-42.bundle.json --expect-key <public key> --expect-pq-key <its post-quantum half>
OK: run-42 - 4 steps (observed 4 / asserted 0 / synthetic 0), chain verified, sealed, signed, signatures: Ed25519 + ML-DSA-65 (post-quantum), post-quantum key pinned
standing when recorded: ok (stated by the producer, not proof)

Licensing

License keys are files signed with the vendor's hybrid Ed25519 + ML-DSA key and validated entirely offline, with no code path that could phone home. bitexact license shows status. BitExact is licensed use only: without a valid key nothing records or replays (agents' calls still pass through the proxy, unrecorded, unless strict mode is on), and a license bound to a recorder key records only under that key, one deployment per key. When a license expires, recording continues through a grace period (14 days unless the license names another); after it the deployment becomes read-only: nothing new is produced, and everything already recorded stays listable, verifiable and exportable. The commercial shape is on the licensing terms page.

Without a valid license only bitexact capture keygen, bitexact keygen, bitexact license, bitexact pki, bitexact recorder-key, bitexact version and bitexact witness answer. bitexact collector, bitexact mcp-proxy, bitexact recorder and bitexact serve start in any license state and check the license on each call: they pass the agents' traffic through, or declare a gap, rather than record.

Environment reference

Every variable the product reads, generated from the code (a test fails when one is missing). The full command reference is the CLI page; which acts are done from the command line rather than the inspector, and why, is administration from the command line, and monitoring and alerting is ops health.

VariableMeaning
BITEXACT_ACTORThis agent's stable name, sent with every event through the collector; replay serves each actor its own calls.
BITEXACT_AGENT_ENTRIESNamed entrypoints (name=module:function,...) a suite can compare.
BITEXACT_AGENT_ENTRYmodule:function the viewer forks, sweeps and regresses with.
BITEXACT_AGENT_PACKAGESPackages to treat as agent code, not infrastructure, when the agent itself is installed under site-packages - their nondeterminism is then captured rather than skipped. Not an import allowlist and not a security control.
BITEXACT_BREAKER_FAILURESConsecutive failed store writes that open the store-write circuit breaker (default 5; [Never in the way](#never-in-the-way)).
BITEXACT_BREAKER_OPEN_SSeconds the open breaker attempts no store write before one trial (default 60).
BITEXACT_CAPTURE_KEYA file holding the capture key (64 hex characters, from bitexact capture keygen), provisioned beside the recorder key and kept out of the data directory and its backups. Hash-only fields are committed under it and a gap's digest of content it did not store is keyed with it; without one, hash-only fields can never be opened and those digests are dropped. A policy's own hash_only_key takes its place.
BITEXACT_CAPTURE_POLICYThe capture policy: a JSON file saying what the evidence keeps of each field - kept, scrubbed by detectors (card numbers, IBANs, credentials by default) or hash-only under a capture key the evidence never holds (see capture-controls.md). Unset, the shipped default scrubs; off keeps every value as captured and every run records that it was off. A policy that cannot be used refuses the process at start.
BITEXACT_CHECKPOINT_EVERYSteps between recorder checkpoints when a signing key is configured, after the one every run gets at its first step (default 100).
BITEXACT_CLI_PREFIXThe command that reaches the CLI on this deployment, when the default for its BITEXACT_DEPLOYMENT is not it (for example docker exec bitexact-viewer bitexact). The viewer's command hints start with it.
BITEXACT_COLLECTORThis host's collector intake (http://127.0.0.1:7378). When set, the SDK and bitexact serve record through the collector to a remote recorder instead of into a local store: events are queued and never waited on, and the agent host needs no store and no license.
BITEXACT_COLLECTOR_HOST_KEYThe file holding bitexact collector's 32-byte host key, which seals its spool segments. Default: <spool>.host.key beside the spool directory, made owner-only on first use; a path inside the spool is refused, so a copy of the spool never carries the key that opens it. A container whose spool is a volume names a writable file on another volume. Not used when BITEXACT_HOST_KEY_KMS_CIPHERTEXT is set.
BITEXACT_COLLECTOR_SECRETThe host secret for BITEXACT_COLLECTOR.
BITEXACT_COLLECTOR_SECRET_FILEA file holding the host secret for BITEXACT_COLLECTOR - the collector writes it as intake.secret in its spool directory. A collector named with no secret turns recording off, never the agent.
BITEXACT_DATABASE_URLPostgreSQL DSN; when set the database is the store and the index (see postgres.md).
BITEXACT_DATA_DIRWhere manifests, sidecars and the run index live (default ./bitexact-data).
BITEXACT_DB_CONNECT_TIMEOUTSeconds to wait for the PostgreSQL pool to open before reporting the database unreachable (default 10).
BITEXACT_DB_LOCK_TIMEOUT_SSeconds a PostgreSQL statement may wait for a lock (default 5): an append never queues for long behind a maintenance lock such as a month's drop.
BITEXACT_DB_STATEMENT_TIMEOUT_SSeconds any one PostgreSQL statement may run before the database cancels it (default 30). A write an agent's call waits on then fails within the bound - a declared gap, never a hung agent; whole-store maintenance (a rebuild, a month's drop) lifts it for its own transaction.
BITEXACT_DEPLOYMENTHow the viewer is deployed - pilot, compose, kubernetes or container, set by the shipped compose files, manifests and image; unset for a pip install. The viewer reports it in /api/env with the command a person types there to reach the CLI, and its license page says where the license file goes.
BITEXACT_DISK_MIN_BYTESFree space below which the proxy's /health/recording answers 503 degraded, naming the shortfall, before the disk fills; /healthz stays 200 for traffic. Recording goes on; a write that then fails is a declared gap.
BITEXACT_DRAIN_TIMEOUTSeconds the recorder waits for in-flight streams to finalize on shutdown, and a Python SDK run waits for the entries its writer thread still holds: at the run's exit, and when the agent reads an entry's handle (default 30). Past it the agent goes on: each entry still unwritten is declared a storage_unavailable gap beside the run, naming the journal bitexact repair folds it from, and the run is left unsealed.
BITEXACT_ENVThe environment label the viewer shows and the sample records (prod, staging, ...).
BITEXACT_EVENTS_CONFIGThe TOML file naming where events are delivered - Standard Webhooks receivers, Splunk HEC, Datadog Logs - with every secret in a file of its own (see events.md). Unset: nothing is enqueued and nothing is sent. A file that does not load turns delivery off and raises events-config-invalid.
BITEXACT_FAILURE_MODEopen (default): a BitExact failure never reaches the agent - the call is forwarded untouched, the response carries x-bitexact-recording: degraded (or off with no valid license), and the run gets a declared capture gap. strict: BitExact's own failures are raised and refused as errors, for development and conformance testing.
BITEXACT_GROUP_PRECEDENCEThe bound groups in the order that decides a principal's role when it is in more than one (comma-separated, most authoritative first). A principal in two or more bound groups the list does not all rank holds no role. A principal in one bound group needs no entry.
BITEXACT_HASH_ALGblake2b-256 (default) or sha256 for FIPS-constrained deployments.
BITEXACT_HEALTH_PORTPort the container's HEALTHCHECK probes /healthz on (default 8787); set it to the inspector's port when running the image as the viewer.
BITEXACT_HEARTBEAT_SSeconds between liveness heartbeats (default 60): the collector's to its recorder, which tells a quiet agent from a dark host, and the row the served proxy and the in-process SDK upsert for each capture path they record through. A collector refuses to start on a value that is not a positive number of seconds; the proxy and the SDK use 60.
BITEXACT_HLC_MAX_SKEW_MSHow far ahead of bitexact recorder's own clock a source's hybrid logical clock may run (default 300000) before the recorder declares a causal_inconsistency gap beside the event.
BITEXACT_HOST_KEYThe file holding this host's 32-byte key, which seals the proxy's stream journals (bitexact capture journal opens them only on the host that sealed them). Default: <data dir>.host.key beside the data directory, made owner-only on first use; a path inside the data directory is refused, so a copy of the store never carries the key that opens it. A container whose data directory is a volume names a writable file outside it.
BITEXACT_HOST_KEY_KMS_CIPHERTEXTA file holding this host's key encrypted by AWS KMS: the CiphertextBlob of aws kms generate-data-key --key-spec AES_256. Set, the host key that seals the collector's spool segments and the proxy's stream journals is that 32-byte data key, decrypted by KMS once per process into memory; no host-key file is read or made, and BITEXACT_HOST_KEY is not used. A ciphertext KMS will not decrypt, a plaintext that is not 32 bytes, or a process without boto3 (pip install 'bitexact-sdk[aws]') is refused at start. Only a host whose IAM role may decrypt it opens what it sealed.
BITEXACT_HOT_MONTHSMonths kept queryable before bitexact archive moves a month to the archive.
BITEXACT_HSM_KEY_LABELLabel of the ed25519 key object inside the HSM.
BITEXACT_HSM_PINPKCS#11 user PIN.
BITEXACT_HSM_PKCS11_LIBPKCS#11 module path for the recorder key.
BITEXACT_HSM_PQ_LABELLabel of the ML-DSA key object inside the HSM: the recorder key's post-quantum half, signed with CKM_ML_DSA.
BITEXACT_HSM_SLOTPKCS#11 slot holding the recorder key.
BITEXACT_IMAGE_DIGESTThe container image digest committed into every run's watermark for attribution: the operator's statement of what is running, never measured and never verified. The pilot sets it from the image ID step 1 resolves; another deployment sets it to the digest it pulled (a Kubernetes pod cannot read its own image digest, so name it in the manifest). Unset, the watermark names none.
BITEXACT_LICENSEPath to the offline license file. Nothing records or replays without one; the proxy passes calls through unrecorded.
BITEXACT_LOG_EVERY_SHow often the recorder service signs the transparency log's head and asks the witnesses to cosign it, when the log has grown (default 60).
BITEXACT_LOG_ORIGINThe transparency log's name in its checkpoints (default bitexact.log/<recorder key id>). Witnesses and auditors name the log by it.
BITEXACT_LOG_WITNESSESComma-separated witness URLs asked to cosign the transparency log's checkpoints (by bitexact log checkpoint and the recorder service). Unset: checkpoints are signed and unwitnessed.
BITEXACT_MAX_BODY_BYTESLargest request or response body, or MCP message, the recording proxy and bitexact mcp-proxy hold to record (default 33554432, 32 MiB; a value that is not a positive whole number uses the default). A body over it is never held: it still reaches the agent whole, as it arrives, and its record is withheld, declared payload_oversize by its hash and length. Strict mode refuses it instead.
BITEXACT_MAX_UPLOAD_BYTESCap on any request body the viewer accepts, including a bundle or report uploaded to its in-app verify (default 64 MiB).
BITEXACT_MODEDefault run mode for the SDK: record or replay.
BITEXACT_OTEL_CONTENTHow content ingested from OpenTelemetry spans by bitexact.otel_span_processor() and bitexact.record_otel_spans() is kept: keep (default) leaves it to the capture policy; hash records a tool call's args and result and a model call's request and response hash-only. Any other value is taken as hash, so a mistyped setting never stores more than was asked. It applies to spans alone, where BITEXACT_CAPTURE_POLICY matches a field by kind and path whichever door recorded it; a processor's or batch's content= argument takes its place (see integrations.md).
BITEXACT_PQ_ALGThe post-quantum parameter set bitexact keygen makes: ML-DSA-65 (default) or ML-DSA-87 (CNSA 2.0).
BITEXACT_PRICING_FILEThe customer's own pricing table, a JSON file: {"currency": "USD", "per_tokens": 1000000, "models": {"<model as the run records it>": {"input": "2.50", "output": "10.00", "aliases": ["gpt-4o"]}}}, with optional cache_read_input, cache_write_input, cache_write_input_5m, cache_write_input_1h, audio_input, audio_output and tool_use_input rates, every rate a decimal string, and no name given twice. A call is priced by the model its provider reported running, else the one it requested; a count with no rate leaves it unpriced, never dropped. Cost is derived from the token counts each provider reported, shown in the inspector and by bitexact cost, never carried in an evidence pack, and never written to a chain. Unset, BitExact reports tokens only: it never supplies prices. A table that cannot be used is refused by the inspector and bitexact cost, never replaced by a default.
BITEXACT_RECORDERThe recorder (HOST:PORT) a keyless inspector or CLI hands its acts to. When set and no recorder key is configured, the process writes no chain itself: holds, releases, redactions, custody records, forks, ledger events, seals, anchors, purge records and log checkpoints are acts the recorder checks, writes and signs, and the license binding and log audit read the key's public halves from it. Its acts are refused when the recorder cannot be reached.
BITEXACT_RECORDER_POLICYJSON file of run-token issuance rules for bitexact recorder: {"rules": [{"peer": glob, "runs": glob, "kinds": [...], "acts": [...]}]}. A collector gets a token only for the kinds a rule gives its certificate identity in that run; acts are the acts a peer may hand the recorder (docs/distributed.md). A deployment act (ledger, log-checkpoint, log-backfill, ledger-heads, access, events-retry) is granted only by a rule whose runs is exactly *, matched as written, never as a glob. With no policy the recorder issues nothing. Read on every request.
BITEXACT_RECORDER_TLS_CAThe CA file a process that names BITEXACT_RECORDER checks the recorder's certificate against.
BITEXACT_RECORDER_TLS_CERTThe client certificate a process that names BITEXACT_RECORDER presents: the identity the recorder's policy grants acts to.
BITEXACT_RECORDER_TLS_KEYThe private key of BITEXACT_RECORDER_TLS_CERT.
BITEXACT_REQUIRE_DUAL_CONTROLSet to 1 to require a second principal to approve every redaction, and to refuse on the command line, where --by is typed and not authenticated, every redaction request or approval and every removal of a critical capture expectation.
BITEXACT_RUN_GRANTSet by the SDK, never by hand: bitexact.mcp.proxy_env() puts the active run's grant (JSON: its id, its loopback endpoint and its secret) in the environment it starts bitexact mcp-proxy with, so the proxy records into that run. Absent, the proxy records only into runs it may write itself.
BITEXACT_SDK_REQUEST_LOGSet to 0 to stop the SDK's in-process recorder printing a JSON line per model call to stderr (default: every call is logged). Failures are always logged; the standalone recorder is unaffected.
BITEXACT_SEAL_GRACE_SHow long bitexact recorder waits, after a run is asked to end, for every source that wrote to it to finish (default 30). Then it seals the run anyway, with a declared source_gap for each source still open.
BITEXACT_SEAL_SECRET_FILEA file holding the secret (at least 32 characters) a client sends as Authorization: Bearer <secret> to end a run through the proxy's POST /bitexact/runs/<run-id>/seal. Unset, the route refuses every seal; set, it still seals only runs the proxy recorded. Read on every request, so replacing the file rotates the secret.
BITEXACT_SIGNING_KEYPath to the recorder's ed25519 private key; with it every run is checkpointed and signed.
BITEXACT_SIGNING_KMS_CIPHERTEXTPath to the AWS KMS-encrypted recorder key envelope (see recorder-key-custody.md).
BITEXACT_SIGNING_KMS_PQ_CIPHERTEXTPath to the AWS KMS-encrypted post-quantum half of the recorder key.
BITEXACT_SIGNING_PKCS11Set to 1 to sign with a PKCS#11 HSM key instead of a file (see recorder-key-custody.md).
BITEXACT_SIGNING_PQ_KEYPath to the recorder key's post-quantum (ML-DSA) half, when it is not <name>.pq.key beside BITEXACT_SIGNING_KEY (see recorder-key-custody.md).
BITEXACT_SSO_AUDIENCEThe client id the recorder requires in an ID token's aud (with several audiences, also its azp).
BITEXACT_SSO_ID_TOKEN_FILEA file holding the operator's ID token, for a command line that names BITEXACT_RECORDER: every act it sends carries it, so a recorder that verifies who acts (BITEXACT_SSO_ISSUER) accepts its acts when --by is the principal the token proves. A file named and unreadable sends no act.
BITEXACT_SSO_ID_TOKEN_HEADERThe request header an inspector in the SSO proxy mode (BITEXACT_TRUST_PROXY_AUTH) reads the signed-in person's ID token from, to hand the recorder with each act (default Authorization, as oauth2-proxy's --pass-authorization-header sends it; a Bearer scheme is taken off). Read only from a trusted proxy peer; sent twice, it is not read.
BITEXACT_SSO_ISSUERSet on the recorder to have it verify who acts: the OpenID Connect issuer, exactly as its ID tokens' iss. Every act that names a person - a hold, a release, a redaction request, approval or rejection, a custody or access record, a dead-letter retry, a ledger event - must then carry that person's ID token, which the recorder verifies itself (signature against the issuer's keys, issuer, audience, expiry and not-before); an act whose by is not the principal the token proves, or that carries none, is refused. The records it writes carry a verified_identity (issuer, subject, claim, principal), never the token; two-person control compares verified people; a role grant needs no stated posture. Needs BITEXACT_SSO_AUDIENCE and one of BITEXACT_SSO_JWKS_FILE / BITEXACT_SSO_JWKS_URL; the recorder refuses to start with any of them set without the rest. Unset, principals are the inspector's statement, recorded as asserted.
BITEXACT_SSO_JWKS_CACHE_SSeconds the recorder uses keys fetched from BITEXACT_SSO_JWKS_URL before fetching them again (default 900).
BITEXACT_SSO_JWKS_FILEA pinned JWKS file holding the issuer's signing keys, for an offline or air-gapped recorder. Read again when it changes, so rotating keys is replacing the file. Exclusive with BITEXACT_SSO_JWKS_URL.
BITEXACT_SSO_JWKS_REFRESH_SThe least seconds between two fetches of BITEXACT_SSO_JWKS_URL for a key id the set does not hold (default 30), so a rotation is picked up at once and unknown key ids cannot make the recorder hammer the issuer.
BITEXACT_SSO_JWKS_URLThe issuer's JWKS endpoint (https://; plain http:// only on loopback). Fetched keys are used for BITEXACT_SSO_JWKS_CACHE_S; a token naming an unknown key id fetches the set again, at most once per BITEXACT_SSO_JWKS_REFRESH_S, and each fetch replaces the whole set, so a withdrawn key is withdrawn. Keys past the cache period that cannot be fetched again are not used.
BITEXACT_SSO_LEEWAY_SSeconds of clock skew the recorder allows on an ID token's exp, nbf and iat (default 60, at most 300).
BITEXACT_SSO_PRINCIPAL_CLAIMThe ID-token claim the recorder takes as the principal: sub (default) or email, which must then be one the issuer verified (email_verified: true). Two acts are one person when they carry one issuer's subject, whatever this claim says.
BITEXACT_TRUSTED_PROXY_IPSPeers whose forwarded identity headers are believed (default loopback; * for any).
BITEXACT_TRUST_PROXY_AUTHSet to 1 to take identity from the reverse proxy's X-Forwarded-* headers.
BITEXACT_TRUST_PROXY_GROUPSRead X-Forwarded-Groups and resolve the role from a bound group, for a proxy that forwards membership rather than a role. Off by default: the header is one many deployments already emit for other consumers, and reading it without an opt-in would silently change what an existing proxy grants. With it on, a request carrying the header twice is refused; with it off the header is not read at all.
BITEXACT_UPSTREAMOpenAI-compatible upstream the recorder forwards to (default https://api.openai.com).
BITEXACT_UPSTREAM_ANTHROPICAnthropic upstream the recorder forwards to (default https://api.anthropic.com).
BITEXACT_UPSTREAM_AZUREAzure OpenAI resource for openai/deployments/... calls, e.g. https://<resource>.openai.azure.com. No default: unset, such a call is refused (502) rather than sent to another provider with its key.
BITEXACT_UPSTREAM_BEDROCKAmazon Bedrock runtime for model/<id>/... calls, e.g. https://bedrock-runtime.us-east-1.amazonaws.com, authenticated with a Bedrock API key. No default: unset, such a call is refused (502).
BITEXACT_UPSTREAM_GEMINIGoogle Gemini API upstream for v1beta/... and models/<m>:<method> calls (default https://generativelanguage.googleapis.com).
BITEXACT_UPSTREAM_VERTEXVertex AI upstream for v1/projects/... calls (default https://aiplatform.googleapis.com; set a regional host such as https://us-central1-aiplatform.googleapis.com).
BITEXACT_VAPI_HMAC_SECRET_FILEA file holding the HMAC secret (at least 32 characters) of a Vapi HMAC credential in its default form: x-signature is the hex HMAC-SHA256 of <x-timestamp>.<body>. bitexact.vapi.route() accepts a request that passes this or BITEXACT_VAPI_SECRET_FILE. Read on every request.
BITEXACT_VAPI_SECRET_FILEA file holding the shared secret (at least 32 characters) Vapi sends to bitexact.vapi.route() as Authorization: Bearer <secret> or X-Vapi-Secret: <secret>. With neither this nor BITEXACT_VAPI_HMAC_SECRET_FILE set the route answers 503 and records nothing. Read on every request, so replacing the file rotates the secret.
BITEXACT_VIEWER_COOKIE_SECURESet to 1 to mark the session cookie Secure even when the inspector cannot see that the browser used HTTPS. Without it the cookie is Secure when the request arrived over HTTPS directly, or through a trusted proxy that sends X-Forwarded-Proto: https.
BITEXACT_VIEWER_REPORT_INLINE_RUNSLargest number of runs the viewer's whole-store reports (overview, health, coverage, retention, trust, a workflow's matrix) cover per request (default 2000). Past it a report is computed in the background, served with the instant it was computed (X-BitExact-As-Of), and answered 202 until the first computation finishes.
BITEXACT_VIEWER_SESSION_HOURSHow long a browser sign-in with a viewer token lasts (default 8, at most 168). The session cookie is signed with the token, so removing a token from the configuration also ends every session it opened.
BITEXACT_VIEWER_TOKENSComma-separated token:role or token:name:role entries giving each token a role. A named token's principal is token:<name>, so two people with named tokens are two principals and can complete a two-person redaction; an unnamed token's principal is token:<role>. The separator is a colon, a name is letters, digits and ._@+-, and a value this cannot parse, or one set but empty, fails closed, refusing every request.
LANGCHAIN_PROJECTOlder name of LANGSMITH_PROJECT, honored the same way.
LANGSMITH_ENDPOINTLangSmith endpoint recorded as a cross-reference by the LangChain adapter.
LANGSMITH_PROJECTLangSmith project recorded as a cross-reference by the LangChain adapter.

CLI reference

Every command. License names the entitlement a command requires — a license that does not grant it refuses the command. Commands marked — need no entitlement; the ones that need no license at all are listed under Licensing above, and say so below.

CommandLicensePurpose
bitexact serve—Run the recording proxy (TLS/mTLS via --tls-cert/--tls-key/--tls-client-ca; /healthz, /version, /metricsz reserved).
bitexact pki—Create a certificate authority (init) and issue the recorder's and each agent host's certificates (issue, with --dns, --ip or --spiffe) for distributed recording. Answers without a license.
bitexact recorder—Run the recorder service that host collectors send to: mutual TLS required, every write authorized by a run token issued under BITEXACT_RECORDER_POLICY (protocol).
bitexact collector—Run a host's collector: a loopback intake any language can post events to, an encrypted spool, and forwarding to the recorder over mutual TLS. Never waits on the recorder.
bitexact mcp-proxy—Relay an MCP server and record every tools/call from the JSON-RPC bytes that cross it: on stdio, the server's command after -- with --run-id; over Streamable HTTP, --listen and --upstream, each request naming its run in X-BitExact-Run-Id; without either, each call names its run in params._meta (integrations). With --replay RUN it answers each tools/call from that recorded run instead, under a license that grants replay.
bitexact loganchorThe transparency log of every run head the recorder signed: sign and witness its head (checkpoint), name runs the store lost or checkpoints the log lacks (audit, backfill), list a run's heads (heads), prove it extends a checkpoint you kept (consistency) (transparency log).
bitexact witness—Run a witness: cosign a log's checkpoint only once it is proven to extend the last one cosigned. Run by an independent party. Answers without a license.
bitexact capture—The capture policy: what the evidence keeps of each field - kept, scrubbed or hash-only under a capture key (policy show, policy check, policy digest, test, keygen); test a value against a hash-only field or a gap's withheld content with the key, recorded on the custody chain (open, open-withheld); open a sealed stream journal (journal) (capture controls).
bitexact list—List recorded runs.
bitexact show—Show a run's verified steps.
bitexact verify—Verify a run's chain, optionally with its whole fork ancestry (--provenance).
bitexact verify-report—Verify a signed regress-suite or qualification report.
bitexact verify-receipts—Verify attestation receipts and their chain against a pinned key.
bitexact seal—Close a proxy-recorded run with a run_end seal.
bitexact repair—Quarantine a torn manifest tail after a crash; refuses to erase evidence of truncation.
bitexact reindex—Rebuild index.db from manifests.
bitexact seed—Record a real, offline sample run so a fresh install is not empty.
bitexact quickstart—Print a copy-paste first-run on-ramp.
bitexact view—Run browser and inspector on loopback.
bitexact health—Store health for ops: verify / seal / anchor / recording failures and orphan journals.
bitexact lint—Flag nondeterminism replay cannot capture.
bitexact strict—Structural completeness checks for a run (a CI gate).
bitexact keygen—Generate a hybrid signing key: Ed25519 and its post-quantum (ML-DSA) half. Answers without a license: the license is issued for the key it makes.
bitexact recorder-key—Print the public half of the configured recorder key, whichever backend holds it. Answers without a license, so an HSM or KMS key's public half can be sent for one.
bitexact roles—Define custom roles over the permission vocabulary and bind your IdP’s groups to them. Every change is signed with the recorder key and chained.
bitexact license—Show offline license status. Answers without a license.
bitexact version—Print the installed version. Answers without a license.
bitexact qualify—Measure a model config's determinism.
bitexact redact—Redact committed fields in the stored manifest, on the record.
bitexact redaction—Dual-control (two-person) redaction lifecycle: request, approve, reject.
bitexact hold—Place a legal hold so purge refuses a run.
bitexact release-hold—Release a legal hold, making a run purgeable.
bitexact exportevidenceExport a verified (optionally signed) bundle.
bitexact evidence-packevidenceAssemble a workpaper-shaped, independently-verifiable evidence pack.
bitexact receiptsevidenceExport a verified run's model calls as signed attestation receipts (IETF draft format).
bitexact provenance-bundleevidencePackage a run and its descendant forks/matrix as a verifiable BagIt bag.
bitexact caseevidenceAn investigation case: pin runs, record notes, assignees and findings.
bitexact coverageevidencereport: capture coverage over a corpus, recorded surfaces and capture-health signals, and with --expected every execution that left no evidence. expect: the declared capture sources on a signed, hash-chained ledger (add, remove, list, import, verify).
bitexact forksforkPrint the fork provenance tree.
bitexact fork-matrixforkN live forks from a step; aggregate outcomes.
bitexact driftforkReport composition drift for a workflow (a CI pre-flight).
bitexact regressregressRegress runs against changed code; nonzero exit on proven divergence.
bitexact suiteregressRegression suites: save a corpus and recipe, run it as a CI gate.
bitexact workflowgovernanceGovernance acts on a workflow's tamper-evident ledger.
bitexact retentionlifecyclePer-workflow retention report: committed deadlines and what is due.
bitexact purgelifecycleArchive or delete runs older than a cutoff; evidence never disappears implicitly.
bitexact archivelifecycleExport runs past the hot window to the archive and drop them.
bitexact db-transferlifecycleCopy this install's evidence into a PostgreSQL database.
bitexact anchoranchorExternally anchor a run's head (WORM / RFC 3161).
bitexact otel-exportotelExport a verified run as OTel GenAI spans (OTLP/JSON).
bitexact eventsotelEvent delivery: check the configuration, the outbox and its dead letters, retry and test delivery, write Standard Webhooks secrets, and check a delivery as a receiver would (event delivery).

Evidence is portable and standards-shaped: hashes are computed over canonical JSON (aer-jcs, a restriction of RFC 8785 to values that canonicalize identically in any language), signatures carry key ids and signed timestamps with trust-file rotation, exports can be wrapped as in-toto/DSSE envelopes for cosign-style tooling, and BITEXACT_HASH_ALG=sha256 selects SHA-256 chain hashes and HMAC-SHA256 commitments for FIPS-constrained environments (key ids remain BLAKE2b-derived labels; they identify keys and carry no security weight). Key procedures: rotation runbook.

Recording never stands between an agent and its provider. When the recorder cannot write, the call is delivered, its response is marked x-bitexact-recording: degraded, and the run records a declared capture gap naming the cause, which bitexact verify reports (exit 3) rather than passing the run as complete. With BITEXACT_FAILURE_MODE=strict recording is fail-closed instead: non-streaming calls return an error rather than an unrecorded response, and a streamed response, passed through a write-ahead journal, is truncated rather than delivered unrecorded. Every read cross-checks a per-run head record, catching crash truncation, and when BITEXACT_SIGNING_KEY names a key file, signed, hash-linked checkpoints attest the chain head every BITEXACT_CHECKPOINT_EVERY steps. The precise guarantee: the local store detects in-place tampering; anti-rollback against an actor with data-dir write access comes from holding an exported signed bundle — whose signature pins the head and the full checkpoint set — or an externally anchored head hash. One writer per run, enforced with an OS-level lock; the data dir must be a local filesystem.

Commercial terms: pricing. See also the compliance mapping and the replay taxonomy.