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.
| Variable | Meaning |
|---|---|
BITEXACT_ACTOR | This agent's stable name, sent with every event through the collector; replay serves each actor its own calls. |
BITEXACT_AGENT_ENTRIES | Named entrypoints (name=module:function,...) a suite can compare. |
BITEXACT_AGENT_ENTRY | module:function the viewer forks, sweeps and regresses with. |
BITEXACT_AGENT_PACKAGES | Packages 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_FAILURES | Consecutive failed store writes that open the store-write circuit breaker (default 5; [Never in the way](#never-in-the-way)). |
BITEXACT_BREAKER_OPEN_S | Seconds the open breaker attempts no store write before one trial (default 60). |
BITEXACT_CAPTURE_KEY | A 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_POLICY | The 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_EVERY | Steps between recorder checkpoints when a signing key is configured, after the one every run gets at its first step (default 100). |
BITEXACT_CLI_PREFIX | The 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_COLLECTOR | This 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_KEY | The 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_SECRET | The host secret for BITEXACT_COLLECTOR. |
BITEXACT_COLLECTOR_SECRET_FILE | A 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_URL | PostgreSQL DSN; when set the database is the store and the index (see postgres.md). |
BITEXACT_DATA_DIR | Where manifests, sidecars and the run index live (default ./bitexact-data). |
BITEXACT_DB_CONNECT_TIMEOUT | Seconds to wait for the PostgreSQL pool to open before reporting the database unreachable (default 10). |
BITEXACT_DB_LOCK_TIMEOUT_S | Seconds 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_S | Seconds 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_DEPLOYMENT | How 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_BYTES | Free 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_TIMEOUT | Seconds 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_ENV | The environment label the viewer shows and the sample records (prod, staging, ...). |
BITEXACT_EVENTS_CONFIG | The 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_MODE | open (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_PRECEDENCE | The 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_ALG | blake2b-256 (default) or sha256 for FIPS-constrained deployments. |
BITEXACT_HEALTH_PORT | Port 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_S | Seconds 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_MS | How 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_KEY | The 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_CIPHERTEXT | A 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_MONTHS | Months kept queryable before bitexact archive moves a month to the archive. |
BITEXACT_HSM_KEY_LABEL | Label of the ed25519 key object inside the HSM. |
BITEXACT_HSM_PIN | PKCS#11 user PIN. |
BITEXACT_HSM_PKCS11_LIB | PKCS#11 module path for the recorder key. |
BITEXACT_HSM_PQ_LABEL | Label of the ML-DSA key object inside the HSM: the recorder key's post-quantum half, signed with CKM_ML_DSA. |
BITEXACT_HSM_SLOT | PKCS#11 slot holding the recorder key. |
BITEXACT_IMAGE_DIGEST | The 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_LICENSE | Path to the offline license file. Nothing records or replays without one; the proxy passes calls through unrecorded. |
BITEXACT_LOG_EVERY_S | How 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_ORIGIN | The transparency log's name in its checkpoints (default bitexact.log/<recorder key id>). Witnesses and auditors name the log by it. |
BITEXACT_LOG_WITNESSES | Comma-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_BYTES | Largest 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_BYTES | Cap on any request body the viewer accepts, including a bundle or report uploaded to its in-app verify (default 64 MiB). |
BITEXACT_MODE | Default run mode for the SDK: record or replay. |
BITEXACT_OTEL_CONTENT | How 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_ALG | The post-quantum parameter set bitexact keygen makes: ML-DSA-65 (default) or ML-DSA-87 (CNSA 2.0). |
BITEXACT_PRICING_FILE | The 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_RECORDER | The 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_POLICY | JSON 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_CA | The CA file a process that names BITEXACT_RECORDER checks the recorder's certificate against. |
BITEXACT_RECORDER_TLS_CERT | The client certificate a process that names BITEXACT_RECORDER presents: the identity the recorder's policy grants acts to. |
BITEXACT_RECORDER_TLS_KEY | The private key of BITEXACT_RECORDER_TLS_CERT. |
BITEXACT_REQUIRE_DUAL_CONTROL | Set 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_GRANT | Set 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_LOG | Set 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_S | How 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_FILE | A 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_KEY | Path to the recorder's ed25519 private key; with it every run is checkpointed and signed. |
BITEXACT_SIGNING_KMS_CIPHERTEXT | Path to the AWS KMS-encrypted recorder key envelope (see recorder-key-custody.md). |
BITEXACT_SIGNING_KMS_PQ_CIPHERTEXT | Path to the AWS KMS-encrypted post-quantum half of the recorder key. |
BITEXACT_SIGNING_PKCS11 | Set to 1 to sign with a PKCS#11 HSM key instead of a file (see recorder-key-custody.md). |
BITEXACT_SIGNING_PQ_KEY | Path 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_AUDIENCE | The client id the recorder requires in an ID token's aud (with several audiences, also its azp). |
BITEXACT_SSO_ID_TOKEN_FILE | A 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_HEADER | The 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_ISSUER | Set 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_S | Seconds the recorder uses keys fetched from BITEXACT_SSO_JWKS_URL before fetching them again (default 900). |
BITEXACT_SSO_JWKS_FILE | A 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_S | The 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_URL | The 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_S | Seconds of clock skew the recorder allows on an ID token's exp, nbf and iat (default 60, at most 300). |
BITEXACT_SSO_PRINCIPAL_CLAIM | The 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_IPS | Peers whose forwarded identity headers are believed (default loopback; * for any). |
BITEXACT_TRUST_PROXY_AUTH | Set to 1 to take identity from the reverse proxy's X-Forwarded-* headers. |
BITEXACT_TRUST_PROXY_GROUPS | Read 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_UPSTREAM | OpenAI-compatible upstream the recorder forwards to (default https://api.openai.com). |
BITEXACT_UPSTREAM_ANTHROPIC | Anthropic upstream the recorder forwards to (default https://api.anthropic.com). |
BITEXACT_UPSTREAM_AZURE | Azure 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_BEDROCK | Amazon 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_GEMINI | Google Gemini API upstream for v1beta/... and models/<m>:<method> calls (default https://generativelanguage.googleapis.com). |
BITEXACT_UPSTREAM_VERTEX | Vertex 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_FILE | A 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_FILE | A 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_SECURE | Set 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_RUNS | Largest 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_HOURS | How 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_TOKENS | Comma-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_PROJECT | Older name of LANGSMITH_PROJECT, honored the same way. |
LANGSMITH_ENDPOINT | LangSmith endpoint recorded as a cross-reference by the LangChain adapter. |
LANGSMITH_PROJECT | LangSmith 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.
| Command | License | Purpose |
|---|---|---|
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 log | anchor | The 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 export | evidence | Export a verified (optionally signed) bundle. |
bitexact evidence-pack | evidence | Assemble a workpaper-shaped, independently-verifiable evidence pack. |
bitexact receipts | evidence | Export a verified run's model calls as signed attestation receipts (IETF draft format). |
bitexact provenance-bundle | evidence | Package a run and its descendant forks/matrix as a verifiable BagIt bag. |
bitexact case | evidence | An investigation case: pin runs, record notes, assignees and findings. |
bitexact coverage | evidence | report: 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 forks | fork | Print the fork provenance tree. |
bitexact fork-matrix | fork | N live forks from a step; aggregate outcomes. |
bitexact drift | fork | Report composition drift for a workflow (a CI pre-flight). |
bitexact regress | regress | Regress runs against changed code; nonzero exit on proven divergence. |
bitexact suite | regress | Regression suites: save a corpus and recipe, run it as a CI gate. |
bitexact workflow | governance | Governance acts on a workflow's tamper-evident ledger. |
bitexact retention | lifecycle | Per-workflow retention report: committed deadlines and what is due. |
bitexact purge | lifecycle | Archive or delete runs older than a cutoff; evidence never disappears implicitly. |
bitexact archive | lifecycle | Export runs past the hot window to the archive and drop them. |
bitexact db-transfer | lifecycle | Copy this install's evidence into a PostgreSQL database. |
bitexact anchor | anchor | Externally anchor a run's head (WORM / RFC 3161). |
bitexact otel-export | otel | Export a verified run as OTel GenAI spans (OTLP/JSON). |
bitexact events | otel | Event 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.