<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aditya Suresh</title>
    <description>The latest articles on DEV Community by Aditya Suresh (@adi-suresh).</description>
    <link>https://dev.to/adi-suresh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3954588%2Facb53780-f0ee-4e77-b0e2-293624367ea9.JPG</url>
      <title>DEV Community: Aditya Suresh</title>
      <link>https://dev.to/adi-suresh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adi-suresh"/>
    <language>en</language>
    <item>
      <title>pip install provedex: a tamper-evident black box for your Python AI agent</title>
      <dc:creator>Aditya Suresh</dc:creator>
      <pubDate>Thu, 18 Jun 2026 05:14:10 +0000</pubDate>
      <link>https://dev.to/adi-suresh/pip-install-provedex-a-tamper-evident-black-box-for-your-python-ai-agent-3l5o</link>
      <guid>https://dev.to/adi-suresh/pip-install-provedex-a-tamper-evident-black-box-for-your-python-ai-agent-3l5o</guid>
      <description>&lt;p&gt;Your AI agent's audit log lives in a database you control. Which means you can edit it. When a regulator, an auditor, or a court later asks what the agent actually did, all anyone has is your word.&lt;/p&gt;

&lt;p&gt;Provedex is an open-source fix for that. &lt;code&gt;pip install provedex&lt;/code&gt; gives your Python backend a black box recorder: every agent action is cryptographically signed at the moment it happens, chained to the action before it, and written to an append-only file. Anyone with the public key can verify the whole thing offline, with no call back to you. Edit or drop a single event and the chain visibly breaks.&lt;/p&gt;

&lt;p&gt;The native SDK is a PyO3 binding over the same Rust core the reference CLI uses, so a ledger you sign from Python verifies byte-for-byte with the Rust verifier. Not a reimplementation, the same primitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;provedex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pre-built wheels ship for cpython 3.11+ on Linux x86_64, Linux aarch64, and macOS arm64. No Rust toolchain needed to install. Add it to the backend service that runs your agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits your backend
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provedex&lt;/code&gt; is a library you embed, not a service you run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your backend (agents + automations)          an auditor, months later
  pip install provedex                          (only needs the public key)
  session.record(event)  ---&amp;gt;  ledger.ndjson  ---&amp;gt;  provedex verify  -&amp;gt;  VALID / BROKEN
  (signing key stays here)     (the evidence)       (offline, no trust in you)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Sign in-process. Wherever the agent does something worth proving, you call &lt;code&gt;session.record(...)&lt;/code&gt;. The event is signed and appended as it happens, no network hop.&lt;/li&gt;
&lt;li&gt;The key and the ledger live on the backend host. The key is read once at startup from a path you control. The ledger is an append-only NDJSON file.&lt;/li&gt;
&lt;li&gt;Verify anywhere, later, by anyone, with only the public key. That separation is the point: the operator never has to be trusted for the integrity of the log.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;

&lt;p&gt;Events carry SHA-256 digests of content, not raw content. What you hash versus keep in clear is your call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provedex&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="c1"&gt;# Once at startup. Key is created on first run, then reused (0600 on unix).
&lt;/span&gt;&lt;span class="n"&gt;keypair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SigningKeypair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.provedex/keys/ed25519.key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# One session per conversation / agent run. Resumes if the ledger exists.
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;keypair&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;keypair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ledger_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.provedex/ledger.ndjson&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_started&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intake-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation-42&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize the patient&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s chief complaint.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_your_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# your code
&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_invoked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;prompt_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sha256_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;response_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sha256_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;response_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_ended&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summary_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;sha256_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole integration: open a session, record events at the points worth proving, done. The signing and chaining happen for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying, offline, with only the public key
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Anyone with the public key can verify this ledger, offline, later.
&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provedex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.provedex/ledger.ndjson&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from the Rust CLI, against the same file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;provedex verify ~/.provedex/ledger.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both go through one canonical-JSON encoder and one Ed25519 signature scheme, both published as specs with byte-level test vectors. That is why the Python-signed ledger and the Rust verifier agree to the byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven things you can record
&lt;/h2&gt;

