Skip to content
Tenbri pangolinTenbri

Install

Tenbri ships one version everywhere. Pick what you need:

You want to Install
Record and verify from a shell, a script or CI the tenbri CLI
Let agents in any language write over HTTP or gRPC tenbrid
Record from a Python agent the tenbri Python package
Record from a Node or TypeScript agent the tenbri npm package
Record from a Rust program the Rust crates
Verify a stream in the browser @tenbri/wasm
Record a Claude, OpenAI or LangGraph agent with one line an adapter

The Python and Node packages and the CLI write the same streams with the same Rust core: a stream written from Python verifies with tenbri verify, and the other way round.

The CLI is the tenbri binary: init, append, verify, query, tail, checkpoint, receipt, keys, erase, index and schema check.

Needs Rust 1.85 or later. The crate is tenbri-cli; the binary it installs is tenbri. Pre-releases only install when you name the version:

Terminal window
cargo install tenbri-cli --version 0.1.0-beta.1

Check the install:

Terminal window
tenbri --version
tenbri 0.1.0-beta.1

The CLI turns on two features by default:

Feature What it adds
keychain tenbri init --keychain: keep secret keys in the OS keychain instead of key files. macOS Keychain and Windows Credential Manager only; on other systems the keychain backend fails and you use key files.
s3 Streams in object stores: s3://bucket/<root>/<stream> (AWS S3, MinIO) and r2://bucket/<root>/<stream> (Cloudflare R2).

To build without the keychain:

Terminal window
cargo install tenbri-cli --version 0.1.0-beta.1 --no-default-features --features s3

Object stores take their credentials from the environment, as the providers’ own tools read them: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION and the other AWS_* variables; AWS_ENDPOINT_URL for MinIO and other S3-compatible stores (with AWS_ALLOW_HTTP=true for plain HTTP); CLOUDFLARE_ACCOUNT_ID (or AWS_ENDPOINT_URL) for R2. The Python and Node packages read the same variables. See Streams on object storage.

tenbrid serves the REST and gRPC API, so agents in any language can write to a stream. The release binaries and the container above include it. With cargo, it comes from the tenbri-server crate:

Terminal window
cargo install tenbri-server --version 0.1.0-beta.1

From a checkout, cargo build -p tenbri-server --release builds target/release/tenbrid.

It reads a TOML configuration file: --config <FILE>, else $TENBRI_CONFIG, else tenbrid.toml in the working directory. The file must exist. A minimal one:

tenbrid.toml
keys = "keys" # default: ~/.config/tenbri/keys
[streams.support] # the stream's name in URLs (its tnb1: id works too)
path = "audit/support" # or s3://bucket/prefix, r2://bucket/prefix
key = "sha256:..." # pin; default: the stream key held in `keys`
[[tokens]] # make one with `tenbrid token`
sha256 = "sha256:..." # SHA-256 of the bearer token; the token is never stored
principal = { kind = "agent", id = "support-assistant" }

Relative paths are relative to the file’s directory. These settings have defaults and an environment variable that overrides the file:

Setting Default Environment
listen (REST) 127.0.0.1:7070 TENBRI_LISTEN
grpc_listen 127.0.0.1:7071 TENBRI_GRPC_LISTEN
keys ~/.config/tenbri/keys TENBRI_KEYS
schema (action schemas) none: nothing is checked TENBRI_SCHEMA
queue (appends waiting per stream) 10000 TENBRI_QUEUE
deadline_ms (how long an append waits for the queue) 2000 TENBRI_DEADLINE_MS

Make a bearer token, then start the server:

Terminal window
tenbrid token
tenbrid --config tenbrid.toml

tenbrid token prints the token once (it is not stored) and the [[tokens]] entry that configures it by its hash. tenbrid serves until SIGINT or SIGTERM, then stops accepting requests and waits for the writes in flight. It exits 0 after a clean shutdown and 2 on a configuration or startup error. Walk through it in tenbrid over REST; the routes are in the API reference.

The tenbri package is an abi3 wheel for Python 3.10 and later, on Linux (manylinux and musllinux, x86_64 and aarch64), macOS and Windows. Nothing to compile.

