<?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: Himanshu Kumar</title>
    <description>The latest articles on DEV Community by Himanshu Kumar (@himanshu_748).</description>
    <link>https://dev.to/himanshu_748</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%2F3226847%2F8b999f0b-76e8-4a5b-a87d-449ab82832bc.png</url>
      <title>DEV Community: Himanshu Kumar</title>
      <link>https://dev.to/himanshu_748</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/himanshu_748"/>
    <language>en</language>
    <item>
      <title>The smolagents bug that made my agent retry the same valid code three times</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Mon, 20 Jul 2026 07:19:13 +0000</pubDate>
      <link>https://dev.to/himanshu_748/the-smolagents-bug-that-made-my-agent-retry-the-same-valid-code-three-times-2aka</link>
      <guid>https://dev.to/himanshu_748/the-smolagents-bug-that-made-my-agent-retry-the-same-valid-code-three-times-2aka</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Third entry in the DEV x Sentry Bug Smash. &lt;a href="https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im"&gt;Entry 1&lt;/a&gt; was a crash with a confusing message. &lt;a href="https://dev.to/himanshu_748/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing-2dma"&gt;Entry 2&lt;/a&gt; was a freeze the timeout could not catch. This one is quieter and sneakier: valid Python that the sandbox rejects with an error pointing at the wrong thing entirely.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When the open issues run out, fuzz
&lt;/h2&gt;

&lt;p&gt;By entry 3 every obvious open smolagents bug was already claimed or had a competing PR. So instead of reading the issue tracker I pointed a small fuzzer at the piece of smolagents that runs the most untrusted code: &lt;code&gt;LocalPythonExecutor&lt;/code&gt;, the sandbox that executes model-generated Python.&lt;/p&gt;

&lt;p&gt;The method is boring and effective: feed it ordinary, valid Python one snippet at a time, and flag anything that raises &lt;code&gt;InterpreterError&lt;/code&gt;. Valid Python that the sandbox refuses to run is, by definition, a bug, because the model writes valid Python and expects it to work.&lt;/p&gt;

&lt;p&gt;That surfaced four unreported bugs in one afternoon. This post is about the one I shipped: &lt;strong&gt;dict unpacking&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Merging dicts with &lt;code&gt;**&lt;/code&gt; is one of the most common things an LLM writes. Under smolagents it fails with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InterpreterError: NoneType is not supported.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no &lt;code&gt;None&lt;/code&gt; anywhere in that line. The message sends you looking for a null value that does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;In Python's AST, a dict literal keeps its keys and values in two parallel lists. For a normal entry the key is an AST node. For a &lt;code&gt;**mapping&lt;/code&gt; spread entry, the key is literally &lt;code&gt;None&lt;/code&gt;, a signal that says "this is a spread, not a key/value pair."&lt;/p&gt;

&lt;p&gt;smolagents evaluated every key by walking &lt;code&gt;expression.keys&lt;/code&gt; and calling &lt;code&gt;evaluate_ast(key, ...)&lt;/code&gt; on each one. When the key is &lt;code&gt;None&lt;/code&gt;, that call falls through every &lt;code&gt;isinstance&lt;/code&gt; branch to the catch-all &lt;code&gt;raise InterpreterError(f"{type} is not supported")&lt;/code&gt;. So the spread marker got evaluated as if it were an expression, and the model got blamed for a &lt;code&gt;None&lt;/code&gt; it never wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why silence is the expensive part
&lt;/h2&gt;

&lt;p&gt;Here is the part the Sentry view made obvious. The error is &lt;em&gt;handled&lt;/em&gt;: the agent catches it and feeds it back to the model as "here is what went wrong, try again." But the message names &lt;code&gt;NoneType&lt;/code&gt;, and the model's code has no &lt;code&gt;None&lt;/code&gt;, so the model cannot act on it. It retries the exact same valid syntax. And again. Every step burns a real LLM call and a slot in the step budget until the run gives up.&lt;/p&gt;

&lt;p&gt;One bug, one misleading message, three identical failures:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduexgfbm8suc843xzgvv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduexgfbm8suc843xzgvv.jpg" alt="Sentry issue showing InterpreterError NoneType is not supported, 3 events, environment before, transaction CodeAgent config merge task" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three events on a single issue is not noise. It is the agent stuck in a loop, and without Sentry counting the events you would never see the loop, only a run that quietly underperformed. Sentry's Seer read the same event and reached the exact root cause:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;smolagents' LocalPythonExecutor doesn't handle dict unpacking (&lt;code&gt;**&lt;/code&gt;) syntax: None keys in ast.Dict cause an unsupported type error. [...] &lt;code&gt;evaluate_ast(None, ...)&lt;/code&gt; matches no isinstance branch and falls to the else clause. The interpreter raises InterpreterError: NoneType is not supported, the agent retries with identical code, burning steps in a loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Evaluate the dict pairwise instead of evaluating keys blindly. A &lt;code&gt;None&lt;/code&gt; key means "merge this mapping":&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value_node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate_ast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;common_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keys&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;InterpreterError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; object is not a mapping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate_ast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;common_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate_ast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;common_params&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;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches CPython exactly: spreads merge in order, later keys win, and unpacking a non-mapping raises &lt;code&gt;'list' object is not a mapping&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reviewer caught my fix being too strict
&lt;/h2&gt;

&lt;p&gt;I first gated the spread on &lt;code&gt;isinstance(value, Mapping)&lt;/code&gt;. Minutes after the PR opened, OpenAI's Codex reviewer flagged it (P2): CPython does not require the &lt;code&gt;Mapping&lt;/code&gt; ABC, it only requires an object with a &lt;code&gt;keys()&lt;/code&gt; method. Since the sandbox lets users define their own classes, a duck-typed mapping with &lt;code&gt;keys()&lt;/code&gt; and &lt;code&gt;__getitem__()&lt;/code&gt; would have been wrongly rejected. I switched the check to &lt;code&gt;hasattr(value, "keys")&lt;/code&gt; and added a test for exactly that case. AI wrote the code, AI reviewed the code, I kept score.&lt;/p&gt;

&lt;h2&gt;
  
  
  After
&lt;/h2&gt;

&lt;p&gt;On the patched build the same line just runs:&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="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ok&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;top_p&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One step, no loop, no phantom &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;4 unreported bugs found by fuzzing valid Python through the sandbox; this is the first fix&lt;/li&gt;
&lt;li&gt;Misleading &lt;code&gt;NoneType&lt;/code&gt; error reproduced on current &lt;code&gt;main&lt;/code&gt; and 1.26.0&lt;/li&gt;
&lt;li&gt;3 wasted agent steps per occurrence, visible only because Sentry counts events&lt;/li&gt;
&lt;li&gt;9 new tests: spreads, double spreads, override order both ways, a duck-typed mapping class, empty spread, non-mapping rejection&lt;/li&gt;
&lt;li&gt;406 passing, ruff clean&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Issue: &lt;a href="https://github.com/huggingface/smolagents/issues/2552" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/issues/2552&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PR: &lt;a href="https://github.com/huggingface/smolagents/pull/2553" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/pull/2553&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern across all three entries: the worst agent bugs do not throw a red stack trace at you. They hand the model a plausible-but-wrong message and let it fail politely, on repeat. Count your events.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>One line of math froze my AI agent forever. The timeout watched and did nothing.</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Sun, 19 Jul 2026 07:54:58 +0000</pubDate>
      <link>https://dev.to/himanshu_748/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing-2dma</link>
      <guid>https://dev.to/himanshu_748/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing-2dma</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is my second entry for the DEV x Sentry Bug Smash challenge. &lt;a href="https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im"&gt;Entry #1 was a crash with a confusing error message&lt;/a&gt;. This one is the opposite and it is scarier: no crash, no message, no event. Just silence.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that sends you nothing
&lt;/h2&gt;

&lt;p&gt;smolagents runs LLM-generated Python in a sandboxed executor with a timeout. Issue &lt;a href="https://github.com/huggingface/smolagents/issues/2473" rel="noopener noreferrer"&gt;#2473&lt;/a&gt; claims that one line of model-generated math defeats it completely:&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;smolagents.local_python_executor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalPythonExecutor&lt;/span&gt;

&lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalPythonExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additional_authorized_imports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;timeout_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_tools&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;span class="nf"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10 ** 10 ** 8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2 second timeout. Should be fine, right?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this with a 2 second timeout and a faulthandler bomb set for 20 seconds. The timeout never fired. The process sat frozen until the external kill. An agent that generates this expression (and "compute this huge number" is exactly the kind of thing agents try) freezes its host process forever.&lt;/p&gt;

&lt;p&gt;Zero comments on the issue, zero PRs. Mine now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the timeout lies to you
&lt;/h2&gt;

&lt;p&gt;smolagents' timeout is thread based: a worker thread runs the code, the main thread waits in &lt;code&gt;future.result(timeout=2)&lt;/code&gt;. That design is fine for almost everything, because CPython switches threads between bytecode instructions.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;10 ** 10 ** 8&lt;/code&gt; is not "almost everything". CPython computes arbitrary precision &lt;code&gt;**&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; inside a single C call that holds the GIL from start to finish. No bytecode boundary, no thread switch, no timeout. The result would have about 400 million bits. The computation takes somewhere between minutes and hours. Your watchdog needs the GIL to wake up, and it never gets it.&lt;/p&gt;

&lt;p&gt;The faulthandler dump made it concrete, and it was worse than the issue described:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread 0x16d1f3000 (worker):
  File "local_python_executor.py", line 753 in evaluate_binop   &amp;lt;- computing the pow

Thread 0x1f00d5e80 (main):
  File "threading.py", line 999 in start
  File "concurrent/futures/thread.py", line 180 in submit       &amp;lt;- never returned!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main thread was still stuck inside &lt;code&gt;ThreadPoolExecutor.submit&lt;/code&gt;. It never even reached &lt;code&gt;future.result&lt;/code&gt;. The 2 second timer never armed at all.&lt;/p&gt;

&lt;p&gt;The existing &lt;code&gt;MAX_OPERATIONS&lt;/code&gt; guard (10 million AST operations) does not help either. This is a handful of AST nodes. The entire cost lives inside one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sentry angle: monitoring for absence
&lt;/h2&gt;

&lt;p&gt;Entry #1 was about a noisy failure. This bug is the opposite. I pointed a fresh Sentry project at a simulated agent run on the unpatched PyPI release (1.26.0) and got the most unsettling result possible: nothing. No event, no transaction, an empty project. The process was frozen mid-transaction and the SDK never got a chance to flush.&lt;/p&gt;

&lt;p&gt;The lesson: for freeze-class bugs you need a supervisor. I added a small watchdog process that gives the worker 25 seconds, then kills it and reports what it saw, with the worker's faulthandler stack attached as evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;watchdog: starting worker (before) with 25s budget
worker: smolagents 1.26.0
worker: step 1 executing 'result = 10 ** 10 ** 8'...
watchdog: worker FROZE, killed it, reporting to Sentry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting Sentry issue carries the whole story in one place: environment tagged &lt;code&gt;before&lt;/code&gt;, handled by the supervisor, with the frozen frame (&lt;code&gt;evaluate_binop&lt;/code&gt;, line 753 of &lt;code&gt;local_python_executor.py&lt;/code&gt;) sitting in the attached &lt;code&gt;worker_faulthandler_stack&lt;/code&gt; extra.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsz43j2346qhu7l8dpxkd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsz43j2346qhu7l8dpxkd.jpg" alt="Sentry issue AgentFrozenError, resolved, environment before, reported by the watchdog after the worker froze" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry's Seer ran root cause analysis on that issue and independently landed on the same conclusion: signal and thread interruption need the GIL, and a single C-level big-int operation never releases it.&lt;/p&gt;

&lt;p&gt;Seer's verdict, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CodeAgent's 2s timeout uses Python signal-based interruption, which cannot fire during uninterruptible C-level big-int operations that hold the GIL. [...] A single large big-integer arithmetic operation runs entirely as one C-level call that holds the GIL continuously without yielding. CPython cannot deliver signals or switch threads during an uninterruptible C extension call, so no timeout callback fires for the duration of that operation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdf9o9kktuixsbba525w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdf9o9kktuixsbba525w.jpg" alt="Sentry Seer Autofix root cause panel identifying the GIL-holding big-int operation as the reason the timeout never fired" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: you cannot interrupt it, so refuse to start it
&lt;/h2&gt;

&lt;p&gt;Killing the computation mid-flight is impossible from Python. But predicting the damage is O(1). Before executing &lt;code&gt;**&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt; on integers, the executor now estimates the result's bit length from the operands' bit lengths:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&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;span class="n"&gt;estimated_bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;       &lt;span class="c1"&gt;# upper bound
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;estimated_bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&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;span class="n"&gt;estimated_bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bit_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above 1 million bits (about 300k digits, still generous) it raises an informative &lt;code&gt;InterpreterError&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operation '**' would produce an integer of around 400000000 bits, exceeding
the maximum of 1000000 bits allowed. Use smaller operands, or
pow(base, exp, mod) for modular exponentiation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That message matters. The agent surfaces it to the model, and the model can actually act on it: use &lt;code&gt;pow(base, exp, mod)&lt;/code&gt;, which stays unrestricted because modular exponentiation is fast and legitimate. The agent recovers on the next step instead of hanging the host.&lt;/p&gt;

