<?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: Kosta Stamboldzhiev</title>
    <description>The latest articles on DEV Community by Kosta Stamboldzhiev (@kostakurta).</description>
    <link>https://dev.to/kostakurta</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%2F4131205%2F18b23550-2ecd-4d66-b486-141bc7f32249.png</url>
      <title>DEV Community: Kosta Stamboldzhiev</title>
      <link>https://dev.to/kostakurta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kostakurta"/>
    <language>en</language>
    <item>
      <title>Your Claude Code session as a 25-second timelapse (and the timestamp that lied)</title>
      <dc:creator>Kosta Stamboldzhiev</dc:creator>
      <pubDate>Wed, 23 Sep 2026 13:59:21 +0000</pubDate>
      <link>https://dev.to/kostakurta/your-claude-code-session-as-a-25-second-timelapse-and-the-timestamp-that-lied-2bhb</link>
      <guid>https://dev.to/kostakurta/your-claude-code-session-as-a-25-second-timelapse-and-the-timestamp-that-lied-2bhb</guid>
      <description>&lt;p&gt;Claude Code writes every session to disk as JSONL: one line per message, tool call and result, and a separate file for every subagent. I have been building a read-only viewer for those files that draws a session as a pixel-art office — each agent is a person at a desk. This week it learned to export a session as a GIF:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37yv7itpsqrodenbrvv0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37yv7itpsqrodenbrvv0.gif" alt="a nine-minute Claude Code session with 24 subagents as a 25-second timelapse" width="480" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a staged session, so nothing private is in it: an orchestrator sends six scouts to map a billing service, hands eight changes to eight builders, and puts a verifier on each. One verifier comes back &lt;code&gt;REFUTED&lt;/code&gt;, so a ninth builder goes to fix it and a ninth verifier checks the fix. Every frame is the real renderer replaying transcript events — the same code path as a real session.&lt;/p&gt;

&lt;p&gt;To make one from your own latest session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx https://github.com/Kostakurta8/roundtable/releases/latest/download/roundtable.tgz &lt;span class="nt"&gt;--gif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It writes &lt;code&gt;roundtable-&amp;lt;session&amp;gt;.gif&lt;/code&gt; where you ran it, in a few seconds, and sends nothing anywhere. By default it shows the task, the agent names and what they said, so look before posting; &lt;code&gt;--bare&lt;/code&gt; draws the same run with none of that text.&lt;/p&gt;

&lt;p&gt;Three things were harder than they looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A real session is mostly waiting
&lt;/h2&gt;

&lt;p&gt;The first version sampled the session at even intervals and produced a still photograph with a few seconds of movement in it. Agents spend most of their time inside a tool call — a nine-minute build, a long read — and nothing moves.&lt;/p&gt;

&lt;p&gt;So time is compressed before it is sped up. Every silence between two events is clamped to a beat (2.5 s, tightened down to 0.5 s for a busy session), and only then is the whole thing played faster to fit the clip. A session still too long at that point becomes its busiest stretch instead, scored by what a viewer can see: an agent arriving is worth 12, leaving 6, a file edit 2, a token count moving 0.&lt;/p&gt;

&lt;p&gt;The wall clock in the office keeps the real time, so the cuts are visible: it jumps where the session waited.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One timestamp in the pipeline was lying
&lt;/h2&gt;

&lt;p&gt;The first full render of the staged session reported it as six days long. It is nine minutes.&lt;/p&gt;

&lt;p&gt;The observer attaches each subagent's name from a sidecar file next to its transcript, and it stamped that event with the moment &lt;em&gt;it read the file&lt;/em&gt;, not the moment the agent started. The live viewer never noticed, because it processes events in arrival order. The clip sorts by timestamp, so every agent's name landed at the moment I ran the command — after all of them had already left the building, and six days after the session ended.&lt;/p&gt;

&lt;p&gt;The fix pins each name to that agent's first transcript line. There is a test that fails if the staged session comes out longer than fifteen minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. I wrote the GIF encoder instead of installing one
&lt;/h2&gt;

&lt;p&gt;The office is drawn with filled rectangles into a 480×270 RGBA buffer by a small software rasteriser that the visual-regression tests already use, so the missing piece was only the file format. It is about three hundred lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One palette for the whole clip&lt;/strong&gt;, cut by median cut from a histogram of every frame, so a colour cannot shimmer between two indices from frame to frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each frame carries only what changed&lt;/strong&gt;: cropped to the bounding box of the changed pixels, with unchanged pixels inside the box written as the transparent index. An office where two people are walking is two small rectangles, not a whole room.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling after indexing&lt;/strong&gt;: the buffer is blown up 2× nearest-neighbour after the palette lookup, which is how the browser shows pixel art anyway, and LZW eats the repeated pixels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tests decode every frame back with a decoder written from the spec and compare pixel for pixel, including a 256×256 noise frame that fills and clears the LZW code table many times over — the path where encoders usually break.&lt;/p&gt;