&lt;p&gt;The event schema is fixed and small. Seven factories cover an agent's lifecycle:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factory&lt;/th&gt;
&lt;th&gt;Signs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.session_started(agent_id, model_id, session_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;session open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.utterance_captured(audio_sha256, transcript, lang, duration_ms)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inbound speech&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.tool_called(tool_name, args_sha256, args_redacted)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tool invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.tool_returned(tool_name, result_sha256, latency_ms, success)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tool result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.model_invoked(model_id, prompt_sha256, response_sha256, prompt_tokens, response_tokens)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LLM call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.utterance_spoken(text_sha256, text, audio_sha256)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;outbound speech&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events.session_ended(reason, summary_sha256)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;session close&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fixed schema is deliberate: a verifier in any language knows exactly what shapes to expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native SDK vs the sidecar
&lt;/h2&gt;

&lt;p&gt;Two ways to integrate, pick by constraint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native SDK (this post): in-process, sub-millisecond signing, no extra process. Best when you can add a compiled wheel to the backend that runs your agents.&lt;/li&gt;
&lt;li&gt;Sidecar (&lt;code&gt;provedex-agent&lt;/code&gt;): a localhost HTTP daemon you POST events to. Best when you do not want a native extension in your runtime, or you are not in Python. It is the default integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest numbers, M4 Pro: in-process sign with no I/O is ~11 us. Full cycle with append plus fsync on every event is ~3.8 ms, so ~261 events/sec if you fsync every write. Batch the flush if you need more throughput and can accept a wider crash window.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;Not observability, not PII redaction, not a compliance dashboard, not a blockchain. One primitive: signed, chained, third-party-verifiable evidence of what your agent did. Apache-2.0 for the core, forever.&lt;/p&gt;

&lt;p&gt;Repo, specs, and architecture decision records: &lt;a href="https://github.com/provedex/provedex" rel="noopener noreferrer"&gt;https://github.com/provedex/provedex&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run AI agents anywhere a regulator or a court might one day ask "prove what it did," try it on one session and run the verifier. Feedback and issues welcome.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>security</category>
      <category>rust</category>
    </item>
    <item>
      <title>Add Tamper-Evident Audit Logs to Pipecat Voice Agents in 5 Minutes</title>
      <dc:creator>Aditya Suresh</dc:creator>
      <pubDate>Thu, 28 May 2026 04:21:39 +0000</pubDate>
      <link>https://dev.to/adi-suresh/add-tamper-evident-audit-logs-to-pipecat-voice-agents-in-5-minutes-3cfp</link>
      <guid>https://dev.to/adi-suresh/add-tamper-evident-audit-logs-to-pipecat-voice-agents-in-5-minutes-3cfp</guid>
      <description>&lt;p&gt;EU AI Act Article 12 enforcement starts on August 2, 2026. Colorado AI Act enforcement started on February 1, 2026. FINRA's 2026 Annual Regulatory Oversight Report named AI agent auditability as a 2026 examination priority. HIPAA already requires audit trails for AI handling of PHI.&lt;/p&gt;

&lt;p&gt;If your voice agent is going into healthcare, finance, insurance, or legal, an auditor will eventually ask: prove what your AI agent said, on what input, and that the log was not edited after the fact.&lt;/p&gt;

&lt;p&gt;This post shows how to add tamper-evident, offline-verifiable audit logs to a Pipecat voice agent in about five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;A standard Pipecat pipeline: Twilio in, STT, LLM, TTS, Twilio out. We will add one extra &lt;code&gt;FrameProcessor&lt;/code&gt; that signs every audit-relevant event - user utterance captured, model invoked, tool called, agent utterance spoken - with Ed25519, chained by SHA-256, written to a local NDJSON ledger. After the call, we will run a CLI tool to verify the ledger has not been tampered with.&lt;/p&gt;

&lt;p&gt;Total install footprint: one pip package + one localhost binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;The Pipecat adapter is on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;provedex-pipecat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signing daemon is a small Rust binary. Multi-arch container or pre-built binaries from GitHub Releases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option A - prebuilt binary&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/provedex/provedex/releases/download/v0.1.0/provedex-agent-aarch64-apple-darwin.tar.gz | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt;
./provedex-agent &amp;amp;

&lt;span class="c"&gt;# Option B - docker&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; host ghcr.io/provedex/provedex-agent:v0.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The daemon binds to localhost only. It is not reachable from outside the machine. No port to firewall, no TLS to manage, no auth to provision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wire it into a Pipecat pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pipecat.pipeline.pipeline&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;provedex_pipecat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProvedexFrameProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProvedexConfig&lt;/span&gt;

&lt;span class="c1"&gt;# Standard Pipecat pieces (your existing setup)
&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;      &lt;span class="c1"&gt;# Twilio, LiveKit, Daily, whatever
&lt;/span&gt;&lt;span class="n"&gt;stt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;            &lt;span class="c1"&gt;# Deepgram, AWS Transcribe, Whisper
&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;            &lt;span class="c1"&gt;# OpenAI, Anthropic, Bedrock
&lt;/span&gt;&lt;span class="n"&gt;tts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;            &lt;span class="c1"&gt;# ElevenLabs, Cartesia, Piper
&lt;/span&gt;
&lt;span class="c1"&gt;# One extra line
&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProvedexFrameProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ProvedexConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patient-session-2026-05-24-abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;stt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# signs user transcriptions
&lt;/span&gt;    &lt;span class="n"&gt;context_aggregator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# signs LLM input and output
&lt;/span&gt;    &lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. Run the pipeline as normal. Every relevant Pipecat Frame gets signed and appended to a local NDJSON ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets signed
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ProvedexFrameProcessor&lt;/code&gt; maps Pipecat frames to the seven AgentEvent variants in &lt;a href="https://github.com/provedex/provedex/blob/main/docs/spec/event-schema-v1.md" rel="noopener noreferrer"&gt;event-schema-v1&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pipecat Frame&lt;/th&gt;
&lt;th&gt;AgentEvent variant&lt;/th&gt;
&lt;th&gt;Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StartFrame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SessionStarted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;session_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;TranscriptionFrame&lt;/code&gt; (final)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UtteranceCaptured&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;transcript_sha256, speaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LLMMessagesFrame&lt;/code&gt; + &lt;code&gt;LLMFullResponseEndFrame&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ModelInvoked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;prompt_sha256, response_sha256, token counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FunctionCallInProgressFrame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ToolCalled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tool_name, args_sha256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FunctionCallResultFrame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ToolReturned&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;result_sha256, success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;TextFrame&lt;/code&gt; at TTS boundary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UtteranceSpoken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;speech_sha256&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EndFrame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SessionEnded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;session_id&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Skipped: raw audio frames (too high frequency), interim STT (only the final), operational metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on PII
&lt;/h2&gt;

&lt;p&gt;The ledger stores SHA-256 commitments, not raw content. Transcripts, prompts, responses, and tool args are hashed at the binding boundary before they ever leave the Pipecat process. PII never enters the chain.&lt;/p&gt;

&lt;p&gt;Operators who need raw content for replay keep it in their own observability stack alongside the signed receipt. Receipt + observability log together = provable + replayable. Two separate concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the log
&lt;/h2&gt;

&lt;p&gt;After the call, the ledger file lives at &lt;code&gt;~/.provedex/ledger.ndjson&lt;/code&gt; (configurable via env). Run the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;provedex-cli verify ~/.provedex/ledger.ndjson &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--public-key&lt;/span&gt; ~/.provedex/keys/signing.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on a clean ledger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verified 247 events
Chain intact (parent_hash matches self_hash for all entries)
All signatures valid against public key 8f3a2e1b...
Session start: 2026-05-24 09:12:33 PDT
Session end:   2026-05-24 09:34:18 PDT
Duration:      21m 45s
Result: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now try tampering. Open the NDJSON file in any text editor, change one byte in one event (flip a digit in a hash, change a single character in a transcript reference), save, run verify again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: chain broken at event 47
ERROR: self_hash mismatch - computed=a4f1..., recorded=a4f0...
Result: FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the property. The chain rejects any silent edit, regardless of who has access to the file - including the operator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The auditor angle
&lt;/h2&gt;

&lt;p&gt;This is the part that matters for compliance buyers.&lt;/p&gt;

&lt;p&gt;The verifier runs offline. It does not call any Provedex server. It does not call any of your servers. The auditor needs only the ledger file and the public key. If your company disappears tomorrow, the auditor can still verify the chain with the open-source CLI or any tool that implements the &lt;a href="https://github.com/provedex/provedex/blob/main/docs/spec/signature-scheme.md" rel="noopener noreferrer"&gt;published spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That is what makes the receipts court-admissible in a way that signed Datadog logs are not. Logs from a vendor-controlled database are evidence only if the vendor cooperates. Logs from a cryptographic chain are evidence regardless of vendor cooperation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Measured on Apple M4 Pro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-event signing: 11.2 microseconds in-process&lt;/li&gt;
&lt;li&gt;Per-event end-to-end including fsync: 3.8 milliseconds&lt;/li&gt;
&lt;li&gt;Sidecar HTTP roundtrip: 4-5 ms p95 single concurrency&lt;/li&gt;
&lt;li&gt;p99 added to your Pipecat pipeline: under 5 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For voice agents at typical 1-10 events/sec, the overhead is invisible to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in scope, what is not
&lt;/h2&gt;

&lt;p&gt;In scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cryptographic signing of agent events&lt;/li&gt;
&lt;li&gt;Hash-chained, tamper-evident NDJSON ledger&lt;/li&gt;
&lt;li&gt;Offline verification with public key&lt;/li&gt;
&lt;li&gt;Single-operator setup (one signing key)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not in scope today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-party signing (multiple keys per receipt)&lt;/li&gt;
&lt;li&gt;RFC 3161 timestamping (planned)&lt;/li&gt;
&lt;li&gt;Hosted aggregator (planned, paid tier)&lt;/li&gt;
&lt;li&gt;PII redaction (operator's job, not the signing layer's)&lt;/li&gt;
&lt;li&gt;Policy enforcement / approval flow (different layer - see &lt;a href="https://github.com/sidclawhq/platform" rel="noopener noreferrer"&gt;SidClaw&lt;/a&gt; for that)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using LangChain instead?
&lt;/h2&gt;

&lt;p&gt;The LangChain adapter ships alongside Pipecat. Same shared core, same signing path, same v1 event schema. Five lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMChain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;provedex_langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProvedexCallbackHandler&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProvedexCallbackHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;callbacks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler hooks into LangChain's &lt;code&gt;BaseCallbackHandler&lt;/code&gt; lifecycle - &lt;code&gt;on_chain_start&lt;/code&gt;, &lt;code&gt;on_chain_end&lt;/code&gt;, &lt;code&gt;on_llm_start&lt;/code&gt;, &lt;code&gt;on_llm_end&lt;/code&gt;, &lt;code&gt;on_tool_start&lt;/code&gt;, &lt;code&gt;on_tool_end&lt;/code&gt;. Each maps to the same v1 AgentEvent variants that the Pipecat adapter uses, so a ledger produced by either adapter verifies identically with the same CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;provedex-langchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A separate walkthrough for LangChain-specific patterns (agents, tools, retrieval chains) is coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;Pipecat and LangChain adapters shipped this weekend. LangGraph is next, then CrewAI and an MCP server adapter, all using the same &lt;code&gt;provedex-client&lt;/code&gt; shared core.&lt;/p&gt;

&lt;p&gt;Post-quantum signature migration is documented in &lt;a href="https://github.com/provedex/provedex/blob/main/docs/adr/0006-post-quantum-migration.md" rel="noopener noreferrer"&gt;ADR-0006&lt;/a&gt; - hybrid Ed25519 + ML-DSA-65 mode for operators with long-term retention concerns, opt-in feature flag, default unchanged.&lt;/p&gt;

&lt;p&gt;There is also a community RFC repo at &lt;a href="https://github.com/provedex/compliance-backend-rfc" rel="noopener noreferrer"&gt;github.com/provedex/compliance-backend-rfc&lt;/a&gt; for cross-implementation interoperability. Multiple backends are converging on cryptographic chains as the primitive. The spec discussion happens there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;provedex-pipecat       &lt;span class="c"&gt;# voice agents&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;provedex-langchain     &lt;span class="c"&gt;# text agents and chains&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full Pipecat example: &lt;a href="https://github.com/provedex/provedex/blob/main/examples/pipecat_voice_basic.py" rel="noopener noreferrer"&gt;github.com/provedex/provedex/blob/main/examples/pipecat_voice_basic.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full LangChain example: &lt;a href="https://github.com/provedex/provedex/blob/main/examples/langchain_basic.py" rel="noopener noreferrer"&gt;github.com/provedex/provedex/blob/main/examples/langchain_basic.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spec stack: &lt;a href="https://github.com/provedex/provedex/tree/main/docs/spec" rel="noopener noreferrer"&gt;github.com/provedex/provedex/tree/main/docs/spec&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am building this in public. Open to questions, design feedback, integration partnership proposals, drop an issue on the repo or reply here.&lt;/p&gt;

&lt;p&gt;Aditya&lt;/p&gt;

</description>
      <category>ai</category>
      <category>pipecat</category>
      <category>compliance</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
