<?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: chiragrajputuss-code</title>
    <description>The latest articles on DEV Community by chiragrajputuss-code (@chiragrajputusscode).</description>
    <link>https://dev.to/chiragrajputusscode</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%2F4195436%2Fc6640fca-ff78-454e-a6a9-f2d851e5ae56.jpeg</url>
      <title>DEV Community: chiragrajputuss-code</title>
      <link>https://dev.to/chiragrajputusscode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chiragrajputusscode"/>
    <language>en</language>
    <item>
      <title>I instrumented 5 agent frameworks. Here's what they silently drop.</title>
      <dc:creator>chiragrajputuss-code</dc:creator>
      <pubDate>Tue, 20 Oct 2026 06:06:48 +0000</pubDate>
      <link>https://dev.to/chiragrajputusscode/i-instrumented-5-agent-frameworks-heres-what-they-silently-drop-3oni</link>
      <guid>https://dev.to/chiragrajputusscode/i-instrumented-5-agent-frameworks-heres-what-they-silently-drop-3oni</guid>
      <description>&lt;p&gt;A while ago I replayed the Claude Code session that built the tool I'm writing about. 906 events. 245 commands that changed something on disk. And 11 failures I never noticed while it was happening.&lt;/p&gt;

&lt;p&gt;Nothing crashed. The session looked fine. The agent said it was done, the tests mostly passed, and I moved on. It was only when I stepped through the recording that I saw the commands that had quietly failed and been worked around.&lt;/p&gt;

&lt;p&gt;That's the problem I keep coming back to: when an agent does something, the logs usually tell you what the agent &lt;em&gt;reported&lt;/em&gt;. Not what actually happened. And they're written by the same system that made the mistake.&lt;/p&gt;

&lt;p&gt;So I started building a recorder for agent runs. It captures every LLM call and tool call, links each tool call back to the model turn that asked for it, and lets you replay a run step by step. Along the way I learned that the hardest part isn't recording. It's making sure the recording isn't lying to you.&lt;/p&gt;

&lt;p&gt;Here's what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I checked
&lt;/h2&gt;

&lt;p&gt;I didn't want to just say "supports CrewAI" because the docs say there's a hook. So I built a small harness for each framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;stub model server&lt;/strong&gt; that returns a scripted tool call, so the run is predictable&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;spy wrapped around the real tool function&lt;/strong&gt;, counting how many times it actually ran&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spy lives outside my recorder entirely. So at the end of a run I can compare two numbers: how many tool calls really happened, and how many my recorder says happened. If they don't match, the recorder is wrong.&lt;/p&gt;

&lt;p&gt;They didn't match. A lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Tool calls disappearing in threads (CrewAI)
&lt;/h2&gt;

&lt;p&gt;The first time I ran the harness against CrewAI, the spy counted 2 tool calls. My recorder captured &lt;strong&gt;0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The tools ran. The trace just didn't know about them.&lt;/p&gt;

&lt;p&gt;The cause turned out to be a general Python thing. My recorder kept the "current run" in a &lt;code&gt;ContextVar&lt;/code&gt;, and context variables don't automatically follow work into a thread pool. CrewAI runs tools in threads, so code inside the tool couldn't see the active trace:&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;contextvars&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;

&lt;span class="n"&gt;current_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ContextVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tool&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;current_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;current_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;          &lt;span class="c1"&gt;# None: context is lost
&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;# run_123: context kept
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After fixing propagation into threads, a single tool call worked. Then a thread-plus-pool scenario came back as &lt;strong&gt;1 of 4&lt;/strong&gt; until I switched to a ContextVar stack.&lt;/p&gt;

&lt;p&gt;If your tracing shows the LLM calls but the tool calls are missing, this is the first thing I'd check.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Recording &lt;code&gt;&amp;lt;APIResponse [200 OK]&amp;gt;&lt;/code&gt; instead of the response
&lt;/h2&gt;