&lt;p&gt;A 25-second, 25-agent clip at 960×562 comes out at about 3 MB, and a whole render takes a few seconds.&lt;/p&gt;




&lt;p&gt;It is also a live viewer on localhost where you can scrub back to any second of a session and see what each agent cost. Read-only, MIT, no account, no API calls: &lt;a href="https://github.com/Kostakurta8/roundtable" rel="noopener noreferrer"&gt;github.com/Kostakurta8/roundtable&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;--gif&lt;/code&gt; on one of yours, I would like to know what it got wrong.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>claude</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Three ways I miscounted my own AI agents, and what the transcripts actually say</title>
      <dc:creator>Kosta Stamboldzhiev</dc:creator>
      <pubDate>Fri, 18 Sep 2026 09:06:44 +0000</pubDate>
      <link>https://dev.to/kostakurta/three-ways-i-miscounted-my-own-ai-agents-and-what-the-transcripts-actually-say-4kjg</link>
      <guid>https://dev.to/kostakurta/three-ways-i-miscounted-my-own-ai-agents-and-what-the-transcripts-actually-say-4kjg</guid>
      <description>&lt;p&gt;Claude Code writes a JSONL transcript for every session, and another one for every subagent it spawns. They sit in &lt;code&gt;~/.claude/projects/&lt;/code&gt; right now, on your machine, and nobody reads them.&lt;/p&gt;

&lt;p&gt;I have spent six weeks reading mine. I got the arithmetic wrong three times in public, and each mistake turned out to be a property of the file format rather than a slip. Here they are, with the one-liners that expose them, so you can skip the part where you post a wrong number on Reddit and get corrected by a stranger with a better corpus.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Most of the output is in files you are not looking at
&lt;/h2&gt;

&lt;p&gt;The transcript you watch live is the parent. Every subagent writes its own file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/projects/&amp;lt;project-slug&amp;gt;/&amp;lt;session-id&amp;gt;.jsonl          the session you were watching
~/.claude/projects/&amp;lt;project-slug&amp;gt;/&amp;lt;session-id&amp;gt;/subagents/     everything it spawned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine, across 51 runs that used subagents, the children wrote &lt;strong&gt;58.4% of all output tokens&lt;/strong&gt;. If you are judging your own usage by what scrolls past you, you are looking at under half of a fan-out run.&lt;/p&gt;

&lt;p&gt;There is a second tier that is easy to miss entirely, because it lives one directory deeper:&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;session&amp;gt;/subagents/&amp;lt;id&amp;gt;.jsonl                          a Task subagent
&amp;lt;session&amp;gt;/subagents/workflows/&amp;lt;wf_id&amp;gt;/agent-*.jsonl     a workflow agent
&amp;lt;session&amp;gt;/subagents/workflows/&amp;lt;wf_id&amp;gt;/journal.jsonl     NOT an agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first walker stopped at the top level and under-counted children by about half. My second walker filtered on &lt;code&gt;.jsonl&lt;/code&gt; and counted 34 workflow &lt;strong&gt;journals&lt;/strong&gt; as silent agents, inflating the count by 7% and dragging down every per-child average. Same directory, opposite errors, two weeks apart.&lt;/p&gt;

&lt;p&gt;Count them properly:&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="nb"&gt;ls&lt;/span&gt; ~/.claude/projects/&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;/subagents/&lt;span class="k"&gt;*&lt;/span&gt;.jsonl | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.claude/projects/&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;/subagents/workflows/&lt;span class="k"&gt;*&lt;/span&gt;/agent-&lt;span class="k"&gt;*&lt;/span&gt;.jsonl | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.claude/projects/&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;/subagents/workflows/&lt;span class="k"&gt;*&lt;/span&gt;/journal.jsonl | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Summing usage per line over-reports you by about 3x
&lt;/h2&gt;

&lt;p&gt;This is the one that will bite anyone writing a cost tool.&lt;/p&gt;

&lt;p&gt;A single assistant response with several content blocks — thinking, then a tool call, then text — is written as &lt;strong&gt;several lines&lt;/strong&gt;, and every one of those lines repeats the same &lt;em&gt;complete&lt;/em&gt; usage object. It is not a running total. It is the same number, three times.&lt;/p&gt;

&lt;p&gt;On my corpus: 130,002 lines carrying a usage block describe &lt;strong&gt;63,014 responses&lt;/strong&gt;. Summed per line that is 90,955,151 output tokens. Deduplicated on &lt;code&gt;message.id&lt;/code&gt; it is 46,959,967. Plus 94%.&lt;/p&gt;