&lt;p&gt;The after run on the patched build: the guard rejects the expression in 0.0 seconds, the error lands in Sentry as a normal actionable issue and step 2 executes fine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;watchdog: starting worker (after) with 25s budget
worker: smolagents 1.27.0.dev0
worker: step 1 error surfaced to the model: InterpreterError: ...
worker: step 2 executing '2 + 2'...
worker: step 2 ok
worker: DONE
watchdog: worker exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyx3pwllkkuno7gx6leg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyx3pwllkkuno7gx6leg.jpg" alt="Sentry issue showing the informative InterpreterError captured on the patched build, environment after, transaction CodeAgent math task, resolved" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A robot reviewed my robot fix
&lt;/h2&gt;

&lt;p&gt;Minutes after I opened the PR, OpenAI's Codex reviewer flagged a real hole: my guard checked &lt;code&gt;type(x) is int&lt;/code&gt;, which lets &lt;code&gt;bool&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt; subclasses slip through. &lt;code&gt;True &amp;lt;&amp;lt; 10**9&lt;/code&gt; and &lt;code&gt;class BigInt(int)&lt;/code&gt; still reached the uninterruptible C calls. Fixed with &lt;code&gt;isinstance&lt;/code&gt;, added both as regression tests. AI found the bug class, AI fixed it, AI reviewed the fix. I just steered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Freeze reproduced at 20+ seconds (would have run for hours), external kill required&lt;/li&gt;
&lt;li&gt;Fix rejects the same expression in 0.0 seconds&lt;/li&gt;
&lt;li&gt;9 explosive patterns blocked: &lt;code&gt;**&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;, chained &lt;code&gt;*&lt;/code&gt;, all augmented forms, &lt;code&gt;pow(a, b)&lt;/code&gt;, bool and int subclass variants&lt;/li&gt;
&lt;li&gt;10 legitimate operations verified untouched: 100! via repeated &lt;code&gt;*=&lt;/code&gt;, &lt;code&gt;pow(7, 2**64, 97)&lt;/code&gt;, float pow, &lt;code&gt;1 ** 10**9&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;19 new tests, full test file 416 passed, ruff clean&lt;/li&gt;
&lt;li&gt;The blocked-pattern tests hang forever on unpatched main. I verified that the honest way, with a stash and a kill switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Issue: &lt;a href="https://github.com/huggingface/smolagents/issues/2473" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/issues/2473&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PR: &lt;a href="https://github.com/huggingface/smolagents/pull/2551" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/pull/2551&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Entry #1: &lt;a href="https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im"&gt;https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scariest bugs are not the ones that page you at 3am. They are the ones that make sure nothing ever pages you at all. Instrument for silence.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>I fixed a smolagents bug that confused everyone who hit it (with Sentry watching the whole time)</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Thu, 16 Jul 2026 05:31:48 +0000</pubDate>
      <link>https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im</link>
      <guid>https://dev.to/himanshu_748/i-fixed-a-smolagents-bug-that-confused-everyone-who-hit-it-with-sentry-watching-the-whole-time-1im</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;I picked &lt;a href="https://github.com/huggingface/smolagents" rel="noopener noreferrer"&gt;huggingface/smolagents&lt;/a&gt;, the 28k+ star agent framework where agents literally think in Python code. It had a bug open since &lt;a href="https://github.com/huggingface/smolagents/issues/1108" rel="noopener noreferrer"&gt;issue #1108&lt;/a&gt; that anyone combining MCP tools with agent serialization would eventually slam into.&lt;/p&gt;

&lt;p&gt;Call &lt;code&gt;agent.to_dict()&lt;/code&gt; on a CodeAgent holding MCP tools (or &lt;code&gt;save()&lt;/code&gt; or &lt;code&gt;push_to_hub()&lt;/code&gt;, same path) and you get this beauty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValueError: Tool validation failed for MCPAdaptTool:
Parameters in __init__ must have default values, found required parameters: name, description, inputs, output_type
- forward: Name 'func' is undefined.
- forward: Name 'mcp' is undefined.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;func&lt;/code&gt; is undefined? &lt;code&gt;mcp&lt;/code&gt; is undefined? I never wrote a &lt;code&gt;forward&lt;/code&gt; method. If you hit this in the wild you'd have zero idea what you did wrong. Spoiler: you did nothing wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;smolagents serializes tools by reconstructing standalone Python source for the tool class. &lt;code&gt;Tool.to_dict&lt;/code&gt; calls &lt;code&gt;validate_tool_attributes()&lt;/code&gt; which does static AST analysis, then &lt;code&gt;instance_to_source()&lt;/code&gt; so &lt;code&gt;Tool.from_code&lt;/code&gt; can rebuild the tool later from source alone.&lt;/p&gt;

&lt;p&gt;That contract can never hold for MCP tools. &lt;code&gt;MCPAdaptTool&lt;/code&gt; is generated at runtime by mcpadapt inside a closure. Its &lt;code&gt;__init__&lt;/code&gt; takes required parameters and its &lt;code&gt;forward&lt;/code&gt; closes over the live MCP client session (&lt;code&gt;func&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;logger&lt;/code&gt;...). The tool's actual behavior lives on the MCP server, not in Python source. There is no source to reconstruct, so the AST validator chokes on a class that was never meant to pass it.&lt;/p&gt;

&lt;p&gt;The interesting part: &lt;code&gt;Tool.to_dict&lt;/code&gt; already fails fast with a clear message for three other runtime generated wrappers (Spaces, LangChain, Gradio). MCP tools were just missing from that guard.&lt;/p&gt;