&lt;p&gt;This one was embarrassing. Under LangChain and CrewAI, some calls go through the SDK's raw-response wrapper. My recorder was happily capturing the wrapper object, so the "response" field in my traces said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;APIResponse [200 OK]&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No content. No error either. Every trace &lt;em&gt;looked&lt;/em&gt; complete. You'd only notice if you actually read a response.&lt;/p&gt;

&lt;p&gt;That's the kind of bug that makes me nervous about any tracing tool, mine included. A missing field is obvious. A field that's filled with the wrong thing isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Token counts ~40x too high
&lt;/h2&gt;

&lt;p&gt;My token accounting summed &lt;code&gt;cache_read_input_tokens&lt;/code&gt; across every turn of a conversation. With prompt caching, each turn re-reads a large cached prefix, so adding those up turn after turn counted the same context over and over. The usage it reported was roughly &lt;strong&gt;40x&lt;/strong&gt; too high.&lt;/p&gt;

&lt;p&gt;If you build cost tracking on top of agent traces, double-check what you're summing. This is an easy mistake to make, and a confusing one to debug, because the numbers look plausible on any single turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The hook that never fires (Pydantic AI)
&lt;/h2&gt;

&lt;p&gt;Pydantic AI has a method called &lt;code&gt;handle_call&lt;/code&gt; that looks exactly like where tool calls go through. I hooked it. Nothing.&lt;/p&gt;

&lt;p&gt;So I spied on all four candidate methods and checked which ones actually ran. The real path is &lt;code&gt;execute_tool_call&lt;/code&gt;. &lt;code&gt;handle_call&lt;/code&gt; never fired in my testing.&lt;/p&gt;

&lt;p&gt;The lesson for me: don't trust a method name. Instrument, then verify against something you didn't write.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Bonus: why &lt;code&gt;json.dumps&lt;/code&gt; broke my hash chains
&lt;/h2&gt;

&lt;p&gt;Every event in a trace is hashed, and each hash covers the previous one, so if anyone edits, removes or reorders an event later, verification fails at that point.&lt;/p&gt;

&lt;p&gt;It worked perfectly in Python, and failed in every other language. The reason:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;50.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same value, different bytes, different hash. A trace written in Python couldn't be verified anywhere else. The fix was &lt;a href="https://www.rfc-editor.org/rfc/rfc8785" rel="noopener noreferrer"&gt;RFC 8785&lt;/a&gt; canonical JSON, and I now check every trace against a Node implementation in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;p&gt;Every one of these bugs had the same shape: &lt;strong&gt;the trace looked fine&lt;/strong&gt;. No errors, no gaps you'd spot at a glance. Just quietly wrong.&lt;/p&gt;

&lt;p&gt;And that's a bit uncomfortable, because the whole reason you record an agent run is so you can trust it later, when something has gone wrong and you need to know what really happened.&lt;/p&gt;

&lt;p&gt;The tool I ended up with is called &lt;strong&gt;Deposition&lt;/strong&gt;. It records LLM calls and tool calls into a hash-chained trace, with optional signing, links tool calls to the model turns that requested them, and replays runs locally. It also has a way to declare what a run was allowed to do (tools, cost, steps) and audit the run against it afterwards, and it can import Claude Code sessions, which is how I found those 11 failures.&lt;/p&gt;

&lt;p&gt;Some honest limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming responses record the request but not the content yet.&lt;/li&gt;
&lt;li&gt;It's tamper-evident, not tamper-proof. A signing key on the same machine raises the bar but doesn't settle it.&lt;/li&gt;
&lt;li&gt;Audits happen after the run. It doesn't stop an agent at runtime.&lt;/li&gt;
&lt;li&gt;Framework hooks move. I re-verify against current releases rather than assuming they still work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's open source (Apache 2.0), with zero required dependencies: &lt;a href="https://github.com/chiragrajputuss-code/deposition" rel="noopener noreferrer"&gt;github.com/chiragrajputuss-code/deposition&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd genuinely like to hear from people running agents in production: &lt;strong&gt;when something goes wrong, what do you need to prove about the run, and to whom?&lt;/strong&gt; That question is what I'm trying to build toward, and I suspect the answers are more varied than I think.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