&lt;p&gt;And here is the part I only saw after splitting it by tier:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tier&lt;/th&gt;
&lt;th&gt;over-count if you sum per line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;main transcripts&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+195%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;subagent transcripts&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Subagent files mostly write one line per response. So a reader with this bug looks &lt;em&gt;correct&lt;/em&gt; on the child files — which are exactly the files you would spot-check when validating it. That is why the bug survived two of my own reviews.&lt;/p&gt;

&lt;p&gt;Deduplicate on &lt;code&gt;message.id&lt;/code&gt;, keeping the last line you see for an id, because a response is written progressively and the final line is the complete one.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A hook firing writes two records, and sometimes zero
&lt;/h2&gt;

&lt;p&gt;Every hook firing is written into the transcript, which means the question "has this guardrail of mine ever actually run?" is answerable &lt;strong&gt;retroactively, for your entire history&lt;/strong&gt;, with no instrumentation:&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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-ho&lt;/span&gt; &lt;span class="s1"&gt;'"hookName":"[^"]*"'&lt;/span&gt; ~/.claude/projects/&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.jsonl | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mine says &lt;code&gt;PostToolUse:Edit&lt;/code&gt; 505 and &lt;code&gt;PostToolUse:Bash&lt;/code&gt; 4. Four. Ever. I would have told you that shell guard was load-bearing.&lt;/p&gt;

&lt;p&gt;Except those are records, not firings. One firing writes &lt;strong&gt;two&lt;/strong&gt; lines that share a &lt;code&gt;toolUseID&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hook_success&lt;/code&gt; — the command, its exit code, duration and stdout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hook_additional_context&lt;/code&gt; — the text it injected back into the session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deduplicate and my 8,831 records become &lt;strong&gt;6,898 firings&lt;/strong&gt;. Counting lines over-reports your own guardrails by 28%.&lt;/p&gt;

&lt;p&gt;The payoff is that &lt;code&gt;hook_success&lt;/code&gt; names the &lt;em&gt;command&lt;/em&gt;, so a matcher carrying two hooks splits apart. Mine on &lt;code&gt;Edit&lt;/code&gt; runs &lt;code&gt;format-after-edit&lt;/code&gt; 185 times against &lt;code&gt;lint-after-edit&lt;/code&gt; 52 — I had no idea one was running a third as often as the other.&lt;/p&gt;

&lt;p&gt;Then the caveat that cost me my favourite claim. I had been saying that a hook absent from that list has never fired. A reader on r/ClaudeAI disproved it on a much bigger corpus: her &lt;code&gt;PreCompact&lt;/code&gt; hook fires and writes &lt;strong&gt;no &lt;code&gt;hookName&lt;/code&gt; at all&lt;/strong&gt; — she proved it by lining its own output file up with nine compaction boundaries. So absence is three cases, not two:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;never registered&lt;/li&gt;
&lt;li&gt;registered, never fired&lt;/li&gt;
&lt;li&gt;fired, never recorded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The transcript cannot tell them apart. What &lt;em&gt;appears&lt;/em&gt; definitely ran; what is missing needs a second check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The command
&lt;/h2&gt;

&lt;p&gt;I got tired of maintaining scratch scripts that were wrong in a new way each week, so the reader I build has it as a flag now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx github:Kostakurta8/roundtable &lt;span class="nt"&gt;--stats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads every transcript under &lt;code&gt;~/.claude&lt;/code&gt;, prints one screen, and exits — no server, no port, nothing written, nothing leaving the machine. It prints the traps rather than silently correcting them, including the caveat that a missing hook proves nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1,469 session transcripts, 51 of them spawned subagents
  513 children  (327 task, 186 workflow)  median 7 per run

  children wrote 58.4% of the output of the runs that spawned them
  cold start: 46,669 cache-creation tokens per child, before it does any work
  median child: 65 tool calls over 1,286 s
  13 of 513 children (2.5%) made no tool call and wrote nothing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is my favourite, because it is the failure you cannot see from the orchestrator: thirteen children that ran for hours and produced nothing at all. Twelve of them belong to a single run — one lane of a fan-out died quietly while the run as a whole looked busy and healthy.&lt;/p&gt;

&lt;p&gt;The rest of the project is a viewer for the same files: it draws the session as a pixel-art office where each agent is a person at a desk, and the timeline rebuilds the room at any second by replaying the events rather than approximating them. It is read-only by construction — 405 to every non-GET, no outbound connection — and MIT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Kostakurta8/roundtable" rel="noopener noreferrer"&gt;github.com/Kostakurta8/roundtable&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;--stats&lt;/code&gt;, I would genuinely like to know your child share. Mine is 58.4% and I have no idea whether that is normal.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>claude</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