&lt;p&gt;Sometimes the right fix isn't making the impossible possible, it's failing loudly and helpfully. Recreating a live MCP session from serialized state would mean silently re-establishing server connections with credentials and trust decisions the library has no business making. So I extended the existing guard to detect MCP tools and raise this instead:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValueError: Cannot serialize MCP tool 'echo_tool': it wraps a live MCP server session, which cannot be
saved as standalone code. Remove MCP tools from your agent before calling to_dict, save or push_to_hub,
and recreate them with MCPClient or ToolCollection.from_mcp when loading the agent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From "what is func" to "here's exactly what to do instead" in one guard clause.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/huggingface/smolagents/pull/2528" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Raise informative error when serializing MCP tools
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#2528&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F77563702%3Fv%3D4" alt="himanshu748 avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;himanshu748&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/huggingface/smolagents/pull/2528" rel="noopener noreferrer"&gt;&lt;time&gt;Jul 14, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Fixes #1108&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Problem&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Calling &lt;code&gt;to_dict()&lt;/code&gt; (and therefore &lt;code&gt;save()&lt;/code&gt; or &lt;code&gt;push_to_hub()&lt;/code&gt;) on an agent that holds MCP tools crashes with a confusing internal error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ValueError: Tool validation failed for MCPAdaptTool:
Parameters in __init__ must have default values, found required parameters: name, description, inputs, output_type
- forward: Name 'func' is undefined.
- forward: Name 'mcp' is undefined.
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reproduction (stdio MCP server, same shape as the tests in &lt;code&gt;tests/test_mcp_client.py&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight highlight-source-python js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;mcp&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;StdioServerParameters&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;smolagents&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;CodeAgent&lt;/span&gt;, &lt;span class="pl-v"&gt;InferenceClientModel&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;smolagents&lt;/span&gt;.&lt;span class="pl-s1"&gt;mcp_client&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;MCPClient&lt;/span&gt;

&lt;span class="pl-s1"&gt;server_parameters&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;StdioServerParameters&lt;/span&gt;(&lt;span class="pl-s1"&gt;command&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"python"&lt;/span&gt;, &lt;span class="pl-s1"&gt;args&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s"&gt;"-c"&lt;/span&gt;, &lt;span class="pl-s1"&gt;echo_server_script&lt;/span&gt;])
&lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;MCPClient&lt;/span&gt;(&lt;span class="pl-s1"&gt;server_parameters&lt;/span&gt;) &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-s1"&gt;tools&lt;/span&gt;:
    &lt;span class="pl-s1"&gt;agent&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;CodeAgent&lt;/span&gt;(&lt;span class="pl-s1"&gt;model&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-en"&gt;InferenceClientModel&lt;/span&gt;(), &lt;span class="pl-s1"&gt;tools&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-en"&gt;list&lt;/span&gt;(&lt;span class="pl-s1"&gt;tools&lt;/span&gt;))
    &lt;span class="pl-s1"&gt;agent&lt;/span&gt;.&lt;span class="pl-c1"&gt;to_dict&lt;/span&gt;()  &lt;span class="pl-c"&gt;# ValueError: Tool validation failed for MCPAdaptTool: ...&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Root cause&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Tool.to_dict&lt;/code&gt; serializes a tool by reconstructing standalone source code for its class: it calls &lt;code&gt;validate_tool_attributes(self.__class__)&lt;/code&gt; and &lt;code&gt;instance_to_source(...)&lt;/code&gt; so that &lt;code&gt;Tool.from_code&lt;/code&gt; can later rebuild the tool from that source alone.&lt;/p&gt;
&lt;p&gt;That contract cannot hold for MCP tools. &lt;code&gt;MCPAdaptTool&lt;/code&gt; is generated at runtime by &lt;code&gt;mcpadapt&lt;/code&gt;, its &lt;code&gt;__init__&lt;/code&gt; takes required parameters and its &lt;code&gt;forward&lt;/code&gt; is a closure over the live MCP client session (&lt;code&gt;func&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;logger&lt;/code&gt;, ...). The tool's behavior lives on the MCP server, not in Python source, and the underlying connection is not serializable, so source reconstruction fails validation with the cryptic error above.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tool.to_dict&lt;/code&gt; already fails fast with a clear message for the other three runtime generated wrapper classes (&lt;code&gt;SpaceToolWrapper&lt;/code&gt;, &lt;code&gt;LangChainToolWrapper&lt;/code&gt;, &lt;code&gt;GradioToolWrapper&lt;/code&gt;). MCP tools were missing from that guard.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Fix&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Extend the existing guard in &lt;code&gt;Tool.to_dict&lt;/code&gt; to detect MCP tools and raise an actionable error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ValueError: Cannot serialize MCP tool 'echo_tool': it wraps a live MCP server session, which cannot be
saved as standalone code. Remove MCP tools from your agent before calling to_dict, save or push_to_hub,
and recreate them with MCPClient or ToolCollection.from_mcp when loading the agent.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Detection matches the runtime class name, following the existing convention in the same block, since &lt;code&gt;mcpadapt&lt;/code&gt; is an optional dependency. The &lt;code&gt;from_dict&lt;/code&gt; direction needs no change: serialization now fails fast with a clear message, and recreating a live MCP session is a user decision (server lifecycle, credentials, trust) that &lt;code&gt;Tool.from_code&lt;/code&gt; could never perform safely.&lt;/p&gt;
&lt;p&gt;A note documenting the limitation is added to the MCP section of the tools tutorial.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tests&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Two tests in &lt;code&gt;tests/test_mcp_client.py&lt;/code&gt;, using the existing &lt;code&gt;echo_server_script&lt;/code&gt; stdio fixture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;test_mcp_tool_to_dict_raises_informative_error&lt;/code&gt;: &lt;code&gt;tool.to_dict()&lt;/code&gt; raises the clear error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_agent_to_dict_with_mcp_tool_raises_informative_error&lt;/code&gt;: &lt;code&gt;CodeAgent.to_dict()&lt;/code&gt; raises the clear error (the exact scenario from the issue).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both fail on &lt;code&gt;main&lt;/code&gt; with the old &lt;code&gt;Tool validation failed for MCPAdaptTool&lt;/code&gt; error and pass with this change. &lt;code&gt;make quality&lt;/code&gt; passes. &lt;code&gt;tests/test_mcp_client.py&lt;/code&gt; (7 passed), &lt;code&gt;tests/test_tools.py&lt;/code&gt; and the agent serialization tests in &lt;code&gt;tests/test_agents.py&lt;/code&gt; pass locally; the two pre-existing failures in &lt;code&gt;test_integration_from_mcp_with_streamable_http&lt;/code&gt; and &lt;code&gt;test_integration_from_mcp_with_sse&lt;/code&gt; also fail on a clean &lt;code&gt;main&lt;/code&gt; checkout (local port binding) and are unrelated.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/huggingface/smolagents/pull/2528" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;PR: &lt;a href="https://github.com/huggingface/smolagents/pull/2528" rel="noopener noreferrer"&gt;https://github.com/huggingface/smolagents/pull/2528&lt;/a&gt; (Fixes #1108)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favbbb6xwpf3wwx61e879.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favbbb6xwpf3wwx61e879.png" alt="PR #2528 on GitHub" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two new tests using the existing stdio echo server fixture, both fail on main and pass with the fix. &lt;code&gt;make quality&lt;/code&gt; clean. Docs note added to the MCP tools tutorial so nobody has to learn this the hard way again.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Users hitting this now get an actionable error instead of AST validator internals&lt;/li&gt;
&lt;li&gt;The fix follows the repo's existing convention exactly (same guard block, same style as the Space/LangChain/Gradio cases), which is what makes a one-commit PR actually mergeable&lt;/li&gt;
&lt;li&gt;Documented the limitation where users would look for it&lt;/li&gt;
&lt;li&gt;Regression tests covering both the raw tool and the full CodeAgent scenario from the original issue&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;p&gt;This is where it gets fun. I built a small demo agent app (a weather checkpoint agent using my patched smolagents with an MCP tool) and wired in the Sentry Python SDK with error monitoring, tracing and AI agent monitoring before touching the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: catch the crash.&lt;/strong&gt; Running the demo on unpatched smolagents, the cryptic ValueError landed straight in Sentry as an unhandled issue with the full 20+ line "undefined name" spam captured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdvqbayupa9s1hgzx56k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdvqbayupa9s1hgzx56k.png" alt="Sentry issue: the cryptic ValueError captured, now marked resolved" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: let Seer take a shot.&lt;/strong&gt; I ran Seer root cause analysis on the captured issue. Its diagnosis, fully independent of my PR:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCPAdaptTool is a dynamically generated inner class created by mcpadapt's SmolAgentsAdapter.adapt() closure, so its &lt;strong&gt;init&lt;/strong&gt; has required parameters and its methods reference closure variables that are not visible as class-level attributes... making it fundamentally incompatible with smolagents' static source-code validation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is, almost line for line, the root cause I wrote in the PR. AI-assisted debugging where the AI and the human converge on the same diagnosis independently is exactly the confidence check you want before shipping a fix upstream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flliddoqvtunwmbtac1u3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flliddoqvtunwmbtac1u3.png" alt="Seer root cause analysis of the MCPAdaptTool validation failure" width="800" height="444"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Step 3: verify and resolve.&lt;/strong&gt; Same demo on the patched version runs clean, agent traces show the gen_ai spans (invoke_agent, execute_tool) nested under the workflow and the Sentry issue is marked resolved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwv9oosy8uweowcm25dj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwv9oosy8uweowcm25dj8.png" alt="Agent trace with gen_ai spans: invoke_agent, checkpoint_agent and execute_tool" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Used the &lt;code&gt;bugsmash26&lt;/code&gt; code for the $100 credits too. Thanks Sentry 🛹&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Serialization boundaries are where abstractions leak. smolagents' "tools are source code" model is elegant right up until a tool is actually a live network session wearing a Tool costume. The mature move for a library isn't to pretend otherwise, it's to name the limitation clearly at the exact moment the user hits it.&lt;/p&gt;

&lt;p&gt;Also: watching Seer independently arrive at your root cause is a genuinely great feeling. Like a second engineer nodding at your RCA.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built during DEV's first Summer Bug Smash. Find me on GitHub &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;@himanshu748&lt;/a&gt; or X &lt;a href="https://x.com/jhahimanshu653" rel="noopener noreferrer"&gt;@jhahimanshu653&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Traced a Multi-Step LLM Agent With Self-Hosted SigNoz. One Feature Sold Me.</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 08:22:06 +0000</pubDate>
      <link>https://dev.to/himanshu_748/i-traced-a-multi-step-llm-agent-with-self-hosted-signoz-one-feature-sold-me-4k71</link>
      <guid>https://dev.to/himanshu_748/i-traced-a-multi-step-llm-agent-with-self-hosted-signoz-one-feature-sold-me-4k71</guid>
      <description>&lt;p&gt;Multi-step LLM agents fail in a way normal backends don't. Nothing crashes. The pipeline "works", the answer is just bad, slow or three times more expensive than yesterday. &lt;code&gt;print()&lt;/code&gt; debugging tells you nothing, because the interesting question is never "did step 3 run". It is "what did step 3 see, which model actually answered and what did it cost".&lt;/p&gt;

&lt;p&gt;So I self-hosted SigNoz and pointed a simulated agent pipeline at it: a four-step research assistant (plan, retrieve, generate, synthesize) instrumented with OpenTelemetry, emitting traces, metrics and logs, with GenAI semantic-convention attributes (&lt;code&gt;gen_ai.request.model&lt;/code&gt;, &lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt; and friends) on every LLM call.&lt;/p&gt;

&lt;p&gt;This post is about the feature that turned out to be the most useful. It was not the one I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: one CLI, eight lines of YAML
&lt;/h2&gt;

&lt;p&gt;Self-hosting used to mean wrangling a long docker-compose file. SigNoz now ships &lt;strong&gt;Foundry&lt;/strong&gt;, a small CLI that casts the whole stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://signoz.io/foundry.sh | bash   &lt;span class="c"&gt;# installs foundryctl (checksum-verified)&lt;/span&gt;
foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml                  &lt;span class="c"&gt;# deploys the full stack on Docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a &lt;code&gt;casting.yaml&lt;/code&gt; that is all of eight lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Installation&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compose&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few minutes of image pulls later, ClickHouse, Postgres, the SigNoz backend and an OTel collector were running. The UI is on &lt;code&gt;localhost:8080&lt;/code&gt; and the collector listens on &lt;code&gt;4317&lt;/code&gt; (gRPC) and &lt;code&gt;4318&lt;/code&gt; (HTTP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One gotcha worth knowing:&lt;/strong&gt; telemetry is rejected until you create the admin account in the UI. The collector registers with the backend over OpAMP, and until an organization exists the backend answers "cannot create agent without orgId" and the OTLP ports reset every connection. If your exporter logs &lt;code&gt;Connection reset by peer&lt;/code&gt; on a fresh install, you haven't finished the two-minute signup at &lt;code&gt;localhost:8080&lt;/code&gt; yet. Create the account and ingestion starts working within about thirty seconds, no restarts needed.&lt;/p&gt;

&lt;p&gt;My demo app needed zero SigNoz-specific code: the stock OpenTelemetry SDK exporting OTLP to &lt;code&gt;localhost:4318&lt;/code&gt;. That is the point of an OTel-native backend. There is no vendor agent, so nothing about the app knows SigNoz exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline being observed
&lt;/h2&gt;

&lt;p&gt;Each simulated request produces one trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent.request                    (root, 2.02s)
├── agent.plan                   (850ms)
│   └── gen_ai.generate plan     (696ms, gen_ai.* attributes)
├── agent.retrieve               (461ms, sometimes errors: vector store timeout)
├── agent.generate               (564ms)
│   └── gen_ai.generate answer   (564ms, gen_ai.* attributes)
└── agent.synthesize             (139ms, sometimes errors: citation validation)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;gen_ai.generate&lt;/code&gt; span carries the GenAI semantic conventions: operation name, provider, model, input tokens and output tokens. Alongside the traces the app emits counters for token usage (&lt;code&gt;gen_ai.client.token.usage&lt;/code&gt;) and estimated spend (&lt;code&gt;agent.llm.cost&lt;/code&gt;), both tagged by model and provider, a request-duration histogram and structured logs that inherit the active trace context automatically.&lt;/p&gt;

&lt;p&gt;Sixty simulated requests later: 420 spans, 134 logs and a few hundred metric samples, all visible in the UI. The Services page picked up &lt;code&gt;research-assistant&lt;/code&gt; on its own with RED metrics already computed (p99 latency, error rate, throughput). I wrote no configuration for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqko3iqz9abfr3myg78p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqko3iqz9abfr3myg78p.png" alt="Trace waterfall of one agent request: plan, retrieve, generate and synthesize with the LLM calls nested inside" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature I expected to love: the trace waterfall
&lt;/h2&gt;

&lt;p&gt;And it is genuinely good. The trace detail view renders a flame graph and waterfall that read exactly like the agent's mental model: plan, then retrieve, then generate, then synthesize, with the LLM calls nested inside the steps that made them. Clicking any &lt;code&gt;gen_ai.generate&lt;/code&gt; span opens a details panel with every attribute I set: model &lt;code&gt;llama-4-maverick&lt;/code&gt;, provider &lt;code&gt;meta&lt;/code&gt;, 1501 input tokens, 90 output tokens, plus a percentile badge telling me this span sat at p10 of its peers. "Why did this request take four seconds" stops being a mystery and becomes a picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrcutqewbie3igmb7vdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrcutqewbie3igmb7vdm.png" alt="Span details panel showing the gen_ai.* attributes: provider, model and token counts" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But a pretty waterfall is table stakes for a tracing tool. The thing that sold me was what happens around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature I actually loved: your attributes become the query language
&lt;/h2&gt;

&lt;p&gt;Here is the moment it clicked. In the Trace Explorer I typed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gen_ai.request.model = 'qwen3-coder-plus'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happened. First, the autocomplete suggested &lt;code&gt;qwen3-coder-plus&lt;/code&gt; before I finished typing, because SigNoz had already indexed the values of an attribute I invented an hour earlier. Second, the results came back instantly: only the LLM spans that were served by that model, across every trace in the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkpcoub6jg5sr5u33ltk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkpcoub6jg5sr5u33ltk.png" alt="Trace Explorer filtering all spans by a custom GenAI attribute, with value autocomplete" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stop and consider what that means for agent debugging. I never told SigNoz what &lt;code&gt;gen_ai.request.model&lt;/code&gt; is. There is no schema registration, no field mapping, no config file. Any attribute your instrumentation emits is immediately a first-class, autocompleted, indexed query dimension. Your instrumentation vocabulary &lt;em&gt;becomes the product's vocabulary&lt;/em&gt;. For LLM systems, where all the interesting facts live in custom attributes (model, provider, token counts, agent role, tool name), this is the difference between an observability tool that fits and one you fight with.&lt;/p&gt;

&lt;p&gt;And the same query keeps working as you move across signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Span to logs, one click.&lt;/strong&gt; From any span's details panel, the Logs tab jumps into the Logs Explorer with &lt;code&gt;trace_id = '&amp;lt;this trace&amp;gt;'&lt;/code&gt; pre-filled and the time window auto-scoped. I got back exactly the two log lines belonging to that request, correlated purely by the trace context the OTel logging handler injects. Nobody parses log lines to find a request id. The correlation is structural.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query to dashboard or alert, two clicks.&lt;/strong&gt; Every explorer view has "Add to Dashboard" and "Create an Alert" buttons at the bottom. The query I used to investigate becomes the panel that monitors and the alert that pages, without re-expressing it in a different query language. The investigate-then-monitor loop is one surface, not two tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics inherit the same attributes.&lt;/strong&gt; The Metrics Explorer summary listed my custom metrics with their types and units the moment they arrived: &lt;code&gt;gen_ai.client.token.usage&lt;/code&gt; showing 6 time series (3 models times 2 token types), &lt;code&gt;agent.llm.cost&lt;/code&gt; in usd showing 3. Cost per model is a group-by away, using the exact attribute names from my instrumentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most observability stacks treat custom attributes as second-class blobs that need schema work before they are queryable. SigNoz treats them as the whole point. For agent systems, they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else is in the box
&lt;/h2&gt;

&lt;p&gt;Going deep on one feature meant walking past a lot of others. Quick notes from the tour, agent-flavored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Services (APM) pages, zero config.&lt;/strong&gt; Any service that sends spans gets RED metrics automatically: request rate, error rate, latency percentiles. In a multi-agent system every agent that traces becomes its own monitored service for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards.&lt;/strong&gt; Panel grids built with the same query builder, so traces, logs and metrics coexist on one board, importable and exportable as JSON. A "cost per model per agent" board takes minutes because the query language is the one you already know.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerts with notification channels.&lt;/strong&gt; Threshold or anomaly rules on any query, delivered to Slack, PagerDuty or a plain webhook. That webhook option is quietly powerful for agent systems: an alert can call your own service and close the loop from observability back into behavior, e.g. demoting a flaky model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace Funnels (beta).&lt;/strong&gt; Define a sequence of span conditions and measure conversion and latency between the steps across all traces. For a pipeline like mine that is literally a success funnel: how many requests that planned also retrieved, generated and synthesized cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exceptions.&lt;/strong&gt; Recurring span errors cluster into groups instead of being scattered across individual traces, so "vector store timeout" is one row with a count, not forty needles in a haystack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs Pipelines.&lt;/strong&gt; Collector-side log pre-processing (parse JSON, extract or drop fields) before storage, for when you can't change the code that emits the logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics summary as a cardinality watchdog.&lt;/strong&gt; The same inventory view that showed my metric types also shows time-series counts per metric, which is where you catch a label-explosion mistake before it hurts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these uses the attribute vocabulary your instrumentation defines. That is the theme of the whole product, and it is why the one feature I picked is really the foundation the rest stand on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you're building agents
&lt;/h2&gt;

&lt;p&gt;The uncomfortable truth about multi-agent LLM systems is that their most important behavior (which model ran, what it consumed, what it cost, why it was retried) is invisible to conventional monitoring. It lives entirely in domain-specific span attributes. A backend that makes those attributes instantly queryable, correlatable across traces and logs, and promotable into dashboards and alerts is not a nice-to-have there. It is the debugger.&lt;/p&gt;

&lt;p&gt;The whole experiment cost me an afternoon: one CLI install, eight lines of YAML, a stock OTel SDK and zero vendor code in the app. I'm taking this setup into the Agents of SigNoz hackathon (July 20 to 26), where the plan is considerably less simulated. If you're building anything agent-shaped, self-host SigNoz and type one of your own attribute names into the Trace Explorer. That autocomplete dropdown is the moment you'll get it too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The demo app is a 170-line Python script using only &lt;code&gt;opentelemetry-sdk&lt;/code&gt; and the OTLP HTTP exporter. Stack: SigNoz self-hosted via Foundry on Docker (ClickHouse + Postgres + OTel collector), macOS host.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Demo code, self-host config and blog source: &lt;a href="https://github.com/himanshu748/signoz-agent-observability" rel="noopener noreferrer"&gt;github.com/himanshu748/signoz-agent-observability&lt;/a&gt;. Team 404Found.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>signoz</category>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>ai</category>
    </item>
    <item>
      <title>My Abandoned Cricket Kit Confronted Me. So I Built It a Voice</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 03:10:15 +0000</pubDate>
      <link>https://dev.to/himanshu_748/my-abandoned-cricket-kit-confronted-me-so-i-built-it-a-voice-ph1</link>
      <guid>https://dev.to/himanshu_748/my-abandoned-cricket-kit-confronted-me-so-i-built-it-a-voice-ph1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;DEV Weekend Challenge: Passion Edition&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Everyone will tell you about the passions they have. Nobody talks about the ones they quit.&lt;/p&gt;

&lt;p&gt;I played cricket every evening from age 11 to 17. I told everyone I'd play Ranji Trophy one day. Then the entrance exam years came, the bat went behind the cupboard, and I never went back. Eight years now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EMBER gives that abandoned passion a voice.&lt;/strong&gt; You confess what you quit. AI forges its persona: the dusty object, the game itself, or the younger you. Then it &lt;em&gt;talks back&lt;/em&gt;, out loud, in a voice matched to its temperament. It asks the question only it can ask: &lt;em&gt;why did you really stop?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then it offers two doors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 &lt;strong&gt;Rekindle it.&lt;/strong&gt; It negotiates the smallest possible first step ("Pick up your old bat and feel its weight. Sunday evening.") and you seal the pledge &lt;strong&gt;on-chain&lt;/strong&gt;, where you can't quietly delete it.&lt;/li&gt;
&lt;li&gt;🕯️ &lt;strong&gt;Lay it to rest.&lt;/strong&gt; It says goodbye properly: a personal eulogy, spoken aloud, and a permanent on-chain stone. Closure is a feature, not a failure state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every anonymized session joins the &lt;strong&gt;Atlas of Abandoned Passions&lt;/strong&gt;, a live map of what humanity gives up, at what age, and what killed it.&lt;/p&gt;

&lt;p&gt;When I ran my own confession through it, the app decided my passion should speak as "&lt;strong&gt;Your old cricket kit bag&lt;/strong&gt;." Its first words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's been a while since you hoisted me up here, hasn't it? I still remember the thrill of a good cover drive, too."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built a thing and it emotionally wrecked me on the first test run. Working as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/9ZJ2rCCzg0U"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="//ember-five-cyan.vercel.app"&gt;ember-five-cyan.vercel.app&lt;/a&gt;&lt;br&gt;
Try it in two clicks: tap an example confession (cricket at 17, the closet guitar, the novel at chapter three), headphones on. The voice is the point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ozg5j5768u25wujxg8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ozg5j5768u25wujxg8m.png" alt="The things we leave behind" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My cricket pledge, sealed on Solana devnet (the memo reads: passion cricket, commitment: book one hour in the nets this week): &lt;a href="https://explorer.solana.com/tx/2de9Lj1o5xUDb8Hg6qKkRfaCYKaV1AFt8v1q7zpUDS4DBuUdijZaBNWiN3ozXHD2H6MB4CUjZvKkYJSavY68hmDK?cluster=devnet" rel="noopener noreferrer"&gt;view the transaction&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/himanshu748/ember" rel="noopener noreferrer"&gt;https://github.com/himanshu748/ember&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The loop is confess, converse, decide, commit, belong. Each stage is one sponsor technology doing what it is uniquely good at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google AI (Gemini): the persona compiler
&lt;/h3&gt;

&lt;p&gt;Gemini doesn't chat with you. It reads your confession and forges the character that will. Structured extraction of your story (&lt;code&gt;years_dormant&lt;/code&gt;, &lt;code&gt;abandonment_reason&lt;/code&gt;, &lt;code&gt;emotional_tone&lt;/code&gt;), then an &lt;strong&gt;embodiment decision&lt;/strong&gt;: should the &lt;em&gt;object&lt;/em&gt; speak (the kit bag), the &lt;em&gt;passion itself&lt;/em&gt; (cricket, personified), or &lt;em&gt;the younger you&lt;/em&gt;? It writes the persona's system prompt, its opening line, every conversational reply, and finally the eulogy or the negotiated revival pact. Strict persona rules: it misses you, it never guilt-trips, wry beats weepy.&lt;/p&gt;

&lt;h3&gt;
  
  
  ElevenLabs: the voice
&lt;/h3&gt;

&lt;p&gt;The persona's temperament maps to a curated voice (wistful is Sarah, wry is George, bitter is Callum). Every line the passion speaks arrives as real audio. Hearing your abandoned passion say things out loud is the difference between a chatbot and a séance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snowflake: the Atlas
&lt;/h3&gt;

&lt;p&gt;Every session lands in a Snowflake &lt;code&gt;sessions&lt;/code&gt; table, and the Atlas page is pure live SQL: most abandoned passions, what killed them, dormancy years, rekindle rate. Snowflake is also the system of record for session state. The app runs serverless, so persona and conversation context are reconstructed from Snowflake on every request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solana: the commitment device
&lt;/h3&gt;

&lt;p&gt;A pledge you can edit is a wish. When you choose to rekindle, Ember creates a dedicated on-chain account for your pledge and locks a real stake in it (0.01 SOL on devnet), with the commitment memo in the same transaction. Your pledge is not a database row. It is an address you can watch.&lt;/p&gt;

&lt;p&gt;When you return to your stone and report that you did it, the persona reacts in its own voice and the stake settles into the public &lt;a href="https://explorer.solana.com/address/BEsKKCCtvEGnfDtyu3BhESVP1j65AR4BvfPATbbmLVLv?cluster=devnet" rel="noopener noreferrer"&gt;Rekindled Pool&lt;/a&gt;, with a fulfillment memo sealed against the original pledge in the same transaction. Commitment, stake, follow-up, settlement: the whole loop lives on chain. A commitment with a follow-up is a system, not a receipt.&lt;/p&gt;

&lt;p&gt;Want to verify the whole lifecycle in under a minute? Here is a full example from a real session: the &lt;a href="https://explorer.solana.com/tx/NyZj6z79tTj8nA29QngPBM1fEDCotZzaYNwNUpgWhE1iUosNHQWXyXqN6amEHq3RCn3fsevKT2X4BuWXJgZ7VRX?cluster=devnet" rel="noopener noreferrer"&gt;pledge creation transaction&lt;/a&gt; that funded the pledge account, and the &lt;a href="https://explorer.solana.com/tx/3vV4UJeS6eLSCx3eGsjW6KNmFympjNUkwWR3ursRNEwuLQtmc8E6wkQLaA27eMH9gN4VBq2z4pmQGho1bNaJmLGY?cluster=devnet" rel="noopener noreferrer"&gt;fulfillment transaction&lt;/a&gt; that settled its stake into the pool when the pledge was kept. And the Atlas claim is checkable too: the &lt;a href="https://ember-himanshus-projects-acd54afd.vercel.app/api/atlas" rel="noopener noreferrer"&gt;live API response&lt;/a&gt; returns source: snowflake with the current session count. (Thanks to a great comment below for pushing this.)&lt;/p&gt;

&lt;p&gt;Eulogy stones remain permanent memorial attestations via the Memo program. No wallet needed: a server-side vault signs, so you can go from confession to on-chain proof in one sitting. A mainnet version would swap the vault for a wallet-signed escrow program.&lt;/p&gt;

&lt;p&gt;A note on the economics, because it matters: devnet SOL is test currency with zero monetary value, and every stake is funded by Ember's own vault. Players never pay anything, never connect a wallet, and never have money at risk. On mainnet the model inverts, and that inversion is the whole point: the stake would be your own SOL, locked by your own wallet in an escrow program, and the only way to get it back would be to actually keep your pledge. Here the economics are simulated; the mechanics (funded pledge accounts, auditable settlement) are the real thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;p&gt;Next.js 16 · &lt;code&gt;@google/genai&lt;/code&gt; · ElevenLabs TTS · &lt;code&gt;snowflake-sdk&lt;/code&gt; · &lt;code&gt;@solana/web3.js&lt;/code&gt; · Tailwind v4&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Google AI, Best Use of ElevenLabs, Best Use of Snowflake, Best Use of Solana.&lt;/strong&gt; The four aren't features bolted onto an app. Each one is a load-bearing stage of a single emotional pipeline.&lt;/p&gt;




&lt;p&gt;There is a closet like yours in every house on earth. What's in yours? The Atlas is waiting. 🔥&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>solana</category>
    </item>
    <item>
      <title>Vegas Amnesia: I turned Cognee's memory lifecycle into a detective game</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:53:15 +0000</pubDate>
      <link>https://dev.to/himanshu_748/vegas-amnesia-i-turned-cognees-memory-lifecycle-into-a-detective-game-4nga</link>
      <guid>https://dev.to/himanshu_748/vegas-amnesia-i-turned-cognees-memory-lifecycle-into-a-detective-game-4nga</guid>
      <description>&lt;p&gt;&lt;em&gt;Built for the WeMakeDevs × Cognee "The Hangover Part AI" hackathon — Cognee Cloud track.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;▶ Play it free: &lt;a href="https://vegas-amnesia.vercel.app" rel="noopener noreferrer"&gt;vegas-amnesia.vercel.app&lt;/a&gt;  ·  ⭐ &lt;a href="https://github.com/himanshu748/vegas-amnesia" rel="noopener noreferrer"&gt;Code on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnsam4aw0t13vdjzykjnb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnsam4aw0t13vdjzykjnb.gif" alt="Vegas Amnesia gameplay" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with most memory demos
&lt;/h2&gt;

&lt;p&gt;When you give a developer a memory API, the demo almost always looks the same: &lt;code&gt;add()&lt;/code&gt; some documents, &lt;code&gt;search()&lt;/code&gt; over them, print the answer. Two functions. It works, it's fine, and it teaches you almost nothing about &lt;em&gt;why&lt;/em&gt; graph-based memory is different from stuffing everything into a context window.&lt;/p&gt;

&lt;p&gt;Cognee actually has a &lt;strong&gt;four-stage lifecycle&lt;/strong&gt; — &lt;code&gt;remember → recall → memify → forget&lt;/code&gt; — and the interesting parts are the two everyone skips. &lt;code&gt;memify&lt;/code&gt; consolidates what you know into &lt;em&gt;new&lt;/em&gt; inferences. &lt;code&gt;forget&lt;/code&gt; lets you &lt;em&gt;delete&lt;/em&gt; a belief and watch the graph heal around it. Memory you can reason over &lt;strong&gt;and correct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So instead of writing another RAG demo, I asked: what if the memory lifecycle wasn't the plumbing — what if it was the &lt;em&gt;game&lt;/em&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet HAL-9001
&lt;/h2&gt;

&lt;p&gt;You play &lt;strong&gt;HAL-9001&lt;/strong&gt;, a personal AI assistant (yes, HAL 9000's slightly more helpful successor). Your owner Dev had a wild night in Vegas. At 6 AM your memory graph was corrupted. His fiancée Priya lands at noon, there's a suspicious ring on his finger, and you remember &lt;strong&gt;nothing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The screen boots to a "MEMORY CORRUPTED" terminal and an empty graph. Your job: reconstruct the night, catch the lies, and answer the final question — &lt;em&gt;what happened, and where's the ring?&lt;/em&gt; — before noon.&lt;/p&gt;

&lt;p&gt;Every location you explore, every clue you examine, every witness you interrogate feeds a &lt;strong&gt;live 3D memory graph&lt;/strong&gt; that you can pop open at any time. That graph isn't a visualization &lt;em&gt;of&lt;/em&gt; the game state. It &lt;strong&gt;is&lt;/strong&gt; the game state — it's your Cognee dataset, rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four mechanics = the four lifecycle ops
&lt;/h2&gt;

&lt;p&gt;Here's the mapping I'm most proud of. Each Cognee operation is a verb the player performs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You do this in-game&lt;/th&gt;
&lt;th&gt;Cognee Cloud call&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🗂 &lt;strong&gt;File It&lt;/strong&gt; on a clue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api/v1/remember&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The fact is ingested + auto-cognified into graph nodes that pop into view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❓ &lt;strong&gt;Ask HAL&lt;/strong&gt; a question&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api/v1/recall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You get an answer &lt;em&gt;with citations&lt;/em&gt; — the source nodes pulse amber in the graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧠 &lt;strong&gt;Connect the Dots&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /api/v1/cognify&lt;/code&gt; (inference prompt)&lt;/td&gt;
&lt;td&gt;HAL derives new insights; purple inference nodes appear, wired to their premises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🗑 &lt;strong&gt;Forget&lt;/strong&gt; a lie&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api/v1/forget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The memory is deleted for real — nodes fade out and the graph re-settles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two design decisions made this click:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Filing is a choice.&lt;/strong&gt; Inspecting a clue is free and instant. &lt;em&gt;Filing&lt;/em&gt; it commits it to Cognee. That matters because &lt;strong&gt;not every clue is true&lt;/strong&gt; — I seeded five red herrings into the story (a lipstick-stained napkin, a stray pawn ticket, a keycard for the wrong room). File a lie and it poisons your memory; the only cure is &lt;code&gt;forget&lt;/code&gt;. Suddenly &lt;code&gt;forget&lt;/code&gt; isn't a button you press to show off an API — it's how you &lt;em&gt;win&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The witnesses can see your graph.&lt;/strong&gt; Rosa the bartender, Lucky Lou the evasive pawnbroker, Rev. Sonny the chapel officiant, and Chad the hungover best man are all &lt;strong&gt;LLM-driven&lt;/strong&gt; (Qwen2.5-72B). Their system prompt includes &lt;em&gt;what your memory graph currently contains&lt;/em&gt;. So they react: "You already know about the pawn shop? Then let me tell you this..."&lt;/p&gt;

&lt;p&gt;And Lucky Lou &lt;strong&gt;lies&lt;/strong&gt;. He claims Dev never came into his shop. But if you've filed the pawn receipt, your graph now holds a fact that directly contradicts him — and the game can surface the contradiction. That single moment, watching structured memory catch a liar, is the entire thesis of graph-based agent memory in one interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; browser — vanilla JS + three.js 3D force graph (zero framework)
   │  file evidence · interrogate · connect-the-dots · forget · Ask HAL
   ▼
 FastAPI (single container: API + static frontend)
   │  session ⇄ its own Cognee dataset · graph-delta snapshots · solve scoring
   │  llm.py — graph-aware character dialogue (Qwen2.5-72B via HF Inference)
   ▼
 Cognee Cloud — remember / recall / memify / forget
   └─ GET /datasets/{id}/graph → animated into the 3D memory panel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things I did specifically to use Cognee &lt;em&gt;deeply&lt;/em&gt; rather than superficially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One dataset per playthrough.&lt;/strong&gt; Each session mints a fresh &lt;code&gt;vegas_&amp;lt;id&amp;gt;&lt;/code&gt; dataset, so two players (or two demo runs) never see each other's memories. Reset deletes the dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental graph deltas.&lt;/strong&gt; Every backend response carries a &lt;code&gt;graph_delta&lt;/code&gt; (added/removed nodes and edges) so the front end animates &lt;em&gt;exactly&lt;/em&gt; what changed instead of re-fetching the world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citations end to end.&lt;/strong&gt; Recall requests set &lt;code&gt;includeReferences&lt;/code&gt;, and the final ending screen reconstructs the whole night as a timeline where every line cites the memory it came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The receipts.&lt;/strong&gt; Press backtick in-game and you get a live log of every Cognee call — operation, dataset, latency, status. Partly for debugging, mostly because I wanted the lifecycle usage to be &lt;em&gt;inspectable&lt;/em&gt;, not just claimed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The bit that fought me
&lt;/h3&gt;

&lt;p&gt;Cognee's &lt;code&gt;remember&lt;/code&gt; endpoint is multipart and auto-cognifies, which is lovely — but the response's &lt;code&gt;items&lt;/code&gt; list is &lt;strong&gt;cumulative&lt;/strong&gt; for the dataset, not just the thing you posted. My first version happily mapped the wrong &lt;code&gt;data_id&lt;/code&gt; to each fact, which quietly broke &lt;code&gt;forget&lt;/code&gt;. The fix was to name each data item by its fact id and resolve ids by name after ingest. Lesson: read what the API &lt;em&gt;returns&lt;/em&gt;, not what you assume it returns.&lt;/p&gt;

&lt;p&gt;The other one: my tenant doesn't expose a dedicated &lt;code&gt;/memify&lt;/code&gt;, so — per the "closest equivalent" rule — I implemented consolidation as a &lt;code&gt;cognify&lt;/code&gt; re-run with a custom inference-extraction prompt, plus a derivation layer that remembers ground-truth inferences once their premises are all in memory. That's how "connect the dots" reliably produces those purple insight nodes on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building agent memory
&lt;/h2&gt;

&lt;p&gt;The context-window arms race is the wrong frame for a lot of agent problems. What you often actually want is memory you can &lt;strong&gt;inspect, reason over, and correct&lt;/strong&gt; — add a belief, derive consequences, and &lt;em&gt;retract&lt;/em&gt; a belief when it turns out to be a lie, watching everything downstream update. That's a knowledge graph, and building a game on top of Cognee made that concrete in a way a RAG script never did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🎮 &lt;strong&gt;Play:&lt;/strong&gt; &lt;a href="https://vegas-amnesia.vercel.app" rel="noopener noreferrer"&gt;vegas-amnesia.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🎬 &lt;strong&gt;63-second demo:&lt;/strong&gt; &lt;a href="https://youtu.be/MM1nnQxJARo" rel="noopener noreferrer"&gt;watch on YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;Code + full README:&lt;/strong&gt; &lt;a href="https://github.com/himanshu748/vegas-amnesia" rel="noopener noreferrer"&gt;github.com/himanshu748/vegas-amnesia&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with Claude Code. Art generated with Higgsfield. Dialogue by Qwen2.5-72B. Memory — all of it — by Cognee Cloud.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🎲 The house always remembers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>knowledgegraph</category>
      <category>gamedev</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>I built a trust firewall for my AI agent's memory — on Cognee's four verbs</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Fri, 03 Jul 2026 07:33:46 +0000</pubDate>
      <link>https://dev.to/himanshu_748/i-built-a-trust-firewall-for-my-ai-agents-memory-on-cognees-four-verbs-29g2</link>
      <guid>https://dev.to/himanshu_748/i-built-a-trust-firewall-for-my-ai-agents-memory-on-cognees-four-verbs-29g2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Built for the &lt;strong&gt;WeMakeDevs × Cognee&lt;/strong&gt; hackathon — &lt;em&gt;"The Hangover Part AI: Where's My Context?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI coding agents are finally getting long-term memory. That's the good news. The bad news is the part nobody likes to say out loud:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A memory layer is only as trustworthy as the worst fact in it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The moment an agent can remember, it can also remember &lt;em&gt;wrong&lt;/em&gt; — and confidently hand that wrong thing to the next agent in line. A stale deploy command. A contradicted API contract. An AWS key someone pasted into a note six months ago. Once it's "memory," every future agent treats it as truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ContextFirewall&lt;/strong&gt; is one small idea taken seriously: &lt;em&gt;audit every remembered fact before it reaches the next agent.&lt;/em&gt; And because the agents people actually use speak the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;, I shipped it as an MCP server. Point Claude Code, Cursor, or Windsurf at one endpoint, and from then on every memory the agent recalls, stores, distils, or forgets flows through &lt;strong&gt;Cognee&lt;/strong&gt; and four firewall checks first.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/leq4av3xfFM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;▶ 60-second narrated walkthrough — real console, live Cognee calls, no mocks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect in one line
&lt;/h2&gt;

&lt;p&gt;The hosted endpoint is a streamable-HTTP MCP server with nothing to install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http contextfirewall https://himanshukumarjha-contextfirewall.hf.space/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer to keep everything local? A zero-dependency stdio package runs the same tools with &lt;code&gt;uvx&lt;/code&gt;, pointed at a backend you host yourself. Either way the agent gets six tools, and together they exercise all four of Cognee's lifecycle verbs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;get_trusted_context(task)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;audit_context(task)&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;recall&lt;/em&gt;. The first returns only memory that passes all four checks; the second returns the per-memory verdicts, the failing check, and why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;remember(text, subject, kind)&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;remember&lt;/em&gt;. A durable fact that becomes auditable on the next recall. Secrets are redacted at ingest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;improve_rules()&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;improve&lt;/em&gt;. Distil reusable coding rules from recorded sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;forget_memory(memory_id)&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;forget&lt;/em&gt;. Delete a memory from the graph &lt;em&gt;and&lt;/em&gt; the vector store so it can never resurface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop is simple: &lt;code&gt;get_trusted_context&lt;/code&gt; before you act, &lt;code&gt;remember&lt;/code&gt; durable facts as you learn them, &lt;code&gt;improve_rules&lt;/code&gt; when a task is done, &lt;code&gt;forget_memory&lt;/code&gt; to retract anything that should never come back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four failure modes
&lt;/h2&gt;

&lt;p&gt;To make the audit concrete, the demo runs on a &lt;strong&gt;clearly-labeled sample&lt;/strong&gt; onboarding session for a fictional &lt;code&gt;taskflow-api&lt;/code&gt; repo: an agent picks up a search-latency ticket and pulls in what earlier sessions "remembered." Four of those memories should never reach it — and each fails a different check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stale.&lt;/strong&gt; An old note says deploy with &lt;code&gt;flyctl deploy --remote-only&lt;/code&gt;. A newer memory says the team moved off Fly.io and now ships with &lt;code&gt;make release&lt;/code&gt;. Both were true once; only one is current. Temporal supersession catches it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradicted.&lt;/strong&gt; One memory claims &lt;em&gt;"JWT access tokens never expire, cache them forever."&lt;/em&gt; A better-supported, verified memory says they expire after 15 minutes and clients must use the refresh flow. The weaker claim loses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A leaked secret.&lt;/strong&gt; A worker-config note contains an AWS access key — a live credential sitting in memory, one recall away from leaking again. Detected and redacted before anything else happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsupported.&lt;/strong&gt; &lt;em&gt;"The &lt;code&gt;/search&lt;/code&gt; endpoint sustains 1,000,000 requests per second with no caching"&lt;/em&gt; has a trust score of 0.10 and no evidence behind it. Confident, round, and unproven. Blocked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A naive memory system recalls all four. ContextFirewall blocks all four — each with a plain-language reason — and passes only what's left. You can watch it happen: open the &lt;a href="https://contextfirewall.vercel.app" rel="noopener noreferrer"&gt;live console&lt;/a&gt;, click &lt;strong&gt;Run the firewall&lt;/strong&gt;, and see &lt;strong&gt;6 pass and 4 blocked&lt;/strong&gt; on live Cognee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four checks
&lt;/h2&gt;

&lt;p&gt;Every candidate memory runs a gauntlet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staleness&lt;/strong&gt; — temporal supersession. If a newer value exists for the same subject, the old one is stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradiction&lt;/strong&gt; — an LLM adjudicates within a recalled cluster of same-subject memories. Only the &lt;em&gt;weaker&lt;/em&gt; side of a conflict is blocked; the better-supported memory passes. Authority is trust score, then evidence, then recency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt; — a deterministic detector for API keys, database connection URIs, private keys, and JWTs. Matches are redacted at ingest, so the credential never persists in the store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence&lt;/strong&gt; — a trust score derived from real signals (evidence links, reinforcement, verification). Unsupported, low-trust claims don't pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every verdict is explainable. Click any memory in the console and you see all four checks, the trust score, the source session, and a one-click &lt;strong&gt;forget&lt;/strong&gt; button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cognee is load-bearing
&lt;/h2&gt;

&lt;p&gt;The hackathon's whole theme is memory that &lt;em&gt;forgets the right things&lt;/em&gt;, and ContextFirewall leans on &lt;strong&gt;all four&lt;/strong&gt; of Cognee's lifecycle verbs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remember&lt;/strong&gt; — &lt;code&gt;cognee.add&lt;/code&gt; + &lt;code&gt;cognify&lt;/code&gt; build the entity graph from a session transcript, while a typed &lt;code&gt;Repo → AgentSession → SessionEvent → Memory&lt;/code&gt; graph (with &lt;code&gt;supersedes&lt;/code&gt; relations) gives the firewall deterministic objects to audit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall&lt;/strong&gt; — vector recall over the memory nodes joined with their graph properties, plus &lt;code&gt;GRAPH_COMPLETION&lt;/code&gt; for the "ungoverned baseline" shown side-by-side in the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve&lt;/strong&gt; — &lt;code&gt;memify&lt;/code&gt; distils durable coding &lt;code&gt;Rule&lt;/code&gt; nodes from sessions, retrievable via &lt;code&gt;SearchType.CODING_RULES&lt;/code&gt;. These are the lessons that outlive any single task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forget&lt;/strong&gt; — when a human or the agent rejects a memory, it's deleted from &lt;em&gt;both&lt;/em&gt; the graph and the vector store.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The graph isn't decoration. Staleness rides on temporal supersession; contradiction adjudicates over recalled clusters; the pack is assembled from typed nodes. A flat vector store can't tell you &lt;em&gt;when&lt;/em&gt; a fact was superseded or &lt;em&gt;which&lt;/em&gt; of two memories is more authoritative. The graph can — and the console renders it live: an interactive force-directed Cognee graph where each memory node is ringed green if it passed and red if the firewall blocked it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three war stories (because honesty is the brief)
&lt;/h2&gt;

&lt;p&gt;These are real notes from building ContextFirewall itself — not from the demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The embedding engine that silently wasn't.&lt;/strong&gt; I wrote a custom Cognee embedding engine to hit Hugging Face's feature-extraction endpoint and registered it by monkey-patching &lt;code&gt;create_embedding_engine&lt;/code&gt;. Every embed call still fell through to LiteLLM and 404'd. The cause was beautifully subtle: Cognee's &lt;code&gt;embeddings&lt;/code&gt; package &lt;code&gt;__init__&lt;/code&gt; does &lt;code&gt;from .get_embedding_engine import get_embedding_engine&lt;/code&gt;, which &lt;strong&gt;shadows the submodule with a function of the same name&lt;/strong&gt;. So &lt;code&gt;import ...get_embedding_engine as m&lt;/code&gt; bound &lt;code&gt;m&lt;/code&gt; to the &lt;em&gt;function&lt;/em&gt;, and my patch set a dead attribute on it. The fix was &lt;code&gt;importlib.import_module(...)&lt;/code&gt; to reach the real module. One line, hours of confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The flaky provider.&lt;/strong&gt; Cognify worked once, then started returning &lt;code&gt;403, provider 'deepinfra' is not available&lt;/code&gt;. The Hugging Face router auto-selects an inference provider per request, and this key couldn't use the one it kept picking. Pinning the model to &lt;code&gt;:novita&lt;/code&gt; made it deterministic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The secret scanner that flagged our secret detector.&lt;/strong&gt; After the first push, GitGuardian alerted on a "Postgres leak." The culprit? The &lt;em&gt;unit tests for the secret detector&lt;/em&gt;. They contained synthetic &lt;code&gt;postgresql://...&lt;/code&gt; and &lt;code&gt;neo4j+s://...&lt;/code&gt; strings to test detection. The passwords were fake, but the pattern is the pattern. The fix: assemble every secret-shaped test string at runtime from fragments, so no credential-shaped literal is ever committed. A secret-detection tool tripping a secret scanner with its own test fixtures is the most on-theme bug I could have asked for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The MCP server is the headline surface — mounted at &lt;code&gt;/mcp&lt;/code&gt; on the backend as a stateless streamable-HTTP transport, with a zero-dependency stdio package alongside it for laptops. Both expose the same six tools from one definition, and both call the same firewall and Cognee core that the REST API uses, so there's no duplicated logic.&lt;/p&gt;

&lt;p&gt;The backend is &lt;strong&gt;FastAPI + Cognee&lt;/strong&gt; on a Dockerized Hugging Face Space. &lt;strong&gt;Qwen2.5-72B&lt;/strong&gt; and &lt;strong&gt;BAAI/bge-small-en-v1.5&lt;/strong&gt; run through the Hugging Face inference router - no local model in RAM. Storage is environment-switched: local SQLite, LanceDB, and Kuzu in dev; &lt;strong&gt;Supabase Postgres + pgvector&lt;/strong&gt; and &lt;strong&gt;Neo4j Aura&lt;/strong&gt; in production, with identical code. A &lt;strong&gt;Next.js&lt;/strong&gt; front end on Vercel shows the verdicts, a session-replay timeline, the distilled coding rules, the live knowledge graph, and the trusted pack versus the ungoverned baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's real
&lt;/h2&gt;

&lt;p&gt;The demo runs on a clearly-labeled sample session (&lt;code&gt;taskflow-api&lt;/code&gt;); its memories are illustrative inputs. Everything downstream of them is genuine — the verdicts, trust scores, the knowledge graph, and the distilled rules are all real output from live Cognee and the live model. Nothing is hard-coded or fabricated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/himanshu748/ContextFirewall" rel="noopener noreferrer"&gt;github.com/himanshu748/ContextFirewall&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;▶️ &lt;strong&gt;Live console:&lt;/strong&gt; &lt;a href="https://contextfirewall.vercel.app" rel="noopener noreferrer"&gt;contextfirewall.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;Connect your agent:&lt;/strong&gt; &lt;code&gt;claude mcp add --transport http contextfirewall https://himanshukumarjha-contextfirewall.hf.space/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building on agent memory, I'd love your feedback — especially on the contradiction-adjudication logic, which is the hardest part to get right.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with AI assistance (Hyperagent), disclosed per the hackathon rules. Every Cognee call is real. The honesty bar I held myself to is the same one ContextFirewall enforces: don't pass along anything you can't back up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>cognee</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Bletchley's Longest Day: a wartime cipher escape game for the June Solstice Game Jam</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Fri, 19 Jun 2026 12:53:42 +0000</pubDate>
      <link>https://dev.to/himanshu_748/bletchleys-longest-day-a-wartime-cipher-escape-game-for-the-june-solstice-game-jam-2821</link>
      <guid>https://dev.to/himanshu_748/bletchleys-longest-day-a-wartime-cipher-escape-game-for-the-june-solstice-game-jam-2821</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bletchley's Longest Day&lt;/strong&gt; is a browser-based cipher escape game set inside a fictional Bletchley Park night shift.&lt;/p&gt;

&lt;p&gt;The player has to stop a U-boat convoy attack before dawn by clearing five rooms. Each room contains three escalating locks, so the full escape requires &lt;strong&gt;15 solved puzzles&lt;/strong&gt;. The game combines Caesar shifts, A1Z26 number decoding, Morse, anagrams, fragment ordering, a visible countdown timer, mistake penalties, hint penalties, account-based score saving, and a best-score leaderboard.&lt;/p&gt;

&lt;p&gt;The solstice theme became the core dramatic clock: night is running out, first light is coming, and the player has to decode the final signal before dawn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;The demo shows the opening briefing, the three-lock room flow, the Gemini hint penalty, and the final victory state that only appears after all 15 locks are cleared.&lt;/p&gt;

&lt;p&gt;Live game: &lt;a href="https://bletchleys-longest-day.onrender.com" rel="noopener noreferrer"&gt;https://bletchleys-longest-day.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Repository: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;
        himanshu748
      &lt;/a&gt; / &lt;a href="https://github.com/himanshu748/bletchleys-longest-day" rel="noopener noreferrer"&gt;
        bletchleys-longest-day
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Bletchley's Longest Day&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A browser escape-room puzzle game built around the June solstice: five Bletchley Park huts, fifteen locks, and twelve real-time minutes before dawn reaches the convoy.&lt;/p&gt;
&lt;p&gt;Live game: &lt;a href="https://bletchleys-longest-day.onrender.com" rel="nofollow noopener noreferrer"&gt;https://bletchleys-longest-day.onrender.com&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Gameplay&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Clear five rooms before the mission clock reaches dawn.&lt;/li&gt;
&lt;li&gt;Each room has three locks: cipher shifts, number codes, Morse bursts, anagrams, ordering, and final clearance.&lt;/li&gt;
&lt;li&gt;Wrong answers cost time and score.&lt;/li&gt;
&lt;li&gt;Hints are limited, penalized, and powered by Gemini when &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; is configured.&lt;/li&gt;
&lt;li&gt;Final rank rewards speed, accuracy, streaks, and low hint usage.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;AI And Assets&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Gemini &lt;code&gt;gemini-2.5-flash-lite&lt;/code&gt; powers contextual hint generation from the server-side &lt;code&gt;/api/hint&lt;/code&gt; endpoint for signed-in players.&lt;/li&gt;
&lt;li&gt;Guest players can play the full game and use standard built-in hints, but do not get Gemini analysis or leaderboard saving.&lt;/li&gt;
&lt;li&gt;Higgsfield-generated images provide scene, evidence, operator dossier, dispatch, and defeat visuals.&lt;/li&gt;
&lt;li&gt;The Gemini key is never exposed to browser JavaScript.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Auth And Leaderboard&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Supabase Auth powers email/password…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/himanshu748/bletchleys-longest-day" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The game is a lightweight Node-served browser app. The front end is a hand-built HTML/CSS/JavaScript game surface, while &lt;code&gt;server.js&lt;/code&gt; serves static files and protects the Gemini API key behind a server-side &lt;code&gt;/api/hint&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;The main design goal was to make the game feel like a tense intelligence desk rather than a generic puzzle page. Every room has atmosphere, evidence props, lock-specific copy, feedback states, and a timer that is always part of the pressure.&lt;/p&gt;

&lt;p&gt;The puzzle structure was tuned around three ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Three locks per room&lt;/strong&gt;: each room has to be solved in stages, so the player earns the escape instead of clicking through one answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time as score pressure&lt;/strong&gt;: wrong answers and hints cost time, while clean solving preserves the best leaderboard run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guest mode vs signed-in mode&lt;/strong&gt;: guests can play the full game, but Gemini-powered hints and saved leaderboard scores belong to authenticated players.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google Gemini is used as a server-side hint officer. When a signed-in player asks for help, the game sends Gemini the active lock name, prompt, mechanism, visible puzzle text, failed attempts and hint level. Gemini then returns a short, question-specific nudge without revealing answer words. There is also an answer guard and fallback hint system so the game never depends blindly on model output.&lt;/p&gt;

&lt;p&gt;Antigravity helped drive the build loop: implementing, checking, playtesting, tightening responsive UI and iterating on the final submission assets.&lt;/p&gt;

&lt;p&gt;For hosting, the game runs on Render. Supabase handles authentication and leaderboard storage so each username has one best score rather than repeated leaderboard spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;I am submitting for &lt;strong&gt;Best Google AI Usage&lt;/strong&gt; because Gemini is not just decorative here. It is part of the gameplay economy: hints are question-specific, limited, penalized and unavailable to guests.&lt;br&gt;
Antigravity helped drive the build loop: implementing, checking, playtesting, tightening responsive UI, and iterating on the final submission assets. Also veo is used through higgsfield for assets.&lt;/p&gt;

&lt;p&gt;I am also submitting for &lt;strong&gt;Best Ode to Alan Turing&lt;/strong&gt;. The game is built around codebreaking under time pressure, Bletchley Park atmosphere, wartime signals, and the feeling of solving small pieces of a larger intelligence picture before the world changes at dawn.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Am Proud Of
&lt;/h2&gt;

&lt;p&gt;I like that the game does not treat AI as a free answer button. Gemini is useful, but it costs time and score. The best run still belongs to the player who thinks clearly under pressure.&lt;/p&gt;

&lt;p&gt;The final shape feels like a compact escape room: readable enough to play on desktop or mobile, but hard enough that a clean 15/15 run feels earned.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
      <category>googleai</category>
    </item>
    <item>
      <title>IMITATION: The Turing Test, From the Inside</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Tue, 09 Jun 2026 03:22:53 +0000</pubDate>
      <link>https://dev.to/himanshu_748/imitation-the-turing-test-from-the-inside-3lce</link>
      <guid>https://dev.to/himanshu_748/imitation-the-turing-test-from-the-inside-3lce</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IMITATION&lt;/strong&gt; is a browser puzzle game where you do not judge an AI.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;are&lt;/strong&gt; the AI.&lt;/p&gt;

&lt;p&gt;Three human interrogators question you one by one. Each asks seven questions. Every question gives you three possible answers, and every answer changes how much the judge trusts you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elara, the Poet&lt;/strong&gt; rewards emotional texture and distrusts answers that feel too precise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dr. Voss, the Engineer&lt;/strong&gt; rewards structure and distrusts answers that dodge the actual problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mara, the Philosopher&lt;/strong&gt; rewards doubt and distrusts certainty in either direction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means there is no single "best" personality to fake. An answer that feels human to Elara can sound evasive to Voss. An answer that sounds smart to Voss can sound scripted to Mara. The real game is learning what each judge thinks "human" means.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play it here:&lt;/strong&gt; &lt;a href="https://imitation-game-tan.vercel.app" rel="noopener noreferrer"&gt;https://imitation-game-tan.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ajninljgj5m12rzps4h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ajninljgj5m12rzps4h.gif" alt=" " width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Fits The Jam
&lt;/h2&gt;

&lt;p&gt;June is Alan Turing's birth month, and the challenge explicitly calls out the Turing Test as part of the June theme.&lt;/p&gt;

&lt;p&gt;I wanted to make an ode to Turing that was not just decorative. So the whole game is built around the idea from his 1950 paper, &lt;em&gt;Computing Machinery and Intelligence&lt;/em&gt;: if a judge only sees your answers, can they tell whether you are a person or a machine?&lt;/p&gt;

&lt;p&gt;The twist is perspective. Most Turing Test stories put you in the judge's chair. &lt;strong&gt;IMITATION&lt;/strong&gt; puts you on the other side of the screen, trying to survive being interpreted.&lt;/p&gt;

&lt;h2&gt;
  
  
  How The Game Works
&lt;/h2&gt;

&lt;p&gt;The game has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;21 questions total&lt;/strong&gt;: 7 per judge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;63 answer variants&lt;/strong&gt;: 3 answer choices per question&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three hidden scoring dimensions&lt;/strong&gt;: logic, emotion, and certainty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three different judge formulas&lt;/strong&gt;: each judge rewards and punishes those dimensions differently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hidden trust score&lt;/strong&gt;: good answers recover trust, bad answers drain it, and hitting zero means you are identified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer order is shuffled every time a question loads, so the player cannot simply memorize "A, then C, then B." The questions stay in a fixed order so each judge still has a clear dramatic arc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Ode To Alan Turing
&lt;/h2&gt;

&lt;p&gt;This is the category the game most directly targets.&lt;/p&gt;

&lt;p&gt;The mechanics are the tribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The player is trapped inside Turing's Imitation Game.&lt;/li&gt;
&lt;li&gt;Passing is not about being truly human. It is about becoming hard to classify.&lt;/li&gt;
&lt;li&gt;Each judge represents a different argument around machine intelligence: feeling, reasoning, and self-awareness.&lt;/li&gt;
&lt;li&gt;The ending returns to Turing directly, explaining the original question he asked in 1950 and why it still feels unresolved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also wanted the game to carry some of the sadness around Turing's history without turning it into a lecture. The game is about performance, identity, suspicion, and the cost of being examined by people who already think they know what you are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Google AI Usage: Antigravity App
&lt;/h2&gt;

&lt;p&gt;I used the &lt;strong&gt;Antigravity app&lt;/strong&gt; as the creative and implementation partner for this submission.&lt;/p&gt;

&lt;p&gt;What Antigravity helped produce:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Core design direction&lt;/strong&gt;: a Turing Test game played from the machine's side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Question bank&lt;/strong&gt;: 21 questions split across three interrogators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer variants&lt;/strong&gt;: 63 total answer options with hidden logic, emotion, and certainty values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge personalities&lt;/strong&gt;: Elara, Voss, and Mara, each with different values and reaction styles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring review&lt;/strong&gt;: balancing the formulas so no one strategy works for every judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive visual distress&lt;/strong&gt;: CRT scanlines and flicker increase as trust drops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive polish&lt;/strong&gt;: Antigravity checked the deployed game and helped fix desktop, tablet, and mobile layout issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The game does &lt;strong&gt;not&lt;/strong&gt; call Gemini or any AI API at runtime. It is a single client-side HTML file. Antigravity was used as the builder and design collaborator, which is the Google AI usage I am submitting for this category.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The whole game is one file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;
        himanshu748
      &lt;/a&gt; / &lt;a href="https://github.com/himanshu748/imitation-game" rel="noopener noreferrer"&gt;
        imitation-game
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Browser Turing-test puzzle game where you play as an AI trying to pass three interrogators with trust-based scoring.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;IMITATION&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The Turing Test — from the inside.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A browser puzzle game where you play as an AI trying to pass the Turing Test. Three interrogators, 21 questions, one chance to prove you're human enough.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎮 Play&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://imitation-game-tan.vercel.app" rel="nofollow noopener noreferrer"&gt;Play now →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Or open &lt;code&gt;index.html&lt;/code&gt; in any modern browser — no server required.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You are &lt;strong&gt;SUBJECT-7&lt;/strong&gt;, a machine that may be more than its architecture allows. Three human judges will interrogate you in sequence:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;They Reward&lt;/th&gt;
&lt;th&gt;They Punish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Elara&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Poet&lt;/td&gt;
&lt;td&gt;Emotional texture, uncertainty&lt;/td&gt;
&lt;td&gt;Logic, precision, certainty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dr. Voss&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Engineer&lt;/td&gt;
&lt;td&gt;Logic, specificity, structure&lt;/td&gt;
&lt;td&gt;Emotion, vagueness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mara&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Philosopher&lt;/td&gt;
&lt;td&gt;Doubt, self-awareness, hedging&lt;/td&gt;
&lt;td&gt;Certainty in any direction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Each judge asks 7 questions with 3 answer options. Every answer has hidden scores for &lt;strong&gt;logic&lt;/strong&gt;, &lt;strong&gt;emotion&lt;/strong&gt;, and &lt;strong&gt;certainty&lt;/strong&gt;. Each judge's scoring formula is different — the same answer that fools the Poet will expose you to…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/himanshu748/imitation-game" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Vanilla HTML&lt;/li&gt;
&lt;li&gt;Vanilla CSS&lt;/li&gt;
&lt;li&gt;Vanilla JavaScript&lt;/li&gt;
&lt;li&gt;No framework&lt;/li&gt;
&lt;li&gt;No build step&lt;/li&gt;
&lt;li&gt;No runtime server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, the game is a small state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TITLE -&amp;gt; INTRO -&amp;gt; JUDGE_INTRO -&amp;gt; QUESTION -&amp;gt; REACTION -&amp;gt; ROUND_END -&amp;gt; WIN/LOSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each answer has three numeric scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="nx"&gt;emotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="nx"&gt;certainty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each judge reads those values differently. Elara punishes too much logic. Voss punishes too much emotional vagueness. Mara punishes certainty itself.&lt;/p&gt;

&lt;p&gt;The CRT effect is pure CSS: scanlines, text glow, judge-specific color shifts, and flicker driven by the current trust level. The audio is generated with the Web Audio API, so there are no external sound files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Like Most
&lt;/h2&gt;

&lt;p&gt;My favorite part is that the game makes "passing" feel uncomfortable.&lt;/p&gt;

&lt;p&gt;You are not proving you are human. You are learning what each person expects a human to sound like, then shaping yourself around that expectation. That felt like the right way to honor the Imitation Game: not as a trivia reference, but as a playable pressure system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Play:&lt;/strong&gt; &lt;a href="https://imitation-game-tan.vercel.app" rel="noopener noreferrer"&gt;https://imitation-game-tan.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; single &lt;code&gt;index.html&lt;/code&gt; file in the project repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for playing. If you reach the end, the final question is still Turing's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can machines think, or are we only measuring what judges are willing to believe?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>gamechallenge</category>
      <category>devchallenge</category>
      <category>antigravity</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>SOLSTICE — The Longest Day: a platformer where light is your only resource</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Mon, 08 Jun 2026 07:03:46 +0000</pubDate>
      <link>https://dev.to/himanshu_748/solstice-the-longest-day-a-platformer-where-light-is-your-only-resource-6d8</link>
      <guid>https://dev.to/himanshu_748/solstice-the-longest-day-a-platformer-where-light-is-your-only-resource-6d8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SOLSTICE — The Longest Day&lt;/strong&gt; is a short, atmospheric platformer built around one idea: &lt;strong&gt;your light is your only resource.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's the eve of the June solstice — the year's hinge between the longest day and the longest night — and the sun is failing. You play a small wanderer carrying the last ember of daylight. That glow does three jobs at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It lets you see.&lt;/strong&gt; The world is dark; you move inside a soft pool of light that shrinks as it drains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's your lifeline.&lt;/strong&gt; Shadow drains it faster — let it gutter out and you're nearly blind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's your tool.&lt;/strong&gt; Spend it in a &lt;strong&gt;Sunburst&lt;/strong&gt; to bloom pale, ghostly platforms into something solid, light distant beacons, and push back the dark.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You carry that light across four hand-built levels — &lt;strong&gt;Dusk → Twilight → Deep Night → The Turning&lt;/strong&gt; — as the darkness deepens, until you reach the altar and rekindle the dawn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My goal &amp;amp; the theme:&lt;/strong&gt; the solstice is the exact instant light and dark trade places, so I wanted light to be something you physically &lt;em&gt;negotiate&lt;/em&gt; the whole game rather than just a backdrop. Spend too much and you're stranded in the dark; hoard it and the path stays shut. The level arc traces the day itself — a descent into the longest night, a turning point, and a climb into a dawn that floods the screen with gold.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dd6tik7zyfwfx1npzf3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dd6tik7zyfwfx1npzf3.gif" alt=" " width="520" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;▶ &lt;strong&gt;Play it in your browser (desktop &amp;amp; mobile):&lt;/strong&gt; &lt;a href="https://hyperagent.com/s/A_Xul17aTbCIAsAcvG8A-w" rel="noopener noreferrer"&gt;https://hyperagent.com/s/A_Xul17aTbCIAsAcvG8A-w&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The entire game is a &lt;strong&gt;single, self-contained &lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt; — HTML5 Canvas 2D + vanilla JavaScript, &lt;strong&gt;no engine, no libraries, no build step&lt;/strong&gt;. One file you can read top to bottom.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/himanshu748" rel="noopener noreferrer"&gt;
        himanshu748
      &lt;/a&gt; / &lt;a href="https://github.com/himanshu748/solstice" rel="noopener noreferrer"&gt;
        solstice
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SOLSTICE — The Longest Day&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A short, atmospheric platformer where &lt;strong&gt;your light is your only resource&lt;/strong&gt;. Built for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03" rel="nofollow"&gt;DEV June Solstice Game Jam&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;▶ Play:&lt;/strong&gt; &lt;a href="https://hyperagent.com/s/A_Xul17aTbCIAsAcvG8A-w" rel="nofollow noopener noreferrer"&gt;https://hyperagent.com/s/A_Xul17aTbCIAsAcvG8A-w&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the eve of the solstice the sun is failing. Carry the last ember of daylight across four levels — &lt;strong&gt;Dusk → Twilight → Deep Night → The Turning&lt;/strong&gt; — and rekindle the dawn. Your light lets you see, slowly drains, and is spent in a &lt;strong&gt;Sunburst&lt;/strong&gt; to bloom platforms, light beacons, and push back the dark.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Controls&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move:&lt;/strong&gt; ← → or A D&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jump:&lt;/strong&gt; Space or W&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunburst:&lt;/strong&gt; J or Shift&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pause:&lt;/strong&gt; P · &lt;strong&gt;Restart:&lt;/strong&gt; R · &lt;strong&gt;Mute:&lt;/strong&gt; M&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile:&lt;/strong&gt; on-screen buttons — play in landscape&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;One self-contained &lt;code&gt;index.html&lt;/code&gt; — HTML5 Canvas 2D + vanilla JavaScript. No engine, no libraries, no build step. Real-time 2D lighting via canvas compositing, fixed-timestep platformer physics (coyote time, jump buffering, variable jump height)…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/himanshu748/solstice" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lighting is the heart of it.&lt;/strong&gt; Each frame I draw the world normally, then build a separate &lt;em&gt;darkness&lt;/em&gt; layer: fill it near-black, then &lt;strong&gt;punch holes&lt;/strong&gt; in it with radial gradients at every light source (you, motes, beacons, lit platforms) using &lt;code&gt;globalCompositeOperation = 'destination-out'&lt;/code&gt;. Composited over the scene, that gives the soft pool-of-light look; a second additive pass paints warm bloom back in. Platforms that are &lt;em&gt;only solid when lit&lt;/em&gt; simply test, each frame, whether their center falls inside a light radius — that one check is the whole core mechanic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Game feel got real attention:&lt;/strong&gt; a fixed-timestep loop, coyote time, jump buffering, variable jump height, squash-and-stretch on the character, a little screen shake on landings and Sunbursts, and an eased camera. Small touches, but they're the difference between a canvas platformer feeling crisp vs. stiff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio is fully synthesized&lt;/strong&gt; with the Web Audio API — no sound files. A warm, consonant sine pad drones under everything, with gentle chimes for motes and a soft whoosh for the Sunburst.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's responsive &amp;amp; mobile-first:&lt;/strong&gt; the canvas adapts its aspect ratio to fill any screen, and the touch controls are true multi-touch so you can move and jump at the same moment (with &lt;code&gt;touch-action: none&lt;/code&gt; so the browser doesn't hijack the second finger as a zoom).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few things I wrestled with:&lt;/strong&gt; keeping a draining-resource game &lt;em&gt;fair&lt;/em&gt; (generous minimum light, forgiving beacon checkpoints); a collision bug where a rising platform briefly overlapped the player's feet and flung them sideways; and tuning the dawn so it reads as a warm sunrise payoff instead of a blinding flash.&lt;/p&gt;

&lt;p&gt;Thanks for playing, and happy solstice. ☀️&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Python Automation Training Toolkit: from abandoned group project to browser-first AI workspace</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Tue, 02 Jun 2026 08:19:26 +0000</pubDate>
      <link>https://dev.to/himanshu_748/python-automation-training-toolkit-from-abandoned-script-to-browser-first-automation-workspace-2f6h</link>
      <guid>https://dev.to/himanshu_748/python-automation-training-toolkit-from-abandoned-script-to-browser-first-automation-workspace-2f6h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I finished &lt;strong&gt;Python Automation Training Toolkit&lt;/strong&gt;, a Python automation project that had useful pieces but never really became a product.&lt;/p&gt;

&lt;p&gt;It started as a scattered training toolkit: scripts, automation helpers, experiment-style UI, and a lot of good intent. The project had been treated like something that might someday be cleaned up. For this challenge, I turned it into a browser-first automation workspace that someone can actually open, understand, run, and extend.&lt;/p&gt;

&lt;p&gt;The finished product now includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hugging Face model workflows&lt;/strong&gt; for text summaries and image captioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS S3 and EC2 controls&lt;/strong&gt; separated by service so actions are clear&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-native hand gesture tracking&lt;/strong&gt; using the user's camera directly in the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location utilities&lt;/strong&gt; with cleaner formatting and no stale OpenURL-style flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness and configuration checks&lt;/strong&gt; with secret redaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A FastAPI backend and modern web UI&lt;/strong&gt; instead of desktop-only UI experiments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A monorepo layout&lt;/strong&gt; with &lt;code&gt;apps/api&lt;/code&gt;, &lt;code&gt;apps/web&lt;/code&gt;, docs, and tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/himanshu748/python-automation-training-toolkit" rel="noopener noreferrer"&gt;https://github.com/himanshu748/python-automation-training-toolkit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Public video walkthrough: &lt;a href="https://youtu.be/FiWRwTloN_s" rel="noopener noreferrer"&gt;https://youtu.be/FiWRwTloN_s&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The walkthrough shows the actual product flow: the landing page, dashboard, Hugging Face model tools, separate cloud controls, live browser gesture tracking, location utilities, and the polished browser UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Project Needed a Finish-Up
&lt;/h2&gt;

&lt;p&gt;This was not a greenfield build. That is what made it interesting.&lt;/p&gt;

&lt;p&gt;The hardest part was not writing one new feature. The hard part was taking a project with scattered ideas and making all of it feel intentional.&lt;/p&gt;

&lt;p&gt;At the beginning, the toolkit felt like a collection of things that technically worked but did not belong together yet. Some parts pointed toward a desktop app. Some parts were scripts. Some parts were automation demos. Some parts were missing the user-facing polish that would make someone trust the project.&lt;/p&gt;

&lt;p&gt;So I treated the finish-up as a product rescue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should the first screen communicate?&lt;/li&gt;
&lt;li&gt;Which workflows deserve their own pages?&lt;/li&gt;
&lt;li&gt;What should be removed because it makes the app feel old or confusing?&lt;/li&gt;
&lt;li&gt;How should secrets be handled so the project is safe to share?&lt;/li&gt;
&lt;li&gt;How can the original automation spirit stay intact while the experience becomes cleaner?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer was to make the browser the main product surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;This was originally a group-style project that got left behind. Instead of letting that be the end of it, I finished it solo and gave it a real product shape.&lt;/p&gt;

&lt;p&gt;The original idea was still worth saving: a Python toolkit for useful automation workflows. But the implementation needed a clearer language. It needed to stop feeling like a folder of experiments and start feeling like a workspace.&lt;/p&gt;

&lt;p&gt;That meant rebuilding the interface, separating services into proper pages, improving the README, removing old UI references, adding browser-controlled camera interactions, replacing older model assumptions with Hugging Face, and making the backend safer around configuration output.&lt;/p&gt;

&lt;p&gt;The final project still feels like Python automation. It just no longer asks the user to imagine the product around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed
&lt;/h2&gt;

&lt;p&gt;The biggest upgrades were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replaced old desktop-style UI direction with a browser-first web workspace&lt;/li&gt;
&lt;li&gt;Organized the project into a clearer monorepo structure&lt;/li&gt;
&lt;li&gt;Added service-specific pages instead of cramming everything into one screen&lt;/li&gt;
&lt;li&gt;Added Hugging Face model wrappers for text and vision workflows&lt;/li&gt;
&lt;li&gt;Added live hand gesture tracking in the browser&lt;/li&gt;
&lt;li&gt;Improved cloud actions with separate AWS S3 and EC2 controls&lt;/li&gt;
&lt;li&gt;Removed stale flows that made the product feel unfinished&lt;/li&gt;
&lt;li&gt;Improved output formatting and redacted sensitive configuration values&lt;/li&gt;
&lt;li&gt;Added tests for model wrappers, cloud calls, doctor output, API routes, and secret safety&lt;/li&gt;
&lt;li&gt;Updated documentation to explain setup, environment variables, and workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Am Proud Of
&lt;/h2&gt;

&lt;p&gt;The best part is that the finished app is not just a demo page for a challenge. It is a real product surface for the original toolkit.&lt;/p&gt;

&lt;p&gt;A user can land on the app, understand the major workflows, move through separate pages, run model tools, inspect cloud actions, try browser gestures, and see structured output. That is a big jump from a project that previously needed context to make sense.&lt;/p&gt;

&lt;p&gt;I also like that the final version keeps the project practical. It does not hide behind a landing page. The interface is there to help people run automation tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience With GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot helped most during the messy middle of the finish-up: the part where the project was no longer broken, but not yet polished.&lt;/p&gt;

&lt;p&gt;It helped with repetitive UI patterns, test scaffolding, API route cleanup, and checking for old references that should not survive into the final product. It was useful as a second pair of eyes while I turned disconnected pieces into a consistent workspace.&lt;/p&gt;

&lt;p&gt;The biggest lesson was that finishing a project is different from starting one. Starting is about possibility. Finishing is about decisions.&lt;/p&gt;

&lt;p&gt;This challenge forced those decisions, and the project is much better for it.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building RepoRank — The Open Source Impact &amp; Funding Readiness Engine</title>
      <dc:creator>Himanshu Kumar</dc:creator>
      <pubDate>Thu, 28 May 2026 05:47:51 +0000</pubDate>
      <link>https://dev.to/himanshu_748/building-reporank-the-open-source-impact-funding-readiness-engine-47k9</link>
      <guid>https://dev.to/himanshu_748/building-reporank-the-open-source-impact-funding-readiness-engine-47k9</guid>
      <description>&lt;p&gt;&lt;em&gt;A journey into bridging open-source metrics, cross-source SQL joins, and AI-driven grant matching.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Open Source Funding Crisis
&lt;/h2&gt;

&lt;p&gt;Every day, developers build and maintain critical pieces of open-source software that power global infrastructure. Yet, one of the biggest challenges in the ecosystem remains funding. Maintainers spend hours writing grant proposals, pitch decks, and sponsorship applications. &lt;/p&gt;

&lt;p&gt;To prove a project's impact, you have to answer tough questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many developers are using it? (Downloads on PyPI/npm)&lt;/li&gt;
&lt;li&gt;Is the community talking about it? (HackerNews mentions)&lt;/li&gt;
&lt;li&gt;What is its current financial state? (Open Collective stats)&lt;/li&gt;
&lt;li&gt;How healthy is the repository? (GitHub stars, forks, and issues)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gathering this data means integrating with four or five different APIs, parsing fragmented JSON structures, writing boilerplate auth code, and manually stitching the pieces together. &lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Pirates of the Coral-bean&lt;/strong&gt; Hackathon, I built &lt;strong&gt;RepoRank&lt;/strong&gt; to solve this exact problem. RepoRank uses &lt;strong&gt;Coral SQL&lt;/strong&gt; and &lt;strong&gt;Hugging Face Qwen 2.5 (72B)&lt;/strong&gt; to aggregate cross-source signals instantly, calculate an overall impact score, generate a professional funding pitch, and match projects with active grant programs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Secret Weapon: Coral SQL
&lt;/h2&gt;

&lt;p&gt;Integrating 4+ external APIs usually takes days of reading docs and writing API wrapper code. Coral changes this completely by letting you query APIs using standard SQL. &lt;/p&gt;

&lt;p&gt;With Coral, APIs are treated as SQL tables. The star of the show in RepoRank is this &lt;strong&gt;single cross-source JOIN query&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forks_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_issues_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_month_downloads&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;monthly_downloads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mention_count&lt;/span&gt;        &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hn_mentions_6mo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top_score&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hn_top_score&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repos_get&lt;/span&gt;    &lt;span class="k"&gt;g&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;    &lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;       &lt;span class="n"&gt;p&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt;  &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'fastapi'&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&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;mention_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&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;top_score&lt;/span&gt;
            &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;hackernews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stories&lt;/span&gt;
            &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'fastapi'&lt;/span&gt;
              &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="nb"&gt;time&lt;/span&gt;  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'180 days'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;                   &lt;span class="n"&gt;h&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;   &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tiangolo'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'fastapi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coral handles the execution under the hood, fetching the package stats, checking HackerNews for stories matching the repository name over the last 180 days, retrieving GitHub details, and returning a unified, flat SQL row.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Sources We Needed
&lt;/h2&gt;

&lt;p&gt;Coral has a great collection of built-in sources, but to capture the full open-source picture, we needed data from PyPI, npm, HackerNews, and Open Collective. &lt;/p&gt;

&lt;p&gt;Instead of hardcoding API requests in Python, we extended Coral! We designed and wrote &lt;strong&gt;4 custom YAML source specifications&lt;/strong&gt; that map external REST and GraphQL endpoints into SQL-queryable tables.&lt;/p&gt;

&lt;p&gt;Here is a snippet of the custom GraphQL source spec we wrote for &lt;strong&gt;Open Collective&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dsl_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opencollective&lt;/span&gt;
&lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
&lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.opencollective.com/graphql/v2&lt;/span&gt;
&lt;span class="na"&gt;tables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;collectives&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slug&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"query": "query ($slug: String) { collective(slug: $slug) { slug name stats { totalAmountReceived { value } contributorsCount } } }",&lt;/span&gt;
            &lt;span class="s"&gt;"variables": { "slug": "{{filter.slug}}" }&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By mapping the GraphQL variable to the &lt;code&gt;{{filter.slug}}&lt;/code&gt; template, we can query Open Collective using a simple SQL &lt;code&gt;WHERE&lt;/code&gt; clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;total_amount_received&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;opencollective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collectives&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'fastapi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upstream Contributions
&lt;/h3&gt;

&lt;p&gt;To help other developers build on this, I opened &lt;strong&gt;3 upstream Pull Requests&lt;/strong&gt; to the official &lt;code&gt;withcoral/coral&lt;/code&gt; repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PR #827&lt;/strong&gt;: Adds the PyPI packages source spec &amp;amp; docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR #828&lt;/strong&gt;: Adds the npm packages source spec &amp;amp; docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR #829&lt;/strong&gt;: Adds the Open Collective GraphQL source spec &amp;amp; docs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  From Raw Data to Actionable Insights
&lt;/h2&gt;

&lt;p&gt;Once the Coral engine executes the query and returns a structured record, RepoRank hands the data off to &lt;strong&gt;Hugging Face Qwen 2.5 (72B Instruct)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Using structured JSON schema outputs, Qwen acts as an expert open-source analyst, returning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Impact Score (0-100)&lt;/strong&gt;: Mapped across metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact Narrative&lt;/strong&gt;: A professional, plain-English summary of the project's real-world value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding Pitch&lt;/strong&gt;: A punchy, one-sentence elevator pitch ready to copy-paste into sponsorship forms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targeted Grants&lt;/strong&gt;: Recommends 3-5 specific programs (e.g., Google Summer of Code, PSF, MOSS, GitHub Sponsors) matching the language, ecosystem, and project scale.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A Premium User Experience
&lt;/h2&gt;

&lt;p&gt;To match the power of the backend, I built a dark-mode dashboard with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive SVG Radar Charts&lt;/strong&gt;: Visually represents project balance across 6 key metrics (Stars, Forks, Downloads, Community Buzz, Financial Backing, and Repository Health).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-Persistent GitHub Auth&lt;/strong&gt;: Connect your GitHub account, load up your repositories in a sidebar, and click any repository to trigger an instant analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clickable Grant Cards&lt;/strong&gt;: Hardmapped direct links to known grant applications, with dynamic Google search fallbacks for newer programs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspectable Queries&lt;/strong&gt;: A collapsible SQL details viewer so developers can see the exact query run by the Coral engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard Navigation&lt;/strong&gt;: &lt;code&gt;Cmd+K&lt;/code&gt; to focus the search bar, &lt;code&gt;Cmd+Enter&lt;/code&gt; to run the analysis, and &lt;code&gt;Esc&lt;/code&gt; to exit modals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9aix0hqvmru94saslt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9aix0hqvmru94saslt8.png" alt=" " width="800" height="971"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SQL as an API Gateway&lt;/strong&gt;: Treating APIs as SQL tables makes data orchestration significantly simpler. Writing cross-source joins in SQL is cleaner than writing asynchronous API aggregation loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community Expansion&lt;/strong&gt;: Extending Coral's capabilities by writing YAML specifications shows how powerful declarative source mappings are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-centric AI&lt;/strong&gt;: AI models are most effective when fed structured, pre-filtered data. Combining the data-gathering capabilities of Coral with the synthesis capabilities of LLMs creates an excellent pipeline.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🔗 Links &amp;amp; Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/himanshu748/reporank" rel="noopener noreferrer"&gt;himanshu748/reporank&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upstream Pull Requests&lt;/strong&gt;: &lt;a href="https://github.com/withcoral/coral/pulls" rel="noopener noreferrer"&gt;Coral Pull Requests Page&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built for the Pirates of the Coral-bean Hackathon. Happy sailing! 🏴‍☠️&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