Terminal window
pip install --pre tenbri

Check it:

Terminal window
python -c "import tenbri; print(tenbri.__version__)"
0.1.0-beta.1

Tenbri blocks (it releases the GIL); AsyncTenbri has the same methods as coroutines for asyncio. Start with the quickstart or Write from code.

The tenbri package is a native addon (napi-rs) for Node 20 and later on Linux (glibc and musl), macOS and Windows, with TypeScript types. npm installs the prebuilt binary for your platform; nothing to compile.

Terminal window
npm install tenbri@next

Check it:

Terminal window
node -e "console.log(require('tenbri').version)"
0.1.0-beta.1

tenbri-writer writes and verifies streams; tenbri-core holds the entry types, tenbri-keys the keys and tenbri-store local and object storage:

Terminal window
cargo add tenbri-core@0.1.0-beta.1 tenbri-keys@0.1.0-beta.1 tenbri-store@0.1.0-beta.1 tenbri-writer@0.1.0-beta.1
cargo add tokio --features macros,rt

Add --features s3 to tenbri-store for s3:// and r2:// streams. The tenbri facade crate is a placeholder that will re-export these; depend on the crates above for now. See Write from code.

@tenbri/wasm is the offline verifier compiled to WebAssembly, about 150 KB gzipped. It checks an export of a stream against a pinned key, in the browser or in Node. It cannot write.

Terminal window
npm install @tenbri/wasm@next

An export is the stream’s batch files concatenated in order. In Node:

Terminal window
cat audit/support/batches/*.jsonl > support.jsonl
verify.mjs
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { initSync, verifyExport } from '@tenbri/wasm'
const require = createRequire(import.meta.url)
initSync({ module: readFileSync(require.resolve('@tenbri/wasm/pkg/tenbri_wasm_bg.wasm')) })
console.log(verifyExport(readFileSync('support.jsonl', 'utf8'), process.argv[2]))
Terminal window
node verify.mjs sha256:13410a435d5a70cc7f96ae05209410bb37cd301124e0719229b3184530789b61
{
broken: null,
checkpoint: null,
entries: 4,
ok: true,
pinnedKey: 'sha256:13410a435d5a70cc7f96ae05209410bb37cd301124e0719229b3184530789b61',
stream: 'tnb1:13410a435d5a70cc7f96ae05209410bb'
}

In the browser, await init() fetches tenbri_wasm_bg.wasm next to the module, then verifyExport(jsonl, key) works the same way.

An adapter records an agent framework’s tool calls, approvals and handoffs as Tenbri decisions, with one line at agent setup. Each installs the matching tenbri package with it.

Framework Python npm
Claude Agent SDK pip install --pre tenbri-claude-agent-sdk npm install @tenbri/claude-agent-sdk tenbri@next @anthropic-ai/claude-agent-sdk
OpenAI Agents SDK pip install --pre tenbri-openai-agents npm install @tenbri/openai-agents@next tenbri@next
LangGraph and LangChain pip install --pre tenbri-langgraph npm install tenbri@next @tenbri/langgraph@next

With uv, poetry, pnpm, yarn or bun, use the same names with the pre-release flags shown above.

All adapters: Adapters.

Every writer (the CLI, tenbrid, the Python and Node packages) keeps signing keys in a keys directory, chosen in this order:

  1. --keys <DIR> (a global CLI flag), or keys= / keys: when you open a stream from code;
  2. $TENBRI_KEYS;
  3. $XDG_CONFIG_HOME/tenbri/keys, else ~/.config/tenbri/keys (%APPDATA%\tenbri\keys on Windows).

Each stream gets its own subdirectory, named after the stream id with : written as - (tnb1-5db31441baa2d080326d9f6c83c95319). Directories are created mode 0700 and key files 0600 on Unix. Without the stream’s keys, a stream opens read-only: verify and query work, writes fail.

The keys directory and a local stream directory must not contain each other: tenbri init refuses that layout. Whoever can read or copy the log must not be able to sign as it. See Keys.

Next: the quickstart.