Skip to content
Tenbri pangolinTenbri

Planned adapters

Three adapters ship today: Claude Agent SDK, OpenAI Agents SDK and LangGraph and LangChain. The Tenbri README plans these next for v0.1.0:

Adapter Planned as
Pydantic AI Experimental
Vercel AI SDK Experimental
Mastra Experimental
Google ADK Experimental
AWS Strands Experimental
Microsoft Agent Framework Experimental
CrewAI Experimental
LlamaIndex Experimental
A2A Experimental

Experimental means documented and tested, but free to change in a 0.x minor release. The three shipped adapters are Stable: covered by the format promise and the 0.x API policy.

An adapter only automates what you can do yourself. Find your tool functions, the ones the agent calls, and record each call as a decision: @tenbri.decision in Python, decision({...}, fn) in TypeScript. Each call then writes:

  1. Before the function runs, an intent: principal is the agent, action.type the action, action.target what it acts on. If the intent cannot be written, the function does not run.
  2. After it runs, the outcome: ok, refused when it raises tenbri.Refused (throws Refused), error for any other exception (which propagates), unknown when cancelled (an AbortError in TypeScript).

Here is a refund tool, issue_refund, recorded that way. tenbri.init() opens the stream, as for the adapters (from TENBRI_STREAM and TENBRI_AGENT when you pass nothing), and tenbri.agent() (agent()) says who acts for a block of code:

wrap.py
import tenbri
tenbri.init("./audit", agent="agent:support") # or TENBRI_STREAM and TENBRI_AGENT
POLICY = "Refunds up to 500 EUR are automatic."
@tenbri.decision(
"issue_refund",
target="stripe:{charge}", # a template of the arguments
policy="policy:refund@14", # the rule that allowed it
inputs=[("policy:refund", "14", POLICY)], # what it relied on, by keyed digest
)
def issue_refund(charge: str, amount: int) -> str:
if amount > 500:
raise tenbri.Refused("over the refund ceiling") # recorded as refused
return f"refunded {amount} EUR of {charge}" # your tool
print(issue_refund("ch_123", 30))
with tenbri.agent("agent:billing"): # who acts, for this block
try:
issue_refund("ch_456", 900)
except tenbri.Refused as e:
print("refused:", e)
log = tenbri.default()
for m in log.query(action="issue_refund"):
e = m.entry
print(e["seq"], e["phase"], e.get("status", ""), e["principal"]["id"], e["action"]["target"])
print("verified:", log.verify(log.fingerprint).ok)
Terminal window
python wrap.py
refunded 30 EUR of ch_123
refused: over the refund ceiling
3 intent support stripe:ch_123
4 outcome ok support stripe:ch_123
5 intent billing stripe:ch_456
6 outcome refused billing stripe:ch_456
verified: True

The decorator works the same on async def functions. target and subject are templates of the function’s arguments ("stripe:{charge}", "{order.id}") or functions of them; inputs can be a function of the arguments too. Who acts is principal=, else the innermost with tenbri.agent(...), else init(agent=...) or TENBRI_AGENT: with none, the call raises InvalidRequestError: no principal.

The first intent, as stored (signatures and hashes shortened). The policy’s text is pinned by its keyed digest, keyed by the stream’s local key store, so no secret goes in your code:

{"action":{"target":"stripe:ch_123","type":"issue_refund"},"durability":"local","id":"01M5D1B9FZC4VQKEK17YG8N8SB","inputs":[{"digest":"hmac-sha256:ec9ac64d…","ref":"policy:refund","version":"14"}],"kind":"decision","phase":"intent","prev":"sha256:ab5571a7…","principal":{"id":"support","kind":"agent"},"rule":{"ref":"policy:refund","result":"allow","version":"14"},"seq":3,"sig":"ed25519:d4e3c10e…","signer":"sess:2026-10-20:acfd88f7edf2","source":"sdk","stream":"tnb1:9ad7a1625fbfd405ed5ae0addf924507","ts":"2026-10-20T16:12:10.627Z","v":1}

If a person approved the call, write an approval entry first and name it from the decision, as the adapters do. For full control (several streams, your own drafts), the stream itself has intent, outcome and append: see the bindings’ READMEs.

Without a binding, write the same intent and outcome with the CLI, or through tenbrid over REST:

For shell-driven agents and scripts. tenbri append reads a draft entry on stdin and prints it as stored; the script uses jq to read the intent’s id. A draft for the CLI carries every required field, source included.

refund.sh
#!/bin/sh
# The tool call, wrapped by hand: intent before, outcome after.
set -e
export TENBRI_KEYS=./keys
[ -d ./audit ] || tenbri init ./audit
intent=$(tenbri append ./audit <<JSON
{"kind":"decision","phase":"intent","source":"sdk","principal":{"kind":"agent","id":"support"},
"action":{"type":"issue_refund","target":"stripe:ch_123"}}
JSON
)
id=$(printf '%s' "$intent" | jq -r .id)
# Run the tool only once its intent is durable.
if ./issue-refund ch_123; then status=ok; else status=error; fi
tenbri append ./audit <<JSON
{"kind":"decision","phase":"outcome","source":"sdk","intent":"$id","status":"$status",
"principal":{"kind":"agent","id":"support"},"action":{"type":"issue_refund","target":"stripe:ch_123"}}
JSON
Terminal window
./refund.sh
created stream tnb1:6ea0f69406b84005a960755dd3cfd8e8 at ./audit
stream key fingerprint (pin it): sha256:6ea0f69406b84005a960755dd3cfd8e8e2c8352d0a4de17430173e60bdcea2b4
keys: ./keys/tnb1-6ea0f69406b84005a960755dd3cfd8e8
verify with: tenbri verify ./audit --key sha256:6ea0f69406b84005a960755dd3cfd8e8e2c8352d0a4de17430173e60bdcea2b4
refunded ch_123
{"action":{"target":"stripe:ch_123","type":"issue_refund"},"id":"01M5D1BJNTJZWCDWZ0E8WS5QHV","intent":"01M5D1BJBSKW9AH2H7S8S8D9FK","kind":"decision","phase":"outcome","prev":"sha256:4d38ec8d…","principal":{"id":"support","kind":"agent"},"seq":4,"sig":"ed25519:298c779b…","signer":"sess:2026-10-20:8439598040cf","source":"sdk","status":"ok","stream":"tnb1:6ea0f69406b84005a960755dd3cfd8e8","ts":"2026-10-20T16:12:20.026Z","v":1}

./issue-refund stands for your tool. A draft the writer refuses prints tenbri: entry rejected: <code>: <detail> and exits 2, so set -e stops before the tool runs: a draft without source, for example, is rejected with invalid_field.

Then read the stream back with tenbri tail, tenbri verify --key, or the bindings’ query and verify, as on any adapter page. See Streams and entries for the fields and the entry format for the exact draft shape.