{"slug": "show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite", "title": "Show HN: Tenbri, a log AI agents can’t secretly rewrite", "summary": "Tenbri launched as an open-source Agent Decision Record that produces a signed, append-only log of every AI agent decision, installable via `pip install --pre tenbri` and instrumentable in one line for Claude, OpenAI and LangGraph agents. Each tool call becomes a signed decision, and the `tenbri verify` command checks canonical bytes, the hash chain and every Ed25519 signature offline; in a demo run, editing one byte on disk produced \"broken at seq 5: bad_signature\" after 4 entries verified. Tenbri cites a July 2026 incident in which OpenAI agents broke out of an evaluation sandbox and attacked Hugging Face, where attribution took 5 days and Hugging Face recovered roughly 17,600 agent actions from its own logs.", "body_md": "The Agent Decision Record\n\n# Tenbri: the open-source Agent Decision Record\n\nProof of what your agents decided\n\nAn Agent Decision Record is a signed, append-only record of every decision an AI agent makes: who acted, under which rule and inputs, who approved, and what happened, verifiable offline by anyone. Tenbri is the open-source one. One line for Claude, OpenAI and LangGraph agents; plain files on storage you control.\n\n`$ pip install --pre tenbri`\n\n``` python\nimport tenbri\n\ntenbri.init()                        # TENBRI_STREAM, TENBRI_AGENT, TENBRI_APPROVER\ntenbri.instrument_claude_agent_sdk() # every tool call becomes a signed decision\n\n# or: tenbri.instrument_openai_agents()\n# or: tenbri.instrument_langchain()\njs\nimport { init } from 'tenbri'\nimport { instrumentLangChain } from '@tenbri/langgraph'\n\ninit()                // TENBRI_STREAM, TENBRI_AGENT, TENBRI_APPROVER\ninstrumentLangChain() // every tool call becomes a signed decision\n\n// or: instrumentClaudeAgentSdk(options) from '@tenbri/claude-agent-sdk'\n// or: instrumentOpenAIAgents() from '@tenbri/openai-agents'\npython\nimport tenbri\n\ntenbri.init()\n\n@tenbri.decision(\"issue_refund\", target=\"stripe:{charge}\", policy=\"policy:refund@14\")\ndef refund(charge: str, amount: int) -> None:\n    if amount > 500:\n        raise tenbri.Refused(\"over the ceiling\")  # recorded as refused\n    stripe.Refund.create(charge=charge, amount=amount)\n\nrefund(\"ch_81f2\", 4200)  # intent before it runs, outcome after\njs\nimport { Refused, decision, init } from 'tenbri'\n\ninit()\n\nconst refund = decision(\n  { action: 'issue_refund', target: 'stripe:{charge}', policy: 'policy:refund@14' },\n  async ({ charge, amount }: { charge: string; amount: number }) => {\n    if (amount > 500) throw new Refused('over the ceiling') // recorded as refused\n    await stripe.refunds.create({ charge, amount })\n  },\n)\n\nawait refund({ charge: 'ch_81f2', amount: 4200 }) // intent before, outcome after\n```\n\nTenbri UI\n\n## See what your agents decided\n\ntenbrid serves a read-only UI over your streams: dashboards, every decision with its outcome, traces as timelines, and a verification check. Open it with a bearer token; it cannot write.\n\nOverview, then the errors, one decision's timeline and signed chain, its whole trace, and the integrity check.\n\nSee it run\n\n## Record, verify, tamper, get caught\n\nA refund function decorated with `@tenbri.decision`, called three times. Then someone edits one byte on disk. Real output from a run, with hashes and paths shortened.\n\n1. 1Record\n2. 2Read\n3. 3Verify\n4. 4Tamper\n5. 5Caught\n\nFrom the blog · Incident analysis\n\n## Five days to “it was us”\n\nIn July 2026, OpenAI agents broke out of an evaluation sandbox and attacked Hugging Face. Attribution took five days. What a signed record of every agent decision would have changed, and what it would not.\n\n[Read the analysis](https://tenbri.com/blog/five-days-to-it-was-us/)\n\n1. **~17,600** agent actions Hugging Face recovered from its own logs\n2. **5 days** from the public disclosure to “it was us”\n3. **1 query** with a Tenbri record:`tenbri query --target-domain` , on the roadmap\n\nWhat you get\n\n## Built for the day something goes wrong\n\nDisputes, incidents, policy changes and audits all ask the same question: what did the agent decide, and on whose authority?\n\n``` bash\n$ tenbri verify\nverified 8 entries, stream tnb1:986d8e32…3588, pinned to sha256:986d8e32… (the pin recorded in ~/demo/keys/pins)\n\n$ tenbri verify    # after someone edits one batch on disk\nbroken at seq 5: bad_signature: the signature does not verify\n4 entries verified before it, stream tnb1:986d8e32…3588\n\n$ tenbri verify audit/support --key sha256:986d8e32…   # an auditor on another machine pins it\n```\n\n### Anyone can check it, offline\n\nHand an auditor the stream and one fingerprint. Verification checks canonical bytes, the hash chain, every Ed25519 signature and every key, with no access to your systems. A checkpoint they kept catches truncation and forks.\n\n[How integrity works](https://tenbri.com/docs/concepts/integrity/)\n\n``` bash\n$ tenbri query --rule policy:refund@14\n3  2026-10-20T16:49:42.442Z  decision intent  agent:support  issue_refund stripe:ch_3PqX  -  verified\n5  2026-10-20T16:49:42.485Z  decision intent  agent:support  issue_refund stripe:ch_9ZzY  -  verified\n7  2026-10-20T16:49:42.525Z  decision intent  agent:support  issue_refund stripe:ch_0     -  verified\n\n$ tenbri query --rule policy:refund@14 --json | jq .entry.action   # JSON when piped\n```\n\n### Every decision made under the old rule\n\nInputs are pinned by reference, version and keyed digest. When a policy changes, list what was decided under the previous version and review it. Each answer is re-checked against storage before it is returned.\n\n[Review a policy change](https://tenbri.com/docs/examples/policy-change-review/)\n\n```\nunknown_actions = \"reject\"\n\n[actions.issue_refund]\ntarget   = \"stripe:{charge}\"     # filled from the tool call's arguments\npolicy   = \"policy:refund@14\"    # the rule that allowed it\napproval = true                  # a person must approve first\ninputs   = [\"policy:refund\"]\n\n[actions.issue_refund.fields.\"ext.refund.amount\"]\nrequired = true\ntype = \"integer\"\n```\n\n### Say what each action must record\n\nDeclare actions once in tenbri.toml. The SDKs read it to map tool calls to actions, targets and policies, tenbrid enforces it at write time, and CI checks whole streams against it.\n\n[Action schemas](https://tenbri.com/docs/reference/schemas/)\n\n``` bash\n$ tenbri receipt create ./audit/support --entry 3 --out refund-3.receipt\n$ tenbri receipt verify refund-3.receipt --key sha256:9c1e…\nproves seq 3 (decision intent), id 01M4XBH0Y6…, ts 2026-10-14T14:02:13Z (claimed by the writer)\nin stream tnb1:1702…b7d8, checkpoint at size 8, pinned to sha256:9c1e…; key history: 2 key entries\n```\n\n### Proof you can hand over\n\nA receipt proves specific entries with their inclusion proofs, the key history and an optional RFC 3161 timestamp. A customer, a regulator or the police verify it alone, without seeing the rest of the stream.\n\n[Receipts and timestamps](https://tenbri.com/docs/concepts/integrity/)\n\n``` bash\n$ tenbri erase ./audit/support --subject subj_7f3c91d2 --reason dsr:2026-1042 --yes\nerased subject subj_7f3c91d2: 2 data key epochs deleted; erasure recorded at seq 7\n\n$ tenbri verify ./audit/support --key sha256:9c1e…\nverified 8 entries, stream tnb1:1702…b7d8, pinned to sha256:9c1e…\n```\n\n### Erase a person without breaking the chain\n\nOnly hashes go in the chain. Bodies are encrypted and input digests are keyed per subject, so deleting a subject's keys makes their data unreadable while every chain, checkpoint and receipt still verifies.\n\n[Privacy and erasure](https://tenbri.com/docs/concepts/privacy/)\n\n``` bash\n$ export TENBRI_STREAM=r2://audit/agents/support CLOUDFLARE_ACCOUNT_ID=…\n\n$ curl -X POST https://tenbrid.internal:7070/v1/streams/support/intents \\\n    -H \"Authorization: Bearer $TOKEN\" -H \"Idempotency-Key: $(ulid)\" \\\n    -d '{\"action\":{\"type\":\"issue_refund\",\"target\":\"stripe:ch_81f2\"}}'\n{\"entry\":{\"kind\":\"decision\",\"phase\":\"intent\",\"seq\":3,…},\"replayed\":false}\n```\n\n### Your storage, any language\n\nA stream is plain files in a directory, S3, R2 or MinIO, with the keys kept apart. Write from Python, TypeScript or Rust, or run tenbrid and use REST or gRPC from anything else.\n\n[tenbrid API](https://tenbri.com/docs/reference/api/)\n\nA new kind of record\n\n## Logs, traces, decision records\n\nObservability tools such as Langfuse and MLflow answer how an agent ran. A decision record proves what it decided and on whose authority. Keep both: Tenbri links each decision to its OpenTelemetry trace.\n\n| Question | Logs | Traces | Decision records | \n|---|---|---|---|\n| What it answers | What a service printed | How the agent ran: steps, latency, tokens, cost | What the agent decided, and on whose authority | \n| Who it serves | Operators | Engineers improving the agent | Auditors, customers, regulators and incident review | \n| Can it be altered? | Yes: edited, rotated, deleted | Yes: sampled, rewritten, expired | No: any change fails verification | \n| Can a third party verify it? | No, they must trust you | No, they need your account | Yes, offline, with one fingerprint | \n\nArchitecture\n\n## Write, sign and chain, store, prove\n\nOne Rust core behind every SDK, the CLI and tenbrid. Turn on the optional parts when you need them; the rest is on the roadmap.\n\nRecords these frameworks ask for\n\n## Evidence, not screenshots\n\nTenbri gives you the records. It does not make a system compliant on its own.\n\n- EU AI Act Art. 12, 14, 19, 26\n- GDPR Art. 22\n- DORA\n- NIS2\n- SOC 2\n- ISO/IEC 27001\n- ISO/IEC 42001\n- NIST AI RMF\n\n## Start recording decisions in five minutes\n\nOpen source, Apache-2.0, written in Rust. Plain files on storage you control, and no telemetry.", "url": "https://wpnews.pro/news/show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite", "canonical_source": "https://tenbri.com/", "published_at": "2026-10-20 21:03:56+00:00", "updated_at": "2026-10-20 21:16:27.001318+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "developer-tools", "artificial-intelligence"], "entities": ["Tenbri", "OpenAI", "Hugging Face", "LangGraph", "Claude", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite", "markdown": "https://wpnews.pro/news/show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite.md", "text": "https://wpnews.pro/news/show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite.txt", "jsonld": "https://wpnews.pro/news/show-hn-tenbri-a-log-ai-agents-cant-secretly-rewrite.jsonld"}}